| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /*
- 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 grpc
- import (
- "context"
- "fmt"
- "time"
- "github.com/go-logr/logr"
- "google.golang.org/grpc"
- "google.golang.org/grpc/connectivity"
- corev1 "k8s.io/api/core/v1"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- pb "github.com/external-secrets/external-secrets/proto/provider"
- v2 "github.com/external-secrets/external-secrets/providers/v2/common"
- )
- const (
- // defaultTimeout is the default timeout for gRPC calls.
- defaultTimeout = 30 * time.Second
- )
- // grpcProviderClient implements the v2.Provider interface using gRPC.
- type grpcProviderClient struct {
- conn *grpc.ClientConn
- client pb.SecretStoreProviderClient
- log logr.Logger
- }
- // Ensure grpcProviderClient implements the Provider interface.
- var _ v2.Provider = &grpcProviderClient{}
- // GetSecret retrieves a single secret from the provider via gRPC.
- func (c *grpcProviderClient) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) ([]byte, error) {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("GetSecret", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("getting secret via gRPC",
- "key", ref.Key,
- "version", ref.Version,
- "property", ref.Property,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Check connection state before call
- state := c.conn.GetState()
- if state != connectivity.Ready && state != connectivity.Idle {
- c.log.Info("connection not ready, attempting to reconnect",
- "state", state.String(),
- "target", c.conn.Target())
- }
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Convert v1 reference to protobuf message
- pbRef := &pb.ExternalSecretDataRemoteRef{
- Key: ref.Key,
- Version: ref.Version,
- Property: ref.Property,
- DecodingStrategy: string(ref.DecodingStrategy),
- MetadataPolicy: string(ref.MetadataPolicy),
- }
- // Make gRPC call with provider reference
- req := &pb.GetSecretRequest{
- RemoteRef: pbRef,
- ProviderRef: providerRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling GetSecret RPC",
- "target", c.conn.Target(),
- "timeout", defaultTimeout.String())
- resp, err := c.client.GetSecret(ctx, req)
- if err != nil {
- c.log.Error(err, "GetSecret RPC failed",
- "key", ref.Key,
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to get secret via gRPC: %w", err)
- return nil, err
- }
- c.log.V(1).Info("GetSecret RPC succeeded",
- "key", ref.Key,
- "valueLength", len(resp.Value))
- return resp.Value, nil
- }
- // GetSecretMap retrieves multiple key/value pairs from a single provider object via gRPC.
- func (c *grpcProviderClient) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("GetSecretMap", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("getting secret map via gRPC",
- "key", ref.Key,
- "version", ref.Version,
- "property", ref.Property,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- pbRef := &pb.ExternalSecretDataRemoteRef{
- Key: ref.Key,
- Version: ref.Version,
- Property: ref.Property,
- DecodingStrategy: string(ref.DecodingStrategy),
- MetadataPolicy: string(ref.MetadataPolicy),
- }
- req := &pb.GetSecretMapRequest{
- RemoteRef: pbRef,
- ProviderRef: providerRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling GetSecretMap RPC",
- "target", c.conn.Target())
- resp, err := c.client.GetSecretMap(ctx, req)
- if err != nil {
- c.log.Error(err, "GetSecretMap RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to get secret map via gRPC: %w", err)
- return nil, err
- }
- c.log.V(1).Info("GetSecretMap RPC succeeded",
- "secretCount", len(resp.Secrets))
- return resp.Secrets, nil
- }
- // Validate checks if the provider is properly configured via gRPC.
- func (c *grpcProviderClient) Validate(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) error {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("Validate", c.conn.Target(), err, time.Since(start))
- }()
- c.log.Info("validating provider via gRPC",
- "target", c.conn.Target(),
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Check connection state before call
- state := c.conn.GetState()
- c.log.V(1).Info("connection details",
- "state", state.String(),
- "target", c.conn.Target(),
- "authority", c.conn.GetMethodConfig("").WaitForReady)
- if state != connectivity.Ready && state != connectivity.Idle {
- c.log.Info("connection not in ready/idle state, will attempt to connect",
- "state", state.String(),
- "target", c.conn.Target())
- }
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Make gRPC call with provider reference
- req := &pb.ValidateRequest{
- ProviderRef: providerRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling Validate RPC",
- "target", c.conn.Target(),
- "timeout", defaultTimeout.String())
- resp, err := c.client.Validate(ctx, req)
- if err != nil {
- c.log.Error(err, "Validate RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target(),
- "errorType", fmt.Sprintf("%T", err))
- err = fmt.Errorf("failed to validate provider via gRPC: %w", err)
- return err
- }
- c.log.V(1).Info("Validate RPC completed",
- "valid", resp.Valid,
- "error", resp.Error)
- // Check for error in response
- if !resp.Valid {
- if resp.Error != "" {
- c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
- "message", resp.Error)
- err = fmt.Errorf("provider validation failed: %s", resp.Error)
- return err
- }
- c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
- "message", "no error message provided")
- err = fmt.Errorf("provider validation failed without error message")
- return err
- }
- c.log.Info("provider validation succeeded")
- return nil
- }
- // GetAllSecrets retrieves multiple secrets based on find criteria via gRPC.
- func (c *grpcProviderClient) GetAllSecrets(ctx context.Context, find esv1.ExternalSecretFind, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("GetAllSecrets", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("getting all secrets via gRPC",
- "tags", find.Tags,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Convert find criteria to protobuf
- pbFind := &pb.ExternalSecretFind{
- Tags: find.Tags,
- ConversionStrategy: string(find.ConversionStrategy),
- DecodingStrategy: string(find.DecodingStrategy),
- }
- if find.Path != nil {
- pbFind.Path = *find.Path
- }
- if find.Name != nil {
- pbFind.Name = &pb.FindName{
- Regexp: find.Name.RegExp,
- }
- }
- // Make gRPC call
- req := &pb.GetAllSecretsRequest{
- ProviderRef: providerRef,
- Find: pbFind,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling GetAllSecrets RPC",
- "target", c.conn.Target())
- resp, err := c.client.GetAllSecrets(ctx, req)
- if err != nil {
- c.log.Error(err, "GetAllSecrets RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to get all secrets via gRPC: %w", err)
- return nil, err
- }
- c.log.V(1).Info("GetAllSecrets RPC succeeded",
- "secretCount", len(resp.Secrets))
- return resp.Secrets, nil
- }
- // PushSecret writes a secret to the provider via gRPC.
- func (c *grpcProviderClient) PushSecret(ctx context.Context, secret *corev1.Secret, pushSecretData *pb.PushSecretData, providerRef *pb.ProviderReference, sourceNamespace string) error {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("PushSecret", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("pushing secret via gRPC",
- "remoteKey", pushSecretData.RemoteKey,
- "property", pushSecretData.Property,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Make gRPC call
- req := &pb.PushSecretRequest{
- ProviderRef: providerRef,
- SecretData: secret.Data,
- PushSecretData: pushSecretData,
- SourceNamespace: sourceNamespace,
- SecretType: string(secret.Type),
- SecretLabels: secret.Labels,
- SecretAnnotations: secret.Annotations,
- }
- c.log.V(1).Info("calling PushSecret RPC",
- "target", c.conn.Target())
- _, err = c.client.PushSecret(ctx, req)
- if err != nil {
- c.log.Error(err, "PushSecret RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to push secret via gRPC: %w", err)
- return err
- }
- c.log.V(1).Info("PushSecret RPC succeeded",
- "remoteKey", pushSecretData.RemoteKey)
- return nil
- }
- // DeleteSecret deletes a secret from the provider via gRPC.
- func (c *grpcProviderClient) DeleteSecret(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) error {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("DeleteSecret", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("deleting secret via gRPC",
- "remoteKey", remoteRef.RemoteKey,
- "property", remoteRef.Property,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Make gRPC call
- req := &pb.DeleteSecretRequest{
- ProviderRef: providerRef,
- RemoteRef: remoteRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling DeleteSecret RPC",
- "target", c.conn.Target())
- _, err = c.client.DeleteSecret(ctx, req)
- if err != nil {
- c.log.Error(err, "DeleteSecret RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to delete secret via gRPC: %w", err)
- return err
- }
- c.log.V(1).Info("DeleteSecret RPC succeeded",
- "remoteKey", remoteRef.RemoteKey)
- return nil
- }
- // SecretExists checks if a secret exists in the provider via gRPC.
- func (c *grpcProviderClient) SecretExists(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (bool, error) {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("SecretExists", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("checking if secret exists via gRPC",
- "remoteKey", remoteRef.RemoteKey,
- "property", remoteRef.Property,
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Make gRPC call
- req := &pb.SecretExistsRequest{
- ProviderRef: providerRef,
- RemoteRef: remoteRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling SecretExists RPC",
- "target", c.conn.Target())
- resp, err := c.client.SecretExists(ctx, req)
- if err != nil {
- c.log.Error(err, "SecretExists RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to check if secret exists via gRPC: %w", err)
- return false, err
- }
- c.log.V(1).Info("SecretExists RPC succeeded",
- "remoteKey", remoteRef.RemoteKey,
- "exists", resp.Exists)
- return resp.Exists, nil
- }
- // Capabilities retrieves the capabilities of the provider via gRPC.
- func (c *grpcProviderClient) Capabilities(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) (pb.SecretStoreCapabilities, error) {
- start := time.Now()
- var err error
- defer func() {
- clientMetrics.ObserveRequest("Capabilities", c.conn.Target(), err, time.Since(start))
- }()
- c.log.V(1).Info("getting provider capabilities via gRPC",
- "target", c.conn.Target(),
- "connectionState", c.conn.GetState().String(),
- "providerRef", providerRef,
- "sourceNamespace", sourceNamespace)
- // Create context with timeout
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
- defer cancel()
- // Make gRPC call with provider reference
- req := &pb.CapabilitiesRequest{
- ProviderRef: providerRef,
- SourceNamespace: sourceNamespace,
- }
- c.log.V(1).Info("calling Capabilities RPC",
- "target", c.conn.Target())
- resp, err := c.client.Capabilities(ctx, req)
- if err != nil {
- c.log.Error(err, "Capabilities RPC failed",
- "connectionState", c.conn.GetState().String(),
- "target", c.conn.Target())
- err = fmt.Errorf("failed to get capabilities via gRPC: %w", err)
- return pb.SecretStoreCapabilities_READ_ONLY, err
- }
- c.log.V(1).Info("Capabilities RPC succeeded",
- "capabilities", resp.Capabilities)
- return resp.Capabilities, nil
- }
- // Close closes the gRPC connection.
- func (c *grpcProviderClient) Close(_ context.Context) error {
- if c.conn != nil {
- c.log.V(1).Info("closing gRPC connection",
- "target", c.conn.Target(),
- "state", c.conn.GetState().String())
- return c.conn.Close()
- }
- c.log.V(1).Info("no connection to close")
- return nil
- }
|