Files
grafana/pkg/registry/apis/secret/inline/inline_secure_value_test.go
T

561 lines
17 KiB
Go

package inline_test
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/inline"
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
)
func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
t.Parallel()
tracer := noop.NewTracerProvider().Tracer("test")
defaultNs := "org-1234"
owner := common.ObjectReference{
APIGroup: "prometheus.datasource.grafana.app",
APIVersion: "v1alpha1",
Kind: "DataSourceConfig",
Name: "test-datasource",
Namespace: defaultNs,
}
t.Run("happy path with owned and shared secure values", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
cfg.Sv.OwnerReferences = []metav1.OwnerReference{owner.ToOwnerReference()}
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
sv2 := "test-secure-value-2"
createdSv2, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv2
cfg.Sv.Namespace = defaultNs
})
require.NoError(t, err)
require.NotNil(t, createdSv2)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{
"securevalues:read": {"securevalues:uid:" + sv2},
})
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient)
err = svc.CanReference(ctx, owner, sv1, sv2)
require.NoError(t, err)
})
t.Run("when the auth info is missing it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
err := svc.CanReference(t.Context(), common.ObjectReference{})
require.Error(t, err)
})
t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
reqNs := "org-2345"
ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{})
err := svc.CanReference(ctx, owner)
require.Error(t, err)
})
t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
err := svc.CanReference(ctx, common.ObjectReference{})
require.Error(t, err)
})
t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
owner := common.ObjectReference{
Namespace: defaultNs,
}
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
err := svc.CanReference(ctx, owner)
require.Error(t, err)
owner.APIGroup = "prometheus.datasource.grafana.app"
require.Error(t, svc.CanReference(ctx, owner))
owner.APIGroup = ""
owner.APIVersion = "v1alpha1"
require.Error(t, svc.CanReference(ctx, owner))
owner.APIVersion = ""
owner.Kind = "DataSourceConfig"
require.Error(t, svc.CanReference(ctx, owner))
owner.Kind = ""
owner.Name = "test-datasource"
require.Error(t, svc.CanReference(ctx, owner))
owner.Name = ""
})
t.Run("when no secure values are provided it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
err := svc.CanReference(ctx, owner)
require.Error(t, err)
})
t.Run("when the secure value does not exist, it returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
err := svc.CanReference(ctx, owner, "non-existent-sv")
require.Error(t, err)
})
t.Run("when the secure value is owned by a different resource, it returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
differentOwner := common.ObjectReference{
APIGroup: "prometheus.datasource.grafana.app",
APIVersion: "v1alpha1",
Kind: "DataSourceConfig",
Name: "different-datasource",
Namespace: defaultNs,
}
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
cfg.Sv.OwnerReferences = []metav1.OwnerReference{differentOwner.ToOwnerReference()}
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
t.Run("when the request identity is not a user nor a service account, it returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
_, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
})
require.NoError(t, err)
ctx := identity.WithServiceIdentityContext(t.Context(), 1234)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
t.Run("when the identity does not have permissions to read the secure value, it returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
_, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
})
require.NoError(t, err)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{
"securevalues:read": {"securevalues:uid:another-sv"}, // can read, but another resource!
})
err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
ctx = testutils.CreateUserAuthContext(t.Context(), defaultNs, nil)
err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
}
func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) {
t.Parallel()
tracer := noop.NewTracerProvider().Tracer("test")
defaultNs := "org-1234"
owner := common.ObjectReference{
APIGroup: "prometheus.datasource.grafana.app",
APIVersion: "v1alpha1",
Kind: "DataSourceConfig",
Name: "test-datasource",
Namespace: defaultNs,
}
t.Run("happy path creates an inline secure value", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
rawSecret := "test-value"
secret := common.NewSecretValue(rawSecret)
serviceIdentity := "service-identity"
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), serviceIdentity, owner.Namespace, nil, nil)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
createdName, err := svc.CreateInline(createAuthCtx, owner, secret)
require.NoError(t, err)
require.NotEmpty(t, createdName)
decryptedValues, err := tu.DecryptService.Decrypt(t.Context(), serviceIdentity, owner.Namespace, createdName)
require.NoError(t, err)
decryptedResult, ok := decryptedValues[createdName]
require.True(t, ok)
require.Equal(t, decryptedResult.Value().DangerouslyExposeAndConsumeValue(), rawSecret)
// can also decrypt with the owner.APIGroup as a decrypter
decryptedValues, err = tu.DecryptService.Decrypt(t.Context(), owner.APIGroup, owner.Namespace, createdName)
require.NoError(t, err)
decryptedResult, ok = decryptedValues[createdName]
require.True(t, ok)
require.Equal(t, decryptedResult.Value().DangerouslyExposeAndConsumeValue(), rawSecret)
// Ignores empty values and duplicates
decryptedValues, err = tu.DecryptService.Decrypt(t.Context(), owner.APIGroup, owner.Namespace,
"", createdName, "", createdName, "") // Empty and duplicate requested names
require.NoError(t, err)
require.Len(t, decryptedValues, 1)
_, ok = decryptedValues[createdName]
require.True(t, ok)
// empty request
decryptedValues, err = tu.DecryptService.Decrypt(t.Context(), owner.APIGroup, owner.Namespace, "")
require.NoError(t, err)
require.Len(t, decryptedValues, 0) // << empty
})
t.Run("when the auth info is missing it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
_, err := svc.CreateInline(t.Context(), common.ObjectReference{}, "")
require.Error(t, err)
})
t.Run("when the request identity is not a user nor a service account, it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
createAuthCtx := testutils.CreateServiceAuthContext(t.Context(), "service-identity", defaultNs, nil)
_, err := svc.CreateInline(createAuthCtx, common.ObjectReference{}, "")
require.Error(t, err)
})
t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
reqNs := "org-2345"
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", reqNs, nil, nil)
_, err := svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
})
t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
_, err := svc.CreateInline(createAuthCtx, common.ObjectReference{}, "")
require.Error(t, err)
})
t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
owner := common.ObjectReference{
Namespace: defaultNs,
}
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
_, err := svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
owner.APIGroup = "prometheus.datasource.grafana.app"
_, err = svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
owner.APIVersion = "v1alpha1"
_, err = svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
owner.Kind = "DataSourceConfig"
_, err = svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
owner.Kind = ""
owner.Name = "test-datasource"
_, err = svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
})
t.Run("when an empty secret is provided it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
_, err := svc.CreateInline(createAuthCtx, owner, "")
require.Error(t, err)
})
}
func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) {
t.Parallel()
tracer := noop.NewTracerProvider().Tracer("test")
defaultNs := "org-1234"
owner := common.ObjectReference{
APIGroup: "prometheus.datasource.grafana.app",
APIVersion: "v1alpha1",
Kind: "DataSourceConfig",
Name: "test-datasource",
Namespace: defaultNs,
}
t.Run("happy path deletes an owned secure value", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
cfg.Sv.OwnerReferences = []metav1.OwnerReference{owner.ToOwnerReference()}
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
require.NoError(t, err)
// make sure it got deleted
sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
require.Nil(t, sv)
})
t.Run("when the auth info is missing it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
err := svc.DeleteWhenOwnedByResource(t.Context(), common.ObjectReference{}, "")
require.Error(t, err)
})
t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
reqNs := "org-2345"
ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{})
err := svc.DeleteWhenOwnedByResource(ctx, owner, "")
require.Error(t, err)
})
t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
err := svc.DeleteWhenOwnedByResource(ctx, common.ObjectReference{}, "")
require.Error(t, err)
})
t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) {
t.Parallel()
svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil)
owner := common.ObjectReference{
Namespace: defaultNs,
}
createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
owner.APIGroup = "prometheus.datasource.grafana.app"
require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
owner.APIVersion = "v1alpha1"
require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
owner.Kind = "DataSourceConfig"
require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
owner.Kind = ""
owner.Name = "test-datasource"
require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
})
t.Run("when the secure value exists but the owner does not match, it returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
cfg.Sv.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: "another.example.com/v0alpha1",
Kind: "another-kind",
Name: "another-name",
},
}
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
require.Error(t, err)
// make sure it still exists
sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
require.NoError(t, err)
require.NotNil(t, sv)
require.Equal(t, sv1, sv.GetName())
})
t.Run("when the secure value exists but it is shared (no owner), it does not return an error (noop)", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
require.NoError(t, err)
// make sure it still exists
sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
require.NoError(t, err)
require.NotNil(t, sv)
require.Equal(t, sv1, sv.GetName())
})
t.Run("when a secure value is owned and exists but another one doesnt, it deletes the first one but returns an error", func(t *testing.T) {
t.Parallel()
tu := testutils.Setup(t)
sv1 := "test-secure-value-1"
createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
cfg.Sv.Name = sv1
cfg.Sv.Namespace = defaultNs
cfg.Sv.OwnerReferences = []metav1.OwnerReference{owner.ToOwnerReference()}
})
require.NoError(t, err)
require.NotNil(t, createdSv1)
svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil)
ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1, "does-not-exist")
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
// got deleted
sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
require.Nil(t, sv)
})
}