github.com/cilium/cilium@v1.16.2/pkg/crypto/certificatemanager/certificate_manager.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package certificatemanager
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/cilium/hive/cell"
    13  	"github.com/spf13/pflag"
    14  
    15  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    16  	"github.com/cilium/cilium/pkg/policy/api"
    17  )
    18  
    19  var Cell = cell.Module(
    20  	"certificate-manager",
    21  	"Provides TLS certificates and secrets",
    22  
    23  	cell.Provide(NewManager),
    24  
    25  	cell.Config(defaultManagerConfig),
    26  )
    27  
    28  type CertificateManager interface {
    29  	GetTLSContext(ctx context.Context, tlsCtx *api.TLSContext, ns string) (ca, public, private string, err error)
    30  }
    31  
    32  type SecretManager interface {
    33  	GetSecrets(ctx context.Context, secret *api.Secret, ns string) (string, map[string][]byte, error)
    34  	GetSecretString(ctx context.Context, secret *api.Secret, ns string) (string, error)
    35  }
    36  
    37  var defaultManagerConfig = managerConfig{
    38  	CertificatesDirectory: "/var/run/cilium/certs",
    39  }
    40  
    41  type managerConfig struct {
    42  	// CertificatesDirectory is the root directory to be used by cilium to find
    43  	// certificates locally.
    44  	CertificatesDirectory string
    45  }
    46  
    47  func (mc managerConfig) Flags(flags *pflag.FlagSet) {
    48  	flags.String("certificates-directory", mc.CertificatesDirectory, "Root directory to find certificates specified in L7 TLS policy enforcement")
    49  }
    50  
    51  // Manager will manage the way certificates are retrieved based in the given
    52  // k8sClient and rootPath.
    53  type manager struct {
    54  	rootPath  string
    55  	k8sClient k8sClient.Clientset
    56  }
    57  
    58  // NewManager returns a new manager.
    59  func NewManager(cfg managerConfig, clientset k8sClient.Clientset) (CertificateManager, SecretManager) {
    60  	m := &manager{
    61  		rootPath:  cfg.CertificatesDirectory,
    62  		k8sClient: clientset,
    63  	}
    64  
    65  	return m, m
    66  }
    67  
    68  // GetSecrets returns either local or k8s secrets, giving precedence for local secrets if configured.
    69  // The 'ns' parameter is used as the secret namespace if 'secret.Namespace' is an empty string.
    70  func (m *manager) GetSecrets(ctx context.Context, secret *api.Secret, ns string) (string, map[string][]byte, error) {
    71  	if secret == nil {
    72  		return "", nil, fmt.Errorf("Secret must not be nil")
    73  	}
    74  
    75  	if secret.Namespace != "" {
    76  		ns = secret.Namespace
    77  	}
    78  
    79  	if secret.Name == "" {
    80  		return ns, nil, fmt.Errorf("Missing Secret name")
    81  	}
    82  	nsName := filepath.Join(ns, secret.Name)
    83  
    84  	// Give priority to local secrets.
    85  	// K8s API request is only done if the local secret directory can't be read!
    86  	certPath := filepath.Join(m.rootPath, nsName)
    87  	files, ioErr := os.ReadDir(certPath)
    88  	if ioErr == nil {
    89  		secrets := make(map[string][]byte, len(files))
    90  		for _, file := range files {
    91  			var bytes []byte
    92  
    93  			path := filepath.Join(certPath, file.Name())
    94  			bytes, ioErr = os.ReadFile(path)
    95  			if ioErr == nil {
    96  				secrets[file.Name()] = bytes
    97  			}
    98  		}
    99  		// Return the (latest) error only if no secrets were found
   100  		if len(secrets) == 0 && ioErr != nil {
   101  			return nsName, nil, ioErr
   102  		}
   103  		return nsName, secrets, nil
   104  	}
   105  	secrets, err := m.k8sClient.GetSecrets(ctx, ns, secret.Name)
   106  	return nsName, secrets, err
   107  }
   108  
   109  const (
   110  	caDefaultName      = "ca.crt"
   111  	publicDefaultName  = "tls.crt"
   112  	privateDefaultName = "tls.key"
   113  )
   114  
   115  // GetTLSContext returns a new ca, public and private certificates found based
   116  // in the given api.TLSContext.
   117  func (m *manager) GetTLSContext(ctx context.Context, tlsCtx *api.TLSContext, ns string) (ca, public, private string, err error) {
   118  	name, secrets, err := m.GetSecrets(ctx, tlsCtx.Secret, ns)
   119  	if err != nil {
   120  		return "", "", "", err
   121  	}
   122  
   123  	caName := caDefaultName
   124  	if tlsCtx.TrustedCA != "" {
   125  		caName = tlsCtx.TrustedCA
   126  	}
   127  	caBytes, ok := secrets[caName]
   128  	if ok {
   129  		ca = string(caBytes)
   130  	} else if tlsCtx.TrustedCA != "" {
   131  		return "", "", "", fmt.Errorf("Trusted CA %s not found in secret %s", caName, name)
   132  	}
   133  
   134  	publicName := publicDefaultName
   135  	if tlsCtx.Certificate != "" {
   136  		publicName = tlsCtx.Certificate
   137  	}
   138  	publicBytes, ok := secrets[publicName]
   139  	if ok {
   140  		public = string(publicBytes)
   141  	} else if tlsCtx.Certificate != "" {
   142  		return "", "", "", fmt.Errorf("Certificate %s not found in secret %s", publicName, name)
   143  	}
   144  
   145  	privateName := privateDefaultName
   146  	if tlsCtx.PrivateKey != "" {
   147  		privateName = tlsCtx.PrivateKey
   148  	}
   149  	privateBytes, ok := secrets[privateName]
   150  	if ok {
   151  		private = string(privateBytes)
   152  	} else if tlsCtx.PrivateKey != "" {
   153  		return "", "", "", fmt.Errorf("Private Key %s not found in secret %s", privateName, name)
   154  	}
   155  
   156  	if caBytes == nil && publicBytes == nil && privateBytes == nil {
   157  		return "", "", "", fmt.Errorf("TLS certificates not found in secret %s ", name)
   158  	}
   159  
   160  	return ca, public, private, nil
   161  }
   162  
   163  // GetSecretString returns a secret string stored in a k8s secret
   164  func (m *manager) GetSecretString(ctx context.Context, secret *api.Secret, ns string) (string, error) {
   165  	name, secrets, err := m.GetSecrets(ctx, secret, ns)
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  
   170  	if len(secrets) == 1 {
   171  		// get the lone item by looping into the map
   172  		for _, value := range secrets {
   173  			return string(value), nil
   174  		}
   175  	}
   176  	return "", fmt.Errorf("Secret %s must have exactly one item", name)
   177  }