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