Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workload identity ACL rules #18769

Merged
merged 3 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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