pkg/controller/common/certificates/secret.go (153 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package certificates import ( "context" "errors" "fmt" pkgerrors "github.com/pkg/errors" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" commonv1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/name" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/volume" "github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s" ) const ( // CAFileName is used for the CA Certificates inside a secret. CAFileName = "ca.crt" // CAKeyFileName is used for the CA certificate's private key inside a secret. CAKeyFileName = "ca.key" // CertFileName is used for Certificates inside a secret. CertFileName = "tls.crt" // KeyFileName is used for Private Keys inside a secret. KeyFileName = "tls.key" // certificate secrets suffixes certsPublicSecretName = "certs-public" certsInternalSecretName = "certs-internal" // http certs volume HTTPCertificatesSecretVolumeName = "elastic-internal-http-certificates" HTTPCertificatesSecretVolumeMountPath = "/mnt/elastic-internal/http-certs" //nolint:gosec ) func InternalCertsSecretName(namer name.Namer, ownerName string) string { return namer.Suffix(ownerName, string(HTTPCAType), certsInternalSecretName) } func PublicCertsSecretName(namer name.Namer, ownerName string) string { return namer.Suffix(ownerName, string(HTTPCAType), certsPublicSecretName) } func PublicTransportCertsSecretName(namer name.Namer, ownerName string) string { return namer.Suffix(ownerName, string(TransportCAType), certsPublicSecretName) } // PublicCertsSecretRef returns the NamespacedName for the Secret containing the publicly available HTTP CA. func PublicCertsSecretRef(namer name.Namer, es types.NamespacedName) types.NamespacedName { return types.NamespacedName{ Name: PublicCertsSecretName(namer, es.Name), Namespace: es.Namespace, } } // HTTPCertSecretVolume returns a SecretVolume to hold the HTTP certs for the given resource. func HTTPCertSecretVolume(namer name.Namer, name string) volume.SecretVolume { return volume.NewSecretVolumeWithMountPath( InternalCertsSecretName(namer, name), HTTPCertificatesSecretVolumeName, HTTPCertificatesSecretVolumeMountPath, ) } type CertificatesSecret struct { //nolint:revive v1.Secret ca *CA } func NewCertificatesSecret(secret v1.Secret) (*CertificatesSecret, error) { result := CertificatesSecret{Secret: secret} if err := result.parse(); err != nil { return nil, err } return &result, nil } // HasCA returns true if this secret has a CA certificate. func (s *CertificatesSecret) HasCA() bool { if s == nil { return false } bytes, exists := s.Data[CAFileName] return exists && len(bytes) > 0 } // CAPem returns the certificate of the certificate authority. func (s *CertificatesSecret) CAPem() []byte { return s.Data[CAFileName] } // CA returns a pointer to the in-memory representation of the CA contained in the secret. func (s *CertificatesSecret) CA() *CA { return s.ca } // CertChain combines the certificate of the CA and the host certificate. func (s *CertificatesSecret) CertChain() []byte { return append(s.CertPem(), s.CAPem()...) } func (s *CertificatesSecret) CertPem() []byte { return s.Data[CertFileName] } func (s *CertificatesSecret) KeyPem() []byte { return s.Data[KeyFileName] } func (s *CertificatesSecret) HasCAPrivateKey() bool { if s == nil { return false } // the presence of the key means by implication that we have a full CA, validation ensures that the CA cert exists _, exists := s.Data[CAKeyFileName] return exists } func (s *CertificatesSecret) HasLeafCertificate() bool { return s != nil && !s.HasCAPrivateKey() } func (s *CertificatesSecret) parseCustomCA() error { // flag up user error when specifying both CA certificate with key and leaf certificate _, tlsKeyExists := s.Data[KeyFileName] _, tlsCertExists := s.Data[CertFileName] if tlsKeyExists || tlsCertExists { return fmt.Errorf("cannot specify %s or %s when %s is set in %s/%s", KeyFileName, CertFileName, CAKeyFileName, s.Namespace, s.Name) } ca, err := parseCAFromSecret(s.Secret, CAKeyFileName, CAFileName) if err == nil { // breaking the validation contract here by remembering the results to avoid parsing everything once more s.ca = ca } return err } // parse checks that mandatory fields are present. // It does not check that the public key matches the private key. func (s *CertificatesSecret) parse() error { if s.HasCAPrivateKey() { return s.parseCustomCA() } // Validate private key key, exist := s.Data[KeyFileName] if !exist { return pkgerrors.Errorf("can't find private key %s in %s/%s", KeyFileName, s.Namespace, s.Name) } _, err := ParsePEMPrivateKey(key) if err != nil && !errors.Is(err, ErrEncryptedPrivateKey) { return err } // Validate host certificate cert, exist := s.Data[CertFileName] if !exist { return pkgerrors.Errorf("can't find certificate %s in %s/%s", CertFileName, s.Namespace, s.Name) } _, err = ParsePEMCerts(cert) if err != nil { return err } // Eventually validate CA certificate ca, exist := s.Data[CAFileName] if !exist { return nil } _, err = ParsePEMCerts(ca) if err != nil { return err } return nil } func GetSecretFromRef(c k8s.Client, owner types.NamespacedName, secretRef commonv1.SecretRef) (*v1.Secret, error) { secretName := secretRef.SecretName if secretName == "" { return nil, nil } var secret v1.Secret if err := c.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: owner.Namespace}, &secret); err != nil { return nil, err } return &secret, nil } // validCustomCertificatesOrNil returns the custom certificates to use or nil if there is none specified func validCustomCertificatesOrNil( c k8s.Client, owner types.NamespacedName, tls commonv1.TLSOptions, ) (*CertificatesSecret, error) { secret, err := GetSecretFromRef(c, owner, tls.Certificate) if err != nil || secret == nil { return nil, err } return NewCertificatesSecret(*secret) }