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:
parent
8073d1d16e
commit
04fff2af26
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue