From d090668c376572ef73d69e08219d0b1006d85b4a Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Tue, 12 Sep 2023 17:22:51 -0400 Subject: [PATCH] Add workload identity ACL rules (#18769) --- .changelog/18769.txt | 3 + acl/MockAuthorizer.go | 25 +++++ acl/acl_test.go | 191 +++++++++++++++++++++++++++++++++ acl/authorizer.go | 55 ++++++++++ acl/authorizer_test.go | 28 +++++ acl/chained_authorizer.go | 29 +++++ acl/chained_authorizer_test.go | 12 +++ acl/policy.go | 39 +++++++ acl/policy_authorizer.go | 73 +++++++++++++ acl/policy_authorizer_test.go | 55 +++++++--- acl/policy_merger.go | 50 +++++++++ acl/policy_test.go | 176 ++++++++++++++++++++++++++++++ acl/static_authorizer.go | 28 +++++ agent/acl_endpoint_test.go | 24 ++++- agent/consul/acl_test.go | 28 ----- agent/structs/acl.go | 4 + 16 files changed, 777 insertions(+), 43 deletions(-) create mode 100644 .changelog/18769.txt diff --git a/.changelog/18769.txt b/.changelog/18769.txt new file mode 100644 index 0000000000..b9cf8aa207 --- /dev/null +++ b/.changelog/18769.txt @@ -0,0 +1,3 @@ +```release-note:feature +acl: Adds a new ACL rule for workload identities +``` diff --git a/acl/MockAuthorizer.go b/acl/MockAuthorizer.go index cabdf1b812..d699ee0a86 100644 --- a/acl/MockAuthorizer.go +++ b/acl/MockAuthorizer.go @@ -59,6 +59,31 @@ func (m *MockAuthorizer) EventWrite(segment string, ctx *AuthorizerContext) Enfo return ret.Get(0).(EnforcementDecision) } +// IdentityRead checks for permission to read a given workload identity. +func (m *MockAuthorizer) IdentityRead(segment string, ctx *AuthorizerContext) EnforcementDecision { + ret := m.Called(segment, ctx) + return ret.Get(0).(EnforcementDecision) +} + +// IdentityReadAll checks for permission to read all workload identities. +func (m *MockAuthorizer) IdentityReadAll(ctx *AuthorizerContext) EnforcementDecision { + ret := m.Called(ctx) + return ret.Get(0).(EnforcementDecision) +} + +// IdentityWrite checks for permission to create or update a given +// workload identity. +func (m *MockAuthorizer) IdentityWrite(segment string, ctx *AuthorizerContext) EnforcementDecision { + ret := m.Called(segment, ctx) + return ret.Get(0).(EnforcementDecision) +} + +// IdentityWriteAny checks for write permission on any workload identity. +func (m *MockAuthorizer) IdentityWriteAny(ctx *AuthorizerContext) EnforcementDecision { + ret := m.Called(ctx) + return ret.Get(0).(EnforcementDecision) +} + // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. func (m *MockAuthorizer) IntentionDefaultAllow(ctx *AuthorizerContext) EnforcementDecision { diff --git a/acl/acl_test.go b/acl/acl_test.go index de95e91f12..0c23b9f200 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -40,6 +40,22 @@ func checkAllowEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx require.Equal(t, Allow, authz.EventWrite(prefix, entCtx)) } +func checkAllowIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Allow, authz.IdentityRead(prefix, entCtx)) +} + +func checkAllowIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Allow, authz.IdentityReadAll(entCtx)) +} + +func checkAllowIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Allow, authz.IdentityWrite(prefix, entCtx)) +} + +func checkAllowIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Allow, authz.IdentityWriteAny(entCtx)) +} + func checkAllowIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Allow, authz.IntentionDefaultAllow(entCtx)) } @@ -172,6 +188,22 @@ func checkDenyEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx * require.Equal(t, Deny, authz.EventWrite(prefix, entCtx)) } +func checkDenyIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Deny, authz.IdentityRead(prefix, entCtx)) +} + +func checkDenyIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Deny, authz.IdentityReadAll(entCtx)) +} + +func checkDenyIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Deny, authz.IdentityWrite(prefix, entCtx)) +} + +func checkDenyIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Deny, authz.IdentityWriteAny(entCtx)) +} + func checkDenyIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Deny, authz.IntentionDefaultAllow(entCtx)) } @@ -304,6 +336,22 @@ func checkDefaultEventWrite(t *testing.T, authz Authorizer, prefix string, entCt require.Equal(t, Default, authz.EventWrite(prefix, entCtx)) } +func checkDefaultIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Default, authz.IdentityRead(prefix, entCtx)) +} + +func checkDefaultIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Default, authz.IdentityReadAll(entCtx)) +} + +func checkDefaultIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { + require.Equal(t, Default, authz.IdentityWrite(prefix, entCtx)) +} + +func checkDefaultIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { + require.Equal(t, Default, authz.IdentityWriteAny(entCtx)) +} + func checkDefaultIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Default, authz.IntentionDefaultAllow(entCtx)) } @@ -440,6 +488,10 @@ func TestACL(t *testing.T) { {name: "DenyIntentionDefaultAllow", check: checkDenyIntentionDefaultAllow}, {name: "DenyIntentionRead", check: checkDenyIntentionRead}, {name: "DenyIntentionWrite", check: checkDenyIntentionWrite}, + {name: "DenyIdentityRead", check: checkDenyIdentityRead}, + {name: "DenyIdentityReadAll", check: checkDenyIdentityReadAll}, + {name: "DenyIdentityWrite", check: checkDenyIdentityWrite}, + {name: "DenyIdentityWriteAny", check: checkDenyIdentityWriteAny}, {name: "DenyKeyRead", check: checkDenyKeyRead}, {name: "DenyKeyringRead", check: checkDenyKeyringRead}, {name: "DenyKeyringWrite", check: checkDenyKeyringWrite}, @@ -458,6 +510,7 @@ func TestACL(t *testing.T) { {name: "DenyServiceRead", check: checkDenyServiceRead}, {name: "DenyServiceReadAll", check: checkDenyServiceReadAll}, {name: "DenyServiceWrite", check: checkDenyServiceWrite}, + {name: "DenyServiceWriteAny", check: checkDenyServiceWriteAny}, {name: "DenySessionRead", check: checkDenySessionRead}, {name: "DenySessionWrite", check: checkDenySessionWrite}, {name: "DenySnapshot", check: checkDenySnapshot}, @@ -473,6 +526,10 @@ func TestACL(t *testing.T) { {name: "AllowAgentWrite", check: checkAllowAgentWrite}, {name: "AllowEventRead", check: checkAllowEventRead}, {name: "AllowEventWrite", check: checkAllowEventWrite}, + {name: "AllowIdentityRead", check: checkAllowIdentityRead}, + {name: "AllowIdentityReadAll", check: checkAllowIdentityReadAll}, + {name: "AllowIdentityWrite", check: checkAllowIdentityWrite}, + {name: "AllowIdentityWriteAny", check: checkAllowIdentityWriteAny}, {name: "AllowIntentionDefaultAllow", check: checkAllowIntentionDefaultAllow}, {name: "AllowIntentionRead", check: checkAllowIntentionRead}, {name: "AllowIntentionWrite", check: checkAllowIntentionWrite}, @@ -494,6 +551,7 @@ func TestACL(t *testing.T) { {name: "AllowServiceRead", check: checkAllowServiceRead}, {name: "AllowServiceReadAll", check: checkAllowServiceReadAll}, {name: "AllowServiceWrite", check: checkAllowServiceWrite}, + {name: "AllowServiceWriteAny", check: checkAllowServiceWriteAny}, {name: "AllowSessionRead", check: checkAllowSessionRead}, {name: "AllowSessionWrite", check: checkAllowSessionWrite}, {name: "DenySnapshot", check: checkDenySnapshot}, @@ -509,6 +567,10 @@ func TestACL(t *testing.T) { {name: "AllowAgentWrite", check: checkAllowAgentWrite}, {name: "AllowEventRead", check: checkAllowEventRead}, {name: "AllowEventWrite", check: checkAllowEventWrite}, + {name: "AllowIdentityRead", check: checkAllowIdentityRead}, + {name: "AllowIdentityReadAll", check: checkAllowIdentityReadAll}, + {name: "AllowIdentityWrite", check: checkAllowIdentityWrite}, + {name: "AllowIdentityWriteAny", check: checkAllowIdentityWriteAny}, {name: "AllowIntentionDefaultAllow", check: checkAllowIntentionDefaultAllow}, {name: "AllowIntentionRead", check: checkAllowIntentionRead}, {name: "AllowIntentionWrite", check: checkAllowIntentionWrite}, @@ -530,6 +592,7 @@ func TestACL(t *testing.T) { {name: "AllowServiceRead", check: checkAllowServiceRead}, {name: "AllowServiceReadAll", check: checkAllowServiceReadAll}, {name: "AllowServiceWrite", check: checkAllowServiceWrite}, + {name: "AllowServiceWriteAny", check: checkAllowServiceWriteAny}, {name: "AllowSessionRead", check: checkAllowSessionRead}, {name: "AllowSessionWrite", check: checkAllowSessionWrite}, {name: "AllowSnapshot", check: checkAllowSnapshot}, @@ -905,6 +968,134 @@ func TestACL(t *testing.T) { {name: "ChildOverrideWriteAllowed", prefix: "override", check: checkAllowAgentWrite}, }, }, + { + name: "IdentityDefaultAllowPolicyDeny", + defaultPolicy: AllowAll(), + policyStack: []*Policy{ + { + PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyDeny, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "prefix", + Policy: PolicyDeny, + }, + }, + }, + }, + }, + checks: []aclCheck{ + {name: "IdentityFooReadDenied", prefix: "foo", check: checkDenyIdentityRead}, + {name: "IdentityFooWriteDenied", prefix: "foo", check: checkDenyIdentityWrite}, + {name: "IdentityPrefixReadDenied", prefix: "prefix", check: checkDenyIdentityRead}, + {name: "IdentityPrefixWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, + {name: "IdentityBarReadAllowed", prefix: "fail", check: checkAllowIdentityRead}, + {name: "IdentityBarWriteAllowed", prefix: "fail", check: checkAllowIdentityWrite}, + }, + }, + { + name: "IdentityDefaultDenyPolicyAllow", + defaultPolicy: DenyAll(), + policyStack: []*Policy{ + { + PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyWrite, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "prefix", + Policy: PolicyRead, + }, + }, + }, + }, + }, + checks: []aclCheck{ + {name: "IdentityFooReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, + {name: "IdentityFooWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, + {name: "IdentityPrefixReadAllowed", prefix: "prefix", check: checkAllowIdentityRead}, + {name: "IdentityPrefixWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, + {name: "IdentityBarReadDenied", prefix: "fail", check: checkDenyIdentityRead}, + {name: "IdentityBarWriteDenied", prefix: "fail", check: checkDenyIdentityWrite}, + }, + }, + { + name: "IdentityDefaultDenyPolicyComplex", + defaultPolicy: DenyAll(), + policyStack: []*Policy{ + { + PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "football", + Policy: PolicyRead, + }, + { + Name: "prefix-forbidden", + Policy: PolicyDeny, + Intentions: PolicyDeny, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "prefix", + Policy: PolicyRead, + Intentions: PolicyWrite, + }, + }, + }, + }, + { + PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foozball", + Policy: PolicyWrite, + Intentions: PolicyRead, + }, + }, + }, + }, + }, + checks: []aclCheck{ + {name: "IdentityReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, + {name: "IdentityWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, + {name: "IntentionReadAllowed", prefix: "foo", check: checkAllowIntentionRead}, + {name: "IntentionWriteAllowed", prefix: "foo", check: checkAllowIntentionWrite}, + {name: "IdentityReadAllowed", prefix: "football", check: checkAllowIdentityRead}, + {name: "IdentityWriteDenied", prefix: "football", check: checkDenyIdentityWrite}, + {name: "IntentionReadAllowed", prefix: "football", check: checkAllowIntentionRead}, + // This might be surprising but omitting intention rule gives at most intention:read + // if we have identity:write perms. This matches services as well. + {name: "IntentionWriteDenied", prefix: "football", check: checkDenyIntentionWrite}, + {name: "IdentityReadAllowed", prefix: "prefix", check: checkAllowIdentityRead}, + {name: "IdentityWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, + {name: "IntentionReadAllowed", prefix: "prefix", check: checkAllowIntentionRead}, + {name: "IntentionWriteDenied", prefix: "prefix", check: checkAllowIntentionWrite}, + {name: "IdentityReadDenied", prefix: "prefix-forbidden", check: checkDenyIdentityRead}, + {name: "IdentityWriteDenied", prefix: "prefix-forbidden", check: checkDenyIdentityWrite}, + {name: "IntentionReadDenied", prefix: "prefix-forbidden", check: checkDenyIntentionRead}, + {name: "IntentionWriteDenied", prefix: "prefix-forbidden", check: checkDenyIntentionWrite}, + {name: "IdentityReadAllowed", prefix: "foozball", check: checkAllowIdentityRead}, + {name: "IdentityWriteAllowed", prefix: "foozball", check: checkAllowIdentityWrite}, + {name: "IntentionReadAllowed", prefix: "foozball", check: checkAllowIntentionRead}, + {name: "IntentionWriteDenied", prefix: "foozball", check: checkDenyIntentionWrite}, + }, + }, { name: "KeyringDefaultAllowPolicyDeny", defaultPolicy: AllowAll(), diff --git a/acl/authorizer.go b/acl/authorizer.go index 21a7dbc801..d1b1da0c1f 100644 --- a/acl/authorizer.go +++ b/acl/authorizer.go @@ -43,6 +43,7 @@ const ( ResourceACL Resource = "acl" ResourceAgent Resource = "agent" ResourceEvent Resource = "event" + ResourceIdentity Resource = "identity" ResourceIntention Resource = "intention" ResourceKey Resource = "key" ResourceKeyring Resource = "keyring" @@ -77,6 +78,19 @@ type Authorizer interface { // EventWrite determines if a specific event may be fired. EventWrite(string, *AuthorizerContext) EnforcementDecision + // IdentityRead checks for permission to read a given workload identity. + IdentityRead(string, *AuthorizerContext) EnforcementDecision + + // IdentityReadAll checks for permission to read all workload identities. + IdentityReadAll(*AuthorizerContext) EnforcementDecision + + // IdentityWrite checks for permission to create or update a given + // workload identity. + IdentityWrite(string, *AuthorizerContext) EnforcementDecision + + // IdentityWriteAny checks for write permission on any workload identity. + IdentityWriteAny(*AuthorizerContext) EnforcementDecision + // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision @@ -239,6 +253,40 @@ func (a AllowAuthorizer) EventWriteAllowed(name string, ctx *AuthorizerContext) return nil } +// IdentityReadAllowed checks for permission to read a given workload identity, +func (a AllowAuthorizer) IdentityReadAllowed(name string, ctx *AuthorizerContext) error { + if a.Authorizer.IdentityRead(name, ctx) != Allow { + return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessRead, name) + } + return nil +} + +// IdentityReadAllAllowed checks for permission to read all workload identities. +func (a AllowAuthorizer) IdentityReadAllAllowed(ctx *AuthorizerContext) error { + if a.Authorizer.IdentityReadAll(ctx) != Allow { + // This is only used to gate certain UI functions right now (e.g metrics) + return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessRead, "all identities") // read + } + return nil +} + +// IdentityWriteAllowed checks for permission to create or update a given +// workload identity. +func (a AllowAuthorizer) IdentityWriteAllowed(name string, ctx *AuthorizerContext) error { + if a.Authorizer.IdentityWrite(name, ctx) != Allow { + return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessWrite, name) + } + return nil +} + +// IdentityWriteAnyAllowed checks for write permission on any workload identity +func (a AllowAuthorizer) IdentityWriteAnyAllowed(ctx *AuthorizerContext) error { + if a.Authorizer.IdentityWriteAny(ctx) != Allow { + return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessWrite, "any identity") + } + return nil +} + // IntentionDefaultAllowAllowed determines the default authorized behavior // when no intentions match a Connect request. func (a AllowAuthorizer) IntentionDefaultAllowAllowed(ctx *AuthorizerContext) error { @@ -503,6 +551,13 @@ func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx case "write": return authz.EventWrite(segment, ctx), nil } + case ResourceIdentity: + switch lowerAccess { + case "read": + return authz.IdentityRead(segment, ctx), nil + case "write": + return authz.IdentityWrite(segment, ctx), nil + } case ResourceIntention: switch lowerAccess { case "read": diff --git a/acl/authorizer_test.go b/acl/authorizer_test.go index 09cba85fa6..d538a04ad7 100644 --- a/acl/authorizer_test.go +++ b/acl/authorizer_test.go @@ -188,6 +188,34 @@ func TestACL_Enforce(t *testing.T) { ret: Deny, err: "Invalid access level", }, + { + method: "IdentityRead", + resource: ResourceIdentity, + segment: "foo", + access: "read", + ret: Deny, + }, + { + method: "IdentityRead", + resource: ResourceIdentity, + segment: "foo", + access: "read", + ret: Allow, + }, + { + method: "IdentityWrite", + resource: ResourceIdentity, + segment: "foo", + access: "write", + ret: Deny, + }, + { + method: "IdentityWrite", + resource: ResourceIdentity, + segment: "foo", + access: "write", + ret: Allow, + }, { method: "IntentionRead", resource: ResourceIntention, diff --git a/acl/chained_authorizer.go b/acl/chained_authorizer.go index 333ad7e90f..da896d5ca6 100644 --- a/acl/chained_authorizer.go +++ b/acl/chained_authorizer.go @@ -80,6 +80,35 @@ func (c *ChainedAuthorizer) EventWrite(name string, entCtx *AuthorizerContext) E }) } +// IdentityRead checks for permission to read a given workload identity. +func (c *ChainedAuthorizer) IdentityRead(name string, entCtx *AuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IdentityRead(name, entCtx) + }) +} + +// IdentityReadAll checks for permission to read all workload identities. +func (c *ChainedAuthorizer) IdentityReadAll(entCtx *AuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IdentityReadAll(entCtx) + }) +} + +// IdentityWrite checks for permission to create or update a given +// workload identity. +func (c *ChainedAuthorizer) IdentityWrite(name string, entCtx *AuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IdentityWrite(name, entCtx) + }) +} + +// IdentityWriteAny checks for write permission on any workload identity. +func (c *ChainedAuthorizer) IdentityWriteAny(entCtx *AuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IdentityWriteAny(entCtx) + }) +} + // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. func (c *ChainedAuthorizer) IntentionDefaultAllow(entCtx *AuthorizerContext) EnforcementDecision { diff --git a/acl/chained_authorizer_test.go b/acl/chained_authorizer_test.go index a198ab6778..137741fe5e 100644 --- a/acl/chained_authorizer_test.go +++ b/acl/chained_authorizer_test.go @@ -29,6 +29,18 @@ func (authz testAuthorizer) EventRead(string, *AuthorizerContext) EnforcementDec func (authz testAuthorizer) EventWrite(string, *AuthorizerContext) EnforcementDecision { return EnforcementDecision(authz) } +func (authz testAuthorizer) IdentityRead(string, *AuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IdentityReadAll(*AuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IdentityWrite(string, *AuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IdentityWriteAny(*AuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} func (authz testAuthorizer) IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision { return EnforcementDecision(authz) } diff --git a/acl/policy.go b/acl/policy.go index ef821563b0..0c88a9041b 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -59,6 +59,8 @@ type PolicyRules struct { ACL string `hcl:"acl,expand"` Agents []*AgentRule `hcl:"agent,expand"` AgentPrefixes []*AgentRule `hcl:"agent_prefix,expand"` + Identities []*IdentityRule `hcl:"identity,expand"` + IdentityPrefixes []*IdentityRule `hcl:"identity_prefix,expand"` Keys []*KeyRule `hcl:"key,expand"` KeyPrefixes []*KeyRule `hcl:"key_prefix,expand"` Nodes []*NodeRule `hcl:"node,expand"` @@ -90,6 +92,19 @@ type AgentRule struct { Policy string } +// IdentityRule represents a policy for a workload identity +type IdentityRule struct { + Name string `hcl:",key"` + Policy string + + // Intentions is the policy for intentions where this workload identity + // is the destination. This may be empty, in which case the Policy determines + // the intentions policy. + Intentions string + + EnterpriseRule `hcl:",squash"` +} + // KeyRule represents a rule for a key type KeyRule struct { Prefix string `hcl:",key"` @@ -168,6 +183,30 @@ func (pr *PolicyRules) Validate(conf *Config) error { } } + // Validate the identity policies + for _, id := range pr.Identities { + if !isPolicyValid(id.Policy, false) { + return fmt.Errorf("Invalid identity policy: %#v", id) + } + if id.Intentions != "" && !isPolicyValid(id.Intentions, false) { + return fmt.Errorf("Invalid identity intentions policy: %#v", id) + } + if err := id.EnterpriseRule.Validate(id.Policy, conf); err != nil { + return fmt.Errorf("Invalid identity enterprise policy: %#v, got error: %v", id, err) + } + } + for _, id := range pr.IdentityPrefixes { + if !isPolicyValid(id.Policy, false) { + return fmt.Errorf("Invalid identity_prefix policy: %#v", id) + } + if id.Intentions != "" && !isPolicyValid(id.Intentions, false) { + return fmt.Errorf("Invalid identity_prefix intentions policy: %#v", id) + } + if err := id.EnterpriseRule.Validate(id.Policy, conf); err != nil { + return fmt.Errorf("Invalid identity_prefix enterprise policy: %#v, got error: %v", id, err) + } + } + // Validate the key policy for _, kp := range pr.Keys { if !isPolicyValid(kp.Policy, true) { diff --git a/acl/policy_authorizer.go b/acl/policy_authorizer.go index 9043dc40cd..d58d340627 100644 --- a/acl/policy_authorizer.go +++ b/acl/policy_authorizer.go @@ -14,6 +14,9 @@ type policyAuthorizer struct { // agentRules contain the exact-match agent policies agentRules *radix.Tree + // identityRules contains the identity exact-match policies + identityRules *radix.Tree + // intentionRules contains the service intention exact-match policies intentionRules *radix.Tree @@ -180,6 +183,48 @@ func (p *policyAuthorizer) loadRules(policy *PolicyRules) error { } } + // Load the identity policy (exact matches) + for _, id := range policy.Identities { + if err := insertPolicyIntoRadix(id.Name, id.Policy, &id.EnterpriseRule, p.identityRules, false); err != nil { + return err + } + + intention := id.Intentions + if intention == "" { + switch id.Policy { + case PolicyRead, PolicyWrite: + intention = PolicyRead + default: + intention = PolicyDeny + } + } + + if err := insertPolicyIntoRadix(id.Name, intention, &id.EnterpriseRule, p.intentionRules, false); err != nil { + return err + } + } + + // Load the identity policy (prefix matches) + for _, id := range policy.IdentityPrefixes { + if err := insertPolicyIntoRadix(id.Name, id.Policy, &id.EnterpriseRule, p.identityRules, true); err != nil { + return err + } + + intention := id.Intentions + if intention == "" { + switch id.Policy { + case PolicyRead, PolicyWrite: + intention = PolicyRead + default: + intention = PolicyDeny + } + } + + if err := insertPolicyIntoRadix(id.Name, intention, &id.EnterpriseRule, p.intentionRules, true); err != nil { + return err + } + } + // Load the key policy (exact matches) for _, kp := range policy.Keys { if err := insertPolicyIntoRadix(kp.Prefix, kp.Policy, &kp.EnterpriseRule, p.keyRules, false); err != nil { @@ -349,6 +394,7 @@ func newPolicyAuthorizer(policies []*Policy, ent *Config) (*policyAuthorizer, er func newPolicyAuthorizerFromRules(rules *PolicyRules, ent *Config) (*policyAuthorizer, error) { p := &policyAuthorizer{ agentRules: radix.New(), + identityRules: radix.New(), intentionRules: radix.New(), keyRules: radix.New(), nodeRules: radix.New(), @@ -528,6 +574,33 @@ func (p *policyAuthorizer) EventWrite(name string, _ *AuthorizerContext) Enforce return Default } +// IdentityRead checks for permission to read a given workload identity. +func (p *policyAuthorizer) IdentityRead(name string, _ *AuthorizerContext) EnforcementDecision { + if rule, ok := getPolicy(name, p.identityRules); ok { + return enforce(rule.access, AccessRead) + } + return Default +} + +// IdentityReadAll checks for permission to read all workload identities. +func (p *policyAuthorizer) IdentityReadAll(_ *AuthorizerContext) EnforcementDecision { + return p.allAllowed(p.identityRules, AccessRead) +} + +// IdentityWrite checks for permission to create or update a given +// workload identity. +func (p *policyAuthorizer) IdentityWrite(name string, _ *AuthorizerContext) EnforcementDecision { + if rule, ok := getPolicy(name, p.identityRules); ok { + return enforce(rule.access, AccessWrite) + } + return Default +} + +// IdentityWriteAny checks for write permission on any workload identity. +func (p *policyAuthorizer) IdentityWriteAny(_ *AuthorizerContext) EnforcementDecision { + return p.anyAllowed(p.identityRules, AccessWrite) +} + // IntentionDefaultAllow returns whether the default behavior when there are // no matching intentions is to allow or deny. func (p *policyAuthorizer) IntentionDefaultAllow(_ *AuthorizerContext) EnforcementDecision { diff --git a/acl/policy_authorizer_test.go b/acl/policy_authorizer_test.go index d144bc8d8c..06e5ee2bb2 100644 --- a/acl/policy_authorizer_test.go +++ b/acl/policy_authorizer_test.go @@ -41,6 +41,9 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "DefaultAgentWrite", prefix: "foo", check: checkDefaultAgentWrite}, {name: "DefaultEventRead", prefix: "foo", check: checkDefaultEventRead}, {name: "DefaultEventWrite", prefix: "foo", check: checkDefaultEventWrite}, + {name: "DefaultIdentityRead", prefix: "foo", check: checkDefaultIdentityRead}, + {name: "DefaultIdentityWrite", prefix: "foo", check: checkDefaultIdentityWrite}, + {name: "DefaultIdentityWriteAny", prefix: "", check: checkDefaultIdentityWriteAny}, {name: "DefaultIntentionDefaultAllow", prefix: "foo", check: checkDefaultIntentionDefaultAllow}, {name: "DefaultIntentionRead", prefix: "foo", check: checkDefaultIntentionRead}, {name: "DefaultIntentionWrite", prefix: "foo", check: checkDefaultIntentionWrite}, @@ -185,6 +188,29 @@ func TestPolicyAuthorizer(t *testing.T) { Policy: PolicyRead, }, }, + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "football", + Policy: PolicyDeny, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "foot", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "fo", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + }, Keys: []*KeyRule{ { Prefix: "foo", @@ -371,20 +397,21 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "ServiceWriteDenied", prefix: "football", check: checkDenyServiceWrite}, {name: "ServiceWriteAnyAllowed", prefix: "", check: checkAllowServiceWriteAny}, - {name: "NodeReadPrefixAllowed", prefix: "fo", check: checkAllowNodeRead}, - {name: "NodeWritePrefixDenied", prefix: "fo", check: checkDenyNodeWrite}, - {name: "NodeReadPrefixAllowed", prefix: "for", check: checkAllowNodeRead}, - {name: "NodeWritePrefixDenied", prefix: "for", check: checkDenyNodeWrite}, - {name: "NodeReadAllowed", prefix: "foo", check: checkAllowNodeRead}, - {name: "NodeWriteAllowed", prefix: "foo", check: checkAllowNodeWrite}, - {name: "NodeReadPrefixAllowed", prefix: "foot", check: checkAllowNodeRead}, - {name: "NodeWritePrefixDenied", prefix: "foot", check: checkDenyNodeWrite}, - {name: "NodeReadPrefixAllowed", prefix: "foot2", check: checkAllowNodeRead}, - {name: "NodeWritePrefixDenied", prefix: "foot2", check: checkDenyNodeWrite}, - {name: "NodeReadPrefixAllowed", prefix: "food", check: checkAllowNodeRead}, - {name: "NodeWritePrefixDenied", prefix: "food", check: checkDenyNodeWrite}, - {name: "NodeReadDenied", prefix: "football", check: checkDenyNodeRead}, - {name: "NodeWriteDenied", prefix: "football", check: checkDenyNodeWrite}, + {name: "IdentityReadPrefixAllowed", prefix: "fo", check: checkAllowIdentityRead}, + {name: "IdentityWritePrefixDenied", prefix: "fo", check: checkDenyIdentityWrite}, + {name: "IdentityReadPrefixAllowed", prefix: "for", check: checkAllowIdentityRead}, + {name: "IdentityWritePrefixDenied", prefix: "for", check: checkDenyIdentityWrite}, + {name: "IdentityReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, + {name: "IdentityWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, + {name: "IdentityReadPrefixAllowed", prefix: "foot", check: checkAllowIdentityRead}, + {name: "IdentityWritePrefixDenied", prefix: "foot", check: checkDenyIdentityWrite}, + {name: "IdentityReadPrefixAllowed", prefix: "foot2", check: checkAllowIdentityRead}, + {name: "IdentityWritePrefixDenied", prefix: "foot2", check: checkDenyIdentityWrite}, + {name: "IdentityReadPrefixAllowed", prefix: "food", check: checkAllowIdentityRead}, + {name: "IdentityWritePrefixDenied", prefix: "food", check: checkDenyIdentityWrite}, + {name: "IdentityReadDenied", prefix: "football", check: checkDenyIdentityRead}, + {name: "IdentityWriteDenied", prefix: "football", check: checkDenyIdentityWrite}, + {name: "IdentityWriteAnyAllowed", prefix: "", check: checkAllowIdentityWriteAny}, {name: "IntentionReadPrefixAllowed", prefix: "fo", check: checkAllowIntentionRead}, {name: "IntentionWritePrefixDenied", prefix: "fo", check: checkDenyIntentionWrite}, diff --git a/acl/policy_merger.go b/acl/policy_merger.go index 7707c4f9a5..83166e167d 100644 --- a/acl/policy_merger.go +++ b/acl/policy_merger.go @@ -9,6 +9,8 @@ type policyRulesMergeContext struct { agentPrefixRules map[string]*AgentRule eventRules map[string]*EventRule eventPrefixRules map[string]*EventRule + identityRules map[string]*IdentityRule + identityPrefixRules map[string]*IdentityRule keyringRule string keyRules map[string]*KeyRule keyPrefixRules map[string]*KeyRule @@ -33,6 +35,8 @@ func (p *policyRulesMergeContext) init() { p.agentPrefixRules = make(map[string]*AgentRule) p.eventRules = make(map[string]*EventRule) p.eventPrefixRules = make(map[string]*EventRule) + p.identityRules = make(map[string]*IdentityRule) + p.identityPrefixRules = make(map[string]*IdentityRule) p.keyringRule = "" p.keyRules = make(map[string]*KeyRule) p.keyPrefixRules = make(map[string]*KeyRule) @@ -98,6 +102,42 @@ func (p *policyRulesMergeContext) merge(policy *PolicyRules) { } } + for _, id := range policy.Identities { + existing, found := p.identityRules[id.Name] + + if !found { + p.identityRules[id.Name] = id + continue + } + + if takesPrecedenceOver(id.Policy, existing.Policy) { + existing.Policy = id.Policy + existing.EnterpriseRule = id.EnterpriseRule + } + + if takesPrecedenceOver(id.Intentions, existing.Intentions) { + existing.Intentions = id.Intentions + } + } + + for _, id := range policy.IdentityPrefixes { + existing, found := p.identityPrefixRules[id.Name] + + if !found { + p.identityPrefixRules[id.Name] = id + continue + } + + if takesPrecedenceOver(id.Policy, existing.Policy) { + existing.Policy = id.Policy + existing.EnterpriseRule = id.EnterpriseRule + } + + if takesPrecedenceOver(id.Intentions, existing.Intentions) { + existing.Intentions = id.Intentions + } + } + if takesPrecedenceOver(policy.Keyring, p.keyringRule) { p.keyringRule = policy.Keyring } @@ -269,6 +309,16 @@ func (p *policyRulesMergeContext) fill(merged *PolicyRules) { merged.EventPrefixes = append(merged.EventPrefixes, policy) } + merged.Identities = []*IdentityRule{} + for _, policy := range p.identityRules { + merged.Identities = append(merged.Identities, policy) + } + + merged.IdentityPrefixes = []*IdentityRule{} + for _, policy := range p.identityPrefixRules { + merged.IdentityPrefixes = append(merged.IdentityPrefixes, policy) + } + merged.Keys = []*KeyRule{} for _, policy := range p.keyRules { merged.Keys = append(merged.Keys, policy) diff --git a/acl/policy_test.go b/acl/policy_test.go index 2ce0b32892..599c8c977e 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -42,6 +42,12 @@ func TestPolicySourceParse(t *testing.T) { event "bar" { policy = "deny" } + identity_prefix "" { + policy = "write" + } + identity "foo" { + policy = "read" + } key_prefix "" { policy = "read" } @@ -117,6 +123,16 @@ func TestPolicySourceParse(t *testing.T) { "policy": "deny" } }, + "identity_prefix": { + "": { + "policy": "write" + } + }, + "identity": { + "foo": { + "policy": "read" + } + }, "key_prefix": { "": { "policy": "read" @@ -217,6 +233,18 @@ func TestPolicySourceParse(t *testing.T) { Policy: PolicyDeny, }, }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "", + Policy: PolicyWrite, + }, + }, + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyRead, + }, + }, Keyring: PolicyDeny, KeyPrefixes: []*KeyRule{ { @@ -303,6 +331,39 @@ func TestPolicySourceParse(t *testing.T) { }, }}, }, + { + Name: "Identity No Intentions", + Rules: `identity "foo" { policy = "write" }`, + RulesJSON: `{ "identity": { "foo": { "policy": "write" }}}`, + Expected: &Policy{PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: "write", + }, + }, + }}, + }, + { + Name: "Identity Intentions", + Rules: `identity "foo" { policy = "write" intentions = "read" }`, + RulesJSON: `{ "identity": { "foo": { "policy": "write", "intentions": "read" }}}`, + Expected: &Policy{PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: "write", + Intentions: "read", + }, + }, + }}, + }, + { + Name: "Identity Intention: invalid value", + Rules: `identity "foo" { policy = "write" intentions = "foo" }`, + RulesJSON: `{ "identity": { "foo": { "policy": "write", "intentions": "foo" }}}`, + Err: "Invalid identity intentions policy", + }, { Name: "Service No Intentions", Rules: `service "foo" { policy = "write" }`, @@ -354,6 +415,18 @@ func TestPolicySourceParse(t *testing.T) { RulesJSON: `{ "agent_prefix": { "foo": { "policy": "nope" }}}`, Err: "Invalid agent_prefix policy", }, + { + Name: "Bad Policy - Identity", + Rules: `identity "foo" { policy = "nope" }`, + RulesJSON: `{ "identity": { "foo": { "policy": "nope" }}}`, + Err: "Invalid identity policy", + }, + { + Name: "Bad Policy - Identity Prefix", + Rules: `identity_prefix "foo" { policy = "nope" }`, + RulesJSON: `{ "identity_prefix": { "foo": { "policy": "nope" }}}`, + Err: "Invalid identity_prefix policy", + }, { Name: "Bad Policy - Key", Rules: `key "foo" { policy = "nope" }`, @@ -685,6 +758,109 @@ func TestMergePolicies(t *testing.T) { }, }}, }, + { + name: "Identities", + input: []*Policy{ + {PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "bar", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "baz", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "000", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "111", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "222", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + }, + }}, + {PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "baz", + Policy: PolicyDeny, + Intentions: PolicyDeny, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "000", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "222", + Policy: PolicyDeny, + Intentions: PolicyDeny, + }, + }, + }}, + }, + expected: &Policy{PolicyRules: PolicyRules{ + Identities: []*IdentityRule{ + { + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "bar", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "baz", + Policy: PolicyDeny, + Intentions: PolicyDeny, + }, + }, + IdentityPrefixes: []*IdentityRule{ + { + Name: "000", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + { + Name: "111", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + { + Name: "222", + Policy: PolicyDeny, + Intentions: PolicyDeny, + }, + }, + }}, + }, { name: "Node", input: []*Policy{ diff --git a/acl/static_authorizer.go b/acl/static_authorizer.go index 2b62320b0a..9231f90b10 100644 --- a/acl/static_authorizer.go +++ b/acl/static_authorizer.go @@ -75,6 +75,34 @@ func (s *staticAuthorizer) EventWrite(string, *AuthorizerContext) EnforcementDec return Deny } +func (s *staticAuthorizer) IdentityRead(string, *AuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *staticAuthorizer) IdentityReadAll(*AuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *staticAuthorizer) IdentityWrite(string, *AuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *staticAuthorizer) IdentityWriteAny(*AuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + func (s *staticAuthorizer) IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision { if s.defaultAllow { return Allow diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index d8860931a4..0a135f38df 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -2190,7 +2190,7 @@ func TestACL_Authorize(t *testing.T) { policyReq := structs.ACLPolicySetRequest{ Policy: structs.ACLPolicy{ Name: "test", - Rules: `acl = "read" operator = "write" service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `, + Rules: `acl = "read" operator = "write" identity_prefix "" { policy = "read"} service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `, }, Datacenter: "dc1", WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken}, @@ -2276,6 +2276,16 @@ func TestACL_Authorize(t *testing.T) { Segment: "foo", Access: "write", }, + { + Resource: "identity", + Segment: "foo", + Access: "read", + }, + { + Resource: "identity", + Segment: "foo", + Access: "write", + }, { Resource: "intention", Segment: "foo", @@ -2426,6 +2436,16 @@ func TestACL_Authorize(t *testing.T) { Segment: "foo", Access: "write", }, + { + Resource: "identity", + Segment: "foo", + Access: "read", + }, + { + Resource: "identity", + Segment: "foo", + Access: "write", + }, { Resource: "intention", Segment: "foo", @@ -2532,6 +2552,8 @@ func TestACL_Authorize(t *testing.T) { false, // agent:write false, // event:read false, // event:write + true, // identity:read + false, // identity:write true, // intentions:read false, // intention:write false, // key:read diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go index b2179edb53..a6d6a77405 100644 --- a/agent/consul/acl_test.go +++ b/agent/consul/acl_test.go @@ -615,10 +615,6 @@ func (d *ACLResolverTestDelegate) ACLDatacenter() string { return d.datacenter } -func (d *ACLResolverTestDelegate) UseLegacyACLs() bool { - return d.legacy -} - func (d *ACLResolverTestDelegate) ResolveIdentityFromToken(token string) (bool, structs.ACLIdentity, error) { if !d.localTokens { return false, nil, nil @@ -731,7 +727,6 @@ func TestACLResolver_Disabled(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: false, datacenter: "dc1", - legacy: false, } r := newTestACLResolver(t, delegate, nil) @@ -746,7 +741,6 @@ func TestACLResolver_ResolveRootACL(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, } r := newTestACLResolver(t, delegate, nil) @@ -797,7 +791,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, @@ -825,7 +818,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, @@ -853,7 +845,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -889,7 +880,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -920,7 +910,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, @@ -971,7 +960,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, @@ -1002,7 +990,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -1038,7 +1025,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -1070,7 +1056,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -1117,7 +1102,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: false, localRoles: false, @@ -1159,7 +1143,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, localRoles: false, @@ -1194,7 +1177,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, localRoles: false, @@ -1230,7 +1212,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, @@ -1276,7 +1257,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { @@ -1340,7 +1320,6 @@ func TestACLResolver_DownPolicy(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { @@ -1398,7 +1377,6 @@ func TestACLResolver_DatacenterScoping(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: true, localRoles: true, @@ -1418,7 +1396,6 @@ func TestACLResolver_DatacenterScoping(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc2", - legacy: false, localTokens: true, localPolicies: true, localRoles: true, @@ -1454,7 +1431,6 @@ func TestACLResolver_Client(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { @@ -1545,7 +1521,6 @@ func TestACLResolver_Client(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, tokenReadFn: func(args *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { @@ -1608,7 +1583,6 @@ func TestACLResolver_Client_TokensPoliciesAndRoles(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: false, localRoles: false, @@ -1625,7 +1599,6 @@ func TestACLResolver_LocalTokensPoliciesAndRoles(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: true, localPolicies: true, localRoles: true, @@ -1641,7 +1614,6 @@ func TestACLResolver_LocalPoliciesAndRoles(t *testing.T) { delegate := &ACLResolverTestDelegate{ enabled: true, datacenter: "dc1", - legacy: false, localTokens: false, localPolicies: true, localRoles: true, diff --git a/agent/structs/acl.go b/agent/structs/acl.go index ebf1e72280..50211b7c74 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -63,6 +63,10 @@ agent_prefix "" { event_prefix "" { policy = "%[1]s" } +identity_prefix "" { + policy = "%[1]s" + intentions = "%[1]s" +} key_prefix "" { policy = "%[1]s" }