acl: adding a new mesh resource
This commit is contained in:
parent
72391dc99c
commit
4206f585f0
385
acl/acl_test.go
385
acl/acl_test.go
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -73,6 +73,7 @@ node_prefix "" {
|
|||
policy = "write"
|
||||
}
|
||||
operator = "write"
|
||||
mesh = "write"
|
||||
query_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue