| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package store
- import (
- "context"
- "fmt"
- "strings"
- corev1 "k8s.io/api/core/v1"
- apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "sigs.k8s.io/controller-runtime/pkg/client"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- pb "github.com/external-secrets/external-secrets/proto/provider"
- )
- // Server wraps v1 providers and generators and exposes them as v2 gRPC services.
- // This allows existing v1 provider and generator implementations to be used in the v2 architecture.
- type Server struct {
- pb.UnimplementedSecretStoreProviderServer
- kubeClient client.Client
- // we support multiple v1 providers, so we need to map the v2 provider
- // with apiVersion+kind to the corresponding v1 provider
- resourceMapping ProviderMapping
- specMapper SpecMapper
- }
- // ProviderMapping maps Kubernetes resources to their provider implementations.
- type ProviderMapping map[schema.GroupVersionKind]esv1.ProviderInterface
- // SpecMapper maps a provider reference to a SecretStoreSpec.
- // This is used to create a synthetic store for the v1 provider.
- type SpecMapper func(ref *pb.ProviderReference, sourceNamespace string) (*esv1.SecretStoreSpec, error)
- // NewServer creates a new AdapterServer that wraps v1 providers and generators.
- func NewServer(kubeClient client.Client, resourceMapping ProviderMapping, specMapping SpecMapper) *Server {
- return &Server{
- kubeClient: kubeClient,
- resourceMapping: resourceMapping,
- specMapper: specMapping,
- }
- }
- func (s *Server) resolveProvider(ref *pb.ProviderReference) (esv1.ProviderInterface, error) {
- if ref == nil {
- return nil, fmt.Errorf("provider reference is nil")
- }
- splitted := strings.Split(ref.ApiVersion, "/")
- if len(splitted) != 2 {
- return nil, fmt.Errorf("invalid api version: %s", ref.ApiVersion)
- }
- group := splitted[0]
- version := splitted[1]
- key := schema.GroupVersionKind{
- Group: group,
- Version: version,
- Kind: ref.Kind,
- }
- v1Provider, ok := s.resourceMapping[key]
- if !ok {
- return nil, fmt.Errorf("resource mapping not found for %q", key)
- }
- return v1Provider, nil
- }
- func (s *Server) getClient(ctx context.Context, ref *pb.ProviderReference, namespace string) (esv1.SecretsClient, error) {
- if ref == nil {
- return nil, fmt.Errorf("request or remote ref is nil")
- }
- spec, err := s.specMapper(ref, namespace)
- if err != nil {
- return nil, fmt.Errorf("failed to map provider reference to spec: %w", err)
- }
- // namespace is the resolved authentication namespace (from Provider/ClusterProvider if applicable)
- syntheticStore, err := NewSyntheticStore(spec, namespace, ref.GetStoreRefKind())
- if err != nil {
- return nil, fmt.Errorf("failed to create synthetic store: %w", err)
- }
- provider, err := s.resolveProvider(ref)
- if err != nil {
- return nil, fmt.Errorf("failed to resolve provider: %w", err)
- }
- return provider.NewClient(ctx, syntheticStore, s.kubeClient, namespace)
- }
- func (s *Server) getClientForRemoteRef(
- ctx context.Context,
- providerRef *pb.ProviderReference,
- sourceNamespace string,
- remoteRef *pb.ExternalSecretDataRemoteRef,
- ) (esv1.SecretsClient, esv1.ExternalSecretDataRemoteRef, error) {
- if remoteRef == nil {
- return nil, esv1.ExternalSecretDataRemoteRef{}, fmt.Errorf("request or remote ref is nil")
- }
- if err := validateSourceNamespace(sourceNamespace); err != nil {
- return nil, esv1.ExternalSecretDataRemoteRef{}, err
- }
- client, err := s.getClient(ctx, providerRef, sourceNamespace)
- if err != nil {
- return nil, esv1.ExternalSecretDataRemoteRef{}, fmt.Errorf("failed to get client: %w", err)
- }
- ref := esv1.ExternalSecretDataRemoteRef{
- Key: remoteRef.Key,
- Version: remoteRef.Version,
- Property: remoteRef.Property,
- }
- if remoteRef.DecodingStrategy != "" {
- ref.DecodingStrategy = esv1.ExternalSecretDecodingStrategy(remoteRef.DecodingStrategy)
- }
- if remoteRef.MetadataPolicy != "" {
- ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicy(remoteRef.MetadataPolicy)
- }
- return client, ref, nil
- }
- // GetSecret retrieves a single secret from the provider.
- func (s *Server) GetSecret(ctx context.Context, req *pb.GetSecretRequest) (*pb.GetSecretResponse, error) {
- if req == nil {
- return nil, fmt.Errorf("request or remote ref is nil")
- }
- client, ref, err := s.getClientForRemoteRef(ctx, req.ProviderRef, req.SourceNamespace, req.RemoteRef)
- if err != nil {
- return nil, err
- }
- defer func() { _ = client.Close(ctx) }()
- value, err := client.GetSecret(ctx, ref)
- if err != nil {
- return nil, fmt.Errorf("failed to get secret: %w", err)
- }
- return &pb.GetSecretResponse{
- Value: value,
- }, nil
- }
- // GetSecretMap retrieves multiple key/value pairs from a single secret object.
- func (s *Server) GetSecretMap(ctx context.Context, req *pb.GetSecretMapRequest) (*pb.GetSecretMapResponse, error) {
- if req == nil {
- return nil, fmt.Errorf("request or remote ref is nil")
- }
- client, ref, err := s.getClientForRemoteRef(ctx, req.ProviderRef, req.SourceNamespace, req.RemoteRef)
- if err != nil {
- return nil, err
- }
- defer func() { _ = client.Close(ctx) }()
- secrets, err := client.GetSecretMap(ctx, ref)
- if err != nil {
- return nil, fmt.Errorf("failed to get secret map: %w", err)
- }
- return &pb.GetSecretMapResponse{
- Secrets: secrets,
- }, nil
- }
- // PushSecret writes a secret to the provider.
- func (s *Server) PushSecret(ctx context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
- if req == nil || req.PushSecretData == nil {
- return nil, fmt.Errorf("request or push secret data is nil")
- }
- if err := validateSourceNamespace(req.SourceNamespace); err != nil {
- return nil, err
- }
- client, err := s.getClient(ctx, req.ProviderRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get client: %w", err)
- }
- defer func() { _ = client.Close(ctx) }()
- // Convert map[string][]byte to *corev1.Secret
- secret := &corev1.Secret{
- Data: req.SecretData,
- Type: corev1.SecretType(req.SecretType),
- ObjectMeta: metav1.ObjectMeta{
- Labels: req.SecretLabels,
- Annotations: req.SecretAnnotations,
- },
- }
- // Convert protobuf PushSecretData to v1 PushSecretData
- pushData := &pushSecretData{
- property: req.PushSecretData.Property,
- secretKey: req.PushSecretData.SecretKey,
- remoteKey: req.PushSecretData.RemoteKey,
- metadata: req.PushSecretData.Metadata,
- }
- // Call v1 PushSecret
- if err := client.PushSecret(ctx, secret, pushData); err != nil {
- return nil, fmt.Errorf("failed to push secret: %w", err)
- }
- return &pb.PushSecretResponse{}, nil
- }
- // DeleteSecret deletes a secret from the provider.
- func (s *Server) DeleteSecret(ctx context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
- if req == nil || req.RemoteRef == nil {
- return nil, fmt.Errorf("request or remote ref is nil")
- }
- if err := validateSourceNamespace(req.SourceNamespace); err != nil {
- return nil, err
- }
- client, err := s.getClient(ctx, req.ProviderRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get client: %w", err)
- }
- defer func() { _ = client.Close(ctx) }()
- // Convert protobuf remote ref to v1 PushSecretRemoteRef
- remoteRef := &pushSecretRemoteRef{
- remoteKey: req.RemoteRef.RemoteKey,
- property: req.RemoteRef.Property,
- }
- // Call v1 DeleteSecret
- if err := client.DeleteSecret(ctx, remoteRef); err != nil {
- return nil, fmt.Errorf("failed to delete secret: %w", err)
- }
- return &pb.DeleteSecretResponse{}, nil
- }
- // SecretExists checks if a secret exists in the provider.
- func (s *Server) SecretExists(ctx context.Context, req *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
- if req == nil || req.RemoteRef == nil {
- return nil, fmt.Errorf("request or remote ref is nil")
- }
- if err := validateSourceNamespace(req.SourceNamespace); err != nil {
- return nil, err
- }
- client, err := s.getClient(ctx, req.ProviderRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get client: %w", err)
- }
- defer func() { _ = client.Close(ctx) }()
- // Convert protobuf remote ref to v1 PushSecretRemoteRef
- remoteRef := &pushSecretRemoteRef{
- remoteKey: req.RemoteRef.RemoteKey,
- property: req.RemoteRef.Property,
- }
- // Call v1 SecretExists
- exists, err := client.SecretExists(ctx, remoteRef)
- if err != nil {
- return nil, fmt.Errorf("failed to check if secret exists: %w", err)
- }
- return &pb.SecretExistsResponse{
- Exists: exists,
- }, nil
- }
- // GetAllSecrets retrieves multiple secrets from the provider.
- func (s *Server) GetAllSecrets(ctx context.Context, req *pb.GetAllSecretsRequest) (*pb.GetAllSecretsResponse, error) {
- if req == nil || req.Find == nil {
- return nil, fmt.Errorf("request or find criteria is nil")
- }
- if err := validateSourceNamespace(req.SourceNamespace); err != nil {
- return nil, err
- }
- client, err := s.getClient(ctx, req.ProviderRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get client: %w", err)
- }
- defer func() { _ = client.Close(ctx) }()
- // Convert protobuf ExternalSecretFind to v1 ExternalSecretFind
- find := esv1.ExternalSecretFind{
- Tags: req.Find.Tags,
- ConversionStrategy: esv1.ExternalSecretConversionStrategy(req.Find.ConversionStrategy),
- DecodingStrategy: esv1.ExternalSecretDecodingStrategy(req.Find.DecodingStrategy),
- }
- // Convert Path from string to *string
- if req.Find.Path != "" {
- path := req.Find.Path
- find.Path = &path
- }
- if req.Find.Name != nil {
- find.Name = &esv1.FindName{
- RegExp: req.Find.Name.Regexp,
- }
- }
- // Call v1 GetAllSecrets
- secrets, err := client.GetAllSecrets(ctx, find)
- if err != nil {
- return nil, fmt.Errorf("failed to get all secrets: %w", err)
- }
- return &pb.GetAllSecretsResponse{
- Secrets: secrets,
- }, nil
- }
- // Validate checks if the provider configuration is valid.
- func (s *Server) Validate(ctx context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
- if req == nil {
- return nil, fmt.Errorf("request is nil")
- }
- client, err := s.getClient(ctx, req.ProviderRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get client: %w", err)
- }
- defer func() { _ = client.Close(ctx) }()
- result, err := client.Validate()
- if err != nil {
- return &pb.ValidateResponse{
- Valid: false,
- Error: err.Error(),
- }, nil
- }
- var valid bool
- switch result {
- case esv1.ValidationResultReady:
- valid = true
- case esv1.ValidationResultUnknown:
- valid = true // Unknown is treated as valid but warns
- case esv1.ValidationResultError:
- valid = false
- }
- return &pb.ValidateResponse{
- Valid: valid,
- Warnings: []string{},
- }, nil
- }
- // Capabilities returns the capabilities of the provider.
- // TODO: remove / rewrite capabilities:
- // the provider should advertise what providers/generators it supports.
- func (s *Server) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
- if req == nil {
- return nil, fmt.Errorf("request is nil")
- }
- provider, err := s.resolveProvider(req.ProviderRef)
- if err != nil {
- return nil, fmt.Errorf("failed to resolve provider: %w", err)
- }
- caps := provider.Capabilities()
- var pbCaps pb.SecretStoreCapabilities
- switch caps {
- case esv1.SecretStoreReadOnly:
- pbCaps = pb.SecretStoreCapabilities_READ_ONLY
- case esv1.SecretStoreWriteOnly:
- pbCaps = pb.SecretStoreCapabilities_WRITE_ONLY
- case esv1.SecretStoreReadWrite:
- pbCaps = pb.SecretStoreCapabilities_READ_WRITE
- default:
- pbCaps = pb.SecretStoreCapabilities_READ_ONLY
- }
- return &pb.CapabilitiesResponse{
- Capabilities: pbCaps,
- }, nil
- }
- func validateSourceNamespace(sourceNamespace string) error {
- if sourceNamespace == "" {
- return fmt.Errorf("source namespace is required")
- }
- return nil
- }
- // pushSecretData implements esv1.PushSecretData.
- type pushSecretData struct {
- property string
- secretKey string
- remoteKey string
- metadata []byte
- }
- func (p *pushSecretData) GetProperty() string {
- return p.property
- }
- func (p *pushSecretData) GetSecretKey() string {
- return p.secretKey
- }
- func (p *pushSecretData) GetRemoteKey() string {
- return p.remoteKey
- }
- func (p *pushSecretData) GetMetadata() *apiextensionsv1.JSON {
- if len(p.metadata) == 0 {
- return nil
- }
- return &apiextensionsv1.JSON{
- Raw: p.metadata,
- }
- }
- // pushSecretRemoteRef implements esv1.PushSecretRemoteRef.
- type pushSecretRemoteRef struct {
- remoteKey string
- property string
- }
- func (p *pushSecretRemoteRef) GetRemoteKey() string {
- return p.remoteKey
- }
- func (p *pushSecretRemoteRef) GetProperty() string {
- return p.property
- }
|