diff --git a/auth/credential_store.go b/auth/credential_store.go index 6eb13774..0d1de7f8 100644 --- a/auth/credential_store.go +++ b/auth/credential_store.go @@ -18,6 +18,10 @@ const ( // PermAll means all actions permitted. PermAll = "all" + // PermJoin means user is permitted to join cluster. + PermJoin = "join" + // PermJoinReadOnly means user is permitted to join the cluster only as a read-only node + PermJoinReadOnly = "join-read-only" // PermRemove means user is permitted to remove a node. PermRemove = "remove" // PermExecute means user can access execute endpoint. diff --git a/cluster/service.go b/cluster/service.go index 4212c844..3792f601 100644 --- a/cluster/service.go +++ b/cluster/service.go @@ -439,6 +439,8 @@ func (s *Service) handleConn(conn net.Conn) { nr := c.GetNotifyRequest() if nr == nil { resp.Error = "NotifyRequest is nil" + } else if !s.checkCommandPermAll(c, auth.PermJoin) { + resp.Error = "unauthorized" } else { if err := s.mgr.Notify(nr); err != nil { resp.Error = err.Error() @@ -454,16 +456,21 @@ func (s *Service) handleConn(conn net.Conn) { if jr == nil { resp.Error = "JoinRequest is nil" } else { - if err := s.mgr.Join(jr); err != nil { - resp.Error = err.Error() - if err.Error() == "not leader" { - laddr, err := s.mgr.LeaderAddr() - if err != nil { - resp.Error = err.Error() - } else { - resp.Leader = laddr + if (jr.Voter && s.checkCommandPerm(c, auth.PermJoin)) || + (!jr.Voter && s.checkCommandPerm(c, auth.PermJoinReadOnly)) { + if err := s.mgr.Join(jr); err != nil { + resp.Error = err.Error() + if err.Error() == "not leader" { + laddr, err := s.mgr.LeaderAddr() + if err != nil { + resp.Error = err.Error() + } else { + resp.Leader = laddr + } } } + } else { + resp.Error = "unauthorized" } } marshalAndWrite(conn, resp) diff --git a/cmd/rqlited/flags.go b/cmd/rqlited/flags.go index 52e7084b..15090708 100644 --- a/cmd/rqlited/flags.go +++ b/cmd/rqlited/flags.go @@ -111,6 +111,9 @@ type Config struct { // JoinInterval is the time between retrying failed join operations. JoinInterval time.Duration + // JoinAs sets the user join attempts should be performed as. May not be set. + JoinAs string + // BootstrapExpect is the minimum number of nodes required for a bootstrap. BootstrapExpect int @@ -441,6 +444,7 @@ func ParseFlags(name, desc string, build *BuildInfo) (*Config, error) { flag.StringVar(&config.JoinAddrs, "join", "", "Comma-delimited list of nodes, through which a cluster can be joined (proto://host:port)") flag.IntVar(&config.JoinAttempts, "join-attempts", 5, "Number of join attempts to make") flag.DurationVar(&config.JoinInterval, "join-interval", 3*time.Second, "Period between join attempts") + flag.StringVar(&config.JoinAs, "join-as", "", "Username in authentication file to join as. If not set, joins anonymously") flag.IntVar(&config.BootstrapExpect, "bootstrap-expect", 0, "Minimum number of nodes required for a bootstrap") flag.DurationVar(&config.BootstrapExpectTimeout, "bootstrap-expect-timeout", 120*time.Second, "Maximum time for bootstrap process") flag.StringVar(&config.DiscoMode, "disco-mode", "", "Choose clustering discovery mode. If not set, no node discovery is performed")