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 }