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 <pglass@hashicorp.com>

* Update field comment

Co-authored-by: Paul Glass <pglass@hashicorp.com>

---------

Co-authored-by: Paul Glass <pglass@hashicorp.com>

* revert naming change

* add testing

* revert naming change

---------

Co-authored-by: Paul Glass <pglass@hashicorp.com>
This commit is contained in:
skpratt 2023-02-09 14:34:02 -06:00 committed by GitHub
parent 99cf421e7b
commit db2bd404bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 35 deletions

View File

@ -6,8 +6,9 @@ const (
// AnonymousTokenID is the AccessorID of the anonymous token. // AnonymousTokenID is the AccessorID of the anonymous token.
// When logging or displaying to users, use acl.AliasIfAnonymousToken // When logging or displaying to users, use acl.AliasIfAnonymousToken
// to convert this to AnonymousTokenAlias. // to convert this to AnonymousTokenAlias.
AnonymousTokenID = "00000000-0000-0000-0000-000000000002" AnonymousTokenID = "00000000-0000-0000-0000-000000000002"
AnonymousTokenAlias = "anonymous token" AnonymousTokenAlias = "anonymous token"
AnonymousTokenSecret = "anonymous"
) )
// Config encapsulates all of the generic configuration parameters used for // Config encapsulates all of the generic configuration parameters used for

View File

@ -148,6 +148,16 @@ func (s *Server) ResolveIdentityFromToken(token string) (bool, structs.ACLIdenti
} else if aclToken != nil && !aclToken.IsExpired(time.Now()) { } else if aclToken != nil && !aclToken.IsExpired(time.Now()) {
return true, aclToken, nil 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 defaultErr := acl.ErrNotFound
canBootstrap, _, _ := s.fsm.State().CanBootstrapACLToken() canBootstrap, _, _ := s.fsm.State().CanBootstrapACLToken()

View File

@ -506,31 +506,8 @@ func (s *Server) initializeACLs(ctx context.Context) error {
} }
// Insert the anonymous token if it does not exist. // Insert the anonymous token if it does not exist.
state := s.fsm.State() if err := s.InsertAnonymousToken(); err != nil {
_, token, err := state.ACLTokenGetBySecret(nil, anonymousToken, nil) return err
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")
} }
// Generate or rotate the server management token on leadership transitions. // Generate or rotate the server management token on leadership transitions.
@ -554,6 +531,36 @@ func (s *Server) initializeACLs(ctx context.Context) error {
return nil 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) { func (s *Server) startACLReplication(ctx context.Context) {
if s.InPrimaryDatacenter() { if s.InPrimaryDatacenter() {
return return

View File

@ -291,14 +291,23 @@ func prepTokenPoliciesInPartition(t *testing.T, acl *ACL, partition string) (pol
func TestAPI_ACLBootstrap(t *testing.T) { func TestAPI_ACLBootstrap(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeNonBootstrappedACLClient(t) c, s := makeNonBootstrappedACLClient(t, "allow")
defer s.Stop()
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() acl := c.ACL()
s.WaitForLeader(t) s.WaitForLeader(t)
//not bootstrapped, default deny
// not bootstrapped _, _, err = acl.TokenList(nil)
_, _, 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)") require.EqualError(t, err, "Unexpected response code: 403 (ACL system must be bootstrapped before making any requests that require authorization: ACL not found)")
// bootstrap // bootstrap
mgmtTok, _, err := acl.Bootstrap() mgmtTok, _, err := acl.Bootstrap()
@ -309,6 +318,7 @@ func TestAPI_ACLBootstrap(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// management and anonymous should be only tokens // management and anonymous should be only tokens
require.Len(t, toks, 2) require.Len(t, toks, 2)
s.Stop()
} }
func TestAPI_ACLToken_CreateReadDelete(t *testing.T) { func TestAPI_ACLToken_CreateReadDelete(t *testing.T) {

View File

@ -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, return makeClientWithConfig(t,
func(clientConfig *Config) { func(clientConfig *Config) {
clientConfig.Token = "root" clientConfig.Token = ""
}, },
func(serverConfig *testutil.TestServerConfig) { func(serverConfig *testutil.TestServerConfig) {
serverConfig.PrimaryDatacenter = "dc1" serverConfig.PrimaryDatacenter = "dc1"
serverConfig.ACL.Enabled = true serverConfig.ACL.Enabled = true
serverConfig.ACL.DefaultPolicy = "deny" serverConfig.ACL.DefaultPolicy = defaultPolicy
serverConfig.Bootstrap = true serverConfig.Bootstrap = true
}) })
} }