package controllers import ( "context" "crypto/x509" "encoding/base64" "errors" "fmt" "strings" "time" "github.com/cert-manager/cert-manager/pkg/util/pki" issuerapi "github.com/cert-manager/issuer-lib/api/v1alpha1" "github.com/cert-manager/issuer-lib/controllers" "github.com/cert-manager/issuer-lib/controllers/signer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" edgeissuerapi "edge-infra.dev/pkg/edge/edge-issuer/api/v1alpha1" ) var ( errSignerBuilder = errors.New("failed to build the signer") errSignerSign = errors.New("failed to sign") ) type Signer interface { Sign(*x509.Certificate) ([]byte, error) } type SignerBuilder func(keyPEM []byte, certPEM []byte, duration time.Duration) (Signer, error) type Issuer struct { SignerBuilder SignerBuilder CAPrivateKey []byte CACert []byte Expiration time.Time Config *Config client client.Client CACertRef string SecretManager secretManager } func (o Issuer) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { o.client = mgr.GetClient() return (&controllers.CombinedController{ IssuerTypes: []issuerapi.Issuer{&edgeissuerapi.EdgeIssuer{}}, ClusterIssuerTypes: []issuerapi.Issuer{&edgeissuerapi.EdgeClusterIssuer{}}, FieldOwner: "edgeissuer.edge-issuer.edge.ncr.com", MaxRetryDuration: 1 * time.Minute, Sign: o.Sign, Check: o.Check, EventRecorder: mgr.GetEventRecorderFor("edgeissuer.edge-issuer.edge.ncr.com"), }).SetupWithManager(ctx, mgr) } // Check checks that the CA it is available. Certificate requests will not be // processed until this check passes. func (o *Issuer) Check(ctx context.Context, _ issuerapi.Issuer) error { log := ctrl.Log.WithName("edge-issuer") log.Info("Checking if CA is available") if o.SecretManager == nil { o.SecretManager = &gcpSecretManager{} } // get the ca cert, ca private key, expiration, and version from SM given a banner's ID ca, key, expiration, version, err := o.getCAInfo(ctx, o.Config.BannerID) if err != nil { return err } o.CACert = []byte(ca) o.CAPrivateKey = []byte(key) o.Expiration = expiration log.Info("Successfully checked CA and stored CA info", "version", version) return nil } // Sign returns a signed certificate for the supplied CertificateRequestObject (a cert-manager CertificateRequest resource or // a kubernetes CertificateSigningRequest resource). The CertificateRequestObject contains a GetRequest method that returns // a certificate template that can be used as a starting point for the generated certificate. // The Sign method should return a PEMBundle containing the signed certificate and any intermediate certificates (see the PEMBundle docs for more information). // If the Sign method returns an error, the issuance will be retried until the MaxRetryDuration is reached. // Special errors and cases can be found in the issuer-lib README: https://github.com/cert-manager/issuer-lib/tree/main?tab=readme-ov-file#how-it-works func (o *Issuer) Sign(ctx context.Context, cr signer.CertificateRequestObject, _ issuerapi.Issuer) (signer.PEMBundle, error) { log := ctrl.Log.WithName("edge-issuer") // get active ca cert ref from DB caCertRef, _, _, err := o.getCAInfoFromDB(ctx, o.Config.BannerID) if err != nil { return signer.PEMBundle{}, fmt.Errorf("failed to get ca cert ref from database: %w", err) } // if cert is expired or caCertRef has changed from what we have stored, get the new CA info if o.Expiration.Before(time.Now()) || o.CACertRef != caCertRef { ca, key, expiration, version, err := o.getCAInfo(ctx, o.Config.BannerID) if err != nil { return signer.PEMBundle{}, err } o.CACert = []byte(ca) o.CAPrivateKey = []byte(key) o.Expiration = expiration log.Info("Successfully updated CA info to latest CA certificate", "version", version) } certTemplate, duration, _, err := cr.GetRequest() if err != nil { return signer.PEMBundle{}, err } signerObj, err := o.SignerBuilder(o.CAPrivateKey, o.CACert, duration) if err != nil { return signer.PEMBundle{}, fmt.Errorf("%w: %v", errSignerBuilder, err) } signed, err := signerObj.Sign(certTemplate) if err != nil { return signer.PEMBundle{}, fmt.Errorf("%w: %v", errSignerSign, err) } bundle, err := pki.ParseSingleCertificateChainPEM(signed) if err != nil { return signer.PEMBundle{}, err } return signer.PEMBundle(bundle), nil } // getCAInfo gets the CA, CA private key, CA expiration, and CA version from SecretManager. func (o *Issuer) getCAInfo(ctx context.Context, bannerID string) (string, string, time.Time, string, error) { var err error // get the certRef and privateKeyRef from the database certRef, privateKeyRef, expiration, err := o.getCAInfoFromDB(ctx, bannerID) if err != nil { return "", "", time.Time{}, "", fmt.Errorf("failed to get certRef, privateKeyRef, and expiration from db: %w", err) } smClient, err := o.SecretManager.NewWithOptions(ctx, o.Config.TopLevelProjectID) if err != nil { return "", "", time.Time{}, "", fmt.Errorf("failed to create secret manager client: %w", err) } // return the ca cert and private key given the certRef and privateKeyRef ca, caVersion, err := getValueFromDBRef(ctx, smClient, certRef) if err != nil { return "", "", time.Time{}, "", fmt.Errorf("failed to get ca cert from db ref: %w", err) } key, _, err := getValueFromDBRef(ctx, smClient, privateKeyRef) if err != nil { return "", "", time.Time{}, "", fmt.Errorf("failed to get private key from db ref: %w", err) } return ca, key, expiration, caVersion, nil } func (o *Issuer) getCAInfoFromDB(ctx context.Context, bannerID string) (string, string, time.Time, error) { var caPoolID, certRef, privateKeyRef string // get ca cert & ca private key reference from edge sql db err := o.Config.DB.QueryRowContext(ctx, "SELECT ca_pool_edge_id FROM ca_pools WHERE banner_edge_id = $1", bannerID).Scan(&caPoolID) if err != nil { return "", "", time.Time{}, err } // get ca cert & ca private key from SM using the reference var expiration time.Time err = o.Config.DB.QueryRowContext(ctx, "SELECT cert_ref, private_key_ref, expiration FROM ca_certificates WHERE ca_pool_edge_id = $1 AND status = 'active'", caPoolID).Scan(&certRef, &privateKeyRef, &expiration) if err != nil { return "", "", time.Time{}, err } return certRef, privateKeyRef, expiration, nil } // given a secretRef, returns the corresponding secret value and it's version from secret manager func getValueFromDBRef(ctx context.Context, smClient secretManagerClient, secretRef string) (string, string, error) { secretName, secretVersion, err := splitRef(secretRef) if err != nil { return "", "", fmt.Errorf("failed to split secretRef ref into secret name and version: %w", err) } secretValue, err := smClient.GetSecretVersionValue(ctx, secretName, secretVersion) if err != nil { return "", "", fmt.Errorf("failed to get latest secret value for key: %w", err) } decodedSecretValue, err := base64.StdEncoding.DecodeString(string(secretValue)) if err != nil { return "", "", fmt.Errorf("failed to decode secret value: %w", err) } return string(decodedSecretValue), secretVersion, nil } func splitRef(ref string) (string, string, error) { // Find the last hyphen in the string lastHyphenIndex := strings.LastIndex(ref, "-") if lastHyphenIndex == -1 { return "", "", fmt.Errorf("invalid certRef format: %s", ref) } // Split the string into two parts secretName := ref[:lastHyphenIndex] version := ref[lastHyphenIndex+1:] return secretName, version, nil }