From c59adee7cbd8c1aa763eed5792173126488df574 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Tue, 19 Jul 2022 13:36:26 -0400 Subject: [PATCH] Add Read-only join permission --- DOC/SECURITY.md | 1 + auth/credential_store.go | 2 ++ http/service.go | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DOC/SECURITY.md b/DOC/SECURITY.md index e2463cfa..ac874155 100644 --- a/DOC/SECURITY.md +++ b/DOC/SECURITY.md @@ -45,6 +45,7 @@ rqlite, via the configuration file, also supports user-level permissions. Each u - _status_: user can retrieve node status and Go runtime information. - _ready_: user can retrieve node readiness. - _join_: user can join a cluster. In practice only a node joins a cluster, so it's the joining node that must supply the credentials. +- _join-read-only_: user can join a cluster, but only as a read-only node. - _remove_: user can remove a node from a cluster. ### Example configuration file diff --git a/auth/credential_store.go b/auth/credential_store.go index ca6bc686..8491ff51 100644 --- a/auth/credential_store.go +++ b/auth/credential_store.go @@ -18,6 +18,8 @@ const ( 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/http/service.go b/http/service.go index cb82ce23..ae61881c 100644 --- a/http/service.go +++ b/http/service.go @@ -392,7 +392,7 @@ func (s *Service) RegisterStatus(key string, stat StatusReporter) error { // handleJoin handles cluster-join requests from other nodes. func (s *Service) handleJoin(w http.ResponseWriter, r *http.Request) { - if !s.CheckRequestPerm(r, auth.PermJoin) { + if !s.CheckRequestPerm(r, auth.PermJoin) && !s.CheckRequestPerm(r, auth.PermJoinReadOnly) { w.WriteHeader(http.StatusUnauthorized) return } @@ -430,6 +430,11 @@ func (s *Service) handleJoin(w http.ResponseWriter, r *http.Request) { voter = true } + if voter.(bool) && !!s.CheckRequestPerm(r, auth.PermJoin) { + http.Error(w, "joining as voter not allowed", http.StatusServiceUnavailable) + return + } + if err := s.store.Join(remoteID.(string), remoteAddr.(string), voter.(bool)); err != nil { if err == store.ErrNotLeader { leaderAPIAddr := s.LeaderAPIAddr()