acl: adding a new mesh resource

This commit is contained in:
R.B. Boyer 2021-08-20 17:11:01 -05:00 committed by freddygv
parent ced8329d80
commit ee372a854a
22 changed files with 1625 additions and 420 deletions

View File

@ -26,6 +26,7 @@ func legacyPolicy(policy *Policy) *Policy {
PreparedQueryPrefixes: policy.PreparedQueries,
Keyring: policy.Keyring,
Operator: policy.Operator,
Mesh: policy.Mesh,
},
}
}
@ -108,6 +109,14 @@ func checkAllowNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx *
require.Equal(t, Allow, authz.NodeWrite(prefix, entCtx))
}
func checkAllowMeshRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Allow, authz.MeshRead(entCtx))
}
func checkAllowMeshWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Allow, authz.MeshWrite(entCtx))
}
func checkAllowOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Allow, authz.OperatorRead(entCtx))
}
@ -220,6 +229,14 @@ func checkDenyNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx *A
require.Equal(t, Deny, authz.NodeWrite(prefix, entCtx))
}
func checkDenyMeshRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Deny, authz.MeshRead(entCtx))
}
func checkDenyMeshWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Deny, authz.MeshWrite(entCtx))
}
func checkDenyOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Deny, authz.OperatorRead(entCtx))
}
@ -332,6 +349,14 @@ func checkDefaultNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx
require.Equal(t, Default, authz.NodeWrite(prefix, entCtx))
}
func checkDefaultMeshRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Default, authz.MeshRead(entCtx))
}
func checkDefaultMeshWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Default, authz.MeshWrite(entCtx))
}
func checkDefaultOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) {
require.Equal(t, Default, authz.OperatorRead(entCtx))
}
@ -407,6 +432,8 @@ func TestACL(t *testing.T) {
{name: "DenyNodeRead", check: checkDenyNodeRead},
{name: "DenyNodeReadAll", check: checkDenyNodeReadAll},
{name: "DenyNodeWrite", check: checkDenyNodeWrite},
{name: "DenyMeshRead", check: checkDenyMeshRead},
{name: "DenyMeshWrite", check: checkDenyMeshWrite},
{name: "DenyOperatorRead", check: checkDenyOperatorRead},
{name: "DenyOperatorWrite", check: checkDenyOperatorWrite},
{name: "DenyPreparedQueryRead", check: checkDenyPreparedQueryRead},
@ -439,6 +466,8 @@ func TestACL(t *testing.T) {
{name: "AllowNodeRead", check: checkAllowNodeRead},
{name: "AllowNodeReadAll", check: checkAllowNodeReadAll},
{name: "AllowNodeWrite", check: checkAllowNodeWrite},
{name: "AllowMeshRead", check: checkAllowMeshRead},
{name: "AllowMeshWrite", check: checkAllowMeshWrite},
{name: "AllowOperatorRead", check: checkAllowOperatorRead},
{name: "AllowOperatorWrite", check: checkAllowOperatorWrite},
{name: "AllowPreparedQueryRead", check: checkAllowPreparedQueryRead},
@ -471,6 +500,8 @@ func TestACL(t *testing.T) {
{name: "AllowNodeRead", check: checkAllowNodeRead},
{name: "AllowNodeReadAll", check: checkAllowNodeReadAll},
{name: "AllowNodeWrite", check: checkAllowNodeWrite},
{name: "AllowMeshRead", check: checkAllowMeshRead},
{name: "AllowMeshWrite", check: checkAllowMeshWrite},
{name: "AllowOperatorRead", check: checkAllowOperatorRead},
{name: "AllowOperatorWrite", check: checkAllowOperatorWrite},
{name: "AllowPreparedQueryRead", check: checkAllowPreparedQueryRead},
@ -861,6 +892,319 @@ func TestACL(t *testing.T) {
{name: "WriteDenied", check: checkDenyKeyringWrite},
},
},
{
name: "MeshDefaultAllowPolicyDeny",
defaultPolicy: AllowAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
name: "MeshDefaultAllowPolicyRead",
defaultPolicy: AllowAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
name: "MeshDefaultAllowPolicyWrite",
defaultPolicy: AllowAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
name: "MeshDefaultAllowPolicyNone",
defaultPolicy: AllowAll(),
policyStack: []*Policy{
{},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
name: "MeshDefaultDenyPolicyDeny",
defaultPolicy: DenyAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
name: "MeshDefaultDenyPolicyRead",
defaultPolicy: DenyAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
name: "MeshDefaultDenyPolicyWrite",
defaultPolicy: DenyAll(),
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Mesh: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
name: "MeshDefaultDenyPolicyNone",
defaultPolicy: DenyAll(),
policyStack: []*Policy{
{},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:deny, m:deny = deny
name: "MeshOperatorDenyPolicyDeny",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyDeny,
Mesh: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:read, m:deny = deny
name: "MeshOperatorReadPolicyDeny",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyRead,
Mesh: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:write, m:deny = deny
name: "MeshOperatorWritePolicyDeny",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyWrite,
Mesh: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:deny, m:read = read
name: "MeshOperatorDenyPolicyRead",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyDeny,
Mesh: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:read, m:read = read
name: "MeshOperatorReadPolicyRead",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyRead,
Mesh: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:write, m:read = read
name: "MeshOperatorWritePolicyRead",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyWrite,
Mesh: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:deny, m:write = write
name: "MeshOperatorDenyPolicyWrite",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyDeny,
Mesh: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
// o:read, m:write = write
name: "MeshOperatorReadPolicyWrite",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyRead,
Mesh: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
// o:write, m:write = write
name: "MeshOperatorWritePolicyWrite",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyWrite,
Mesh: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
// o:deny, m:<none> = deny
name: "MeshOperatorDenyPolicyNone",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyDeny,
},
},
},
checks: []aclCheck{
{name: "ReadDenied", check: checkDenyMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:read, m:<none> = read
name: "MeshOperatorReadPolicyNone",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyRead,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteDenied", check: checkDenyMeshWrite},
},
},
{
// o:write, m:<none> = write
name: "MeshOperatorWritePolicyNone",
defaultPolicy: nil, // test both
policyStack: []*Policy{
{
PolicyRules: PolicyRules{
Operator: PolicyWrite,
},
},
},
checks: []aclCheck{
{name: "ReadAllowed", check: checkAllowMeshRead},
{name: "WriteAllowed", check: checkAllowMeshWrite},
},
},
{
name: "OperatorDefaultAllowPolicyDeny",
defaultPolicy: AllowAll(),
@ -2002,23 +2346,36 @@ func TestACL(t *testing.T) {
},
}
run := func(t *testing.T, tcase aclTest, defaultPolicy Authorizer) {
acl := defaultPolicy
for _, policy := range tcase.policyStack {
newACL, err := NewPolicyAuthorizerWithDefaults(acl, []*Policy{policy}, nil)
require.NoError(t, err)
acl = newACL
}
for _, check := range tcase.checks {
checkName := check.name
if check.prefix != "" {
checkName = fmt.Sprintf("%s.Prefix(%s)", checkName, check.prefix)
}
t.Run(checkName, func(t *testing.T) {
check.check(t, acl, check.prefix, nil)
})
}
}
for _, tcase := range tests {
t.Run(tcase.name, func(t *testing.T) {
acl := tcase.defaultPolicy
for _, policy := range tcase.policyStack {
newACL, err := NewPolicyAuthorizerWithDefaults(acl, []*Policy{policy}, nil)
require.NoError(t, err)
acl = newACL
}
for _, check := range tcase.checks {
checkName := check.name
if check.prefix != "" {
checkName = fmt.Sprintf("%s.Prefix(%s)", checkName, check.prefix)
}
t.Run(checkName, func(t *testing.T) {
check.check(t, acl, check.prefix, nil)
if tcase.defaultPolicy == nil {
t.Run("default-allow", func(t *testing.T) {
run(t, tcase, AllowAll())
})
t.Run("default-deny", func(t *testing.T) {
run(t, tcase, DenyAll())
})
} else {
run(t, tcase, tcase.defaultPolicy)
}
})
}

View File

@ -45,6 +45,7 @@ const (
ResourceKeyring Resource = "keyring"
ResourceNode Resource = "node"
ResourceOperator Resource = "operator"
ResourceMesh Resource = "mesh"
ResourceQuery Resource = "query"
ResourceService Resource = "service"
ResourceSession Resource = "session"
@ -104,6 +105,14 @@ type Authorizer interface {
// KeyringWrite determines if the keyring can be manipulated
KeyringWrite(*AuthorizerContext) EnforcementDecision
// MeshRead determines if the read-only Consul mesh functions
// can be used.
MeshRead(*AuthorizerContext) EnforcementDecision
// MeshWrite determines if the state-changing Consul mesh
// functions can be used.
MeshWrite(*AuthorizerContext) EnforcementDecision
// NodeRead checks for permission to read (discover) a given node.
NodeRead(string, *AuthorizerContext) EnforcementDecision
@ -204,6 +213,13 @@ func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx
case "write":
return authz.KeyringWrite(ctx), nil
}
case ResourceMesh:
switch lowerAccess {
case "read":
return authz.MeshRead(ctx), nil
case "write":
return authz.MeshWrite(ctx), nil
}
case ResourceNode:
switch lowerAccess {
case "read":

View File

@ -129,6 +129,16 @@ func (m *mockAuthorizer) NodeWrite(segment string, ctx *AuthorizerContext) Enfor
return ret.Get(0).(EnforcementDecision)
}
func (m *mockAuthorizer) MeshRead(ctx *AuthorizerContext) EnforcementDecision {
ret := m.Called(ctx)
return ret.Get(0).(EnforcementDecision)
}
func (m *mockAuthorizer) MeshWrite(ctx *AuthorizerContext) EnforcementDecision {
ret := m.Called(ctx)
return ret.Get(0).(EnforcementDecision)
}
// OperatorRead determines if the read-only Consul operator functions
// can be used. ret := m.Called(segment, ctx)
func (m *mockAuthorizer) OperatorRead(ctx *AuthorizerContext) EnforcementDecision {

View File

@ -145,6 +145,22 @@ func (c *ChainedAuthorizer) KeyringWrite(entCtx *AuthorizerContext) EnforcementD
})
}
// MeshRead determines if the read-only Consul mesh functions
// can be used.
func (c *ChainedAuthorizer) MeshRead(entCtx *AuthorizerContext) EnforcementDecision {
return c.executeChain(func(authz Authorizer) EnforcementDecision {
return authz.MeshRead(entCtx)
})
}
// MeshWrite determines if the state-changing Consul mesh
// functions can be used.
func (c *ChainedAuthorizer) MeshWrite(entCtx *AuthorizerContext) EnforcementDecision {
return c.executeChain(func(authz Authorizer) EnforcementDecision {
return authz.MeshWrite(entCtx)
})
}
// NodeRead checks for permission to read (discover) a given node.
func (c *ChainedAuthorizer) NodeRead(node string, entCtx *AuthorizerContext) EnforcementDecision {
return c.executeChain(func(authz Authorizer) EnforcementDecision {

View File

@ -62,6 +62,12 @@ func (authz testAuthorizer) NodeReadAll(*AuthorizerContext) EnforcementDecision
func (authz testAuthorizer) NodeWrite(string, *AuthorizerContext) EnforcementDecision {
return EnforcementDecision(authz)
}
func (authz testAuthorizer) MeshRead(*AuthorizerContext) EnforcementDecision {
return EnforcementDecision(authz)
}
func (authz testAuthorizer) MeshWrite(*AuthorizerContext) EnforcementDecision {
return EnforcementDecision(authz)
}
func (authz testAuthorizer) OperatorRead(*AuthorizerContext) EnforcementDecision {
return EnforcementDecision(authz)
}
@ -113,6 +119,8 @@ func TestChainedAuthorizer(t *testing.T) {
checkDenyKeyWritePrefix(t, authz, "foo", nil)
checkDenyNodeRead(t, authz, "foo", nil)
checkDenyNodeWrite(t, authz, "foo", nil)
checkDenyMeshRead(t, authz, "foo", nil)
checkDenyMeshWrite(t, authz, "foo", nil)
checkDenyOperatorRead(t, authz, "foo", nil)
checkDenyOperatorWrite(t, authz, "foo", nil)
checkDenyPreparedQueryRead(t, authz, "foo", nil)
@ -143,6 +151,8 @@ func TestChainedAuthorizer(t *testing.T) {
checkDenyKeyWritePrefix(t, authz, "foo", nil)
checkDenyNodeRead(t, authz, "foo", nil)
checkDenyNodeWrite(t, authz, "foo", nil)
checkDenyMeshRead(t, authz, "foo", nil)
checkDenyMeshWrite(t, authz, "foo", nil)
checkDenyOperatorRead(t, authz, "foo", nil)
checkDenyOperatorWrite(t, authz, "foo", nil)
checkDenyPreparedQueryRead(t, authz, "foo", nil)
@ -173,6 +183,8 @@ func TestChainedAuthorizer(t *testing.T) {
checkAllowKeyWritePrefix(t, authz, "foo", nil)
checkAllowNodeRead(t, authz, "foo", nil)
checkAllowNodeWrite(t, authz, "foo", nil)
checkAllowMeshRead(t, authz, "foo", nil)
checkAllowMeshWrite(t, authz, "foo", nil)
checkAllowOperatorRead(t, authz, "foo", nil)
checkAllowOperatorWrite(t, authz, "foo", nil)
checkAllowPreparedQueryRead(t, authz, "foo", nil)
@ -203,6 +215,8 @@ func TestChainedAuthorizer(t *testing.T) {
checkDenyKeyWritePrefix(t, authz, "foo", nil)
checkDenyNodeRead(t, authz, "foo", nil)
checkDenyNodeWrite(t, authz, "foo", nil)
checkDenyMeshRead(t, authz, "foo", nil)
checkDenyMeshWrite(t, authz, "foo", nil)
checkDenyOperatorRead(t, authz, "foo", nil)
checkDenyOperatorWrite(t, authz, "foo", nil)
checkDenyPreparedQueryRead(t, authz, "foo", nil)
@ -231,6 +245,8 @@ func TestChainedAuthorizer(t *testing.T) {
checkAllowKeyWritePrefix(t, authz, "foo", nil)
checkAllowNodeRead(t, authz, "foo", nil)
checkAllowNodeWrite(t, authz, "foo", nil)
checkAllowMeshRead(t, authz, "foo", nil)
checkAllowMeshWrite(t, authz, "foo", nil)
checkAllowOperatorRead(t, authz, "foo", nil)
checkAllowOperatorWrite(t, authz, "foo", nil)
checkAllowPreparedQueryRead(t, authz, "foo", nil)

View File

@ -84,6 +84,7 @@ type PolicyRules struct {
PreparedQueryPrefixes []*PreparedQueryRule `hcl:"query_prefix,expand"`
Keyring string `hcl:"keyring"`
Operator string `hcl:"operator"`
Mesh string `hcl:"mesh"`
}
// Policy is used to represent the policy specified by an ACL configuration.
@ -285,6 +286,11 @@ func (pr *PolicyRules) Validate(conf *Config) error {
return fmt.Errorf("Invalid operator policy: %#v", pr.Operator)
}
// Validate the mesh policy - this one is allowed to be empty
if pr.Mesh != "" && !isPolicyValid(pr.Mesh, false) {
return fmt.Errorf("Invalid mesh policy: %#v", pr.Mesh)
}
return nil
}
@ -318,6 +324,7 @@ func parseLegacy(rules string, conf *Config) (*Policy, error) {
PreparedQueries []*PreparedQueryRule `hcl:"query,expand"`
Keyring string `hcl:"keyring"`
Operator string `hcl:"operator"`
// NOTE: mesh resources not supported here
}
lp := &LegacyPolicy{}
@ -446,6 +453,7 @@ func NewPolicyFromSource(id string, revision uint64, rules string, syntax Syntax
return policy, err
}
// TODO(ACL-Legacy): remove this
func (policy *Policy) ConvertToLegacy() *Policy {
converted := &Policy{
ID: policy.ID,
@ -474,6 +482,7 @@ func (policy *Policy) ConvertToLegacy() *Policy {
return converted
}
// TODO(ACL-Legacy): remove this
func (policy *Policy) ConvertFromLegacy() *Policy {
return &Policy{
ID: policy.ID,

View File

@ -40,6 +40,9 @@ type policyAuthorizer struct {
// operatorRule contains the operator policies.
operatorRule *policyAuthorizerRule
// meshRule contains the mesh policies.
meshRule *policyAuthorizerRule
// embedded enterprise policy authorizer
enterprisePolicyAuthorizer
}
@ -310,6 +313,15 @@ func (p *policyAuthorizer) loadRules(policy *PolicyRules) error {
p.operatorRule = &policyAuthorizerRule{access: access}
}
// Load the mesh policy
if policy.Mesh != "" {
access, err := AccessLevelFromString(policy.Mesh)
if err != nil {
return err
}
p.meshRule = &policyAuthorizerRule{access: access}
}
return nil
}
@ -664,6 +676,25 @@ func (p *policyAuthorizer) KeyringWrite(*AuthorizerContext) EnforcementDecision
return Default
}
// MeshRead determines if the read-only mesh functions are allowed.
func (p *policyAuthorizer) MeshRead(ctx *AuthorizerContext) EnforcementDecision {
if p.meshRule != nil {
return enforce(p.meshRule.access, AccessRead)
}
// default to OperatorRead access
return p.OperatorRead(ctx)
}
// MeshWrite determines if the state-changing mesh functions are
// allowed.
func (p *policyAuthorizer) MeshWrite(ctx *AuthorizerContext) EnforcementDecision {
if p.meshRule != nil {
return enforce(p.meshRule.access, AccessWrite)
}
// default to OperatorWrite access
return p.OperatorWrite(ctx)
}
// OperatorRead determines if the read-only operator functions are allowed.
func (p *policyAuthorizer) OperatorRead(*AuthorizerContext) EnforcementDecision {
if p.operatorRule != nil {

View File

@ -48,6 +48,8 @@ func TestPolicyAuthorizer(t *testing.T) {
{name: "DefaultKeyWritePrefix", prefix: "foo", check: checkDefaultKeyWritePrefix},
{name: "DefaultNodeRead", prefix: "foo", check: checkDefaultNodeRead},
{name: "DefaultNodeWrite", prefix: "foo", check: checkDefaultNodeWrite},
{name: "DefaultMeshRead", prefix: "foo", check: checkDefaultMeshRead},
{name: "DefaultMeshWrite", prefix: "foo", check: checkDefaultMeshWrite},
{name: "DefaultOperatorRead", prefix: "foo", check: checkDefaultOperatorRead},
{name: "DefaultOperatorWrite", prefix: "foo", check: checkDefaultOperatorWrite},
{name: "DefaultPreparedQueryRead", prefix: "foo", check: checkDefaultPreparedQueryRead},

View File

@ -17,6 +17,7 @@ type policyRulesMergeContext struct {
keyringRule string
keyRules map[string]*KeyRule
keyPrefixRules map[string]*KeyRule
meshRule string
nodeRules map[string]*NodeRule
nodePrefixRules map[string]*NodeRule
operatorRule string
@ -37,6 +38,7 @@ func (p *policyRulesMergeContext) init() {
p.keyringRule = ""
p.keyRules = make(map[string]*KeyRule)
p.keyPrefixRules = make(map[string]*KeyRule)
p.meshRule = ""
p.nodeRules = make(map[string]*NodeRule)
p.nodePrefixRules = make(map[string]*NodeRule)
p.operatorRule = ""
@ -123,6 +125,10 @@ func (p *policyRulesMergeContext) merge(policy *PolicyRules) {
}
}
if takesPrecedenceOver(policy.Mesh, p.meshRule) {
p.meshRule = policy.Mesh
}
for _, np := range policy.Nodes {
update := true
if permission, found := p.nodeRules[np.Name]; found {
@ -234,6 +240,7 @@ func (p *policyRulesMergeContext) update(merged *PolicyRules) {
merged.ACL = p.aclRule
merged.Keyring = p.keyringRule
merged.Operator = p.operatorRule
merged.Mesh = p.meshRule
// All the for loop appends are ugly but Go doesn't have a way to get
// a slice of all values within a map so this is necessary

File diff suppressed because it is too large Load Diff

View File

@ -156,6 +156,20 @@ func (s *staticAuthorizer) NodeWrite(string, *AuthorizerContext) EnforcementDeci
return Deny
}
func (s *staticAuthorizer) MeshRead(*AuthorizerContext) EnforcementDecision {
if s.defaultAllow {
return Allow
}
return Deny
}
func (s *staticAuthorizer) MeshWrite(*AuthorizerContext) EnforcementDecision {
if s.defaultAllow {
return Allow
}
return Deny
}
func (s *staticAuthorizer) OperatorRead(*AuthorizerContext) EnforcementDecision {
if s.defaultAllow {
return Allow

View File

@ -1963,6 +1963,14 @@ func TestACL_Authorize(t *testing.T) {
Resource: "operator",
Access: "write",
},
{
Resource: "mesh",
Access: "read",
},
{
Resource: "mesh",
Access: "write",
},
{
Resource: "query",
Segment: "foo",
@ -2097,6 +2105,14 @@ func TestACL_Authorize(t *testing.T) {
Resource: "operator",
Access: "write",
},
{
Resource: "mesh",
Access: "read",
},
{
Resource: "mesh",
Access: "write",
},
{
Resource: "query",
Segment: "foo",
@ -2147,6 +2163,8 @@ func TestACL_Authorize(t *testing.T) {
true, // node:write
true, // operator:read
true, // operator:write
true, // mesh:read
true, // mesh:write
false, // query:read
false, // query:write
true, // service:read

View File

@ -584,6 +584,7 @@ func (s *HTTPHandlers) AgentForceLeave(resp http.ResponseWriter, req *http.Reque
if err != nil {
return nil, err
}
// TODO(partitions): should this be possible in a partition?
if authz.OperatorWrite(nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -1536,6 +1537,7 @@ func (s *HTTPHandlers) AgentHost(resp http.ResponseWriter, req *http.Request) (i
return nil, err
}
// TODO(partitions): should this be possible in a partition?
if authz.OperatorRead(nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}

View File

@ -1379,6 +1379,8 @@ func makeACLETag(parent string, policy *acl.Policy) string {
// GetPolicy is used to retrieve a compiled policy object with a TTL. Does not
// support a blocking query.
//
// TODO(ACL-Legacy): remove this
func (a *ACL) GetPolicy(args *structs.ACLPolicyResolveLegacyRequest, reply *structs.ACLPolicyResolveLegacyResponse) error {
if done, err := a.srv.ForwardRPC("ACL.GetPolicy", args, reply); done {
return err

View File

@ -1906,6 +1906,7 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega
authz, err := r.ResolveToken("legacy-client")
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, acl.Deny, authz.MeshRead(nil))
require.Equal(t, acl.Deny, authz.OperatorRead(nil))
require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
})

View File

@ -253,6 +253,7 @@ func (s *HTTPHandlers) handler(enableDebug bool) http.Handler {
// If the token provided does not have the necessary permissions,
// write a forbidden response
// TODO(partitions): should this be possible in a partition?
if authz.OperatorRead(nil) != acl.Allow {
resp.WriteHeader(http.StatusForbidden)
return

View File

@ -73,6 +73,7 @@ node_prefix "" {
policy = "write"
}
operator = "write"
mesh = "write"
query_prefix "" {
policy = "write"
}

View File

@ -315,7 +315,7 @@ func (e *ProxyConfigEntry) CanRead(authz acl.Authorizer) bool {
func (e *ProxyConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
return authz.MeshWrite(&authzContext) == acl.Allow
}
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {

View File

@ -7,14 +7,24 @@ import (
"testing"
"time"
"github.com/hashicorp/consul/acl"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
)
func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
// This test tests both of these because they are related functions.
newAuthz := func(t *testing.T, src string) acl.Authorizer {
policy, err := acl.NewPolicyFromSource("", 0, src, acl.SyntaxCurrent, nil, nil)
require.NoError(t, err)
authorizer, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
return authorizer
}
newServiceACL := func(t *testing.T, canRead, canWrite []string) acl.Authorizer {
var buf bytes.Buffer
for _, s := range canRead {
@ -32,13 +42,37 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
return authorizer
}
type testACL struct {
name string
authorizer acl.Authorizer
canRead bool
canWrite bool
newServiceAndOperatorACL := func(t *testing.T, service, operator string) acl.Authorizer {
switch {
case service != "" && operator != "":
return newAuthz(t, fmt.Sprintf(`service "test" { policy = %q } operator = %q`, service, operator))
case service == "" && operator != "":
return newAuthz(t, fmt.Sprintf(`operator = %q`, operator))
case service != "" && operator == "":
return newAuthz(t, fmt.Sprintf(`service "test" { policy = %q }`, service))
default:
t.Fatalf("one of these should be set")
return nil
}
}
newServiceAndMeshACL := func(t *testing.T, service, mesh string) acl.Authorizer {
switch {
case service != "" && mesh != "":
return newAuthz(t, fmt.Sprintf(`service "test" { policy = %q } mesh = %q`, service, mesh))
case service == "" && mesh != "":
return newAuthz(t, fmt.Sprintf(`mesh = %q`, mesh))
case service != "" && mesh == "":
return newAuthz(t, fmt.Sprintf(`service "test" { policy = %q }`, service))
default:
t.Fatalf("one of these should be set")
return nil
}
}
type testACL = configEntryTestACL
type testcase = configEntryACLTestCase
defaultDenyCase := testACL{
name: "deny",
authorizer: newServiceACL(t, nil, nil),
@ -64,12 +98,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
canWrite: false,
}
for _, tc := range []struct {
name string
entry discoveryChainConfigEntry
expectServices []ServiceID
expectACLs []testACL
}{
cases := []testcase{
{
name: "resolver: self",
entry: &ServiceResolverConfigEntry{
@ -226,25 +255,261 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// sanity check inputs
require.NoError(t, tc.entry.Normalize())
require.NoError(t, tc.entry.Validate())
{
name: "ingress-gateway",
entry: &IngressGatewayConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
got := tc.entry.ListRelatedServices()
require.Equal(t, tc.expectServices, got)
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
for _, a := range tc.expectACLs {
a := a
t.Run(a.name, func(t *testing.T) {
require.Equal(t, a.canRead, tc.entry.CanRead(a.authorizer))
require.Equal(t, a.canWrite, tc.entry.CanWrite(a.authorizer))
})
}
})
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
{
name: "terminating-gateway",
entry: &TerminatingGatewayConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
}
testConfigEntries_ListRelatedServices_AndACLs(t, cases)
}
func TestServiceResolverConfigEntry(t *testing.T) {

View File

@ -271,7 +271,7 @@ func (e *IngressGatewayConfigEntry) CanRead(authz acl.Authorizer) bool {
func (e *IngressGatewayConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
return authz.MeshWrite(&authzContext) == acl.Allow
}
func (e *IngressGatewayConfigEntry) GetRaftIndex() *RaftIndex {
@ -407,15 +407,13 @@ func (e *TerminatingGatewayConfigEntry) Validate() error {
func (e *TerminatingGatewayConfigEntry) CanRead(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ServiceRead(e.Name, &authzContext) == acl.Allow
}
func (e *TerminatingGatewayConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
return authz.MeshWrite(&authzContext) == acl.Allow
}
func (e *TerminatingGatewayConfigEntry) GetRaftIndex() *RaftIndex {

View File

@ -71,7 +71,7 @@ func (e *MeshConfigEntry) CanRead(authz acl.Authorizer) bool {
func (e *MeshConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
return authz.MeshWrite(&authzContext) == acl.Allow
}
func (e *MeshConfigEntry) GetRaftIndex() *RaftIndex {

View File

@ -11,10 +11,195 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestConfigEntries_ACLs(t *testing.T) {
type testACL = configEntryTestACL
type testcase = configEntryACLTestCase
newAuthz := func(t *testing.T, src string) acl.Authorizer {
policy, err := acl.NewPolicyFromSource("", 0, src, acl.SyntaxCurrent, nil, nil)
require.NoError(t, err)
authorizer, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
return authorizer
}
cases := []testcase{
// =================== proxy-defaults ===================
{
name: "proxy-defaults",
entry: &ProxyConfigEntry{},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: operator deny",
authorizer: newAuthz(t, `operator = "deny"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: operator read",
authorizer: newAuthz(t, `operator = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: operator write",
authorizer: newAuthz(t, `operator = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
{
name: "proxy-defaults: mesh deny",
authorizer: newAuthz(t, `mesh = "deny"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: mesh read",
authorizer: newAuthz(t, `mesh = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: mesh write",
authorizer: newAuthz(t, `mesh = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
{
name: "proxy-defaults: operator deny and mesh read",
authorizer: newAuthz(t, `operator = "deny" mesh = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "proxy-defaults: operator deny and mesh write",
authorizer: newAuthz(t, `operator = "deny" mesh = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
},
},
// =================== mesh ===================
{
name: "mesh",
entry: &MeshConfigEntry{},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: operator deny",
authorizer: newAuthz(t, `operator = "deny"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: operator read",
authorizer: newAuthz(t, `operator = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: operator write",
authorizer: newAuthz(t, `operator = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
{
name: "mesh: mesh deny",
authorizer: newAuthz(t, `mesh = "deny"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: mesh read",
authorizer: newAuthz(t, `mesh = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: mesh write",
authorizer: newAuthz(t, `mesh = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
{
name: "mesh: operator deny and mesh read",
authorizer: newAuthz(t, `operator = "deny" mesh = "read"`),
canRead: true, // unauthenticated
canWrite: false,
},
{
name: "mesh: operator deny and mesh write",
authorizer: newAuthz(t, `operator = "deny" mesh = "write"`),
canRead: true, // unauthenticated
canWrite: true,
},
},
},
}
testConfigEntries_ListRelatedServices_AndACLs(t, cases)
}
type configEntryTestACL struct {
name string
authorizer acl.Authorizer
canRead bool
canWrite bool
}
type configEntryACLTestCase struct {
name string
entry ConfigEntry
expectServices []ServiceID // optional
expectACLs []configEntryTestACL
}
func testConfigEntries_ListRelatedServices_AndACLs(t *testing.T, cases []configEntryACLTestCase) {
// This test tests both of these because they are related functions.
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// verify test inputs
require.NoError(t, tc.entry.Normalize())
require.NoError(t, tc.entry.Validate())
if dce, ok := tc.entry.(discoveryChainConfigEntry); ok {
got := dce.ListRelatedServices()
require.Equal(t, tc.expectServices, got)
}
if len(tc.expectACLs) == 1 {
a := tc.expectACLs[0]
require.Empty(t, a.name)
} else {
for _, a := range tc.expectACLs {
require.NotEmpty(t, a.name)
t.Run(a.name, func(t *testing.T) {
require.Equal(t, a.canRead, tc.entry.CanRead(a.authorizer), "unexpected CanRead result")
require.Equal(t, a.canWrite, tc.entry.CanWrite(a.authorizer), "unexpected CanWrite result")
})
}
}
})
}
}
// TestDecodeConfigEntry is the 'structs' mirror image of
// command/config/write/config_write_test.go:TestParseConfigEntry
func TestDecodeConfigEntry(t *testing.T) {