Generate ACL token for server management

This commit introduces a new ACL token used for internal server
management purposes.

It has a few key properties:
- It has unlimited permissions.
- It is persisted through Raft as System Metadata rather than in the
ACL tokens table. This is to avoid users seeing or modifying it.
- It is re-generated on leadership establishment.
pull/14556/head
freddygv 2022-09-09 13:05:38 -06:00
parent 0ea3353537
commit 0e5131bd33
7 changed files with 122 additions and 0 deletions

View File

@ -132,6 +132,7 @@ type ACLResolverBackend interface {
ResolveIdentityFromToken(token string) (bool, structs.ACLIdentity, error)
ResolvePolicyFromID(policyID string) (bool, *structs.ACLPolicy, error)
ResolveRoleFromID(roleID string) (bool, *structs.ACLRole, error)
IsServerManagementToken(token string) bool
// TODO: separate methods for each RPC call (there are 4)
RPC(method string, args interface{}, reply interface{}) error
EnterpriseACLResolverDelegate
@ -980,6 +981,10 @@ func (r *ACLResolver) resolveLocallyManagedToken(token string) (structs.ACLIdent
return structs.NewAgentRecoveryTokenIdentity(r.config.NodeName, token), r.agentRecoveryAuthz, true
}
if r.backend.IsServerManagementToken(token) {
return structs.NewACLServerIdentity(token), acl.ManageAll(), true
}
return r.resolveLocallyManagedEnterpriseToken(token)
}

View File

@ -27,6 +27,10 @@ type clientACLResolverBackend struct {
*Client
}
func (c *clientACLResolverBackend) IsServerManagementToken(_ string) bool {
return false
}
func (c *clientACLResolverBackend) ACLDatacenter() string {
// For resolution running on clients servers within the current datacenter
// must be queried first to pick up local tokens.

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/subtle"
"fmt"
"time"
@ -108,6 +109,19 @@ type serverACLResolverBackend struct {
*Server
}
func (s *serverACLResolverBackend) IsServerManagementToken(token string) bool {
mgmt, err := s.getSystemMetadata(structs.ServerManagementToken)
if err != nil {
s.logger.Debug("failed to fetch server management token: %w", err)
return false
}
if mgmt == "" {
s.logger.Debug("server management token has not been initialized")
return false
}
return subtle.ConstantTimeCompare([]byte(mgmt), []byte(token)) == 1
}
func (s *serverACLResolverBackend) ACLDatacenter() string {
// For resolution running on servers the only option is to contact the
// configured ACL Datacenter

View File

@ -438,6 +438,8 @@ type ACLResolverTestDelegate struct {
// testRoles is used by plainRoleResolveFn if not nil
testRoles map[string]*structs.ACLRole
testServerManagementToken string
localTokenResolutions int32
remoteTokenResolutions int32
localPolicyResolutions int32
@ -456,6 +458,10 @@ type ACLResolverTestDelegate struct {
EnterpriseACLResolverTestDelegate
}
func (d *ACLResolverTestDelegate) IsServerManagementToken(token string) bool {
return token == d.testServerManagementToken
}
// UseTestLocalData will force delegate-local maps to be used in lieu of the
// global factory functions.
func (d *ACLResolverTestDelegate) UseTestLocalData(data []interface{}) {
@ -2187,6 +2193,27 @@ func TestACLResolver_AgentRecovery(t *testing.T) {
require.Equal(t, acl.Deny, authz.NodeWrite("bar", nil))
}
func TestACLResolver_ServerManagementToken(t *testing.T) {
const testToken = "1bb0900e-3683-46a5-b04c-4882d7773b83"
d := &ACLResolverTestDelegate{
datacenter: "dc1",
enabled: true,
testServerManagementToken: testToken,
}
r := newTestACLResolver(t, d, func(cfg *ACLResolverConfig) {
cfg.Tokens = &token.Store{}
cfg.Config.NodeName = "foo"
})
authz, err := r.ResolveToken(testToken)
require.NoError(t, err)
require.NotNil(t, authz.ACLIdentity)
require.Equal(t, structs.ServerManagementToken, authz.ACLIdentity.ID())
require.NotNil(t, authz.Authorizer)
require.Equal(t, acl.ManageAll(), authz.Authorizer)
}
func TestACLResolver_ACLsEnabled(t *testing.T) {
type testCase struct {
name string

View File

@ -501,6 +501,7 @@ 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 {
@ -527,6 +528,20 @@ func (s *Server) initializeACLs(ctx context.Context) error {
}
s.logger.Info("Created ACL anonymous token from configuration")
}
// Generate or rotate the server management token on leadership transitions.
// This token is used by Consul servers for authn/authz when making
// requests to themselves through public APIs such as the agent cache.
// It is stored as system metadata because it is internally
// managed and users are not meant to see it or interact with it.
secretID, err := lib.GenerateUUID(nil)
if err != nil {
return fmt.Errorf("failed to generate the secret ID for the server management token: %w", err)
}
if err := s.setSystemMetadataKey(structs.ServerManagementToken, secretID); err != nil {
return fmt.Errorf("failed to persist server management token: %w", err)
}
// launch the upgrade go routine to generate accessors for everything
s.startACLUpgrade(ctx)
} else {

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/serf/serf"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
@ -1295,6 +1296,13 @@ func TestLeader_ACL_Initialization(t *testing.T) {
_, policy, err := s1.fsm.State().ACLPolicyGetByID(nil, structs.ACLPolicyGlobalManagementID, nil)
require.NoError(t, err)
require.NotNil(t, policy)
serverToken, err := s1.getSystemMetadata(structs.ServerManagementToken)
require.NoError(t, err)
require.NotEmpty(t, serverToken)
_, err = uuid.ParseUUID(serverToken)
require.NoError(t, err)
})
}
}

View File

@ -104,6 +104,7 @@ type ACLIdentity interface {
IsLocal() bool
EnterpriseMetadata() *acl.EnterpriseMeta
}
type ACLTokenPolicyLink struct {
ID string
Name string `hash:"ignore"`
@ -1838,3 +1839,51 @@ func (id *AgentRecoveryTokenIdentity) IsLocal() bool {
func (id *AgentRecoveryTokenIdentity) EnterpriseMetadata() *acl.EnterpriseMeta {
return nil
}
const ServerManagementToken = "server-management-token"
type ACLServerIdentity struct {
secretID string
}
func NewACLServerIdentity(secretID string) *ACLServerIdentity {
return &ACLServerIdentity{
secretID: secretID,
}
}
func (i *ACLServerIdentity) ID() string {
return ServerManagementToken
}
func (i *ACLServerIdentity) SecretToken() string {
return i.secretID
}
func (i *ACLServerIdentity) PolicyIDs() []string {
return nil
}
func (i *ACLServerIdentity) RoleIDs() []string {
return nil
}
func (i *ACLServerIdentity) ServiceIdentityList() []*ACLServiceIdentity {
return nil
}
func (i *ACLServerIdentity) NodeIdentityList() []*ACLNodeIdentity {
return nil
}
func (i *ACLServerIdentity) IsExpired(asOf time.Time) bool {
return false
}
func (i *ACLServerIdentity) IsLocal() bool {
return true
}
func (i *ACLServerIdentity) EnterpriseMetadata() *acl.EnterpriseMeta {
return acl.DefaultEnterpriseMeta()
}