From 04fff2af266752f0aa9347fcf494782c077c9beb Mon Sep 17 00:00:00 2001 From: skpratt Date: Thu, 9 Feb 2023 14:34:02 -0600 Subject: [PATCH] Synthesize anonymous token pre-bootstrap when needed (#16200) * add bootstrapping detail for acl errors * error detail improvements * update acl bootstrapping test coverage * update namespace errors * update test coverage * consolidate error message code and update changelog * synthesize anonymous token * Update token language to distinguish Accessor and Secret ID usage (#16044) * remove legacy tokens * remove lingering legacy token references from docs * update language and naming for token secrets and accessor IDs * updates all tokenID references to clarify accessorID * remove token type references and lookup tokens by accessorID index * remove unnecessary constants * replace additional tokenID param names * Add warning info for deprecated -id parameter Co-authored-by: Paul Glass * Update field comment Co-authored-by: Paul Glass --------- Co-authored-by: Paul Glass * revert naming change * add testing * revert naming change --------- Co-authored-by: Paul Glass --- acl/acl.go | 5 ++-- agent/consul/acl_server.go | 10 +++++++ agent/consul/leader.go | 57 +++++++++++++++++++++----------------- api/acl_test.go | 20 +++++++++---- api/api_test.go | 6 ++-- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/acl/acl.go b/acl/acl.go index 20b042666..c18ba0b07 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -6,8 +6,9 @@ const ( // AnonymousTokenID is the AccessorID of the anonymous token. // When logging or displaying to users, use acl.AliasIfAnonymousToken // to convert this to AnonymousTokenAlias. - AnonymousTokenID = "00000000-0000-0000-0000-000000000002" - AnonymousTokenAlias = "anonymous token" + AnonymousTokenID = "00000000-0000-0000-0000-000000000002" + AnonymousTokenAlias = "anonymous token" + AnonymousTokenSecret = "anonymous" ) // Config encapsulates all of the generic configuration parameters used for diff --git a/agent/consul/acl_server.go b/agent/consul/acl_server.go index 9ee36448f..48e80191e 100644 --- a/agent/consul/acl_server.go +++ b/agent/consul/acl_server.go @@ -148,6 +148,16 @@ func (s *Server) ResolveIdentityFromToken(token string) (bool, structs.ACLIdenti } else if aclToken != nil && !aclToken.IsExpired(time.Now()) { return true, aclToken, nil } + if aclToken == nil && token == acl.AnonymousTokenSecret { + // synthesize the anonymous token for early use, bootstrapping has not completed + s.InsertAnonymousToken() + fallbackId := structs.ACLToken{ + AccessorID: acl.AnonymousTokenID, + SecretID: acl.AnonymousTokenSecret, + Description: "synthesized anonymous token", + } + return true, &fallbackId, nil + } defaultErr := acl.ErrNotFound canBootstrap, _, _ := s.fsm.State().CanBootstrapACLToken() diff --git a/agent/consul/leader.go b/agent/consul/leader.go index fa8d414d0..fce72a32d 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -506,31 +506,8 @@ func (s *Server) initializeACLs(ctx context.Context) error { } // Insert the anonymous token if it does not exist. - state := s.fsm.State() - _, token, err := state.ACLTokenGetBySecret(nil, anonymousToken, nil) - if err != nil { - return fmt.Errorf("failed to get anonymous token: %v", err) - } - // Ignoring expiration times to avoid an insertion collision. - if token == nil { - token = &structs.ACLToken{ - AccessorID: acl.AnonymousTokenID, - SecretID: anonymousToken, - Description: "Anonymous Token", - CreateTime: time.Now(), - EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), - } - token.SetHash(true) - - req := structs.ACLTokenBatchSetRequest{ - Tokens: structs.ACLTokens{token}, - CAS: false, - } - _, err := s.raftApply(structs.ACLTokenSetRequestType, &req) - if err != nil { - return fmt.Errorf("failed to create anonymous token: %v", err) - } - s.logger.Info("Created ACL anonymous token from configuration") + if err := s.InsertAnonymousToken(); err != nil { + return err } // Generate or rotate the server management token on leadership transitions. @@ -554,6 +531,36 @@ func (s *Server) initializeACLs(ctx context.Context) error { return nil } +func (s *Server) InsertAnonymousToken() error { + state := s.fsm.State() + _, token, err := state.ACLTokenGetBySecret(nil, anonymousToken, nil) + if err != nil { + return fmt.Errorf("failed to get anonymous token: %v", err) + } + // Ignoring expiration times to avoid an insertion collision. + if token == nil { + token = &structs.ACLToken{ + AccessorID: acl.AnonymousTokenID, + SecretID: anonymousToken, + Description: "Anonymous Token", + CreateTime: time.Now(), + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + token.SetHash(true) + + req := structs.ACLTokenBatchSetRequest{ + Tokens: structs.ACLTokens{token}, + CAS: false, + } + _, err := s.raftApply(structs.ACLTokenSetRequestType, &req) + if err != nil { + return fmt.Errorf("failed to create anonymous token: %v", err) + } + s.logger.Info("Created ACL anonymous token from configuration") + } + return nil +} + func (s *Server) startACLReplication(ctx context.Context) { if s.InPrimaryDatacenter() { return diff --git a/api/acl_test.go b/api/acl_test.go index 1cf8708d2..35ea38049 100644 --- a/api/acl_test.go +++ b/api/acl_test.go @@ -291,14 +291,23 @@ func prepTokenPoliciesInPartition(t *testing.T, acl *ACL, partition string) (pol func TestAPI_ACLBootstrap(t *testing.T) { t.Parallel() - c, s := makeNonBootstrappedACLClient(t) - defer s.Stop() + c, s := makeNonBootstrappedACLClient(t, "allow") + s.WaitForLeader(t) + // not bootstrapped, default allow + mems, err := c.Agent().Members(false) + require.NoError(t, err) + require.True(t, len(mems) == 1) + + s.Stop() + c, s = makeNonBootstrappedACLClient(t, "deny") acl := c.ACL() s.WaitForLeader(t) - - // not bootstrapped - _, _, err := acl.TokenList(nil) + //not bootstrapped, default deny + _, _, err = acl.TokenList(nil) + require.EqualError(t, err, "Unexpected response code: 403 (Permission denied: anonymous token lacks permission 'acl:read'. The anonymous token is used implicitly when a request does not specify a token.)") + c.config.Token = "root" + _, _, err = acl.TokenList(nil) require.EqualError(t, err, "Unexpected response code: 403 (ACL system must be bootstrapped before making any requests that require authorization: ACL not found)") // bootstrap mgmtTok, _, err := acl.Bootstrap() @@ -309,6 +318,7 @@ func TestAPI_ACLBootstrap(t *testing.T) { require.NoError(t, err) // management and anonymous should be only tokens require.Len(t, toks, 2) + s.Stop() } func TestAPI_ACLToken_CreateReadDelete(t *testing.T) { diff --git a/api/api_test.go b/api/api_test.go index 94eadd959..7c8048cb4 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -50,15 +50,15 @@ func makeACLClient(t *testing.T) (*Client, *testutil.TestServer) { }) } -func makeNonBootstrappedACLClient(t *testing.T) (*Client, *testutil.TestServer) { +func makeNonBootstrappedACLClient(t *testing.T, defaultPolicy string) (*Client, *testutil.TestServer) { return makeClientWithConfig(t, func(clientConfig *Config) { - clientConfig.Token = "root" + clientConfig.Token = "" }, func(serverConfig *testutil.TestServerConfig) { serverConfig.PrimaryDatacenter = "dc1" serverConfig.ACL.Enabled = true - serverConfig.ACL.DefaultPolicy = "deny" + serverConfig.ACL.DefaultPolicy = defaultPolicy serverConfig.Bootstrap = true }) }