diff --git a/agent/agent.go b/agent/agent.go
index 23df443dcc..98f02b6924 100644
--- a/agent/agent.go
+++ b/agent/agent.go
@@ -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
}
diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go
index 52250ba681..0296c3e1cc 100644
--- a/agent/agent_endpoint.go
+++ b/agent/agent_endpoint.go
@@ -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)
diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go
index e9bee3f54c..1d9fe5ff7e 100644
--- a/agent/agent_endpoint_test.go
+++ b/agent/agent_endpoint_test.go
@@ -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)
}
diff --git a/agent/config.go b/agent/config.go
index 9662647e88..e9e5b9966b 100644
--- a/agent/config.go
+++ b/agent/config.go
@@ -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
}
diff --git a/agent/config_test.go b/agent/config_test.go
index c7a8f23f54..42e4366a79 100644
--- a/agent/config_test.go
+++ b/agent/config_test.go
@@ -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},
diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go
index 6c6666844f..246d66db8b 100644
--- a/agent/consul/acl_endpoint_test.go
+++ b/agent/consul/acl_endpoint_test.go
@@ -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)
diff --git a/agent/consul/acl_replication.go b/agent/consul/acl_replication.go
index e60df9407f..f0b40d4d45 100644
--- a/agent/consul/acl_replication.go
+++ b/agent/consul/acl_replication.go
@@ -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.
diff --git a/agent/consul/acl_replication_test.go b/agent/consul/acl_replication_test.go
index 56eb862893..6040c062df 100644
--- a/agent/consul/acl_replication_test.go
+++ b/agent/consul/acl_replication_test.go
@@ -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()
diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go
index 9cdf2b65ba..2f7746b3a3 100644
--- a/agent/consul/acl_test.go
+++ b/agent/consul/acl_test.go
@@ -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()
diff --git a/agent/consul/config.go b/agent/consul/config.go
index 93234dada6..bb6a431a17 100644
--- a/agent/consul/config.go
+++ b/agent/consul/config.go
@@ -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
diff --git a/agent/consul/server.go b/agent/consul/server.go
index 1d0418cf87..3160ad73ce 100644
--- a/agent/consul/server.go
+++ b/agent/consul/server.go
@@ -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),
diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go
index a142dbb6c8..dda19c484a 100644
--- a/agent/consul/server_test.go
+++ b/agent/consul/server_test.go
@@ -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
}
diff --git a/agent/token/store.go b/agent/token/store.go
index 119f2a9d2e..03c92bc796 100644
--- a/agent/token/store.go
+++ b/agent/token/store.go
@@ -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 {
diff --git a/agent/token/store_test.go b/agent/token/store_test.go
index a21bc54e2c..00dabc107e 100644
--- a/agent/token/store_test.go
+++ b/agent/token/store_test.go
@@ -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")
}
diff --git a/api/agent.go b/api/agent.go
index 86c9414aeb..383d2816ed 100644
--- a/api/agent.go
+++ b/api/agent.go
@@ -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.
diff --git a/api/agent_test.go b/api/agent_test.go
index 2f6d02c816..6e6292eee9 100644
--- a/api/agent_test.go
+++ b/api/agent_test.go
@@ -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)
+ }
}
diff --git a/website/source/api/agent.html.md b/website/source/api/agent.html.md
index 2186a23e48..5adee9766e 100644
--- a/website/source/api/agent.html.md
+++ b/website/source/api/agent.html.md
@@ -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),
diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md
index 01dbfdf002..69e22a1085 100644
--- a/website/source/docs/agent/options.html.md
+++ b/website/source/docs/agent/options.html.md
@@ -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.
-* `enable-script-checks` This
+* `-enable-script-checks` 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
* `acl_replication_token` -
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.
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
* `domain` Equivalent to the
[`-domain` command-line flag](#_domain).
+* `enable_acl_replication` 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.
+
* `enable_debug` When set, enables some
additional debugging features. Currently, this is only used to set the runtime profiling HTTP endpoints.
diff --git a/website/source/docs/guides/acl.html.md b/website/source/docs/guides/acl.html.md
index b16ebb49c2..60a4a7dd86 100644
--- a/website/source/docs/guides/acl.html.md
+++ b/website/source/docs/guides/acl.html.md
@@ -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