|
|
|
@ -16,127 +16,323 @@ func TestDecryptAuthorizer(t *testing.T) { |
|
|
|
|
ctx := context.Background() |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when token permissions are empty, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{}) |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when service identity is empty, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "", []string{}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission format is malformed (missing verb), it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues/group1"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
// nameless
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
// named
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission verb is not exactly `decrypt`, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues/group1:something"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
// nameless
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:*"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
// named
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name:something"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission does not have 3 parts, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
t.Run("when permission does not have 2 or 3 parts, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission has group that is not `secret.grafana.app`, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"wrong.group/securevalues/invalid:decrypt"}) |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"wrong.group/securevalues/invalid:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission has resource that is not `securevalues`, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/invalid-resource/invalid:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(nil) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
// nameless
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/invalid-resource:decrypt"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
// named
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/invalid-resource/name:decrypt"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when the actor is not in the allow list, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues/allowed2:decrypt"}) |
|
|
|
|
t.Run("when the identity is not in the allow list, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"allowed1": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, nil) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", nil) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when the actor doesn't match any allowed decrypters, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues/group1:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"group1": {}}) |
|
|
|
|
t.Run("when the identity doesn't match any allowed decrypters, it returns false", func(t *testing.T) { |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, []string{"group2"}) |
|
|
|
|
require.Empty(t, identity) |
|
|
|
|
// nameless
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"group2"}) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
// named
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", []string{"group2"}) |
|
|
|
|
require.NotEmpty(t, identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when the actor matches an allowed decrypter, it returns true", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{"secret.grafana.app/securevalues/group1:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"group1": {}}) |
|
|
|
|
t.Run("when the identity matches an allowed decrypter, it returns true", func(t *testing.T) { |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
// nameless
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.True(t, allowed) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, []string{"group1"}) |
|
|
|
|
// named
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "name", []string{"identity"}) |
|
|
|
|
require.True(t, allowed) |
|
|
|
|
require.Equal(t, "group1", identity) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when there are multiple permissions, some invalid, only valid ones are considered", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{ |
|
|
|
|
"secret.grafana.app/securevalues/group1:decrypt", |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{ |
|
|
|
|
"secret.grafana.app/securevalues/name1:decrypt", |
|
|
|
|
"secret.grafana.app/securevalues/name2:decrypt", |
|
|
|
|
"secret.grafana.app/securevalues/invalid:read", |
|
|
|
|
"wrong.group/securevalues/group2:decrypt", |
|
|
|
|
"secret.grafana.app/securevalues/group2:decrypt", |
|
|
|
|
"secret.grafana.app/securevalues/identity:decrypt", // old style of identity+permission
|
|
|
|
|
}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"group1": {}, "group2": {}}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, []string{"group2", "group3"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "name1", []string{"identity"}) |
|
|
|
|
require.True(t, allowed) |
|
|
|
|
require.Equal(t, "group2", identity) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
|
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "name2", []string{"identity"}) |
|
|
|
|
require.True(t, allowed) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when multiple valid actors match decrypters, the first match already returns true", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), []string{ |
|
|
|
|
"secret.grafana.app/securevalues/group1:decrypt", |
|
|
|
|
"secret.grafana.app/securevalues/group2:decrypt", |
|
|
|
|
}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"group1": {}, "group2": {}}) |
|
|
|
|
t.Run("when empty secure value name with specific permission, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when permission has an extra / but no name, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when the decrypters list is empty, meaning nothing can decrypt the secure value, it returns false", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "name", []string{}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("when one of decrypters matches the identity, it returns true", func(t *testing.T) { |
|
|
|
|
ctx := createAuthContext(context.Background(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"}) |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity1": {}, "identity2": {}}) |
|
|
|
|
|
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, []string{"group2", "group1"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"identity1", "identity2", "identity3"}) |
|
|
|
|
require.Equal(t, "identity1", identity) |
|
|
|
|
require.True(t, allowed) |
|
|
|
|
require.Equal(t, "group2", identity) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("permissions must be case-sensitive and return false", func(t *testing.T) { |
|
|
|
|
authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) |
|
|
|
|
|
|
|
|
|
ctx := createAuthContext(context.Background(), "identity", []string{"SECRET.grafana.app/securevalues:decrypt"}) |
|
|
|
|
identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/SECUREVALUES:decrypt"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
|
|
|
|
|
ctx = createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:DECRYPT"}) |
|
|
|
|
identity, allowed = authorizer.Authorize(ctx, "", []string{"identity"}) |
|
|
|
|
require.Equal(t, "identity", identity) |
|
|
|
|
require.False(t, allowed) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func createAuthContext(ctx context.Context, permissions []string) context.Context { |
|
|
|
|
func createAuthContext(ctx context.Context, serviceIdentity string, permissions []string) context.Context { |
|
|
|
|
requester := &identity.StaticRequester{ |
|
|
|
|
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{ |
|
|
|
|
Rest: authn.AccessTokenClaims{ |
|
|
|
|
Permissions: permissions, |
|
|
|
|
Permissions: permissions, |
|
|
|
|
ServiceIdentity: serviceIdentity, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return types.WithAuthInfo(ctx, requester) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Adapted from https://github.com/grafana/authlib/blob/1492b99410603ca15730a1805a9220ce48232bc3/authz/client_test.go#L18
|
|
|
|
|
func TestHasPermissionInToken(t *testing.T) { |
|
|
|
|
t.Parallel() |
|
|
|
|
|
|
|
|
|
tests := []struct { |
|
|
|
|
test string |
|
|
|
|
tokenPermissions []string |
|
|
|
|
name string |
|
|
|
|
want bool |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
test: "Permission matches group/resource", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues:decrypt"}, |
|
|
|
|
want: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission does not match verb", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues:create"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission does not have support for wildcard verb", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues:*"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Invalid permission missing verb", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission on the wrong group", |
|
|
|
|
tokenPermissions: []string{"other-group.grafana.app/securevalues:decrypt"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission on the wrong resource", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/other-resource:decrypt"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission without group are skipped", |
|
|
|
|
tokenPermissions: []string{":decrypt"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Group level permission is not supported", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app:decrypt"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission with name matches group/resource/name", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues/name:decrypt"}, |
|
|
|
|
name: "name", |
|
|
|
|
want: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission with name2 does not matche group/resource/name1", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues/name1:decrypt"}, |
|
|
|
|
name: "name2", |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Parts need an exact match", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/secure:*"}, |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Resource specific permission should not allow access to all resources", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues/name:decrypt"}, |
|
|
|
|
name: "", |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Permission at group/resource should allow access to all resources", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues:decrypt"}, |
|
|
|
|
name: "name", |
|
|
|
|
want: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Empty name trying to match everything is not allowed", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues/:decrypt"}, |
|
|
|
|
name: "", |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
test: "Empty name trying to match a specific name is not allowed", |
|
|
|
|
tokenPermissions: []string{"secret.grafana.app/securevalues/:decrypt"}, |
|
|
|
|
name: "name", |
|
|
|
|
want: false, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, tt := range tests { |
|
|
|
|
t.Run(tt.test, func(t *testing.T) { |
|
|
|
|
t.Parallel() |
|
|
|
|
|
|
|
|
|
got := hasPermissionInToken(tt.tokenPermissions, tt.name) |
|
|
|
|
require.Equal(t, tt.want, got) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|