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.UpdateUserToken(a.config.ACLToken)
|
||||||
a.tokens.UpdateAgentToken(a.config.ACLAgentToken)
|
a.tokens.UpdateAgentToken(a.config.ACLAgentToken)
|
||||||
a.tokens.UpdateAgentMasterToken(a.config.ACLAgentMasterToken)
|
a.tokens.UpdateAgentMasterToken(a.config.ACLAgentMasterToken)
|
||||||
|
a.tokens.UpdateACLReplicationToken(a.config.ACLReplicationToken)
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
@ -266,7 +267,7 @@ func (a *Agent) Start() error {
|
||||||
|
|
||||||
// Setup either the client or the server.
|
// Setup either the client or the server.
|
||||||
if c.Server {
|
if c.Server {
|
||||||
server, err := consul.NewServerLogger(consulCfg, a.logger)
|
server, err := consul.NewServerLogger(consulCfg, a.logger, a.tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to start Consul server: %v", err)
|
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 != "" {
|
if a.config.ACLDownPolicy != "" {
|
||||||
base.ACLDownPolicy = a.config.ACLDownPolicy
|
base.ACLDownPolicy = a.config.ACLDownPolicy
|
||||||
}
|
}
|
||||||
if a.config.ACLReplicationToken != "" {
|
base.EnableACLReplication = a.config.EnableACLReplication
|
||||||
base.ACLReplicationToken = a.config.ACLReplicationToken
|
|
||||||
}
|
|
||||||
if a.config.ACLEnforceVersion8 != nil {
|
if a.config.ACLEnforceVersion8 != nil {
|
||||||
base.ACLEnforceVersion8 = *a.config.ACLEnforceVersion8
|
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":
|
case "acl_agent_master_token":
|
||||||
s.agent.tokens.UpdateAgentMasterToken(args.Token)
|
s.agent.tokens.UpdateAgentMasterToken(args.Token)
|
||||||
|
|
||||||
|
case "acl_replication_token":
|
||||||
|
s.agent.tokens.UpdateACLReplicationToken(args.Token)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
resp.WriteHeader(http.StatusNotFound)
|
resp.WriteHeader(http.StatusNotFound)
|
||||||
fmt.Fprintf(resp, "Token %q is unknown", target)
|
fmt.Fprintf(resp, "Token %q is unknown", target)
|
||||||
|
|
|
@ -1664,7 +1664,18 @@ func TestAgent_Token(t *testing.T) {
|
||||||
a := NewTestAgent(t.Name(), cfg)
|
a := NewTestAgent(t.Name(), cfg)
|
||||||
defer a.Shutdown()
|
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})
|
return jsonReader(&api.AgentToken{Token: token})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1677,22 +1688,97 @@ func TestAgent_Token(t *testing.T) {
|
||||||
method, url string
|
method, url string
|
||||||
body io.Reader
|
body io.Reader
|
||||||
code int
|
code int
|
||||||
userToken string
|
got, want tokens
|
||||||
agentToken string
|
|
||||||
masterToken string
|
|
||||||
}{
|
}{
|
||||||
{"bad method", "GET", "acl_token", b("X"), http.StatusMethodNotAllowed, "", "", ""},
|
{
|
||||||
{"bad token name", "PUT", "nope?token=root", b("X"), http.StatusNotFound, "", "", ""},
|
name: "bad method",
|
||||||
{"bad JSON", "PUT", "acl_token?token=root", badJSON(), http.StatusBadRequest, "", "", ""},
|
method: "GET",
|
||||||
{"set user", "PUT", "acl_token?token=root", b("U"), http.StatusOK, "U", "U", ""},
|
url: "acl_token",
|
||||||
{"set agent", "PUT", "acl_agent_token?token=root", b("A"), http.StatusOK, "U", "A", ""},
|
code: http.StatusMethodNotAllowed,
|
||||||
{"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"},
|
name: "bad token name",
|
||||||
{"clear master", "PUT", "acl_agent_master_token?token=root", b(""), http.StatusOK, "", "", ""},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resetTokens(tt.got)
|
||||||
url := fmt.Sprintf("/v1/agent/token/%s", tt.url)
|
url := fmt.Sprintf("/v1/agent/token/%s", tt.url)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(tt.method, url, tt.body)
|
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 {
|
if got, want := resp.Code, tt.code; got != want {
|
||||||
t.Fatalf("got %d want %d", 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)
|
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)
|
t.Fatalf("got %q want %q", got, want)
|
||||||
}
|
}
|
||||||
if tt.masterToken != "" && !a.tokens.IsAgentMasterToken(tt.masterToken) {
|
if tt.want.master != "" && !a.tokens.IsAgentMasterToken(tt.want.master) {
|
||||||
t.Fatalf("%q should be the master token", tt.masterToken)
|
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
|
// This one returns an error that is interpreted by the HTTP wrapper, so
|
||||||
// doesn't fit into our table above.
|
// doesn't fit into our table above.
|
||||||
t.Run("permission denied", func(t *testing.T) {
|
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) {
|
if _, err := a.srv.AgentToken(nil, req); !isPermissionDenied(err) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -696,6 +696,12 @@ type Config struct {
|
||||||
// this acts like deny.
|
// this acts like deny.
|
||||||
ACLDownPolicy string `mapstructure:"acl_down_policy"`
|
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
|
// ACLReplicationToken is used to fetch ACLs from the ACLDatacenter in
|
||||||
// order to replicate them locally. Setting this to a non-empty value
|
// order to replicate them locally. Setting this to a non-empty value
|
||||||
// also enables replication. Replication is only available in datacenters
|
// also enables replication. Replication is only available in datacenters
|
||||||
|
@ -1448,6 +1454,13 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
||||||
}
|
}
|
||||||
result.DeprecatedHTTPAPIResponseHeaders = nil
|
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
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2023,6 +2036,9 @@ func MergeConfig(a, b *Config) *Config {
|
||||||
if b.ACLDefaultPolicy != "" {
|
if b.ACLDefaultPolicy != "" {
|
||||||
result.ACLDefaultPolicy = b.ACLDefaultPolicy
|
result.ACLDefaultPolicy = b.ACLDefaultPolicy
|
||||||
}
|
}
|
||||||
|
if b.EnableACLReplication {
|
||||||
|
result.EnableACLReplication = true
|
||||||
|
}
|
||||||
if b.ACLReplicationToken != "" {
|
if b.ACLReplicationToken != "" {
|
||||||
result.ACLReplicationToken = b.ACLReplicationToken
|
result.ACLReplicationToken = b.ACLReplicationToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,10 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"acl_replication_token":"a"}`,
|
in: `{"acl_replication_token":"a"}`,
|
||||||
c: &Config{ACLReplicationToken: "a"},
|
c: &Config{
|
||||||
|
EnableACLReplication: true,
|
||||||
|
ACLReplicationToken: "a",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"acl_token":"a"}`,
|
in: `{"acl_token":"a"}`,
|
||||||
|
@ -366,6 +369,10 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
in: `{"domain":"a"}`,
|
in: `{"domain":"a"}`,
|
||||||
c: &Config{Domain: "a"},
|
c: &Config{Domain: "a"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
in: `{"enable_acl_replication":true}`,
|
||||||
|
c: &Config{EnableACLReplication: true},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
in: `{"enable_debug":true}`,
|
in: `{"enable_debug":true}`,
|
||||||
c: &Config{EnableDebug: true},
|
c: &Config{EnableDebug: true},
|
||||||
|
|
|
@ -535,9 +535,10 @@ func TestACLEndpoint_ReplicationStatus(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.ACLDatacenter = "dc2"
|
c.ACLDatacenter = "dc2"
|
||||||
c.ACLReplicationToken = "secret"
|
c.EnableACLReplication = true
|
||||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||||
})
|
})
|
||||||
|
s1.tokens.UpdateACLReplicationToken("secret")
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
|
|
|
@ -154,7 +154,7 @@ func (s *Server) fetchRemoteACLs(lastRemoteIndex uint64) (*structs.IndexedACLs,
|
||||||
args := structs.DCSpecificRequest{
|
args := structs.DCSpecificRequest{
|
||||||
Datacenter: s.config.ACLDatacenter,
|
Datacenter: s.config.ACLDatacenter,
|
||||||
QueryOptions: structs.QueryOptions{
|
QueryOptions: structs.QueryOptions{
|
||||||
Token: s.config.ACLReplicationToken,
|
Token: s.tokens.ACLReplicationToken(),
|
||||||
MinQueryIndex: lastRemoteIndex,
|
MinQueryIndex: lastRemoteIndex,
|
||||||
AllowStale: true,
|
AllowStale: true,
|
||||||
},
|
},
|
||||||
|
@ -247,7 +247,7 @@ func (s *Server) replicateACLs(lastRemoteIndex uint64) (uint64, error) {
|
||||||
func (s *Server) IsACLReplicationEnabled() bool {
|
func (s *Server) IsACLReplicationEnabled() bool {
|
||||||
authDC := s.config.ACLDatacenter
|
authDC := s.config.ACLDatacenter
|
||||||
return len(authDC) > 0 && (authDC != s.config.Datacenter) &&
|
return len(authDC) > 0 && (authDC != s.config.Datacenter) &&
|
||||||
len(s.config.ACLReplicationToken) > 0
|
s.config.EnableACLReplication
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateACLReplicationStatus safely updates the ACL replication status.
|
// 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) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.Datacenter = "dc2"
|
c.Datacenter = "dc2"
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLReplicationToken = "secret"
|
|
||||||
c.ACLReplicationApplyLimit = 1
|
c.ACLReplicationApplyLimit = 1
|
||||||
})
|
})
|
||||||
|
s1.tokens.UpdateACLReplicationToken("secret")
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc2")
|
testrpc.WaitForLeader(t, s1.RPC, "dc2")
|
||||||
|
@ -300,7 +300,7 @@ func TestACLReplication_IsACLReplicationEnabled(t *testing.T) {
|
||||||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.Datacenter = "dc2"
|
c.Datacenter = "dc2"
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLReplicationToken = "secret"
|
c.EnableACLReplication = true
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir3)
|
defer os.RemoveAll(dir3)
|
||||||
defer s3.Shutdown()
|
defer s3.Shutdown()
|
||||||
|
@ -308,12 +308,12 @@ func TestACLReplication_IsACLReplicationEnabled(t *testing.T) {
|
||||||
t.Fatalf("should be enabled")
|
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.
|
// so replication should be disabled.
|
||||||
dir4, s4 := testServerWithConfig(t, func(c *Config) {
|
dir4, s4 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.Datacenter = "dc1"
|
c.Datacenter = "dc1"
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLReplicationToken = "secret"
|
c.EnableACLReplication = true
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir4)
|
defer os.RemoveAll(dir4)
|
||||||
defer s4.Shutdown()
|
defer s4.Shutdown()
|
||||||
|
@ -336,10 +336,11 @@ func TestACLReplication(t *testing.T) {
|
||||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.Datacenter = "dc2"
|
c.Datacenter = "dc2"
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLReplicationToken = "root"
|
c.EnableACLReplication = true
|
||||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||||
c.ACLReplicationApplyLimit = 1000000
|
c.ACLReplicationApplyLimit = 1000000
|
||||||
})
|
})
|
||||||
|
s2.tokens.UpdateACLReplicationToken("root")
|
||||||
defer os.RemoveAll(dir2)
|
defer os.RemoveAll(dir2)
|
||||||
defer s2.Shutdown()
|
defer s2.Shutdown()
|
||||||
|
|
||||||
|
|
|
@ -598,10 +598,11 @@ func TestACL_Replication(t *testing.T) {
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLDefaultPolicy = "deny"
|
c.ACLDefaultPolicy = "deny"
|
||||||
c.ACLDownPolicy = "extend-cache"
|
c.ACLDownPolicy = "extend-cache"
|
||||||
c.ACLReplicationToken = "root"
|
c.EnableACLReplication = true
|
||||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||||
c.ACLReplicationApplyLimit = 1000000
|
c.ACLReplicationApplyLimit = 1000000
|
||||||
})
|
})
|
||||||
|
s2.tokens.UpdateACLReplicationToken("root")
|
||||||
defer os.RemoveAll(dir2)
|
defer os.RemoveAll(dir2)
|
||||||
defer s2.Shutdown()
|
defer s2.Shutdown()
|
||||||
|
|
||||||
|
@ -609,10 +610,11 @@ func TestACL_Replication(t *testing.T) {
|
||||||
c.Datacenter = "dc3"
|
c.Datacenter = "dc3"
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLDownPolicy = "deny"
|
c.ACLDownPolicy = "deny"
|
||||||
c.ACLReplicationToken = "root"
|
c.EnableACLReplication = true
|
||||||
c.ACLReplicationInterval = 10 * time.Millisecond
|
c.ACLReplicationInterval = 10 * time.Millisecond
|
||||||
c.ACLReplicationApplyLimit = 1000000
|
c.ACLReplicationApplyLimit = 1000000
|
||||||
})
|
})
|
||||||
|
s3.tokens.UpdateACLReplicationToken("root")
|
||||||
defer os.RemoveAll(dir3)
|
defer os.RemoveAll(dir3)
|
||||||
defer s3.Shutdown()
|
defer s3.Shutdown()
|
||||||
|
|
||||||
|
|
|
@ -218,11 +218,8 @@ type Config struct {
|
||||||
// "allow" can be used to allow all requests. This is not recommended.
|
// "allow" can be used to allow all requests. This is not recommended.
|
||||||
ACLDownPolicy string
|
ACLDownPolicy string
|
||||||
|
|
||||||
// ACLReplicationToken is used to fetch ACLs from the ACLDatacenter in
|
// EnableACLReplication is used to control ACL replication.
|
||||||
// order to replicate them locally. Setting this to a non-empty value
|
EnableACLReplication bool
|
||||||
// also enables replication. Replication is only available in datacenters
|
|
||||||
// other than the ACLDatacenter.
|
|
||||||
ACLReplicationToken string
|
|
||||||
|
|
||||||
// ACLReplicationInterval is the interval at which replication passes
|
// ACLReplicationInterval is the interval at which replication passes
|
||||||
// will occur. Queries to the ACLDatacenter may block, so replication
|
// 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/state"
|
||||||
"github.com/hashicorp/consul/agent/consul/structs"
|
"github.com/hashicorp/consul/agent/consul/structs"
|
||||||
"github.com/hashicorp/consul/agent/pool"
|
"github.com/hashicorp/consul/agent/pool"
|
||||||
|
"github.com/hashicorp/consul/agent/token"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
|
@ -99,6 +100,11 @@ type Server struct {
|
||||||
// Consul configuration
|
// Consul configuration
|
||||||
config *Config
|
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
|
// Connection pool to other consul servers
|
||||||
connPool *pool.ConnPool
|
connPool *pool.ConnPool
|
||||||
|
|
||||||
|
@ -215,12 +221,12 @@ type endpoints struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *Config) (*Server, error) {
|
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
|
// NewServer is used to construct a new Consul server from the
|
||||||
// configuration, potentially returning an error
|
// 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.
|
// Check the protocol version.
|
||||||
if err := config.CheckProtocolVersion(); err != nil {
|
if err := config.CheckProtocolVersion(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -285,6 +291,7 @@ func NewServerLogger(config *Config, logger *log.Logger) (*Server, error) {
|
||||||
autopilotRemoveDeadCh: make(chan struct{}),
|
autopilotRemoveDeadCh: make(chan struct{}),
|
||||||
autopilotShutdownCh: make(chan struct{}),
|
autopilotShutdownCh: make(chan struct{}),
|
||||||
config: config,
|
config: config,
|
||||||
|
tokens: tokens,
|
||||||
connPool: connPool,
|
connPool: connPool,
|
||||||
eventChLAN: make(chan serf.Event, 256),
|
eventChLAN: make(chan serf.Event, 256),
|
||||||
eventChWAN: make(chan serf.Event, 256),
|
eventChWAN: make(chan serf.Event, 256),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/consul/agent"
|
"github.com/hashicorp/consul/agent/consul/agent"
|
||||||
|
"github.com/hashicorp/consul/agent/token"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
"github.com/hashicorp/consul/testutil"
|
"github.com/hashicorp/consul/testutil"
|
||||||
"github.com/hashicorp/consul/testutil/retry"
|
"github.com/hashicorp/consul/testutil/retry"
|
||||||
|
@ -146,7 +147,7 @@ func newServer(c *Config) (*Server, error) {
|
||||||
w = os.Stderr
|
w = os.Stderr
|
||||||
}
|
}
|
||||||
logger := log.New(w, c.NodeName+" - ", log.LstdFlags|log.Lmicroseconds)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ type Store struct {
|
||||||
// access to the /v1/agent utility operations if the servers aren't
|
// access to the /v1/agent utility operations if the servers aren't
|
||||||
// available.
|
// available.
|
||||||
agentMasterToken string
|
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.
|
// UpdateUserToken replaces the current user token in the store.
|
||||||
|
@ -49,6 +53,13 @@ func (t *Store) UpdateAgentMasterToken(token string) {
|
||||||
t.l.Unlock()
|
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.
|
// UserToken returns the best token to use for user operations.
|
||||||
func (t *Store) UserToken() string {
|
func (t *Store) UserToken() string {
|
||||||
t.l.RLock()
|
t.l.RLock()
|
||||||
|
@ -68,6 +79,14 @@ func (t *Store) AgentToken() string {
|
||||||
return t.userToken
|
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.
|
// IsAgentMasterToken checks to see if a given token is the agent master token.
|
||||||
// This will never match an empty token for safety.
|
// This will never match an empty token for safety.
|
||||||
func (t *Store) IsAgentMasterToken(token string) bool {
|
func (t *Store) IsAgentMasterToken(token string) bool {
|
||||||
|
|
|
@ -4,40 +4,69 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStore_UserAndAgentTokens(t *testing.T) {
|
func TestStore_RegularTokens(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
type tokens struct {
|
||||||
|
user, agent, repl string
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
user, agent, wantUser, wantAgent string
|
name string
|
||||||
|
set, want tokens
|
||||||
}{
|
}{
|
||||||
{"", "", "", ""},
|
{
|
||||||
{"user", "", "user", "user"},
|
name: "set user",
|
||||||
{"user", "agent", "user", "agent"},
|
set: tokens{user: "U"},
|
||||||
{"", "agent", "", "agent"},
|
want: tokens{user: "U", agent: "U"},
|
||||||
{"user", "agent", "user", "agent"},
|
},
|
||||||
{"user", "", "user", "user"},
|
{
|
||||||
{"", "", "", ""},
|
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 {
|
for _, tt := range tests {
|
||||||
tokens.UpdateUserToken(tt.user)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tokens.UpdateAgentToken(tt.agent)
|
s := new(Store)
|
||||||
if got, want := tokens.UserToken(), tt.wantUser; got != want {
|
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)
|
t.Fatalf("got token %q want %q", got, want)
|
||||||
}
|
}
|
||||||
if got, want := tokens.AgentToken(), tt.wantAgent; got != want {
|
if got, want := s.AgentToken(), tt.want.agent; got != want {
|
||||||
t.Fatalf("got token %q want %q", 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) {
|
func TestStore_AgentMasterToken(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tokens := new(Store)
|
s := new(Store)
|
||||||
|
|
||||||
verify := func(want bool, toks ...string) {
|
verify := func(want bool, toks ...string) {
|
||||||
for _, tok := range toks {
|
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)
|
t.Fatalf("token %q got %v want %v", tok, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,14 +74,14 @@ func TestStore_AgentMasterToken(t *testing.T) {
|
||||||
|
|
||||||
verify(false, "", "nope")
|
verify(false, "", "nope")
|
||||||
|
|
||||||
tokens.UpdateAgentMasterToken("master")
|
s.UpdateAgentMasterToken("master")
|
||||||
verify(true, "master")
|
verify(true, "master")
|
||||||
verify(false, "", "nope")
|
verify(false, "", "nope")
|
||||||
|
|
||||||
tokens.UpdateAgentMasterToken("another")
|
s.UpdateAgentMasterToken("another")
|
||||||
verify(true, "another")
|
verify(true, "another")
|
||||||
verify(false, "", "nope", "master")
|
verify(false, "", "nope", "master")
|
||||||
|
|
||||||
tokens.UpdateAgentMasterToken("")
|
s.UpdateAgentMasterToken("")
|
||||||
verify(false, "", "nope", "master", "another")
|
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)
|
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
|
// 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
|
// started. The tokens are not persisted, so will need to be updated again if
|
||||||
// the agent is restarted.
|
// the agent is restarted.
|
||||||
|
|
|
@ -785,4 +785,8 @@ func TestAPI_AgentUpdateToken(t *testing.T) {
|
||||||
if _, err := agent.UpdateACLAgentMasterToken("root", nil); err != nil {
|
if _, err := agent.UpdateACLAgentMasterToken("root", nil); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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_token` | `application/json` |
|
||||||
| `PUT` | `/agent/token/acl_agent_token` | `application/json` |
|
| `PUT` | `/agent/token/acl_agent_token` | `application/json` |
|
||||||
| `PUT` | `/agent/token/acl_agent_master_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,
|
The paths above correspond to the token names as found in the agent configuration:
|
||||||
[`acl_token`](/docs/agent/options.html#acl_token),
|
[`acl_token`](/docs/agent/options.html#acl_token), [`acl_agent_token`](/docs/agent/options.html#acl_agent_token),
|
||||||
[`acl_agent_token`](/docs/agent/options.html#acl_agent_token),
|
[`acl_agent_master_token`](/docs/agent/options.html#acl_agent_master_token), and
|
||||||
and [`acl_agent_master_token`](/docs/agent/options.html#acl_agent_master_token).
|
[`acl_replication_token`](/docs/agent/options.html#acl_replication_token).
|
||||||
|
|
||||||
The table below shows this endpoint's support for
|
The table below shows this endpoint's support for
|
||||||
[blocking queries](/api/index.html#blocking-queries),
|
[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
|
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.
|
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
|
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,
|
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
|
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> -
|
* <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.
|
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
|
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>
|
<br><br>
|
||||||
If there's a partition or other outage affecting the authoritative datacenter, and the
|
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
|
[`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
|
* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
|
||||||
[`-domain` command-line flag](#_domain).
|
[`-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
|
* <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.
|
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
|
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
|
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
|
[`acl_replication_token`](/docs/agent/options.html#acl_replication_token) in the
|
||||||
configuration on the servers in the non-authoritative datacenters. With replication
|
configuration on the servers in the non-authoritative datacenters. In Consul
|
||||||
enabled, the servers will maintain a replica of the authoritative datacenter's full
|
0.9.1 and later you can enable ACL replication using
|
||||||
set of ACLs on the non-authoritative servers. The ACL replication token needs to be
|
[`enable_acl_replication`](/docs/agent/options.html#enable_acl_replication) and
|
||||||
a valid ACL token with management privileges, it can also be the same as the master
|
then set the token later using the
|
||||||
ACL token.
|
[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
|
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
|
every 30 seconds. Replicated changes are written at a rate that's throttled to
|
||||||
|
|
Loading…
Reference in a new issue