diff --git a/.changelog/11433.txt b/.changelog/11433.txt new file mode 100644 index 0000000000..dfa6aa4ac9 --- /dev/null +++ b/.changelog/11433.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: **(Enterprise only)** updates ServiceRead and NodeRead to account for the partition-exports config entry. +``` \ No newline at end of file diff --git a/acl/acl.go b/acl/acl.go index 87295aa005..a59f380446 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -14,6 +14,11 @@ type Config struct { EnterpriseConfig } +type PartitionExportInfo interface { + // DownstreamPartitions returns the list of partitions the given service has been exported to. + DownstreamPartitions(service string, anyService bool, ctx *AuthorizerContext) []string +} + // GetWildcardName will retrieve the configured wildcard name or provide a default // in the case that the config is Nil or the wildcard name is unset. func (c *Config) GetWildcardName() string { diff --git a/acl/authorizer.go b/acl/authorizer.go index 43d50544bf..427eb1a162 100644 --- a/acl/authorizer.go +++ b/acl/authorizer.go @@ -149,6 +149,9 @@ type Authorizer interface { // service ServiceWrite(string, *AuthorizerContext) EnforcementDecision + // ServiceWriteAny checks for permission to read any service + ServiceWriteAny(*AuthorizerContext) EnforcementDecision + // SessionRead checks for permission to read sessions for a given node. SessionRead(string, *AuthorizerContext) EnforcementDecision diff --git a/acl/authorizer_test.go b/acl/authorizer_test.go index d74029f239..7d32a78bf7 100644 --- a/acl/authorizer_test.go +++ b/acl/authorizer_test.go @@ -185,6 +185,11 @@ func (m *mockAuthorizer) ServiceWrite(segment string, ctx *AuthorizerContext) En return ret.Get(0).(EnforcementDecision) } +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 1b3aed4978..33a05f9f1e 100644 --- a/acl/chained_authorizer.go +++ b/acl/chained_authorizer.go @@ -235,6 +235,12 @@ func (c *ChainedAuthorizer) ServiceWrite(name string, entCtx *AuthorizerContext) }) } +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 7a1aba2396..ac4880ba09 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 8cce120eb6..0e9496bf78 100644 --- a/acl/policy_authorizer.go +++ b/acl/policy_authorizer.go @@ -767,6 +767,10 @@ func (p *policyAuthorizer) ServiceWrite(name string, _ *AuthorizerContext) Enfor return Default } +func (p *policyAuthorizer) ServiceWriteAny(_ *AuthorizerContext) EnforcementDecision { + return p.anyAllowed(p.serviceRules, AccessWrite) +} + // SessionRead checks for permission to read sessions for a given node. func (p *policyAuthorizer) SessionRead(node string, _ *AuthorizerContext) EnforcementDecision { if rule, ok := getPolicy(node, p.sessionRules); ok { diff --git a/acl/static_authorizer.go b/acl/static_authorizer.go index f257d6b68a..2837b8f0ac 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 diff --git a/agent/consul/acl.go b/agent/consul/acl.go index 2bbe961d16..6d3414ce3b 100644 --- a/agent/consul/acl.go +++ b/agent/consul/acl.go @@ -292,7 +292,10 @@ func agentMasterAuthorizer(nodeName string, entMeta *structs.EnterpriseMeta) (ac }, }, } - return acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) + + cfg := acl.Config{} + setEnterpriseConf(entMeta, &cfg) + return acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, &cfg) } func NewACLResolver(config *ACLResolverConfig) (*ACLResolver, error) { @@ -1090,8 +1093,13 @@ func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs // Build the Authorizer var chain []acl.Authorizer + var conf acl.Config + if r.aclConf != nil { + conf = *r.aclConf + } + setEnterpriseConf(identity.EnterpriseMetadata(), &conf) - authz, err := policies.Compile(r.cache, r.aclConf) + authz, err := policies.Compile(r.cache, &conf) if err != nil { return nil, nil, err } @@ -1895,3 +1903,9 @@ func filterACL(r *ACLResolver, token string, subj interface{}) error { filterACLWithAuthorizer(r.logger, authorizer, subj) return nil } + +type partitionInfoNoop struct{} + +func (p *partitionInfoNoop) DownstreamPartitions(service string, anyService bool, ctx *acl.AuthorizerContext) []string { + return []string{} +} diff --git a/agent/consul/acl_oss.go b/agent/consul/acl_oss.go index 8b0417b413..f601f4ce17 100644 --- a/agent/consul/acl_oss.go +++ b/agent/consul/acl_oss.go @@ -15,7 +15,11 @@ func (s *Server) replicationEnterpriseMeta() *structs.EnterpriseMeta { return structs.ReplicationEnterpriseMeta() } -func newACLConfig(hclog.Logger) *acl.Config { +func serverPartitionInfo(s *Server) acl.PartitionExportInfo { + return &partitionInfoNoop{} +} + +func newACLConfig(_ acl.PartitionExportInfo, _ hclog.Logger) *acl.Config { return &acl.Config{ WildcardName: structs.WildcardSpecifier, } @@ -41,3 +45,5 @@ func (_ *ACLResolver) resolveEnterpriseIdentityAndPolicies(_ structs.ACLIdentity func (_ *ACLResolver) resolveLocallyManagedEnterpriseToken(_ string) (structs.ACLIdentity, acl.Authorizer, bool) { return nil, nil, false } + +func setEnterpriseConf(entMeta *structs.EnterpriseMeta, conf *acl.Config) {} diff --git a/agent/consul/client.go b/agent/consul/client.go index a2abc7fb76..031308e19e 100644 --- a/agent/consul/client.go +++ b/agent/consul/client.go @@ -123,7 +123,7 @@ func NewClient(config *Config, deps Deps) (*Client, error) { Logger: c.logger, DisableDuration: aclClientDisabledTTL, CacheConfig: clientACLCacheConfig, - ACLConfig: newACLConfig(c.logger), + ACLConfig: newACLConfig(&partitionInfoNoop{}, c.logger), Tokens: deps.Tokens, } var err error diff --git a/agent/consul/server.go b/agent/consul/server.go index 5069e4d802..969785a23a 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -427,7 +427,8 @@ func NewServer(config *Config, flat Deps) (*Server, error) { // Initialize the stats fetcher that autopilot will use. s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter) - s.aclConfig = newACLConfig(logger) + partitionInfo := serverPartitionInfo(s) + s.aclConfig = newACLConfig(partitionInfo, logger) aclConfig := ACLResolverConfig{ Config: config.ACLResolverSettings, Delegate: s,