Adds secure introduction for the ACL replication token. (#3357)
Adds secure introduction for the ACL replication token, as well as a separate enable config for ACL replication.
This commit is contained in:
parent
4c0c912a52
commit
803ed9a245
|
@ -231,6 +231,7 @@ func New(c *Config) (*Agent, error) {
|
|||
a.tokens.UpdateUserToken(a.config.ACLToken)
|
||||
a.tokens.UpdateAgentToken(a.config.ACLAgentToken)
|
||||
a.tokens.UpdateAgentMasterToken(a.config.ACLAgentMasterToken)
|
||||
a.tokens.UpdateACLReplicationToken(a.config.ACLReplicationToken)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
@ -266,7 +267,7 @@ func (a *Agent) Start() error {
|
|||
|
||||
// Setup either the client or the server.
|
||||
if c.Server {
|
||||
server, err := consul.NewServerLogger(consulCfg, a.logger)
|
||||
server, err := consul.NewServerLogger(consulCfg, a.logger, a.tokens)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to start Consul server: %v", err)
|
||||
}
|
||||
|
@ -675,9 +676,7 @@ func (a *Agent) consulConfig() (*consul.Config, error) {
|
|||
if a.config.ACLDownPolicy != "" {
|
||||
base.ACLDownPolicy = a.config.ACLDownPolicy
|
||||
}
|
||||
if a.config.ACLReplicationToken != "" {
|
||||
base.ACLReplicationToken = a.config.ACLReplicationToken
|
||||
}
|
||||
base.EnableACLReplication = a.config.EnableACLReplication
|
||||
if a.config.ACLEnforceVersion8 != nil {
|
||||
base.ACLEnforceVersion8 = *a.config.ACLEnforceVersion8
|
||||
}
|
||||
|
|
|
@ -737,6 +737,9 @@ func (s *HTTPServer) AgentToken(resp http.ResponseWriter, req *http.Request) (in
|
|||
case "acl_agent_master_token":
|
||||
s.agent.tokens.UpdateAgentMasterToken(args.Token)
|
||||
|
||||
case "acl_replication_token":
|
||||
s.agent.tokens.UpdateACLReplicationToken(args.Token)
|
||||
|
||||
default:
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(resp, "Token %q is unknown", target)
|
||||
|
|
|
@ -1664,7 +1664,18 @@ func TestAgent_Token(t *testing.T) {
|
|||
a := NewTestAgent(t.Name(), cfg)
|
||||
defer a.Shutdown()
|
||||
|
||||
b := func(token string) io.Reader {
|
||||
type tokens struct {
|
||||
user, agent, master, repl string
|
||||
}
|
||||
|
||||
resetTokens := func(got tokens) {
|
||||
a.tokens.UpdateUserToken(got.user)
|
||||
a.tokens.UpdateAgentToken(got.agent)
|
||||
a.tokens.UpdateAgentMasterToken(got.master)
|
||||
a.tokens.UpdateACLReplicationToken(got.repl)
|
||||
}
|
||||
|
||||
body := func(token string) io.Reader {
|
||||
return jsonReader(&api.AgentToken{Token: token})
|
||||
}
|
||||
|
||||
|
@ -1677,22 +1688,97 @@ func TestAgent_Token(t *testing.T) {
|
|||
method, url string
|
||||
body io.Reader
|
||||
code int
|
||||
userToken string
|
||||
agentToken string
|
||||
masterToken string
|
||||
got, want tokens
|
||||
}{
|
||||
{"bad method", "GET", "acl_token", b("X"), http.StatusMethodNotAllowed, "", "", ""},
|
||||
{"bad token name", "PUT", "nope?token=root", b("X"), http.StatusNotFound, "", "", ""},
|
||||
{"bad JSON", "PUT", "acl_token?token=root", badJSON(), http.StatusBadRequest, "", "", ""},
|
||||
{"set user", "PUT", "acl_token?token=root", b("U"), http.StatusOK, "U", "U", ""},
|
||||
{"set agent", "PUT", "acl_agent_token?token=root", b("A"), http.StatusOK, "U", "A", ""},
|
||||
{"set master", "PUT", "acl_agent_master_token?token=root", b("M"), http.StatusOK, "U", "A", "M"},
|
||||
{"clear user", "PUT", "acl_token?token=root", b(""), http.StatusOK, "", "A", "M"},
|
||||
{"clear agent", "PUT", "acl_agent_token?token=root", b(""), http.StatusOK, "", "", "M"},
|
||||
{"clear master", "PUT", "acl_agent_master_token?token=root", b(""), http.StatusOK, "", "", ""},
|
||||
{
|
||||
name: "bad method",
|
||||
method: "GET",
|
||||
url: "acl_token",
|
||||
code: http.StatusMethodNotAllowed,
|
||||
},
|
||||
{
|
||||
name: "bad token name",
|
||||
method: "PUT",
|
||||
url: "nope?token=root",
|
||||
body: body("X"),
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "bad JSON",
|
||||
method: "PUT",
|
||||
url: "acl_token?token=root",
|
||||
body: badJSON(),
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "set user",
|
||||
method: "PUT",
|
||||
url: "acl_token?token=root",
|
||||
body: body("U"),
|
||||
code: http.StatusOK,
|
||||
want: tokens{user: "U", agent: "U"},
|
||||
},
|
||||
{
|
||||
name: "set agent",
|
||||
method: "PUT",
|
||||
url: "acl_agent_token?token=root",
|
||||
body: body("A"),
|
||||
code: http.StatusOK,
|
||||
got: tokens{user: "U", agent: "U"},
|
||||
want: tokens{user: "U", agent: "A"},
|
||||
},
|
||||
{
|
||||
name: "set master",
|
||||
method: "PUT",
|
||||
url: "acl_agent_master_token?token=root",
|
||||
body: body("M"),
|
||||
code: http.StatusOK,
|
||||
want: tokens{master: "M"},
|
||||
},
|
||||
{
|
||||
name: "set repl",
|
||||
method: "PUT",
|
||||
url: "acl_replication_token?token=root",
|
||||
body: body("R"),
|
||||
code: http.StatusOK,
|
||||
want: tokens{repl: "R"},
|
||||
},
|
||||
{
|
||||
name: "clear user",
|
||||
method: "PUT",
|
||||
url: "acl_token?token=root",
|
||||
body: body(""),
|
||||
code: http.StatusOK,
|
||||
got: tokens{user: "U"},
|
||||
},
|
||||
{
|
||||
name: "clear agent",
|
||||
method: "PUT",
|
||||
url: "acl_agent_token?token=root",
|
||||
body: body(""),
|
||||
code: http.StatusOK,
|
||||
got: tokens{agent: "A"},
|
||||
},
|
||||
{
|
||||
name: "clear master",
|
||||
method: "PUT",
|
||||
url: "acl_agent_master_token?token=root",
|
||||
body: body(""),
|
||||
code: http.StatusOK,
|
||||
got: tokens{master: "M"},
|
||||
},
|
||||
{
|
||||
name: "clear repl",
|
||||
method: "PUT",
|
||||
url: "acl_replication_token?token=root",
|
||||
body: body(""),
|
||||
code: http.StatusOK,
|
||||
got: tokens{repl: "R"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetTokens(tt.got)
|
||||
url := fmt.Sprintf("/v1/agent/token/%s", tt.url)
|
||||
resp := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(tt.method, url, tt.body)
|
||||
|
@ -1702,14 +1788,17 @@ func TestAgent_Token(t *testing.T) {
|
|||
if got, want := resp.Code, tt.code; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
if got, want := a.tokens.UserToken(), tt.userToken; got != want {
|
||||
if got, want := a.tokens.UserToken(), tt.want.user; got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
if got, want := a.tokens.AgentToken(), tt.agentToken; got != want {
|
||||
if got, want := a.tokens.AgentToken(), tt.want.agent; got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
if tt.masterToken != "" && !a.tokens.IsAgentMasterToken(tt.masterToken) {
|
||||
t.Fatalf("%q should be the master token", tt.masterToken)
|
||||
if tt.want.master != "" && !a.tokens.IsAgentMasterToken(tt.want.master) {
|
||||
t.Fatalf("%q should be the master token", tt.want.master)
|
||||
}
|
||||
if got, want := a.tokens.ACLReplicationToken(), tt.want.repl; got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1717,7 +1806,8 @@ func TestAgent_Token(t *testing.T) {
|
|||
// This one returns an error that is interpreted by the HTTP wrapper, so
|
||||
// doesn't fit into our table above.
|
||||
t.Run("permission denied", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/token/acl_token", b("X"))
|
||||
resetTokens(tokens{})
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/token/acl_token", body("X"))
|
||||
if _, err := a.srv.AgentToken(nil, req); !isPermissionDenied(err) {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -696,6 +696,12 @@ type Config struct {
|
|||
// this acts like deny.
|
||||
ACLDownPolicy string `mapstructure:"acl_down_policy"`
|
||||
|
||||
// EnableACLReplication is used to turn on ACL replication when using
|
||||
// /v1/agent/token/acl_replication_token to introduce the token, instead
|
||||
// of setting acl_replication_token in the config. Setting the token via
|
||||
// config will also set this to true for backward compatibility.
|
||||
EnableACLReplication bool `mapstructure:"enable_acl_replication"`
|
||||
|
||||
// ACLReplicationToken is used to fetch ACLs from the ACLDatacenter in
|
||||
// order to replicate them locally. Setting this to a non-empty value
|
||||
// also enables replication. Replication is only available in datacenters
|
||||
|
@ -1448,6 +1454,13 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
|||
}
|
||||
result.DeprecatedHTTPAPIResponseHeaders = nil
|
||||
}
|
||||
|
||||
// Set the ACL replication enable if they set a token, for backwards
|
||||
// compatibility.
|
||||
if result.ACLReplicationToken != "" {
|
||||
result.EnableACLReplication = true
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
|
@ -2023,6 +2036,9 @@ func MergeConfig(a, b *Config) *Config {
|
|||
if b.ACLDefaultPolicy != "" {
|
||||
result.ACLDefaultPolicy = b.ACLDefaultPolicy
|
||||
}
|
||||
if b.EnableACLReplication {
|
||||
result.EnableACLReplication = true
|
||||
}
|
||||
if b.ACLReplicationToken != "" {
|
||||
result.ACLReplicationToken = b.ACLReplicationToken
|
||||
}
|
||||
|
|
|
@ -108,7 +108,10 @@ func TestDecodeConfig(t *testing.T) {
|
|||
},
|
||||
{
|
||||
in: `{"acl_replication_token":"a"}`,
|
||||
c: &Config{ACLReplicationToken: "a"},
|
||||
c: &Config{
|
||||
EnableACLReplication: true,
|
||||
ACLReplicationToken: "a",
|
||||
},
|
||||
},
|
||||
{
|
||||
in: `{"acl_token":"a"}`,
|
||||
|
@ -366,6 +369,10 @@ func TestDecodeConfig(t *testing.T) {
|
|||
in: `{"domain":"a"}`,
|
||||
c: &Config{Domain: "a"},
|
||||
},
|
||||
{
|
||||
in: `{"enable_acl_replication":true}`,
|
||||
c: &Config{EnableACLReplication: true},
|
||||
},
|
||||
{
|
||||
in: `{"enable_debug":true}`,
|
||||
c: &Config{EnableDebug: true},
|
||||
|
|
|
@ -535,9 +535,10 @@ func TestACLEndpoint_ReplicationStatus(t *testing.T) {
|
|||
t.Parallel()
|
||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.ACLDatacenter = "dc2"
|
||||
c.ACLReplicationToken = "secret"
|
||||
c.EnableACLReplication = true
|
||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||
})
|
||||
s1.tokens.UpdateACLReplicationToken("secret")
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
|
|
|
@ -154,7 +154,7 @@ func (s *Server) fetchRemoteACLs(lastRemoteIndex uint64) (*structs.IndexedACLs,
|
|||
args := structs.DCSpecificRequest{
|
||||
Datacenter: s.config.ACLDatacenter,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Token: s.config.ACLReplicationToken,
|
||||
Token: s.tokens.ACLReplicationToken(),
|
||||
MinQueryIndex: lastRemoteIndex,
|
||||
AllowStale: true,
|
||||
},
|
||||
|
@ -247,7 +247,7 @@ func (s *Server) replicateACLs(lastRemoteIndex uint64) (uint64, error) {
|
|||
func (s *Server) IsACLReplicationEnabled() bool {
|
||||
authDC := s.config.ACLDatacenter
|
||||
return len(authDC) > 0 && (authDC != s.config.Datacenter) &&
|
||||
len(s.config.ACLReplicationToken) > 0
|
||||
s.config.EnableACLReplication
|
||||
}
|
||||
|
||||
// updateACLReplicationStatus safely updates the ACL replication status.
|
||||
|
|
|
@ -228,9 +228,9 @@ func TestACLReplication_updateLocalACLs_RateLimit(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc2"
|
||||
c.ACLDatacenter = "dc1"
|
||||
c.ACLReplicationToken = "secret"
|
||||
c.ACLReplicationApplyLimit = 1
|
||||
})
|
||||
s1.tokens.UpdateACLReplicationToken("secret")
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
testrpc.WaitForLeader(t, s1.RPC, "dc2")
|
||||
|
@ -300,7 +300,7 @@ func TestACLReplication_IsACLReplicationEnabled(t *testing.T) {
|
|||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc2"
|
||||
c.ACLDatacenter = "dc1"
|
||||
c.ACLReplicationToken = "secret"
|
||||
c.EnableACLReplication = true
|
||||
})
|
||||
defer os.RemoveAll(dir3)
|
||||
defer s3.Shutdown()
|
||||
|
@ -308,12 +308,12 @@ func TestACLReplication_IsACLReplicationEnabled(t *testing.T) {
|
|||
t.Fatalf("should be enabled")
|
||||
}
|
||||
|
||||
// ACLs enabled and replication token set, but inside the ACL datacenter
|
||||
// ACLs enabled with replication, but inside the ACL datacenter
|
||||
// so replication should be disabled.
|
||||
dir4, s4 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc1"
|
||||
c.ACLDatacenter = "dc1"
|
||||
c.ACLReplicationToken = "secret"
|
||||
c.EnableACLReplication = true
|
||||
})
|
||||
defer os.RemoveAll(dir4)
|
||||
defer s4.Shutdown()
|
||||
|
@ -336,10 +336,11 @@ func TestACLReplication(t *testing.T) {
|
|||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc2"
|
||||
c.ACLDatacenter = "dc1"
|
||||
c.ACLReplicationToken = "root"
|
||||
c.EnableACLReplication = true
|
||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||
c.ACLReplicationApplyLimit = 1000000
|
||||
})
|
||||
s2.tokens.UpdateACLReplicationToken("root")
|
||||
defer os.RemoveAll(dir2)
|
||||
defer s2.Shutdown()
|
||||
|
||||
|
|
|
@ -598,10 +598,11 @@ func TestACL_Replication(t *testing.T) {
|
|||
c.ACLDatacenter = "dc1"
|
||||
c.ACLDefaultPolicy = "deny"
|
||||
c.ACLDownPolicy = "extend-cache"
|
||||
c.ACLReplicationToken = "root"
|
||||
c.EnableACLReplication = true
|
||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||
c.ACLReplicationApplyLimit = 1000000
|
||||
})
|
||||
s2.tokens.UpdateACLReplicationToken("root")
|
||||
defer os.RemoveAll(dir2)
|
||||
defer s2.Shutdown()
|
||||
|
||||
|
@ -609,10 +610,11 @@ func TestACL_Replication(t *testing.T) {
|
|||
c.Datacenter = "dc3"
|
||||
c.ACLDatacenter = "dc1"
|
||||
c.ACLDownPolicy = "deny"
|
||||
c.ACLReplicationToken = "root"
|
||||
c.EnableACLReplication = true
|
||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||
c.ACLReplicationApplyLimit = 1000000
|
||||
})
|
||||
s3.tokens.UpdateACLReplicationToken("root")
|
||||
defer os.RemoveAll(dir3)
|
||||
defer s3.Shutdown()
|
||||
|
||||
|
|
|
@ -218,11 +218,8 @@ type Config struct {
|
|||
// "allow" can be used to allow all requests. This is not recommended.
|
||||
ACLDownPolicy string
|
||||
|
||||
// ACLReplicationToken is used to fetch ACLs from the ACLDatacenter in
|
||||
// order to replicate them locally. Setting this to a non-empty value
|
||||
// also enables replication. Replication is only available in datacenters
|
||||
// other than the ACLDatacenter.
|
||||
ACLReplicationToken string
|
||||
// EnableACLReplication is used to control ACL replication.
|
||||
EnableACLReplication bool
|
||||
|
||||
// ACLReplicationInterval is the interval at which replication passes
|
||||
// will occur. Queries to the ACLDatacenter may block, so replication
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/hashicorp/consul/agent/consul/state"
|
||||
"github.com/hashicorp/consul/agent/consul/structs"
|
||||
"github.com/hashicorp/consul/agent/pool"
|
||||
"github.com/hashicorp/consul/agent/token"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/consul/types"
|
||||
|
@ -99,6 +100,11 @@ type Server struct {
|
|||
// Consul configuration
|
||||
config *Config
|
||||
|
||||
// tokens holds ACL tokens initially from the configuration, but can
|
||||
// be updated at runtime, so should always be used instead of going to
|
||||
// the configuration directly.
|
||||
tokens *token.Store
|
||||
|
||||
// Connection pool to other consul servers
|
||||
connPool *pool.ConnPool
|
||||
|
||||
|
@ -215,12 +221,12 @@ type endpoints struct {
|
|||
}
|
||||
|
||||
func NewServer(config *Config) (*Server, error) {
|
||||
return NewServerLogger(config, nil)
|
||||
return NewServerLogger(config, nil, new(token.Store))
|
||||
}
|
||||
|
||||
// NewServer is used to construct a new Consul server from the
|
||||
// configuration, potentially returning an error
|
||||
func NewServerLogger(config *Config, logger *log.Logger) (*Server, error) {
|
||||
func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store) (*Server, error) {
|
||||
// Check the protocol version.
|
||||
if err := config.CheckProtocolVersion(); err != nil {
|
||||
return nil, err
|
||||
|
@ -285,6 +291,7 @@ func NewServerLogger(config *Config, logger *log.Logger) (*Server, error) {
|
|||
autopilotRemoveDeadCh: make(chan struct{}),
|
||||
autopilotShutdownCh: make(chan struct{}),
|
||||
config: config,
|
||||
tokens: tokens,
|
||||
connPool: connPool,
|
||||
eventChLAN: make(chan serf.Event, 256),
|
||||
eventChWAN: make(chan serf.Event, 256),
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/agent/consul/agent"
|
||||
"github.com/hashicorp/consul/agent/token"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/consul/testutil"
|
||||
"github.com/hashicorp/consul/testutil/retry"
|
||||
|
@ -146,7 +147,7 @@ func newServer(c *Config) (*Server, error) {
|
|||
w = os.Stderr
|
||||
}
|
||||
logger := log.New(w, c.NodeName+" - ", log.LstdFlags|log.Lmicroseconds)
|
||||
srv, err := NewServerLogger(c, logger)
|
||||
srv, err := NewServerLogger(c, logger, new(token.Store))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ type Store struct {
|
|||
// access to the /v1/agent utility operations if the servers aren't
|
||||
// available.
|
||||
agentMasterToken string
|
||||
|
||||
// aclReplicationToken is a special token that's used by servers to
|
||||
// replicate ACLs from the ACL datacenter.
|
||||
aclReplicationToken string
|
||||
}
|
||||
|
||||
// UpdateUserToken replaces the current user token in the store.
|
||||
|
@ -49,6 +53,13 @@ func (t *Store) UpdateAgentMasterToken(token string) {
|
|||
t.l.Unlock()
|
||||
}
|
||||
|
||||
// UpdateACLReplicationToken replaces the current ACL replication token in the store.
|
||||
func (t *Store) UpdateACLReplicationToken(token string) {
|
||||
t.l.Lock()
|
||||
t.aclReplicationToken = token
|
||||
t.l.Unlock()
|
||||
}
|
||||
|
||||
// UserToken returns the best token to use for user operations.
|
||||
func (t *Store) UserToken() string {
|
||||
t.l.RLock()
|
||||
|
@ -68,6 +79,14 @@ func (t *Store) AgentToken() string {
|
|||
return t.userToken
|
||||
}
|
||||
|
||||
// ACLReplicationToken returns the ACL replication token.
|
||||
func (t *Store) ACLReplicationToken() string {
|
||||
t.l.RLock()
|
||||
defer t.l.RUnlock()
|
||||
|
||||
return t.aclReplicationToken
|
||||
}
|
||||
|
||||
// IsAgentMasterToken checks to see if a given token is the agent master token.
|
||||
// This will never match an empty token for safety.
|
||||
func (t *Store) IsAgentMasterToken(token string) bool {
|
||||
|
|
|
@ -4,40 +4,69 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestStore_UserAndAgentTokens(t *testing.T) {
|
||||
func TestStore_RegularTokens(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
user, agent, wantUser, wantAgent string
|
||||
}{
|
||||
{"", "", "", ""},
|
||||
{"user", "", "user", "user"},
|
||||
{"user", "agent", "user", "agent"},
|
||||
{"", "agent", "", "agent"},
|
||||
{"user", "agent", "user", "agent"},
|
||||
{"user", "", "user", "user"},
|
||||
{"", "", "", ""},
|
||||
type tokens struct {
|
||||
user, agent, repl string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
set, want tokens
|
||||
}{
|
||||
{
|
||||
name: "set user",
|
||||
set: tokens{user: "U"},
|
||||
want: tokens{user: "U", agent: "U"},
|
||||
},
|
||||
{
|
||||
name: "set agent",
|
||||
set: tokens{agent: "A"},
|
||||
want: tokens{agent: "A"},
|
||||
},
|
||||
{
|
||||
name: "set user and agent",
|
||||
set: tokens{agent: "A", user: "U"},
|
||||
want: tokens{agent: "A", user: "U"},
|
||||
},
|
||||
{
|
||||
name: "set repl",
|
||||
set: tokens{repl: "R"},
|
||||
want: tokens{repl: "R"},
|
||||
},
|
||||
{
|
||||
name: "set all",
|
||||
set: tokens{user: "U", agent: "A", repl: "R"},
|
||||
want: tokens{user: "U", agent: "A", repl: "R"},
|
||||
},
|
||||
}
|
||||
tokens := new(Store)
|
||||
for _, tt := range tests {
|
||||
tokens.UpdateUserToken(tt.user)
|
||||
tokens.UpdateAgentToken(tt.agent)
|
||||
if got, want := tokens.UserToken(), tt.wantUser; got != want {
|
||||
t.Fatalf("got token %q want %q", got, want)
|
||||
}
|
||||
if got, want := tokens.AgentToken(), tt.wantAgent; got != want {
|
||||
t.Fatalf("got token %q want %q", got, want)
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := new(Store)
|
||||
s.UpdateUserToken(tt.set.user)
|
||||
s.UpdateAgentToken(tt.set.agent)
|
||||
s.UpdateACLReplicationToken(tt.set.repl)
|
||||
if got, want := s.UserToken(), tt.want.user; got != want {
|
||||
t.Fatalf("got token %q want %q", got, want)
|
||||
}
|
||||
if got, want := s.AgentToken(), tt.want.agent; got != want {
|
||||
t.Fatalf("got token %q want %q", got, want)
|
||||
}
|
||||
if got, want := s.ACLReplicationToken(), tt.want.repl; got != want {
|
||||
t.Fatalf("got token %q want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AgentMasterToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
tokens := new(Store)
|
||||
s := new(Store)
|
||||
|
||||
verify := func(want bool, toks ...string) {
|
||||
for _, tok := range toks {
|
||||
if got := tokens.IsAgentMasterToken(tok); got != want {
|
||||
if got := s.IsAgentMasterToken(tok); got != want {
|
||||
t.Fatalf("token %q got %v want %v", tok, got, want)
|
||||
}
|
||||
}
|
||||
|
@ -45,14 +74,14 @@ func TestStore_AgentMasterToken(t *testing.T) {
|
|||
|
||||
verify(false, "", "nope")
|
||||
|
||||
tokens.UpdateAgentMasterToken("master")
|
||||
s.UpdateAgentMasterToken("master")
|
||||
verify(true, "master")
|
||||
verify(false, "", "nope")
|
||||
|
||||
tokens.UpdateAgentMasterToken("another")
|
||||
s.UpdateAgentMasterToken("another")
|
||||
verify(true, "another")
|
||||
verify(false, "", "nope", "master")
|
||||
|
||||
tokens.UpdateAgentMasterToken("")
|
||||
s.UpdateAgentMasterToken("")
|
||||
verify(false, "", "nope", "master", "another")
|
||||
}
|
||||
|
|
|
@ -497,6 +497,12 @@ func (c *Agent) UpdateACLAgentMasterToken(token string, q *WriteOptions) (*Write
|
|||
return c.updateToken("acl_agent_master_token", token, q)
|
||||
}
|
||||
|
||||
// UpdateACLReplicationToken updates the agent's "acl_replication_token". See
|
||||
// updateToken for more details.
|
||||
func (c *Agent) UpdateACLReplicationToken(token string, q *WriteOptions) (*WriteMeta, error) {
|
||||
return c.updateToken("acl_replication_token", token, q)
|
||||
}
|
||||
|
||||
// updateToken can be used to update an agent's ACL token after the agent has
|
||||
// started. The tokens are not persisted, so will need to be updated again if
|
||||
// the agent is restarted.
|
||||
|
|
|
@ -785,4 +785,8 @@ func TestAPI_AgentUpdateToken(t *testing.T) {
|
|||
if _, err := agent.UpdateACLAgentMasterToken("root", nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if _, err := agent.UpdateACLReplicationToken("root", nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -395,11 +395,12 @@ not persisted, so will need to be updated again if the agent is restarted.
|
|||
| `PUT` | `/agent/token/acl_token` | `application/json` |
|
||||
| `PUT` | `/agent/token/acl_agent_token` | `application/json` |
|
||||
| `PUT` | `/agent/token/acl_agent_master_token` | `application/json` |
|
||||
| `PUT` | `/agent/token/acl_replication_token` | `application/json` |
|
||||
|
||||
The paths above correspond to the token names as found in the agent configuration,
|
||||
[`acl_token`](/docs/agent/options.html#acl_token),
|
||||
[`acl_agent_token`](/docs/agent/options.html#acl_agent_token),
|
||||
and [`acl_agent_master_token`](/docs/agent/options.html#acl_agent_master_token).
|
||||
The paths above correspond to the token names as found in the agent configuration:
|
||||
[`acl_token`](/docs/agent/options.html#acl_token), [`acl_agent_token`](/docs/agent/options.html#acl_agent_token),
|
||||
[`acl_agent_master_token`](/docs/agent/options.html#acl_agent_master_token), and
|
||||
[`acl_replication_token`](/docs/agent/options.html#acl_replication_token).
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries),
|
||||
|
|
|
@ -158,7 +158,7 @@ will exit with an error at startup.
|
|||
in the "consul." domain. This flag can be used to change that domain. All queries in this domain
|
||||
are assumed to be handled by Consul and will not be recursively resolved.
|
||||
|
||||
* <a name="_enable_script_checks"></a><a href="#_enable_script_checks">`enable-script-checks`</a> This
|
||||
* <a name="_enable_script_checks"></a><a href="#_enable_script_checks">`-enable-script-checks`</a> This
|
||||
controls whether [health checks that execute scripts](/docs/agent/checks.html) are enabled on
|
||||
this agent, and defaults to `false` so operators must opt-in to allowing these. If enabled,
|
||||
it is recommended to [enable ACLs](/docs/guides/acl.html) as well to control which users are
|
||||
|
@ -583,7 +583,11 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
* <a name="acl_replication_token"></a><a href="#acl_replication_token">`acl_replication_token`</a> -
|
||||
Only used for servers outside the [`acl_datacenter`](#acl_datacenter) running Consul 0.7 or later.
|
||||
When provided, this will enable [ACL replication](/docs/guides/acl.html#replication) using this
|
||||
token to retrieve and replicate the ACLs to the non-authoritative local datacenter.
|
||||
token to retrieve and replicate the ACLs to the non-authoritative local datacenter. In Consul 0.9.1
|
||||
and later you can enable ACL replication using [`enable_acl_replication`](#enable_acl_replication)
|
||||
and then set the token later using the [agent token API](/api/agent.html#update-acl-tokens) on each
|
||||
server. If the `acl_replication_token` is set in the config, it will automatically set
|
||||
[`enable_acl_replication`](#enable_acl_replication) to true for backward compatibility.
|
||||
<br><br>
|
||||
If there's a partition or other outage affecting the authoritative datacenter, and the
|
||||
[`acl_down_policy`](/docs/agent/options.html#acl_down_policy) is set to "extend-cache", tokens not
|
||||
|
@ -803,6 +807,12 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
|
||||
[`-domain` command-line flag](#_domain).
|
||||
|
||||
* <a name="enable_acl_replication"></a><a href="#enable_acl_replication">`enable_acl_replication`</a> When
|
||||
set on a Consul server, enables [ACL replication](/docs/guides/acl.html#replication) without having to set
|
||||
the replication token via [`acl_replication_token`](#acl_replication_token). Instead, enable ACL replication
|
||||
and then introduce the token using the [agent token API](/api/agent.html#update-acl-tokens) on each server.
|
||||
See [`acl_replication_token`](#acl_replication_token) for more details.
|
||||
|
||||
* <a name="enable_debug"></a><a href="#enable_debug">`enable_debug`</a> When set, enables some
|
||||
additional debugging features. Currently, this is only used to set the runtime profiling HTTP endpoints.
|
||||
|
||||
|
|
|
@ -969,11 +969,17 @@ for any previously resolved tokens and to deny any uncached tokens.
|
|||
Consul 0.7 added an ACL Replication capability that can allow non-authoritative
|
||||
datacenter agents to resolve even uncached tokens. This is enabled by setting an
|
||||
[`acl_replication_token`](/docs/agent/options.html#acl_replication_token) in the
|
||||
configuration on the servers in the non-authoritative datacenters. With replication
|
||||
enabled, the servers will maintain a replica of the authoritative datacenter's full
|
||||
set of ACLs on the non-authoritative servers. The ACL replication token needs to be
|
||||
a valid ACL token with management privileges, it can also be the same as the master
|
||||
ACL token.
|
||||
configuration on the servers in the non-authoritative datacenters. In Consul
|
||||
0.9.1 and later you can enable ACL replication using
|
||||
[`enable_acl_replication`](/docs/agent/options.html#enable_acl_replication) and
|
||||
then set the token later using the
|
||||
[agent token API](/api/agent.html#update-acl-tokens) on each server. This can
|
||||
also be used to rotate the token without restarting the Consul servers.
|
||||
|
||||
With replication enabled, the servers will maintain a replica of the authoritative
|
||||
datacenter's full set of ACLs on the non-authoritative servers. The ACL replication
|
||||
token needs to be a valid ACL token with management privileges, it can also be the
|
||||
same as the master ACL token.
|
||||
|
||||
Replication occurs with a background process that looks for new ACLs approximately
|
||||
every 30 seconds. Replicated changes are written at a rate that's throttled to
|
||||
|
|
Loading…
Reference in New Issue