mirror of https://github.com/hashicorp/consul
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
parent
0ea3353537
commit
0e5131bd33
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue