package internal import ( "context" "crypto/x509" "fmt" acmapi "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" acmmeta "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/meta/v1" certs "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/controller/certificates" logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" utilpki "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/util/pki" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" coreclient "k8s.io/client-go/kubernetes/typed/core/v1" corelisters "k8s.io/client-go/listers/core/v1" ) var ( certificateGvk = acmapi.SchemeGroupVersion.WithKind("Certificate") ) // SecretsManager creates and updates secrets with certificate and key data. type SecretsManager struct { secretClient coreclient.SecretsGetter secretLister corelisters.SecretLister // fieldManager is the manager name used for the Apply operations on Secrets. fieldManager string // if true, Secret resources created by the controller will have an // 'owner reference' set, meaning when the Certificate is deleted, the // Secret resource will be automatically deleted. // This option is disabled by default. enableSecretOwnerReferences bool } // SecretData is a structure wrapping private key, Certificate and CA data type SecretData struct { PrivateKey, Certificate, CA []byte } // NewSecretsManager returns a new SecretsManager. Setting // enableSecretOwnerReferences to true will mean that secrets will be deleted // when the corresponding Certificate is deleted. func NewSecretsManager( secretClient coreclient.SecretsGetter, secretLister corelisters.SecretLister, fieldManager string, enableSecretOwnerReferences bool, ) *SecretsManager { return &SecretsManager{ secretClient: secretClient, secretLister: secretLister, fieldManager: fieldManager, enableSecretOwnerReferences: enableSecretOwnerReferences, } } // UpdateData will ensure the Secret resource contains the given secret data as // well as appropriate metadata using an Apply call. // If the Secret resource does not exist, it will be created on Apply. // UpdateData will also update deprecated annotations if they exist. func (s *SecretsManager) UpdateData(ctx context.Context, crt *acmapi.Certificate, data SecretData) error { secret, err := s.getCertificateSecret(ctx, crt) if err != nil { return err } log := logf.FromContext(ctx).WithName("secrets_manager") log = logf.WithResource(log, secret) if err := s.setValues(crt, secret, data); err != nil { return err } // Build Secret apply configuration and options. applyOpts := metav1.ApplyOptions{FieldManager: s.fieldManager, Force: true} applyCnf := applycorev1.Secret(secret.Name, secret.Namespace). WithAnnotations(secret.Annotations).WithLabels(secret.Labels). WithData(secret.Data).WithType(secret.Type) // If Secret owner reference is enabled, set it on the Secret. This results // in a no-op if the Secret already exists and has the owner reference set, // and visa-versa. if s.enableSecretOwnerReferences { ref := *metav1.NewControllerRef(crt, certificateGvk) applyCnf = applyCnf.WithOwnerReferences(&applymetav1.OwnerReferenceApplyConfiguration{ APIVersion: &ref.APIVersion, Kind: &ref.Kind, Name: &ref.Name, UID: &ref.UID, Controller: ref.Controller, BlockOwnerDeletion: ref.BlockOwnerDeletion, }) } log.V(logf.DebugLevel).Info("applying secret") _, err = s.secretClient.Secrets(secret.Namespace).Apply(ctx, applyCnf, applyOpts) if err != nil { return fmt.Errorf("failed to apply secret %s/%s: %w", secret.Namespace, secret.Name, err) } return nil } // setValues will update the Secret resource 'secret' with the data contained // in the given secretData. // It will update labels and annotations on the Secret resource appropriately. // The Secret resource 's' must be non-nil, although may be a resource that does // not exist in the Kubernetes apiserver yet. // setValues will NOT actually update the resource in the apiserver. // It will also update depreciated issuer name and kind annotations if they // exist. func (s *SecretsManager) setValues(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { if err := s.setKeystores(crt, secret, data); err != nil { return fmt.Errorf("failed to add keystores to Secret: %w", err) } // // Add additional output formats if feature enabled. // if utilfeature.DefaultFeatureGate.Enabled(feature.AdditionalCertificateOutputFormats) { // if err := setAdditionalOutputFormats(crt, secret, data); err != nil { // return fmt.Errorf("failed to add additional output formats to Secret: %w", err) // } // } secret.Data[corev1.TLSPrivateKeyKey] = data.PrivateKey secret.Data[corev1.TLSCertKey] = data.Certificate if len(data.CA) > 0 { secret.Data[acmmeta.TLSCAKey] = data.CA } var certificate *x509.Certificate if len(data.Certificate) > 0 { var err error certificate, err = utilpki.DecodeX509CertificateBytes(data.Certificate) // TODO: handle InvalidData here. if err != nil { return err } } secret.Annotations = certs.AnnotationsForCertificateSecret(crt, certificate) if secret.Labels == nil { secret.Labels = make(map[string]string) } if crt.Spec.SecretTemplate != nil { for k, v := range crt.Spec.SecretTemplate.Labels { secret.Labels[k] = v } for k, v := range crt.Spec.SecretTemplate.Annotations { secret.Annotations[k] = v } } return nil } // getCertificateSecret will return a secret which is ready for fields to be // applied. Only the Secret Type will be persisted from the original Secret. func (s *SecretsManager) getCertificateSecret(ctx context.Context, crt *acmapi.Certificate) (*corev1.Secret, error) { // Get existing secret if it exists. existingSecret, err := s.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) // If secret doesn't exist yet, return an empty secret that should be // created. if apierrors.IsNotFound(err) { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: crt.Spec.SecretName, Namespace: crt.Namespace, }, Data: make(map[string][]byte), Type: corev1.SecretTypeTLS, }, nil } // Transient error. if err != nil { return nil, err } // Only copy Secret Type to not take ownership of annotations or labels on // Apply. return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: crt.Spec.SecretName, Namespace: crt.Namespace, }, Data: make(map[string][]byte), // Use the existing Secret's type since this may not be of type // `kubernetes.io/tls`, if for example it was created beforehand. Type is // immutable, so we must keep it to its original value. Type: existingSecret.Type, }, nil } // setKeystores will set extra Secret Data keys according to any Keystores // which have been configured. func (s *SecretsManager) setKeystores(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { // Handle the experimental PKCS12 support if crt.Spec.Keystores != nil && crt.Spec.Keystores.PKCS12 != nil && crt.Spec.Keystores.PKCS12.Create { ref := crt.Spec.Keystores.PKCS12.PasswordSecretRef pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) if err != nil { return fmt.Errorf("fetching PKCS12 keystore password from Secret: %v", err) } if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { return fmt.Errorf("PKCS12 keystore password Secret contains no data for key %q", ref.Key) } pw := pwSecret.Data[ref.Key] keystoreData, err := encodePKCS12Keystore(string(pw), data.PrivateKey, data.Certificate, data.CA) if err != nil { return fmt.Errorf("error encoding PKCS12 bundle: %w", err) } // always overwrite the keystore entry for now secret.Data[pkcs12SecretKey] = keystoreData if len(data.CA) > 0 { truststoreData, err := encodePKCS12Truststore(string(pw), data.CA) if err != nil { return fmt.Errorf("error encoding PKCS12 trust store bundle: %w", err) } // always overwrite the truststore entry secret.Data[pkcs12TruststoreKey] = truststoreData } } // Handle the experimental JKS support if crt.Spec.Keystores != nil && crt.Spec.Keystores.JKS != nil && crt.Spec.Keystores.JKS.Create { ref := crt.Spec.Keystores.JKS.PasswordSecretRef pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) if err != nil { return fmt.Errorf("fetching JKS keystore password from Secret: %v", err) } if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { return fmt.Errorf("JKS keystore password Secret contains no data for key %q", ref.Key) } pw := pwSecret.Data[ref.Key] keystoreData, err := encodeJKSKeystore(pw, data.PrivateKey, data.Certificate, data.CA) if err != nil { return fmt.Errorf("error encoding JKS bundle: %w", err) } // always overwrite the keystore entry secret.Data[jksSecretKey] = keystoreData if len(data.CA) > 0 { truststoreData, err := encodeJKSTruststore(pw, data.CA) if err != nil { return fmt.Errorf("error encoding JKS trust store bundle: %w", err) } // always overwrite the keystore entry secret.Data[jksTruststoreKey] = truststoreData } } return nil } // // setAdditionalOutputFormat will set extra Secret Data keys with additional // // output formats according to any OutputFormats which have been configured. // func setAdditionalOutputFormats(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { // for _, format := range crt.Spec.AdditionalOutputFormats { // switch format.Type { // case acmapi.CertificateOutputFormatDER: // // Store binary format of the private key // secret.Data[cmapi.CertificateOutputFormatDERKey] = certificates.OutputFormatDER(data.PrivateKey) // case acmapi.CertificateOutputFormatCombinedPEM: // // Combine tls.key and tls.crt // secret.Data[cmapi.CertificateOutputFormatCombinedPEMKey] = certificates.OutputFormatCombinedPEM(data.PrivateKey, data.Certificate) // default: // return fmt.Errorf("unknown additional output format %s", format.Type) // } // } // return nil // }