Secrets: Add inline secure value delete when owned method (#108993)

This commit is contained in:
Matheus Macabu
2025-08-01 14:45:44 +02:00
committed by GitHub
parent 4f30a3b62b
commit 491c74b688
2 changed files with 199 additions and 1 deletions
@@ -217,5 +217,40 @@ func (s *inlineSecureValueService) CreateInline(ctx context.Context, owner commo
}
func (s *inlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) error {
return fmt.Errorf("not implemented yet")
ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.DeleteWhenOwnedByResource", trace.WithAttributes(
attribute.String("owner.namespace", owner.Namespace),
attribute.String("owner.apiGroup", owner.APIGroup),
attribute.String("owner.apiVersion", owner.APIVersion),
attribute.String("owner.kind", owner.Kind),
attribute.String("owner.name", owner.Name),
attribute.String("secureValue.name", name),
))
defer span.End()
authInfo, ok := authlib.AuthInfoFrom(ctx)
if !ok {
return fmt.Errorf("missing auth info in context")
}
if owner.Namespace == "" || !authlib.NamespaceMatches(authInfo.GetNamespace(), owner.Namespace) {
return fmt.Errorf("owner namespace %s does not match auth info namespace %s", owner.Namespace, authInfo.GetNamespace())
}
if owner.APIGroup == "" || owner.APIVersion == "" || owner.Kind == "" || owner.Name == "" {
return fmt.Errorf("owner reference must have a valid API group, API version, kind and name")
}
owned, err := s.isSecureValueOwnedByResource(ctx, owner, name)
if err != nil {
return fmt.Errorf("error checking if secure value %s is owned by %v: %w", name, owner, err)
}
if owned {
if _, err := s.secureValueService.Delete(ctx, xkube.Namespace(owner.Namespace), name); err != nil {
return fmt.Errorf("error deleting secure value %s for owner %v: %w", name, owner, err)
}
}
// if it is not owned, this is a no-op
return nil
}
@@ -5,8 +5,10 @@ import (
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/service"
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -347,3 +349,164 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) {
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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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 := service.ProvideInlineSecureValueService(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())
})
}