diff --git a/command/agent/agent.go b/command/agent/agent.go index eb2099915..5970a0c0f 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -146,6 +146,9 @@ func convertServerConfig(agentConfig *Config, logOutput io.Writer) (*nomad.Confi if agentConfig.ACL.Enabled { conf.ACLEnabled = true } + if agentConfig.ACL.ReplicationToken != "" { + conf.ReplicationToken = agentConfig.ACL.ReplicationToken + } // Set up the bind addresses rpcAddr, err := net.ResolveTCPAddr("tcp", agentConfig.normalizedAddrs.RPC) diff --git a/command/agent/config-test-fixtures/basic.hcl b/command/agent/config-test-fixtures/basic.hcl index 7e4a84309..459474b3c 100644 --- a/command/agent/config-test-fixtures/basic.hcl +++ b/command/agent/config-test-fixtures/basic.hcl @@ -87,6 +87,7 @@ acl { enabled = true token_ttl = "60s" policy_ttl = "60s" + replication_token = "foobar" } telemetry { statsite_address = "127.0.0.1:1234" diff --git a/command/agent/config.go b/command/agent/config.go index 7382a2666..36052df18 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -247,6 +247,11 @@ type ACLConfig struct { // to "30s". Reducing this impacts performance by forcing more // frequent resolution. PolicyTTL time.Duration `mapstructure:"policy_ttl"` + + // ReplicationToken is used by servers to replicate tokens and policies + // from the authoritative region. This must be a valid management token + // within the authoritative region. + ReplicationToken string `mapstructure:"replication_token"` } // ServerConfig is configuration specific to the server mode @@ -953,6 +958,9 @@ func (a *ACLConfig) Merge(b *ACLConfig) *ACLConfig { if b.PolicyTTL != 0 { result.PolicyTTL = b.PolicyTTL } + if b.ReplicationToken != "" { + result.ReplicationToken = b.ReplicationToken + } return &result } diff --git a/command/agent/config_parse.go b/command/agent/config_parse.go index b0dfefb89..2894f7901 100644 --- a/command/agent/config_parse.go +++ b/command/agent/config_parse.go @@ -573,6 +573,7 @@ func parseACL(result **ACLConfig, list *ast.ObjectList) error { "enabled", "token_ttl", "policy_ttl", + "replication_token", } if err := checkHCLKeys(listVal, valid); err != nil { return err diff --git a/command/agent/config_parse_test.go b/command/agent/config_parse_test.go index dcf7a05b7..e088926a2 100644 --- a/command/agent/config_parse_test.go +++ b/command/agent/config_parse_test.go @@ -105,9 +105,10 @@ func TestConfig_Parse(t *testing.T) { EncryptKey: "abc", }, ACL: &ACLConfig{ - Enabled: true, - TokenTTL: 60 * time.Second, - PolicyTTL: 60 * time.Second, + Enabled: true, + TokenTTL: 60 * time.Second, + PolicyTTL: 60 * time.Second, + ReplicationToken: "foobar", }, Telemetry: &Telemetry{ StatsiteAddr: "127.0.0.1:1234", diff --git a/command/agent/config_test.go b/command/agent/config_test.go index ed84c6dfe..2e8d20710 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -103,9 +103,10 @@ func TestConfig_Merge(t *testing.T) { MaxHeartbeatsPerSecond: 30.0, }, ACL: &ACLConfig{ - Enabled: true, - TokenTTL: 60 * time.Second, - PolicyTTL: 60 * time.Second, + Enabled: true, + TokenTTL: 60 * time.Second, + PolicyTTL: 60 * time.Second, + ReplicationToken: "foo", }, Ports: &Ports{ HTTP: 4646, @@ -247,9 +248,10 @@ func TestConfig_Merge(t *testing.T) { retryInterval: time.Second * 10, }, ACL: &ACLConfig{ - Enabled: true, - TokenTTL: 20 * time.Second, - PolicyTTL: 20 * time.Second, + Enabled: true, + TokenTTL: 20 * time.Second, + PolicyTTL: 20 * time.Second, + ReplicationToken: "foobar", }, Ports: &Ports{ HTTP: 20000, diff --git a/nomad/config.go b/nomad/config.go index a5add4ebd..418d3f79a 100644 --- a/nomad/config.go +++ b/nomad/config.go @@ -235,6 +235,10 @@ type Config struct { // ReplicationBackoff is how much we backoff when replication errors. // This is a tunable knob for testing primarily. ReplicationBackoff time.Duration + + // ReplicationToken is the ACL Token Secret ID used to fetch from + // the Authoritative Region. + ReplicationToken string } // CheckVersion is used to check if the ProtocolVersion is valid diff --git a/nomad/leader.go b/nomad/leader.go index 51b991ad1..212d80bcf 100644 --- a/nomad/leader.go +++ b/nomad/leader.go @@ -691,6 +691,7 @@ START: // Fetch the list of policies var resp structs.ACLPolicyListResponse + req.SecretID = s.ReplicationToken() err := s.forwardRegion(s.config.AuthoritativeRegion, "ACL.ListPolicies", &req, &resp) if err != nil { @@ -719,7 +720,8 @@ START: req := structs.ACLPolicySetRequest{ Names: update, QueryOptions: structs.QueryOptions{ - Region: s.config.AuthoritativeRegion, + Region: s.config.AuthoritativeRegion, + SecretID: s.ReplicationToken(), }, } var reply structs.ACLPolicySetResponse @@ -831,6 +833,7 @@ START: // Fetch the list of tokens var resp structs.ACLTokenListResponse + req.SecretID = s.ReplicationToken() err := s.forwardRegion(s.config.AuthoritativeRegion, "ACL.ListTokens", &req, &resp) if err != nil { @@ -859,7 +862,8 @@ START: req := structs.ACLTokenSetRequest{ AccessorIDS: update, QueryOptions: structs.QueryOptions{ - Region: s.config.AuthoritativeRegion, + Region: s.config.AuthoritativeRegion, + SecretID: s.ReplicationToken(), }, } var reply structs.ACLTokenSetResponse diff --git a/nomad/leader_test.go b/nomad/leader_test.go index 3dab4440b..c6827566f 100644 --- a/nomad/leader_test.go +++ b/nomad/leader_test.go @@ -629,17 +629,18 @@ func TestLeader_RestoreVaultAccessors(t *testing.T) { func TestLeader_ReplicateACLPolicies(t *testing.T) { t.Parallel() - s1 := testServer(t, func(c *Config) { + s1, root := testACLServer(t, func(c *Config) { c.Region = "region1" c.AuthoritativeRegion = "region1" c.ACLEnabled = true }) defer s1.Shutdown() - s2 := testServer(t, func(c *Config) { + s2, _ := testACLServer(t, func(c *Config) { c.Region = "region2" c.AuthoritativeRegion = "region1" c.ACLEnabled = true c.ReplicationBackoff = 20 * time.Millisecond + c.ReplicationToken = root.SecretID }) defer s2.Shutdown() testJoin(t, s1, s2) @@ -699,17 +700,18 @@ func TestLeader_DiffACLPolicies(t *testing.T) { func TestLeader_ReplicateACLTokens(t *testing.T) { t.Parallel() - s1 := testServer(t, func(c *Config) { + s1, root := testACLServer(t, func(c *Config) { c.Region = "region1" c.AuthoritativeRegion = "region1" c.ACLEnabled = true }) defer s1.Shutdown() - s2 := testServer(t, func(c *Config) { + s2, _ := testACLServer(t, func(c *Config) { c.Region = "region2" c.AuthoritativeRegion = "region1" c.ACLEnabled = true c.ReplicationBackoff = 20 * time.Millisecond + c.ReplicationToken = root.SecretID }) defer s2.Shutdown() testJoin(t, s1, s2) diff --git a/nomad/server.go b/nomad/server.go index 7b0c31805..0455fe1d0 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -1147,6 +1147,12 @@ func (s *Server) GetConfig() *Config { return s.config } +// ReplicationToken returns the token used for replication. We use a method to support +// dynamic reloading of this value later. +func (s *Server) ReplicationToken() string { + return s.config.ReplicationToken +} + // peersInfoContent is used to help operators understand what happened to the // peers.json file. This is written to a file called peers.info in the same // location.