From 95362cc5eac758f90d73be7c649795c23b6dc024 Mon Sep 17 00:00:00 2001 From: Riddhi Shah Date: Thu, 24 Mar 2022 16:55:05 +0530 Subject: [PATCH] ACL pkg updates to support Agentless RPCs For many of the new RPCs that will be added in Consul servers for Agentless work, the ACL token will need to be authorized for service:write on any service in any namespace in any partition. The ACL package updates are to make ServiceWriteAny related helpers available on the different authorizers. --- acl/acl_test.go | 12 ++++++++++++ acl/authorizer.go | 11 +++++++++++ acl/authorizer_test.go | 6 ++++++ acl/chained_authorizer.go | 7 +++++++ acl/chained_authorizer_test.go | 3 +++ acl/policy_authorizer.go | 2 +- acl/policy_authorizer_test.go | 2 ++ acl/static_authorizer.go | 7 +++++++ 8 files changed, 49 insertions(+), 1 deletion(-) diff --git a/acl/acl_test.go b/acl/acl_test.go index 3bbfed25eb..3ce0fa59b9 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -145,6 +145,10 @@ func checkAllowServiceWrite(t *testing.T, authz Authorizer, prefix string, entCt require.Equal(t, Allow, authz.ServiceWrite(prefix, entCtx)) } +func checkAllowServiceWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Allow, authz.ServiceWriteAny(entCtx)) +} + func checkAllowSessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Allow, authz.SessionRead(prefix, entCtx)) } @@ -265,6 +269,10 @@ func checkDenyServiceWrite(t *testing.T, authz Authorizer, prefix string, entCtx require.Equal(t, Deny, authz.ServiceWrite(prefix, entCtx)) } +func checkDenyServiceWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Deny, authz.ServiceWriteAny(entCtx)) +} + func checkDenySessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Deny, authz.SessionRead(prefix, entCtx)) } @@ -385,6 +393,10 @@ func checkDefaultServiceWrite(t *testing.T, authz Authorizer, prefix string, ent require.Equal(t, Default, authz.ServiceWrite(prefix, entCtx)) } +func checkDefaultServiceWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Default, authz.ServiceWriteAny(entCtx)) +} + func checkDefaultSessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Default, authz.SessionRead(prefix, entCtx)) } diff --git a/acl/authorizer.go b/acl/authorizer.go index 7dc961c573..dfe2eda1db 100644 --- a/acl/authorizer.go +++ b/acl/authorizer.go @@ -149,6 +149,9 @@ type Authorizer interface { // service ServiceWrite(string, *AuthorizerContext) EnforcementDecision + // ServiceWriteAny checks for write permission on any service + ServiceWriteAny(*AuthorizerContext) EnforcementDecision + // SessionRead checks for permission to read sessions for a given node. SessionRead(string, *AuthorizerContext) EnforcementDecision @@ -411,6 +414,14 @@ func (a AllowAuthorizer) ServiceWriteAllowed(name string, ctx *AuthorizerContext return nil } +// ServiceWriteAnyAllowed checks for write permission on any service +func (a AllowAuthorizer) ServiceWriteAnyAllowed(ctx *AuthorizerContext) error { + if a.Authorizer.ServiceWriteAny(ctx) != Allow { + return PermissionDeniedByACL(a, ctx, ResourceService, AccessWrite, "any service") + } + return nil +} + // SessionReadAllowed checks for permission to read sessions for a given node. func (a AllowAuthorizer) SessionReadAllowed(name string, ctx *AuthorizerContext) error { if a.Authorizer.SessionRead(name, ctx) != Allow { diff --git a/acl/authorizer_test.go b/acl/authorizer_test.go index 63eb57fd7b..b8f4d21c1d 100644 --- a/acl/authorizer_test.go +++ b/acl/authorizer_test.go @@ -185,6 +185,12 @@ func (m *mockAuthorizer) ServiceWrite(segment string, ctx *AuthorizerContext) En return ret.Get(0).(EnforcementDecision) } +// ServiceWriteAny checks for service:write on any service +func (m *mockAuthorizer) ServiceWriteAny(ctx *AuthorizerContext) EnforcementDecision { + ret := m.Called(ctx) + return ret.Get(0).(EnforcementDecision) +} + // SessionRead checks for permission to read sessions for a given node. func (m *mockAuthorizer) SessionRead(segment string, ctx *AuthorizerContext) EnforcementDecision { ret := m.Called(segment, ctx) diff --git a/acl/chained_authorizer.go b/acl/chained_authorizer.go index f0d7fc3294..77df69a3ea 100644 --- a/acl/chained_authorizer.go +++ b/acl/chained_authorizer.go @@ -235,6 +235,13 @@ func (c *ChainedAuthorizer) ServiceWrite(name string, entCtx *AuthorizerContext) }) } +// ServiceWriteAny checks for write permission on any service +func (c *ChainedAuthorizer) ServiceWriteAny(entCtx *AuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.ServiceWriteAny(entCtx) + }) +} + // SessionRead checks for permission to read sessions for a given node. func (c *ChainedAuthorizer) SessionRead(node string, entCtx *AuthorizerContext) EnforcementDecision { return c.executeChain(func(authz Authorizer) EnforcementDecision { diff --git a/acl/chained_authorizer_test.go b/acl/chained_authorizer_test.go index f6ca7184d1..5f33d01667 100644 --- a/acl/chained_authorizer_test.go +++ b/acl/chained_authorizer_test.go @@ -89,6 +89,9 @@ func (authz testAuthorizer) ServiceReadAll(*AuthorizerContext) EnforcementDecisi func (authz testAuthorizer) ServiceWrite(string, *AuthorizerContext) EnforcementDecision { return EnforcementDecision(authz) } +func (authz testAuthorizer) ServiceWriteAny(*AuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} func (authz testAuthorizer) SessionRead(string, *AuthorizerContext) EnforcementDecision { return EnforcementDecision(authz) } diff --git a/acl/policy_authorizer.go b/acl/policy_authorizer.go index 1fdf44543b..3b79a63169 100644 --- a/acl/policy_authorizer.go +++ b/acl/policy_authorizer.go @@ -767,7 +767,7 @@ func (p *policyAuthorizer) ServiceWrite(name string, _ *AuthorizerContext) Enfor return Default } -func (p *policyAuthorizer) serviceWriteAny(_ *AuthorizerContext) EnforcementDecision { +func (p *policyAuthorizer) ServiceWriteAny(_ *AuthorizerContext) EnforcementDecision { return p.anyAllowed(p.serviceRules, AccessWrite) } diff --git a/acl/policy_authorizer_test.go b/acl/policy_authorizer_test.go index f873260326..d2f69a4ebc 100644 --- a/acl/policy_authorizer_test.go +++ b/acl/policy_authorizer_test.go @@ -56,6 +56,7 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "DefaultPreparedQueryWrite", prefix: "foo", check: checkDefaultPreparedQueryWrite}, {name: "DefaultServiceRead", prefix: "foo", check: checkDefaultServiceRead}, {name: "DefaultServiceWrite", prefix: "foo", check: checkDefaultServiceWrite}, + {name: "DefaultServiceWriteAny", prefix: "", check: checkDefaultServiceWriteAny}, {name: "DefaultSessionRead", prefix: "foo", check: checkDefaultSessionRead}, {name: "DefaultSessionWrite", prefix: "foo", check: checkDefaultSessionWrite}, {name: "DefaultSnapshot", prefix: "foo", check: checkDefaultSnapshot}, @@ -267,6 +268,7 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "ServiceWritePrefixDenied", prefix: "food", check: checkDenyServiceWrite}, {name: "ServiceReadDenied", prefix: "football", check: checkDenyServiceRead}, {name: "ServiceWriteDenied", prefix: "football", check: checkDenyServiceWrite}, + {name: "ServiceWriteAnyAllowed", prefix: "", check: checkAllowServiceWriteAny}, {name: "NodeReadPrefixAllowed", prefix: "fo", check: checkAllowNodeRead}, {name: "NodeWritePrefixDenied", prefix: "fo", check: checkDenyNodeWrite}, diff --git a/acl/static_authorizer.go b/acl/static_authorizer.go index 1807d06847..951b026f37 100644 --- a/acl/static_authorizer.go +++ b/acl/static_authorizer.go @@ -219,6 +219,13 @@ func (s *staticAuthorizer) ServiceWrite(string, *AuthorizerContext) EnforcementD return Deny } +func (s *staticAuthorizer) ServiceWriteAny(*AuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + func (s *staticAuthorizer) SessionRead(string, *AuthorizerContext) EnforcementDecision { if s.defaultAllow { return Allow