diff --git a/pkg/registry/apis/secret/inline/inline_secure_value.go b/pkg/registry/apis/secret/inline/inline_secure_value.go index 9a24f6ace52..4e675486f48 100644 --- a/pkg/registry/apis/secret/inline/inline_secure_value.go +++ b/pkg/registry/apis/secret/inline/inline_secure_value.go @@ -5,13 +5,13 @@ import ( "errors" "fmt" - "github.com/grafana/authlib/authn" - authlib "github.com/grafana/authlib/types" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/authlib/authn" + authlib "github.com/grafana/authlib/types" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -158,6 +158,25 @@ func (s *LocalInlineSecureValueService) canIdentityReadSecureValue(ctx context.C return nil } +func (s *LocalInlineSecureValueService) verifyOwnerAndAuth(ctx context.Context, owner common.ObjectReference) (authlib.AuthInfo, error) { + // Any valid identity can create inline secure values + authInfo, ok := authlib.AuthInfoFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing auth info in context") + } + + // Make sure the owner matches the identity when it is not global + if owner.Namespace == "" || !authlib.NamespaceMatches(authInfo.GetNamespace(), owner.Namespace) { + return nil, fmt.Errorf("owner namespace %s does not match auth info namespace %s", owner.Namespace, authInfo.GetNamespace()) + } + + if owner.Namespace == "" || owner.APIGroup == "" || owner.APIVersion == "" || owner.Kind == "" || owner.Name == "" { + return nil, fmt.Errorf("owner reference must have a valid API group, API version, kind, namespace and name") + } + + return authInfo, nil +} + func (s *LocalInlineSecureValueService) CreateInline(ctx context.Context, owner common.ObjectReference, value common.RawSecureValue) (string, error) { ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.CreateInline", trace.WithAttributes( attribute.String("owner.namespace", owner.Namespace), @@ -168,27 +187,9 @@ func (s *LocalInlineSecureValueService) CreateInline(ctx context.Context, owner )) defer span.End() - authInfo, ok := authlib.AuthInfoFrom(ctx) - if !ok { - return "", fmt.Errorf("missing auth info in context") - } - - if authInfo.GetIdentityType() != authlib.TypeUser && authInfo.GetIdentityType() != authlib.TypeServiceAccount { - return "", fmt.Errorf("identity type %s not allowed, expected either %s or %s", authInfo.GetIdentityType(), authlib.TypeUser, authlib.TypeServiceAccount) - } - - serviceIdentityList, ok := authInfo.GetExtra()[authn.ServiceIdentityKey] - if !ok || len(serviceIdentityList) != 1 { - return "", fmt.Errorf("expected exactly one service identity, found %d", len(serviceIdentityList)) - } - serviceIdentity := serviceIdentityList[0] - - 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") + authInfo, err := s.verifyOwnerAndAuth(ctx, owner) + if err != nil { + return "", err } if value.IsZero() { @@ -198,14 +199,18 @@ func (s *LocalInlineSecureValueService) CreateInline(ctx context.Context, owner // TODO(2025-07-31): when we migrate to using the common type, we don't need this conversion. secret := secretv1beta1.ExposedSecureValue(value) - decrypters := []string{serviceIdentity} - if owner.APIGroup != serviceIdentity { - decrypters = append(decrypters, owner.APIGroup) + // The owner group can always decrypt + decrypters := []string{owner.APIGroup} + + serviceIdentity, ok := authInfo.GetExtra()[authn.ServiceIdentityKey] + if ok && len(serviceIdentity) > 0 && serviceIdentity[0] != owner.APIGroup { + decrypters = append(decrypters, serviceIdentity[0]) } - spec := &secretv1beta1.SecureValue{ + obj := &secretv1beta1.SecureValue{ ObjectMeta: metav1.ObjectMeta{ - Name: "sv-" + util.GenerateShortUID(), + //GenerateName: "inline-", + Name: "inline-" + util.GenerateShortUID(), Namespace: owner.Namespace, OwnerReferences: []metav1.OwnerReference{owner.ToOwnerReference()}, }, @@ -216,9 +221,9 @@ func (s *LocalInlineSecureValueService) CreateInline(ctx context.Context, owner }, } - createdSv, err := s.secureValueService.Create(ctx, spec, authInfo.GetUID()) + createdSv, err := s.secureValueService.Create(ctx, obj, authInfo.GetUID()) if err != nil { - return "", fmt.Errorf("error creating secure value %s for owner %v: %w", spec.Name, owner, err) + return "", fmt.Errorf("error creating secure value %s for owner %v: %w", obj.Name, owner, err) } return createdSv.GetName(), nil @@ -235,17 +240,8 @@ func (s *LocalInlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Co )) 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") + if _, err := s.verifyOwnerAndAuth(ctx, owner); err != nil { + return err } for _, name := range names {