diff --git a/nomad/acl.go b/nomad/acl.go index 4e9433390..1743c85e4 100644 --- a/nomad/acl.go +++ b/nomad/acl.go @@ -19,6 +19,12 @@ func (s *Server) ResolveToken(secretID string) (*acl.ACL, error) { } defer metrics.MeasureSince([]string{"nomad", "acl", "resolveToken"}, time.Now()) + // Check if the secret ID is the leader secret ID, in which case treat it as + // a management token. + if secretID == s.getLeaderAcl() { + return acl.ManagementACL, nil + } + // Snapshot the state snap, err := s.fsm.State().Snapshot() if err != nil { diff --git a/nomad/acl_test.go b/nomad/acl_test.go index b123098c2..8fce091bb 100644 --- a/nomad/acl_test.go +++ b/nomad/acl_test.go @@ -9,10 +9,13 @@ import ( "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/testutil" "github.com/stretchr/testify/assert" ) func TestResolveACLToken(t *testing.T) { + t.Parallel() + // Create mock state store and cache state := state.TestStateStore(t) cache, err := lru.New2Q(16) @@ -88,3 +91,19 @@ func TestResolveACLToken(t *testing.T) { t.Fatalf("unexpected cached value") } } + +func TestResolveACLToken_LeaderToken(t *testing.T) { + t.Parallel() + assert := assert.New(t) + s1, _ := testACLServer(t, nil) + defer s1.Shutdown() + testutil.WaitForLeader(t, s1.RPC) + + leaderAcl := s1.getLeaderAcl() + assert.NotEmpty(leaderAcl) + token, err := s1.ResolveToken(leaderAcl) + assert.Nil(err) + if assert.NotNil(token) { + assert.True(token.IsManagement()) + } +} diff --git a/nomad/leader.go b/nomad/leader.go index 568563d93..45f353dae 100644 --- a/nomad/leader.go +++ b/nomad/leader.go @@ -117,6 +117,10 @@ WAIT: // previously inflight transactions have been committed and that our // state is up-to-date. func (s *Server) establishLeadership(stopCh chan struct{}) error { + // Generate a leader ACL token. This will allow the leader to issue work + // that requires a valid ACL token. + s.setLeaderAcl(uuid.Generate()) + // Disable workers to free half the cores for use in the plan queue and // evaluation broker if numWorkers := len(s.workers); numWorkers > 1 { diff --git a/nomad/server.go b/nomad/server.go index bfe967eb9..afdffce77 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -166,6 +166,11 @@ type Server struct { // aclCache is used to maintain the parsed ACL objects aclCache *lru.TwoQueueCache + // leaderAcl is the management ACL token that is valid when resolved by the + // current leader. + leaderAcl string + leaderAclLock sync.Mutex + // EnterpriseState is used to fill in state for Pro/Ent builds EnterpriseState @@ -1070,6 +1075,20 @@ func (s *Server) State() *state.StateStore { return s.fsm.State() } +// setLeaderAcl stores the given ACL token as the current leader's ACL token. +func (s *Server) setLeaderAcl(token string) { + s.leaderAclLock.Lock() + s.leaderAcl = token + s.leaderAclLock.Unlock() +} + +// getLeaderAcl retrieves the leader's ACL token +func (s *Server) getLeaderAcl() string { + s.leaderAclLock.Lock() + defer s.leaderAclLock.Unlock() + return s.leaderAcl +} + // Regions returns the known regions in the cluster. func (s *Server) Regions() []string { s.peerLock.RLock()