package metadata import ( "cmp" "context" "errors" "fmt" "strings" "time" "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" "github.com/grafana/authlib/authn" 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" "github.com/grafana/grafana/pkg/storage/secret/metadata/metrics" ) func ProvideDecryptStorage( tracer trace.Tracer, keeperService contracts.KeeperService, keeperMetadataStorage contracts.KeeperMetadataStorage, secureValueMetadataStorage contracts.SecureValueMetadataStorage, decryptAuthorizer contracts.DecryptAuthorizer, reg prometheus.Registerer, ) (contracts.DecryptStorage, error) { if decryptAuthorizer == nil { return nil, fmt.Errorf("a decrypt authorizer is required") } return &decryptStorage{ tracer: tracer, keeperMetadataStorage: keeperMetadataStorage, keeperService: keeperService, secureValueMetadataStorage: secureValueMetadataStorage, decryptAuthorizer: decryptAuthorizer, metrics: metrics.NewStorageMetrics(reg), }, nil } // decryptStorage is the actual implementation of the decrypt storage. type decryptStorage struct { tracer trace.Tracer keeperMetadataStorage contracts.KeeperMetadataStorage keeperService contracts.KeeperService secureValueMetadataStorage contracts.SecureValueMetadataStorage decryptAuthorizer contracts.DecryptAuthorizer metrics *metrics.StorageMetrics } // Decrypt decrypts a secure value from the keeper. func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace, name string) (_ secretv1beta1.ExposedSecureValue, decryptErr error) { ctx, span := s.tracer.Start(ctx, "DecryptStorage.Decrypt", trace.WithAttributes( attribute.String("namespace", namespace.String()), attribute.String("name", name), )) defer span.End() var decrypterIdentity string start := time.Now() // TEMPORARY: While we evaluate all of our auditing needs, provide one for decrypt operations. defer func() { // If at this point the identity is still empty, try to get it from the auth info in context. if decrypterIdentity == "" { if authInfo, ok := claims.AuthInfoFrom(ctx); authInfo != nil && ok { if serviceIdentityList, ok := authInfo.GetExtra()[authn.ServiceIdentityKey]; ok && len(serviceIdentityList) > 0 { decrypterIdentity = strings.TrimSpace(serviceIdentityList[0]) } } } span.SetAttributes(attribute.String("decrypter.identity", decrypterIdentity)) args := []any{ "namespace", namespace.String(), "secret_name", name, "decrypter_identity", decrypterIdentity, } // The service identity used for decryption is always what is from the signed token, but if the request is // coming from grafana, the service identity will be grafana, but the request metadata will contain // additional service identity information (such as coming from the provisioning service in grafana). // we do this for auditing purposes. if md, ok := metadata.FromIncomingContext(ctx); ok { if svcIdentities := md.Get(contracts.HeaderGrafanaServiceIdentityName); len(svcIdentities) > 0 { args = append(args, "grafana_decrypter_identity", svcIdentities[0]) span.SetAttributes(attribute.String("grafana_decrypter.identity", svcIdentities[0])) } } decryptResultLabel := metrics.DecryptResultLabel(decryptErr) if decryptErr == nil { span.SetStatus(codes.Ok, "Decrypt succeeded") args = append(args, "operation", "decrypt_secret_success") } else { span.SetStatus(codes.Error, "Decrypt failed") span.RecordError(decryptErr) args = append(args, "operation", "decrypt_secret_error", "error", decryptErr.Error(), "result", decryptResultLabel) } logging.FromContext(ctx).Info("Secrets Audit Log", args...) s.metrics.DecryptDuration.WithLabelValues(decryptResultLabel, cmp.Or(decrypterIdentity, "unknown")).Observe(time.Since(start).Seconds()) // Do not leak error details to caller, return only the wrapped domain errors. if decryptErr != nil { decryptErr = cmp.Or(errors.Unwrap(decryptErr), contracts.ErrDecryptFailed) } }() // Basic authn check before reading a secure value metadata, it is here on purpose. if _, ok := claims.AuthInfoFrom(ctx); !ok { return "", fmt.Errorf("no auth info in context (%w)", contracts.ErrDecryptNotAuthorized) } // The auth token will not necessarily have the permission to read the secure value metadata, // but we still need to do it to inspect the `decrypters` field, hence the actual `authorize` // function call happens after this. sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{}) if err != nil { if errors.Is(err, context.Canceled) { return "", fmt.Errorf("operation canceled while reading secure value metadata storage: %v (%w)", err, context.Canceled) } return "", fmt.Errorf("failed to read secure value metadata storage: %v (%w)", err, contracts.ErrDecryptNotFound) } decrypterIdentity, authorized, reason := s.decryptAuthorizer.Authorize(ctx, namespace, name, sv.Spec.Decrypters, sv.OwnerReferences) if !authorized { return "", fmt.Errorf("failed to authorize decryption with reason %v (%w)", reason, contracts.ErrDecryptNotAuthorized) } keeperConfig, err := s.keeperMetadataStorage.GetKeeperConfig(ctx, namespace.String(), sv.Status.Keeper, contracts.ReadOpts{}) if err != nil { if errors.Is(err, context.Canceled) { return "", fmt.Errorf("operation canceled while reading keeper config metadata storage: %v (%w)", err, context.Canceled) } return "", fmt.Errorf("failed to read keeper config metadata storage: %v (%w)", err, contracts.ErrDecryptFailed) } keeper, err := s.keeperService.KeeperForConfig(keeperConfig) if err != nil { return "", fmt.Errorf("failed to get keeper for config: %v (%w)", err, contracts.ErrDecryptFailed) } if sv.Spec.Ref != nil { exposedValue, err := keeper.RetrieveReference(ctx, keeperConfig, *sv.Spec.Ref) if err != nil { if errors.Is(err, context.Canceled) { return "", fmt.Errorf("operation canceled while exposing secret using reference: %v (%w)", err, context.Canceled) } return "", fmt.Errorf("failed to expose secret using reference: %v (%w)", err, contracts.ErrDecryptFailed) } return exposedValue, nil } exposedValue, err := keeper.Expose(ctx, keeperConfig, namespace, name, sv.Status.Version) if err != nil { if errors.Is(err, context.Canceled) { return "", fmt.Errorf("operation canceled while exposing secret: %v (%w)", err, context.Canceled) } return "", fmt.Errorf("failed to expose secret: %v (%w)", err, contracts.ErrDecryptFailed) } return exposedValue, nil }