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
// }