| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- /*
- 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 providercerts manages TLS certificates for external secrets providers.
- package providercerts
- import (
- "context"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/pem"
- "fmt"
- "math/big"
- "slices"
- "time"
- corev1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- )
- const (
- caCertName = "ca.crt"
- caKeyName = "ca.key"
- certName = "tls.crt"
- keyName = "tls.key"
- clientCertName = "client.crt"
- clientKeyName = "client.key"
- // we're using a fixed secret name for the provider certificates
- // otherwise we would need to introduce a lot of complexity to the core controller
- // because we would have to maintain a mapping of provider names (aws) to provider kinds (parameterstore, generators etc.)
- providerSecretName = "external-secrets-provider-tls"
- // certValidityDuration is the validity period for generated certificates (10 years).
- certValidityDuration = 87600 * time.Hour
- errGeneratePrivateKey = "failed to generate private key: %w"
- )
- // ProviderCertConfig defines configuration for a single provider's certificates.
- type ProviderCertConfig struct {
- Namespace string // Namespace where provider is deployed
- ServiceNames []string // Service names for DNS SANs
- }
- // ProviderCertificates holds the generated certificates for a provider.
- type ProviderCertificates struct {
- ServerCert []byte
- ServerKey []byte
- ClientCert []byte
- ClientKey []byte
- }
- // KeyPairArtifacts holds a certificate and private key.
- type KeyPairArtifacts struct {
- Cert *x509.Certificate
- Key *rsa.PrivateKey
- CertPEM []byte
- KeyPEM []byte
- }
- // ReconcileProviderCert ensures provider certificates exist and are valid.
- func (r *ProviderCertReconciler) ReconcileProviderCert(ctx context.Context, config *ProviderCertConfig) error {
- if config == nil {
- return nil
- }
- log := r.Log.WithValues("secret", providerSecretName, "namespace", config.Namespace)
- // Get or create the secret
- secretName := types.NamespacedName{
- Name: providerSecretName,
- Namespace: config.Namespace,
- }
- var secret corev1.Secret
- err := r.Get(ctx, secretName, &secret)
- if err != nil {
- if !apierrors.IsNotFound(err) {
- return fmt.Errorf("failed to get provider secret: %w", err)
- }
- // Secret doesn't exist, create it
- log.Info("creating provider certificate secret")
- secret = corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: providerSecretName,
- Namespace: config.Namespace,
- Labels: map[string]string{
- "app.kubernetes.io/managed-by": "external-secrets",
- "app.kubernetes.io/component": "provider-certificates",
- },
- },
- Type: corev1.SecretTypeTLS,
- }
- }
- // Check if certificates need refresh
- needRefresh := r.needProviderCertRefresh(&secret, config)
- if needRefresh {
- log.Info("refreshing provider certificates")
- if err := r.refreshProviderCerts(&secret, config); err != nil {
- return fmt.Errorf("failed to refresh provider certificates: %w", err)
- }
- // Create or update the secret
- if secret.UID == "" {
- if err := r.Create(ctx, &secret); err != nil {
- return fmt.Errorf("failed to create provider secret: %w", err)
- }
- log.Info("created provider certificate secret")
- } else {
- if err := r.Update(ctx, &secret); err != nil {
- return fmt.Errorf("failed to update provider secret: %w", err)
- }
- log.Info("updated provider certificate secret")
- }
- }
- return nil
- }
- // needProviderCertRefresh checks if provider certificates need to be refreshed.
- func (r *ProviderCertReconciler) needProviderCertRefresh(secret *corev1.Secret, config *ProviderCertConfig) bool {
- // If secret has no data, we need to generate certificates
- if secret.Data == nil {
- return true
- }
- // Check if all required keys exist
- requiredKeys := []string{caCertName, caKeyName, certName, keyName, clientCertName, clientKeyName}
- for _, key := range requiredKeys {
- if _, ok := secret.Data[key]; !ok {
- return true
- }
- }
- // Validate CA certificate
- if !r.validCACert(secret.Data[caCertName], secret.Data[caKeyName]) {
- return true
- }
- // Validate server certificate
- dnsNames := r.getProviderDNSNames(config)
- if !r.validProviderCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName], dnsNames) {
- return true
- }
- // Validate client certificate
- if !r.validProviderClientCert(secret.Data[caCertName], secret.Data[clientCertName], secret.Data[clientKeyName]) {
- return true
- }
- return false
- }
- // refreshProviderCerts generates new certificates for the provider.
- func (r *ProviderCertReconciler) refreshProviderCerts(secret *corev1.Secret, config *ProviderCertConfig) error {
- now := time.Now()
- begin := now.Add(-1 * time.Hour)
- end := now.Add(certValidityDuration)
- // Check if we need to generate a new CA or reuse existing
- var caArtifacts *KeyPairArtifacts
- var err error
- if secret.Data != nil && r.validCACert(secret.Data[caCertName], secret.Data[caKeyName]) {
- // Reuse existing CA
- caArtifacts, err = buildArtifactsFromSecret(secret)
- if err != nil {
- return fmt.Errorf("failed to load existing CA: %w", err)
- }
- } else {
- // Generate new CA
- caArtifacts, err = r.createCACert(begin, end)
- if err != nil {
- return fmt.Errorf("failed to create CA certificate: %w", err)
- }
- }
- // Generate server certificate
- serverCert, serverKey, err := r.createProviderServerCert(caArtifacts, config, begin, end)
- if err != nil {
- return fmt.Errorf("failed to create server certificate: %w", err)
- }
- // Generate client certificate
- clientCert, clientKey, err := r.createProviderClientCert(caArtifacts, begin, end)
- if err != nil {
- return fmt.Errorf("failed to create client certificate: %w", err)
- }
- // Populate secret
- if secret.Data == nil {
- secret.Data = make(map[string][]byte)
- }
- secret.Data[caCertName] = caArtifacts.CertPEM
- secret.Data[caKeyName] = caArtifacts.KeyPEM
- secret.Data[certName] = serverCert
- secret.Data[keyName] = serverKey
- secret.Data[clientCertName] = clientCert
- secret.Data[clientKeyName] = clientKey
- return nil
- }
- // createCACert creates a new CA certificate.
- func (r *ProviderCertReconciler) createCACert(begin, end time.Time) (*KeyPairArtifacts, error) {
- template := &x509.Certificate{
- SerialNumber: big.NewInt(0),
- Subject: pkix.Name{
- CommonName: r.CAName,
- Organization: []string{r.CAOrganization},
- },
- DNSNames: []string{r.CAName},
- NotBefore: begin,
- NotAfter: end,
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
- BasicConstraintsValid: true,
- IsCA: true,
- }
- key, err := rsa.GenerateKey(rand.Reader, 2048)
- if err != nil {
- return nil, fmt.Errorf(errGeneratePrivateKey, err)
- }
- certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
- if err != nil {
- return nil, fmt.Errorf("failed to create certificate: %w", err)
- }
- cert, err := x509.ParseCertificate(certDER)
- if err != nil {
- return nil, fmt.Errorf("failed to parse certificate: %w", err)
- }
- certPEM := pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: certDER,
- })
- keyPEM := pem.EncodeToMemory(&pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: x509.MarshalPKCS1PrivateKey(key),
- })
- return &KeyPairArtifacts{
- Cert: cert,
- Key: key,
- CertPEM: certPEM,
- KeyPEM: keyPEM,
- }, nil
- }
- // createProviderServerCert generates a server certificate for the provider.
- func (r *ProviderCertReconciler) createProviderServerCert(
- caArtifacts *KeyPairArtifacts,
- config *ProviderCertConfig,
- begin, end time.Time,
- ) ([]byte, []byte, error) {
- dnsNames := r.getProviderDNSNames(config)
- // Create certificate template
- template := &x509.Certificate{
- SerialNumber: big.NewInt(time.Now().UnixNano()),
- Subject: pkix.Name{
- CommonName: "external-secrets-provider",
- Organization: []string{r.CAOrganization},
- },
- DNSNames: dnsNames,
- NotBefore: begin,
- NotAfter: end,
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- }
- // Generate private key
- key, err := rsa.GenerateKey(rand.Reader, 2048)
- if err != nil {
- return nil, nil, fmt.Errorf(errGeneratePrivateKey, err)
- }
- // Create certificate
- certDER, err := x509.CreateCertificate(rand.Reader, template, caArtifacts.Cert, &key.PublicKey, caArtifacts.Key)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
- }
- // Encode certificate
- certPEM := pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: certDER,
- })
- // Encode private key
- keyPEM := pem.EncodeToMemory(&pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: x509.MarshalPKCS1PrivateKey(key),
- })
- return certPEM, keyPEM, nil
- }
- // createProviderClientCert generates a client certificate for the ESO controller.
- func (r *ProviderCertReconciler) createProviderClientCert(
- caArtifacts *KeyPairArtifacts,
- begin, end time.Time,
- ) ([]byte, []byte, error) {
- // Create certificate template
- template := &x509.Certificate{
- SerialNumber: big.NewInt(time.Now().UnixNano()),
- Subject: pkix.Name{
- CommonName: "external-secrets-controller",
- Organization: []string{r.CAOrganization},
- },
- NotBefore: begin,
- NotAfter: end,
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
- }
- // Generate private key
- key, err := rsa.GenerateKey(rand.Reader, 2048)
- if err != nil {
- return nil, nil, fmt.Errorf(errGeneratePrivateKey, err)
- }
- // Create certificate
- certDER, err := x509.CreateCertificate(rand.Reader, template, caArtifacts.Cert, &key.PublicKey, caArtifacts.Key)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
- }
- // Encode certificate
- certPEM := pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: certDER,
- })
- // Encode private key
- keyPEM := pem.EncodeToMemory(&pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: x509.MarshalPKCS1PrivateKey(key),
- })
- return certPEM, keyPEM, nil
- }
- // getProviderDNSNames returns the DNS names for the provider service.
- func (r *ProviderCertReconciler) getProviderDNSNames(config *ProviderCertConfig) []string {
- dnsNames := make([]string, 0, len(config.ServiceNames)*4)
- for _, serviceName := range config.ServiceNames {
- dnsNames = append(dnsNames,
- serviceName,
- fmt.Sprintf("%s.%s", serviceName, config.Namespace),
- fmt.Sprintf("%s.%s.svc", serviceName, config.Namespace),
- fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, config.Namespace),
- )
- }
- return dnsNames
- }
- // validCACert validates a CA certificate.
- func (r *ProviderCertReconciler) validCACert(caCert, caKey []byte) bool {
- if len(caCert) == 0 || len(caKey) == 0 {
- return false
- }
- // Parse CA certificate
- caDer, _ := pem.Decode(caCert)
- if caDer == nil {
- return false
- }
- caCertParsed, err := x509.ParseCertificate(caDer.Bytes)
- if err != nil {
- return false
- }
- // Check if CA is still valid with lookahead
- if time.Now().After(lookaheadTime()) && lookaheadTime().After(caCertParsed.NotAfter) {
- return false
- }
- return true
- }
- // validProviderCert validates a provider server certificate.
- func (r *ProviderCertReconciler) validProviderCert(caCert, cert, key []byte, dnsNames []string) bool {
- if len(caCert) == 0 || len(cert) == 0 || len(key) == 0 {
- return false
- }
- // Parse CA certificate
- pool := x509.NewCertPool()
- caDer, _ := pem.Decode(caCert)
- if caDer == nil {
- return false
- }
- caCertParsed, err := x509.ParseCertificate(caDer.Bytes)
- if err != nil {
- return false
- }
- pool.AddCert(caCertParsed)
- // Parse server certificate
- certDer, _ := pem.Decode(cert)
- if certDer == nil {
- return false
- }
- certParsed, err := x509.ParseCertificate(certDer.Bytes)
- if err != nil {
- return false
- }
- // Verify certificate is signed by CA
- opts := x509.VerifyOptions{
- Roots: pool,
- CurrentTime: lookaheadTime(),
- KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- }
- if _, err := certParsed.Verify(opts); err != nil {
- return false
- }
- // Check DNS names are present
- for _, dnsName := range dnsNames {
- if !slices.Contains(certParsed.DNSNames, dnsName) {
- return false
- }
- }
- return true
- }
- // validProviderClientCert validates a provider client certificate.
- func (r *ProviderCertReconciler) validProviderClientCert(caCert, cert, key []byte) bool {
- if len(caCert) == 0 || len(cert) == 0 || len(key) == 0 {
- return false
- }
- // Parse CA certificate
- pool := x509.NewCertPool()
- caDer, _ := pem.Decode(caCert)
- if caDer == nil {
- return false
- }
- caCertParsed, err := x509.ParseCertificate(caDer.Bytes)
- if err != nil {
- return false
- }
- pool.AddCert(caCertParsed)
- // Parse client certificate
- certDer, _ := pem.Decode(cert)
- if certDer == nil {
- return false
- }
- certParsed, err := x509.ParseCertificate(certDer.Bytes)
- if err != nil {
- return false
- }
- // Verify certificate is signed by CA
- opts := x509.VerifyOptions{
- Roots: pool,
- CurrentTime: lookaheadTime(),
- KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
- }
- if _, err := certParsed.Verify(opts); err != nil {
- return false
- }
- return true
- }
- // buildArtifactsFromSecret builds KeyPairArtifacts from a secret.
- func buildArtifactsFromSecret(secret *corev1.Secret) (*KeyPairArtifacts, error) {
- caCertPEM := secret.Data[caCertName]
- caKeyPEM := secret.Data[caKeyName]
- caDer, _ := pem.Decode(caCertPEM)
- if caDer == nil {
- return nil, fmt.Errorf("failed to decode CA certificate")
- }
- caCert, err := x509.ParseCertificate(caDer.Bytes)
- if err != nil {
- return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
- }
- keyDer, _ := pem.Decode(caKeyPEM)
- if keyDer == nil {
- return nil, fmt.Errorf("failed to decode CA key")
- }
- caKey, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes)
- if err != nil {
- return nil, fmt.Errorf("failed to parse CA key: %w", err)
- }
- return &KeyPairArtifacts{
- Cert: caCert,
- Key: caKey,
- CertPEM: caCertPEM,
- KeyPEM: caKeyPEM,
- }, nil
- }
- // lookaheadTime returns the current time plus a lookahead duration
- // to ensure certificates are refreshed before expiration.
- func lookaheadTime() time.Time {
- return time.Now().Add(365 * 24 * time.Hour) // 1 year lookahead
- }
|