Skip to content

Commit

Permalink
Add workload identity ACL rules (#18769)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris S. Kim authored Sep 12, 2023
1 parent a55c4a1 commit d090668
Show file tree
Hide file tree
Showing 16 changed files with 777 additions and 43 deletions.
3 changes: 3 additions & 0 deletions .changelog/18769.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
acl: Adds a new ACL rule for workload identities
```
25 changes: 25 additions & 0 deletions acl/MockAuthorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
191 changes: 191 additions & 0 deletions acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand Down Expand Up @@ -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(),
Expand Down
55 changes: 55 additions & 0 deletions acl/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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":
Expand Down
Loading

0 comments on commit d090668

Please sign in to comment.