github.com/cilium/cilium@v1.16.2/pkg/envoy/secretsync.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package envoy
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	envoy_config_core_v3 "github.com/cilium/proxy/go/envoy/config/core/v3"
    12  	envoy_entensions_tls_v3 "github.com/cilium/proxy/go/envoy/extensions/transport_sockets/tls/v3"
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/cilium/cilium/pkg/k8s/resource"
    16  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    17  	"github.com/cilium/cilium/pkg/logging/logfields"
    18  )
    19  
    20  const (
    21  	tlsCrtAttribute = "tls.crt"
    22  	tlsKeyAttribute = "tls.key"
    23  
    24  	// Key for CA certificate is fixed as 'ca.crt' even though is not as "standard"
    25  	// as 'tls.crt' and 'tls.key' are via k8s tls secret type.
    26  	caCrtAttribute = "ca.crt"
    27  )
    28  
    29  // secretSyncer is responsible to sync K8s TLS Secrets in pre-defined namespaces
    30  // via xDS SDS to Envoy.
    31  type secretSyncer struct {
    32  	logger         logrus.FieldLogger
    33  	envoyXdsServer XDSServer
    34  }
    35  
    36  func newSecretSyncer(logger logrus.FieldLogger, envoyXdsServer XDSServer) *secretSyncer {
    37  	return &secretSyncer{
    38  		logger:         logger,
    39  		envoyXdsServer: envoyXdsServer,
    40  	}
    41  }
    42  
    43  func (r *secretSyncer) handleSecretEvent(ctx context.Context, event resource.Event[*slim_corev1.Secret]) error {
    44  	scopedLogger := r.logger.
    45  		WithField(logfields.K8sNamespace, event.Key.Namespace).
    46  		WithField("name", event.Key.Name)
    47  
    48  	var err error
    49  
    50  	switch event.Kind {
    51  	case resource.Upsert:
    52  		scopedLogger.Debug("Received Secret upsert event")
    53  		err = r.upsertK8sSecretV1(ctx, event.Object)
    54  		if err != nil {
    55  			scopedLogger.WithError(err).Error("failed to handle Secret upsert")
    56  			err = fmt.Errorf("failed to handle CEC upsert: %w", err)
    57  		}
    58  	case resource.Delete:
    59  		scopedLogger.Debug("Received Secret delete event")
    60  		err = r.deleteK8sSecretV1(ctx, event.Key)
    61  		if err != nil {
    62  			scopedLogger.WithError(err).Error("failed to handle Secret delete")
    63  			err = fmt.Errorf("failed to handle Secret delete: %w", err)
    64  		}
    65  	}
    66  
    67  	event.Done(err)
    68  
    69  	return err
    70  }
    71  
    72  // updateK8sSecretV1 performs Envoy upsert operation for added or updated secret.
    73  func (r *secretSyncer) upsertK8sSecretV1(ctx context.Context, secret *slim_corev1.Secret) error {
    74  	if secret == nil {
    75  		return errors.New("secret is nil")
    76  	}
    77  
    78  	resource := Resources{
    79  		Secrets: []*envoy_entensions_tls_v3.Secret{k8sToEnvoySecret(secret)},
    80  	}
    81  	return r.envoyXdsServer.UpsertEnvoyResources(ctx, resource)
    82  }
    83  
    84  // deleteK8sSecretV1 makes sure the related secret values in Envoy SDS is removed.
    85  func (r *secretSyncer) deleteK8sSecretV1(ctx context.Context, key resource.Key) error {
    86  	if len(key.Namespace) == 0 || len(key.Name) == 0 {
    87  		return errors.New("key has empty namespace and/or name")
    88  	}
    89  
    90  	resource := Resources{
    91  		Secrets: []*envoy_entensions_tls_v3.Secret{
    92  			{
    93  				// For deletion, only the name is required.
    94  				Name: getEnvoySecretName(key.Namespace, key.Name),
    95  			},
    96  		},
    97  	}
    98  	return r.envoyXdsServer.DeleteEnvoyResources(ctx, resource)
    99  }
   100  
   101  // k8sToEnvoySecret converts k8s secret object to envoy TLS secret object
   102  func k8sToEnvoySecret(secret *slim_corev1.Secret) *envoy_entensions_tls_v3.Secret {
   103  	if secret == nil {
   104  		return nil
   105  	}
   106  	envoySecret := &envoy_entensions_tls_v3.Secret{
   107  		Name: getEnvoySecretName(secret.GetNamespace(), secret.GetName()),
   108  	}
   109  
   110  	if len(secret.Data[tlsCrtAttribute]) > 0 || len(secret.Data[tlsKeyAttribute]) > 0 {
   111  		envoySecret.Type = &envoy_entensions_tls_v3.Secret_TlsCertificate{
   112  			TlsCertificate: &envoy_entensions_tls_v3.TlsCertificate{
   113  				CertificateChain: &envoy_config_core_v3.DataSource{
   114  					Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
   115  						InlineBytes: secret.Data[tlsCrtAttribute],
   116  					},
   117  				},
   118  				PrivateKey: &envoy_config_core_v3.DataSource{
   119  					Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
   120  						InlineBytes: secret.Data[tlsKeyAttribute],
   121  					},
   122  				},
   123  			},
   124  		}
   125  	} else if len(secret.Data[caCrtAttribute]) > 0 {
   126  		envoySecret.Type = &envoy_entensions_tls_v3.Secret_ValidationContext{
   127  			ValidationContext: &envoy_entensions_tls_v3.CertificateValidationContext{
   128  				TrustedCa: &envoy_config_core_v3.DataSource{
   129  					Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
   130  						InlineBytes: secret.Data[caCrtAttribute],
   131  					},
   132  				},
   133  				// TODO: Consider support for other ValidationContext config.
   134  			},
   135  		}
   136  	}
   137  
   138  	return envoySecret
   139  }
   140  
   141  func getEnvoySecretName(namespace, name string) string {
   142  	return fmt.Sprintf("%s/%s", namespace, name)
   143  }