Update the ACL Resolver to allow for Consul Enterprise specific hooks. (#6687)

This commit is contained in:
Matt Keeler 2019-10-25 11:06:16 -04:00 committed by GitHub
parent 1270a93274
commit a688ea952d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 37 deletions

View File

@ -19,6 +19,10 @@ func NewChainedAuthorizer(chain []Authorizer) *ChainedAuthorizer {
} }
} }
func (c *ChainedAuthorizer) AuthorizerChain() []Authorizer {
return c.chain
}
func (c *ChainedAuthorizer) executeChain(enforce func(authz Authorizer) EnforcementDecision) EnforcementDecision { func (c *ChainedAuthorizer) executeChain(enforce func(authz Authorizer) EnforcementDecision) EnforcementDecision {
for _, authz := range c.chain { for _, authz := range c.chain {
decision := enforce(authz) decision := enforce(authz)

View File

@ -97,6 +97,7 @@ type ACLResolverDelegate interface {
ResolvePolicyFromID(policyID string) (bool, *structs.ACLPolicy, error) ResolvePolicyFromID(policyID string) (bool, *structs.ACLPolicy, error)
ResolveRoleFromID(roleID string) (bool, *structs.ACLRole, error) ResolveRoleFromID(roleID string) (bool, *structs.ACLRole, error)
RPC(method string, args interface{}, reply interface{}) error RPC(method string, args interface{}, reply interface{}) error
EnterpriseACLResolverDelegate
} }
type policyOrRoleTokenError struct { type policyOrRoleTokenError struct {
@ -291,7 +292,12 @@ func (r *ACLResolver) resolveTokenLegacy(token string) (acl.Authorizer, error) {
return nil, err return nil, err
} }
return policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf) authz, err := policies.Compile(r.cache, r.entConf)
if err != nil {
return nil, err
}
return acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.RootAuthorizer(r.config.ACLDefaultPolicy)}), nil
} }
return nil, err return nil, err
@ -992,7 +998,7 @@ func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) {
defer metrics.MeasureSince([]string{"acl", "ResolveToken"}, time.Now()) defer metrics.MeasureSince([]string{"acl", "ResolveToken"}, time.Now())
policies, err := r.resolveTokenToPolicies(token) identity, policies, err := r.resolveTokenToIdentityAndPolicies(token)
if err != nil { if err != nil {
r.disableACLsWhenUpstreamDisabled(err) r.disableACLsWhenUpstreamDisabled(err)
if IsACLRemoteError(err) { if IsACLRemoteError(err) {
@ -1004,9 +1010,27 @@ func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) {
} }
// Build the Authorizer // Build the Authorizer
authorizer, err := policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf) var chain []acl.Authorizer
return authorizer, err
authz, err := policies.Compile(r.cache, r.entConf)
if err != nil {
return nil, err
}
chain = append(chain, authz)
authz, err = r.resolveEnterpriseDefaultsForIdentity(identity)
if err != nil {
if IsACLRemoteError(err) {
r.logger.Printf("[ERR] consul.acl: %v", err)
return r.down, nil
}
return nil, err
} else if authz != nil {
chain = append(chain, authz)
}
chain = append(chain, acl.RootAuthorizer(r.config.ACLDefaultPolicy))
return acl.NewChainedAuthorizer(chain), nil
} }
func (r *ACLResolver) ACLsEnabled() bool { func (r *ACLResolver) ACLsEnabled() bool {

View File

@ -6,8 +6,16 @@ import (
"log" "log"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
) )
// EnterpriseACLResolverDelegate stub
type EnterpriseACLResolverDelegate interface{}
func newEnterpriseACLConfig(*log.Logger) *acl.EnterpriseACLConfig { func newEnterpriseACLConfig(*log.Logger) *acl.EnterpriseACLConfig {
return nil return nil
} }
func (r *ACLResolver) resolveEnterpriseDefaultsForIdentity(identity structs.ACLIdentity) (acl.Authorizer, error) {
return nil, nil
}

View File

@ -0,0 +1,28 @@
// +build !consulent
package consul
import (
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
)
func testIdentityForTokenEnterprise(string) (bool, structs.ACLIdentity, error) {
return true, nil, acl.ErrNotFound
}
func testPolicyForIDEnterprise(string) (bool, *structs.ACLPolicy, error) {
return true, nil, acl.ErrNotFound
}
func testRoleForIDEnterprise(string) (bool, *structs.ACLRole, error) {
return true, nil, acl.ErrNotFound
}
// EnterpriseACLResolverTestDelegate stub
type EnterpriseACLResolverTestDelegate struct{}
// RPC stub for the EnterpriseACLResolverTestDelegate
func (d *EnterpriseACLResolverTestDelegate) RPC(string, interface{}, interface{}) (bool, error) {
return false, nil
}

View File

@ -40,6 +40,24 @@ type asyncResolutionResult struct {
err error err error
} }
func verifyAuthorizerChain(t *testing.T, expected acl.Authorizer, actual acl.Authorizer) {
expectedChainAuthz, ok := expected.(*acl.ChainedAuthorizer)
require.True(t, ok, "expected Authorizer is not a ChainedAuthorizer")
actualChainAuthz, ok := actual.(*acl.ChainedAuthorizer)
require.True(t, ok, "actual Authorizer is not a ChainedAuthorizer")
expectedChain := expectedChainAuthz.AuthorizerChain()
actualChain := actualChainAuthz.AuthorizerChain()
require.Equal(t, len(expectedChain), len(actualChain), "ChainedAuthorizers have different length chains")
for idx, expectedAuthz := range expectedChain {
actualAuthz := actualChain[idx]
// pointer equality - because we want to verify authorizer reuse
require.True(t, expectedAuthz == actualAuthz, "Authorizer pointers are not equal")
}
}
func resolveTokenAsync(r *ACLResolver, token string, ch chan *asyncResolutionResult) { func resolveTokenAsync(r *ACLResolver, token string, ch chan *asyncResolutionResult) {
authz, err := r.ResolveToken(token) authz, err := r.ResolveToken(token)
ch <- &asyncResolutionResult{authz: authz, err: err} ch <- &asyncResolutionResult{authz: authz, err: err}
@ -226,7 +244,7 @@ func testIdentityForToken(token string) (bool, structs.ACLIdentity, error) {
}, },
}, nil }, nil
default: default:
return true, nil, acl.ErrNotFound return testIdentityForTokenEnterprise(token)
} }
} }
@ -289,7 +307,7 @@ func testPolicyForID(policyID string) (bool, *structs.ACLPolicy, error) {
RaftIndex: structs.RaftIndex{CreateIndex: 1, ModifyIndex: 2}, RaftIndex: structs.RaftIndex{CreateIndex: 1, ModifyIndex: 2},
}, nil }, nil
default: default:
return true, nil, acl.ErrNotFound return testPolicyForIDEnterprise(policyID)
} }
} }
@ -424,7 +442,7 @@ func testRoleForID(roleID string) (bool, *structs.ACLRole, error) {
}, },
}, nil }, nil
default: default:
return true, nil, acl.ErrNotFound return testRoleForIDEnterprise(roleID)
} }
} }
@ -448,6 +466,8 @@ type ACLResolverTestDelegate struct {
policyCached bool policyCached bool
// state for the optional default resolver function defaultRoleResolveFn // state for the optional default resolver function defaultRoleResolveFn
roleCached bool roleCached bool
EnterpriseACLResolverTestDelegate
} }
func (d *ACLResolverTestDelegate) Reset() { func (d *ACLResolverTestDelegate) Reset() {
@ -590,6 +610,9 @@ func (d *ACLResolverTestDelegate) RPC(method string, args interface{}, reply int
} }
panic("Bad Test Implementation: should provide a roleResolveFn to the ACLResolverTestDelegate") panic("Bad Test Implementation: should provide a roleResolveFn to the ACLResolverTestDelegate")
} }
if handled, err := d.EnterpriseACLResolverTestDelegate.RPC(method, args, reply); handled {
return err
}
panic("Bad Test Implementation: Was the ACLResolver updated to use new RPC methods") panic("Bad Test Implementation: Was the ACLResolver updated to use new RPC methods")
} }
@ -773,7 +796,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz2, err := r.ResolveToken("found") authz2, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.False(t, authz == authz2) require.NotEqual(t, authz, authz2)
require.Equal(t, acl.Deny, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz2.NodeWrite("foo", nil))
requirePolicyCached(t, r, "node-wr", false, "expired") // from "found" token requirePolicyCached(t, r, "node-wr", false, "expired") // from "found" token
@ -839,8 +862,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz2, err := r.ResolveToken("found") authz2, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. verifyAuthorizerChain(t, authz, authz2)
require.True(t, authz == authz2)
require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil))
}) })
@ -872,7 +894,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil))
}) })
@ -906,7 +928,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz2, err := r.ResolveToken("found") authz2, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
requirePolicyCached(t, r, "node-wr", true, "still cached") // from "found" token requirePolicyCached(t, r, "node-wr", true, "still cached") // from "found" token
@ -941,7 +963,8 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz2, err := r.ResolveToken("found-role") authz2, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
}) })
@ -978,12 +1001,9 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token
// the go routine spawned will eventually return with a authz that doesn't have the policy // the go routine spawned will eventually return with a authz that doesn't have the policy
retry.Run(t, func(t *retry.R) { retry.Run(t, func(t *retry.R) {
authz3, err := r.ResolveToken("found") authz3, err := r.ResolveToken("found")
@ -1027,7 +1047,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
// the go routine spawned will eventually return with a authz that doesn't have the policy // the go routine spawned will eventually return with a authz that doesn't have the policy
@ -1071,7 +1091,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil))
}) })
@ -1108,7 +1128,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2, "\n[1]={%+v} != \n[2]={%+v}", authz, authz2) verifyAuthorizerChain(t, authz, authz2)
require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil))
}) })
@ -1140,12 +1160,9 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz2, err := r.ResolveToken("found") authz2, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. verifyAuthorizerChain(t, authz, authz2)
require.True(t, authz == authz2)
require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil))
requireIdentityCached(t, r, "found", true, "cached")
// the go routine spawned will eventually return and this will be a not found error // the go routine spawned will eventually return and this will be a not found error
retry.Run(t, func(t *retry.R) { retry.Run(t, func(t *retry.R) {
authz3, err := r.ResolveToken("found") authz3, err := r.ResolveToken("found")

View File

@ -169,7 +169,6 @@ func (s *ACLServiceIdentity) EstimateSize() int {
func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy { func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy {
// Given that we validate this string name before persisting, we do not // Given that we validate this string name before persisting, we do not
// have to escape it before doing the following interpolation. // have to escape it before doing the following interpolation.
// TODO (namespaces) include namespace
rules := aclServiceIdentityRules(s.ServiceName, entMeta) rules := aclServiceIdentityRules(s.ServiceName, entMeta)
hasher := fnv.New128a() hasher := fnv.New128a()
@ -673,7 +672,7 @@ func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, entConf *acl.Ente
return parsed, nil return parsed, nil
} }
func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, entConf *acl.EnterpriseACLConfig) (acl.Authorizer, error) { func (policies ACLPolicies) Compile(cache *ACLCaches, entConf *acl.EnterpriseACLConfig) (acl.Authorizer, error) {
// Determine the cache key // Determine the cache key
cacheKey := policies.HashKey() cacheKey := policies.HashKey()
entry := cache.GetAuthorizer(cacheKey) entry := cache.GetAuthorizer(cacheKey)
@ -688,7 +687,7 @@ func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, ent
} }
// Create the ACL object // Create the ACL object
authorizer, err := acl.NewPolicyAuthorizerWithDefaults(parent, parsed, entConf) authorizer, err := acl.NewPolicyAuthorizer(parsed, entConf)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err) return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err)
} }
@ -1083,8 +1082,6 @@ type ACLTokenListResponse struct {
type ACLTokenBatchGetRequest struct { type ACLTokenBatchGetRequest struct {
AccessorIDs []string // List of accessor ids to fetch AccessorIDs []string // List of accessor ids to fetch
Datacenter string // The datacenter to perform the request within Datacenter string // The datacenter to perform the request within
// TODO (namespaces) should this use enterprise meta? - it would be hard to
// update in a backwards compatible manner an accessor ids should be unique
QueryOptions QueryOptions
} }
@ -1111,7 +1108,6 @@ type ACLTokenBatchSetRequest struct {
// multiple tokens need to be removed from the local DCs state. // multiple tokens need to be removed from the local DCs state.
type ACLTokenBatchDeleteRequest struct { type ACLTokenBatchDeleteRequest struct {
TokenIDs []string // Tokens to delete TokenIDs []string // Tokens to delete
// TODO (namespaces) should we update with ent meta?
} }
// ACLTokenBootstrapRequest is used only at the Raft layer // ACLTokenBootstrapRequest is used only at the Raft layer
@ -1496,8 +1492,6 @@ func (r *ACLLoginRequest) RequestDatacenter() string {
type ACLLogoutRequest struct { type ACLLogoutRequest struct {
Datacenter string // The datacenter to perform the request within Datacenter string // The datacenter to perform the request within
// TODO (namespaces) do we need the ent meta here? tokens are again
// unique across namespaces so its likely we don't need it.
WriteRequest WriteRequest
} }

View File

@ -661,7 +661,7 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
} }
t.Run("Cache Miss", func(t *testing.T) { t.Run("Cache Miss", func(t *testing.T) {
authz, err := testPolicies.Compile(acl.DenyAll(), cache, nil) authz, err := testPolicies.Compile(cache, nil)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
@ -669,7 +669,7 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
require.Equal(t, acl.Allow, authz.AgentRead("foo", nil)) require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.Equal(t, acl.Allow, authz.KeyRead("foo", nil)) require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil)) require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.Equal(t, acl.Deny, authz.ACLRead(nil)) require.Equal(t, acl.Default, authz.ACLRead(nil))
}) })
t.Run("Check Cache", func(t *testing.T) { t.Run("Check Cache", func(t *testing.T) {
@ -682,18 +682,18 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
require.Equal(t, acl.Allow, authz.AgentRead("foo", nil)) require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.Equal(t, acl.Allow, authz.KeyRead("foo", nil)) require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil)) require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.Equal(t, acl.Deny, authz.ACLRead(nil)) require.Equal(t, acl.Default, authz.ACLRead(nil))
// setup the cache for the next test // setup the cache for the next test
cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll()) cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll())
}) })
t.Run("Cache Hit", func(t *testing.T) { t.Run("Cache Hit", func(t *testing.T) {
authz, err := testPolicies.Compile(acl.DenyAll(), cache, nil) authz, err := testPolicies.Compile(cache, nil)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// we reset the Authorizer in the cache so now everything should be denied // we reset the Authorizer in the cache so now everything should be defaulted
require.Equal(t, acl.Deny, authz.NodeRead("foo", nil)) require.Equal(t, acl.Deny, authz.NodeRead("foo", nil))
require.Equal(t, acl.Deny, authz.AgentRead("foo", nil)) require.Equal(t, acl.Deny, authz.AgentRead("foo", nil))
require.Equal(t, acl.Deny, authz.KeyRead("foo", nil)) require.Equal(t, acl.Deny, authz.KeyRead("foo", nil))