Secret: add ability to configure extra owner decrypters (#111301)
--------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
8 files changed
+164
-71
No files matched your search
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
@@ -24,5 +26,5 @@ type DecryptStorage interface {
|
||||
|
||||
// DecryptAuthorizer is the interface for authorizing decryption requests.
|
||||
type DecryptAuthorizer interface {
|
||||
Authorize(ctx context.Context, namespace xkube.Namespace, secureValueName string, secureValueDecrypters []string) (identity string, allowed bool)
|
||||
Authorize(ctx context.Context, namespace xkube.Namespace, secureValueName string, secureValueDecrypters []string, owner []metav1.OwnerReference) (identity string, allowed bool)
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
claims "github.com/grafana/authlib/types"
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
@@ -17,16 +18,26 @@ import (
|
||||
// decryptAuthorizer is the authorizer implementation for decrypt operations.
|
||||
type decryptAuthorizer struct {
|
||||
tracer trace.Tracer
|
||||
extra []ExtraOwnerDecrypter
|
||||
}
|
||||
|
||||
func ProvideDecryptAuthorizer(tracer trace.Tracer) contracts.DecryptAuthorizer {
|
||||
type ExtraOwnerDecrypter struct {
|
||||
Identity string
|
||||
Group string
|
||||
}
|
||||
|
||||
func ProvideDecryptAuthorizer(
|
||||
tracer trace.Tracer,
|
||||
extra []ExtraOwnerDecrypter,
|
||||
) contracts.DecryptAuthorizer {
|
||||
return &decryptAuthorizer{
|
||||
tracer: tracer,
|
||||
extra: extra,
|
||||
}
|
||||
}
|
||||
|
||||
// authorize checks whether the auth info token has the right permissions to decrypt the secure value.
|
||||
func (a *decryptAuthorizer) Authorize(ctx context.Context, ns xkube.Namespace, secureValueName string, secureValueDecrypters []string) (id string, isAllowed bool) {
|
||||
// Authorize checks whether the auth info token has the right permissions to decrypt the secure value.
|
||||
func (a *decryptAuthorizer) Authorize(ctx context.Context, ns xkube.Namespace, secureValueName string, secureValueDecrypters []string, owners []metav1.OwnerReference) (id string, isAllowed bool) {
|
||||
ctx, span := a.tracer.Start(ctx, "DecryptAuthorizer.Authorize", trace.WithAttributes(
|
||||
attribute.String("name", secureValueName),
|
||||
attribute.StringSlice("decrypters", secureValueDecrypters),
|
||||
@@ -69,16 +80,27 @@ func (a *decryptAuthorizer) Authorize(ctx context.Context, ns xkube.Namespace, s
|
||||
return serviceIdentity, false
|
||||
}
|
||||
|
||||
// Finally check whether the service identity is allowed to decrypt this secure value.
|
||||
allowed := false
|
||||
// Check whether the service identity is allowed to decrypt this secure value.
|
||||
for _, decrypter := range secureValueDecrypters {
|
||||
if decrypter == serviceIdentity {
|
||||
allowed = true
|
||||
break
|
||||
return serviceIdentity, true
|
||||
}
|
||||
}
|
||||
|
||||
return serviceIdentity, allowed
|
||||
// finally check if the owner matches any hardcoded service identities
|
||||
if owners != nil && a.extra != nil {
|
||||
for _, extra := range a.extra {
|
||||
if extra.Identity == serviceIdentity {
|
||||
for _, owner := range owners {
|
||||
if strings.HasPrefix(owner.APIVersion, extra.Group) {
|
||||
return serviceIdentity, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serviceIdentity, false
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/grafana/authlib/blob/1492b99410603ca15730a1805a9220ce48232bc3/authz/client.go#L138
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
@@ -19,143 +21,143 @@ func TestDecryptAuthorizer(t *testing.T) {
|
||||
|
||||
t.Run("when no auth info is present, it returns false", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, 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(), defaultNs.String(), "identity", []string{})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, 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(), defaultNs.String(), "", []string{})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.Empty(t, identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when service identity is empty string, it returns false", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), " ", []string{})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, 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) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
// nameless
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
// named
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/name"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil, 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) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
// nameless
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:*"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
// named
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/name:something"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when permission does not have 2 or 3 parts, it returns false", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, 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(), defaultNs.String(), "identity", []string{"wrong.group/securevalues/invalid:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, 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) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
// nameless
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/invalid-resource:decrypt"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
// named
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/invalid-resource/name:decrypt"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil)
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", nil, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when the allow list is empty, it allows all identities", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.True(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when the identity doesn't match any allowed decrypters, it returns false", func(t *testing.T) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
// nameless
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"group2"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"group2"}, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
// named
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"group2"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"group2"}, nil)
|
||||
require.NotEmpty(t, identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when the identity matches an allowed decrypter, it returns true", func(t *testing.T) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
// nameless
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
require.True(t, allowed)
|
||||
require.Equal(t, "identity", identity)
|
||||
|
||||
// named
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "name", []string{"identity"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "name", []string{"identity"}, nil)
|
||||
require.True(t, allowed)
|
||||
require.Equal(t, "identity", identity)
|
||||
})
|
||||
@@ -168,77 +170,134 @@ func TestDecryptAuthorizer(t *testing.T) {
|
||||
"wrong.group/securevalues/group2:decrypt",
|
||||
"secret.grafana.app/securevalues/identity:decrypt", // old style of identity+permission
|
||||
})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "name1", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "name1", []string{"identity"}, nil)
|
||||
require.True(t, allowed)
|
||||
require.Equal(t, "identity", identity)
|
||||
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "name2", []string{"identity"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "name2", []string{"identity"}, nil)
|
||||
require.True(t, allowed)
|
||||
require.Equal(t, "identity", identity)
|
||||
})
|
||||
|
||||
t.Run("when empty secure value name with specific permission, it returns false", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
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(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues/:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
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(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "name", []string{})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "name", []string{}, nil)
|
||||
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(), defaultNs.String(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity1", "identity2", "identity3"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity1", "identity2", "identity3"}, nil)
|
||||
require.Equal(t, "identity1", identity)
|
||||
require.True(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when one of extra owner decrypters matches the identity, it returns true", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, []ExtraOwnerDecrypter{
|
||||
{
|
||||
Identity: "identity1",
|
||||
Group: "test.grafana.app",
|
||||
},
|
||||
})
|
||||
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{}, []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "test.grafana.app/v1",
|
||||
Kind: "Test",
|
||||
Name: "test",
|
||||
},
|
||||
})
|
||||
require.Equal(t, "identity1", identity)
|
||||
require.True(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when there are extra owner decrypters but it does not match the identity, it returns false", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, []ExtraOwnerDecrypter{
|
||||
{
|
||||
Identity: "identity2",
|
||||
Group: "test.grafana.app",
|
||||
},
|
||||
})
|
||||
|
||||
_, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{}, []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "test.grafana.app/v1",
|
||||
Kind: "Test",
|
||||
Name: "test",
|
||||
},
|
||||
})
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when one of extra owner decrypters matches the identity but not the group, it returns false", func(t *testing.T) {
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, []ExtraOwnerDecrypter{
|
||||
{
|
||||
Identity: "identity1",
|
||||
Group: "wrong.grafana.app",
|
||||
},
|
||||
})
|
||||
|
||||
_, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{}, []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "test.grafana.app/v1",
|
||||
Kind: "Test",
|
||||
Name: "test",
|
||||
},
|
||||
})
|
||||
require.False(t, allowed)
|
||||
})
|
||||
t.Run("permissions must be case-sensitive and return false", func(t *testing.T) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
ctx := createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"SECRET.grafana.app/securevalues:decrypt"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
require.Equal(t, "identity", identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/SECUREVALUES:decrypt"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
require.Equal(t, "identity", identity)
|
||||
require.False(t, allowed)
|
||||
|
||||
ctx = createAuthContext(context.Background(), defaultNs.String(), "identity", []string{"secret.grafana.app/securevalues:DECRYPT"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"identity"})
|
||||
identity, allowed = authorizer.Authorize(ctx, defaultNs, "", []string{"identity"}, nil)
|
||||
require.Equal(t, "identity", identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
t.Run("when namespace doesn't match the token's, it returns false", func(t *testing.T) {
|
||||
authorizer := ProvideDecryptAuthorizer(tracer)
|
||||
authorizer := ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
ctx := createAuthContext(context.Background(), "namespace1", "identity", []string{"secret.grafana.app/securevalues:decrypt"})
|
||||
identity, allowed := authorizer.Authorize(ctx, "namespace2", "", []string{"identity"})
|
||||
identity, allowed := authorizer.Authorize(ctx, "namespace2", "", []string{"identity"}, nil)
|
||||
require.Empty(t, identity)
|
||||
require.False(t, allowed)
|
||||
})
|
||||
|
||||
@@ -3,6 +3,8 @@ package decrypt
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
@@ -12,6 +14,6 @@ type NoopAlwaysAllowedAuthorizer struct{}
|
||||
|
||||
var _ contracts.DecryptAuthorizer = &NoopAlwaysAllowedAuthorizer{}
|
||||
|
||||
func (a *NoopAlwaysAllowedAuthorizer) Authorize(context.Context, xkube.Namespace, string, []string) (string, bool) {
|
||||
func (a *NoopAlwaysAllowedAuthorizer) Authorize(context.Context, xkube.Namespace, string, []string, []metav1.OwnerReference) (string, bool) {
|
||||
return "", true
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
|
||||
secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, secureValueValidator, secureValueMutator, keeperMetadataStorage, keeperService, nil)
|
||||
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer)
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, nil)
|
||||
|
||||
decryptStorage, err := metadata.ProvideDecryptStorage(tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -437,6 +437,7 @@ var wireBasicSet = wire.NewSet(
|
||||
secretmetadata.ProvideKeeperMetadataStorage,
|
||||
secretmetadata.ProvideDecryptStorage,
|
||||
secretdecrypt.ProvideDecryptAuthorizer,
|
||||
wire.Value([]secretdecrypt.ExtraOwnerDecrypter(nil)),
|
||||
secretdecrypt.ProvideDecryptService,
|
||||
secretinline.ProvideInlineSecureValueService,
|
||||
secretencryption.ProvideDataKeyStorage,
|
||||
|
||||
+13
-7
@@ -831,7 +831,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
v3 := extras.ProvideProvisioningExtraAPIs(webhookExtraBuilder)
|
||||
pullRequestWorker := pullrequest.ProvidePullRequestWorker(cfg, renderingService, resourceClient, eventualRestConfigProvider)
|
||||
v4 := extras.ProvideExtraWorkers(pullRequestWorker)
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer)
|
||||
v5 := _wireValue
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, v5)
|
||||
decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -841,8 +842,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
return nil, err
|
||||
}
|
||||
factory := github.ProvideFactory()
|
||||
v5 := extras.ProvideProvisioningOSSRepositoryExtras(cfg, decryptService, factory, webhookExtraBuilder)
|
||||
repositoryFactory, err := extras.ProvideFactoryFromConfig(cfg, v5)
|
||||
v6 := extras.ProvideProvisioningOSSRepositoryExtras(cfg, decryptService, factory, webhookExtraBuilder)
|
||||
repositoryFactory, err := extras.ProvideFactoryFromConfig(cfg, v6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -884,6 +885,10 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
return server, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_wireValue = []decrypt.ExtraOwnerDecrypter(nil)
|
||||
)
|
||||
|
||||
func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interface {
|
||||
Cleanup(func())
|
||||
mock.TestingT
|
||||
@@ -1431,7 +1436,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
v3 := extras.ProvideProvisioningExtraAPIs(webhookExtraBuilder)
|
||||
pullRequestWorker := pullrequest.ProvidePullRequestWorker(cfg, renderingService, resourceClient, eventualRestConfigProvider)
|
||||
v4 := extras.ProvideExtraWorkers(pullRequestWorker)
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer)
|
||||
v5 := _wireValue
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, v5)
|
||||
decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1441,8 +1447,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
return nil, err
|
||||
}
|
||||
factory := github.ProvideFactory()
|
||||
v5 := extras.ProvideProvisioningOSSRepositoryExtras(cfg, decryptService, factory, webhookExtraBuilder)
|
||||
repositoryFactory, err := extras.ProvideFactoryFromConfig(cfg, v5)
|
||||
v6 := extras.ProvideProvisioningOSSRepositoryExtras(cfg, decryptService, factory, webhookExtraBuilder)
|
||||
repositoryFactory, err := extras.ProvideFactoryFromConfig(cfg, v6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1666,7 +1672,7 @@ var withOTelSet = wire.NewSet(
|
||||
otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator,
|
||||
)
|
||||
|
||||
var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioninLine truncated
|
||||
var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioninLine truncated
|
||||
|
||||
var wireSet = wire.NewSet(
|
||||
wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)),
|
||||
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
@@ -108,7 +109,7 @@ func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace,
|
||||
return "", contracts.ErrDecryptNotFound
|
||||
}
|
||||
|
||||
decrypterIdentity, authorized := s.decryptAuthorizer.Authorize(ctx, namespace, name, sv.Spec.Decrypters)
|
||||
decrypterIdentity, authorized := s.decryptAuthorizer.Authorize(ctx, namespace, name, sv.Spec.Decrypters, sv.OwnerReferences)
|
||||
if !authorized {
|
||||
return "", contracts.ErrDecryptNotAuthorized
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user