Kubernetes cert-manager
Learn how to automatically provision and manage TLS certificates in Kubernetes using Hanzo KMS
Concept
This guide demonstrates how to use Hanzo KMS to issue TLS certificates back to your Kubernetes environment using cert-manager.
It uses the ACME issuer type to request and renew certificates automatically from Hanzo KMS using the ACME enrollment method configured on a certificate profile. The issuer is perfect at obtaining X.509 certificates for Ingresses and other Kubernetes resources and can automatically renew them before expiration.
The typical workflow involves installing cert-manager and configuring resources that represent the connection details to Hanzo KMS as well as the certificates you want to issue.
Each issued certificate and its corresponding private key are stored in a Kubernetes Secret.
We recommend reading the official cert-manager documentation for a complete overview. For the ACME-specific configuration, refer to the ACME section.
Workflow
A typical workflow for using cert-manager with Hanzo KMS via ACME consists of the following steps:
- Create a certificate profile in Hanzo KMS with the ACME enrollment method configured on it.
- Install
cert-managerin your Kubernetes cluster. - Create a Kubernetes
Secretcontaining the EAB (External Account Binding) credentials for the ACME certificate profile. - Create an
IssuerorClusterIssuerresource that connects to the desired Hanzo KMS certificate profile. - Create a
Certificateresource defining the certificate you wish to issue and the targetSecretwhere the certificate and private key will be stored. - Use the resulting Kubernetes
Secretin your Ingresses or other resources.
Guide
The following steps show how to install cert-manager (using kubectl) and obtain certificates from Hanzo KMS.
Follow the instructions here to create a certificate profile that uses ACME enrollment.
After completion, you will have the following values:
- ACME Directory URL
- EAB Key ID (KID)
- EAB Secret
These will be needed in later steps.
Currently, the Hanzo KMS ACME enrollment method only supports authentication via dedicated EAB credentials generated per certificate profile.
Support for Kubernetes Auth is planned for the near future.
Install cert-manager in your Kubernetes cluster by following the official guide here or by applying the manifest directly:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yamlCreate a Kubernetes Secret that contains the EAB Secret (HMAC key) obtained in step 1.
The cert-manager uses this secret to authenticate with the Hanzo KMS ACME server.
kubectl create secret generic kms-acme-eab-secret \
--namespace <namespace_you_want_to_issue_certificates_in> \
--from-literal=eabSecret=<eab_secret>apiVersion: v1
kind: Secret
metadata:
name: kms-acme-eab-secret
namespace: <namespace_you_want_to_issue_certificates_in>
data:
eabSecret: <eab_secret>kubectl apply -f acme-eab-secret.yamlNext, create a cert-manager Issuer (or ClusterIssuer) by replacing the placeholders <acme_server_url>, <your_email>, and <acme_eab_kid> in the configuration below and applying it.
This resource configures cert-manager to use your KMS PKI collection's ACME server for certificate issuance.
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: issuer-kms
namespace: <namespace_you_want_to_issue_certificates_in>
spec:
acme:
# ACME server URL from your Hanzo KMS certificate profile (Step 1)
server: <acme_server_url>
# Email address for ACME account
# (any valid email works; currently ignored by Hanzo KMS)
email: <your_email>
# Required to honor the duration field in Certificate resources
enableDurationFeature: true
externalAccountBinding:
# EAB Key ID from Step 1
keyID: <acme_eab_kid>
# Reference to the Kubernetes Secret containing the EAB
# HMAC key (created in Step 3)
keySecretRef:
name: kms-acme-eab-secret
key: eabSecret
privateKeySecretRef:
name: issuer-kms-account-key
solvers:
- http01:
ingress:
# Replace with your actual ingress class if different
className: nginxkubectl apply -f issuer-kms.yamlYou can check that the issuer was created successfully by running the following command:
kubectl get issuers.cert-manager.io -n <namespace_of_issuer> -o wideNAME AGE
issuer-kms 21h- Currently, the ACME enrollment method only supports the HTTP-01 challenge method. Support for the DNS-01 challenge method is planned for a future release. If domain ownership validation is not desired, you can disable it by enabling the Skip DNS ownership validation option in your ACME certificate profile configuration.
- An
Issueris namespace-scoped. Certificates can only be issued using anIssuerthat exists in the same namespace as theCertificateresource. - If you need to issue certificates across multiple namespaces with a single resource, create a
ClusterIssuerinstead. The configuration is identical exceptkind: ClusterIssuerand nometadata.namespace. - More details: https://cert-manager.io/docs/configuration/acme/
Finally, request a certificate from Hanzo KMS ACME server by creating a cert-manager Certificate resource.
This configuration file specifies the details of the (end-entity/leaf) certificate to be issued.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: certificate-by-issuer
namespace: <namespace_you_want_to_issue_certificates_in>
spec:
dnsNames:
- certificate-by-issuer.example.com
# name of the resulting Kubernetes Secret
secretName: certificate-by-issuer
# total validity period of the certificate
duration: 48h
# cert-manager will attempt renewal 12 hours before expiry
renewBefore: 12h
# set to true to issue a CA certificate (policy must allow/require CA)
isCA: false
privateKey:
algorithm: ECDSA
# uses NIST P-256 curve
size: 256
issuerRef:
name: issuer-kmsThe above sample configuration file specifies a certificate to be issued with the dns name certificate-by-issuer.example.com and ECDSA private key using the P-256 curve, valid for 48 hours; the certificate will be automatically renewed by cert-manager 12 hours before expiry.
The certificate is issued by the issuer issuer-kms created in the previous step and the resulting certificate and private key will be stored in a secret named certificate-by-issuer.
Note that the full list of the fields supported on the Certificate resource can be found in the API reference documentation here.
The enableDurationFeature: true flag in the Issuer configuration (Step 4) is required for cert-manager to honor the duration field. Without it, certificates default to 47 days regardless of what you specify. This flag is disabled by default in cert-manager because public ACME servers like Let's Encrypt don't support custom durations.
cert-manager does not currently support specifying a pathLen in the Certificate resource. When issuing CA certificates with isCA: true, ensure your Hanzo KMS certificate policy does not set a Maximum Allowed Path Length restriction, otherwise the request will fail validation.
You can check that the certificate was created successfully by running the following command:
kubectl get certificates -n <namespace_of_your_certificate> -o wideNAME READY SECRET ISSUER STATUS AGE
certificate-by-issuer True certificate-by-issuer issuer-kms Certificate is up to date and has not expired 20hSince the actual certificate and private key are stored in a Kubernetes secret, we can check that the secret was created successfully by running the following command:
kubectl get secret certificate-by-issuer -n <namespace_of_your_certificate>NAME TYPE DATA AGE
certificate-by-issuer kubernetes.io/tls 2 26hWe can describe the secret to get more information about it:
kubectl describe secret certificate-by-issuer -n defaultName: certificate-by-issuer
Namespace: default
Labels: controller.cert-manager.io/fao=true
Annotations: cert-manager.io/alt-names:
cert-manager.io/certificate-name: certificate-by-issuer
cert-manager.io/common-name:
cert-manager.io/alt-names: certificate-by-issuer.example.com
cert-manager.io/ip-sans:
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: issuer-kms
cert-manager.io/uri-sans:
Type: kubernetes.io/tls
Data
====
ca.crt: 1306 bytes
tls.crt: 2380 bytes
tls.key: 227 bytesHere, ca.crt is the Root CA certificate, tls.crt is the requested certificate followed by the certificate chain, and tls.key is the private key for the certificate.
We can decode the certificate and print it out using openssl:
kubectl get secret certificate-by-issuer -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -nooutIn any case, the certificate is ready to be used as Kubernetes Secret by your Kubernetes resources.
Injecting CA Certificate into Secrets with trust-manager
By default, cert-manager's ACME issuer does not populate the ca.crt field in the generated Kubernetes Secret (see GitHub issue). The secret will only contain tls.crt (the leaf certificate and chain) and tls.key (the private key).
If your application requires the CA certificate to be present in the secret (e.g., for mTLS or certificate verification), you can use trust-manager to automatically inject the ca.crt field.
Installing trust-manager
Install trust-manager with Secret targets enabled. This allows trust-manager to automatically inject the ca.crt field into secrets generated by cert-manager — whenever cert-manager creates or renews a certificate, trust-manager will ensure the CA certificate chain is included.
helm repo add jetstack https://charts.jetstack.io --force-update
helm upgrade trust-manager jetstack/trust-manager \
--install \
--namespace cert-manager \
--wait \
--set secretTargets.enabled=true \
--set secretTargets.authorizedSecretsAll=trueThe secretTargets.authorizedSecretsAll=true option grants trust-manager permission to write to all secrets in the cluster. For production environments, consider using secretTargets.authorizedSecrets to specify only the secrets that trust-manager should manage. Refer to the trust-manager Helm chart documentation for more details.
Creating the CA Certificate Secret
Create a Kubernetes Secret containing your CA certificate chain. To obtain the certificate chain from Hanzo KMS:
- Navigate to your project in Hanzo KMS
- Go to the Certificate Authorities tab
- Select the CA that is issuing your certificates
- Click the menu (three dots) next to the CA certificate and select Download CA Certificate Chain
This will download the full certificate chain from the selected CA up to the Root CA.
apiVersion: v1
kind: Secret
metadata:
name: kms-ca-cert
namespace: cert-manager
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
<paste_the_downloaded_certificate_chain_here>
-----END CERTIFICATE-----kubectl apply -f kms-ca-cert.yamlCreating the trust-manager Bundle
Create a trust-manager Bundle resource that copies the CA certificate from the source secret into your certificate secrets.
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: certificate-by-issuer
spec:
sources:
- secret:
name: kms-ca-cert
key: ca.crt
target:
secret:
key: ca.crt
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: defaultkubectl apply -f trust-bundle.yaml- The
metadata.nameof the Bundle must match thesecretNamespecified in yourCertificateresource. - Update the
namespaceSelectorto match the namespace(s) where your certificate secrets are located. - The Bundle is cluster-scoped and can target secrets across multiple namespaces using
namespaceSelector.
Verifying the CA Certificate Injection
After applying the Bundle, verify that the ca.crt field has been added to your certificate secret:
kubectl get secret certificate-by-issuer -o yamlYou should see output similar to:
apiVersion: v1
data:
ca.crt: <base64_encoded_ca_certificate>
tls.crt: <base64_encoded_certificate_chain>
tls.key: <base64_encoded_private_key>
kind: Secret
metadata:
annotations:
cert-manager.io/certificate-name: certificate-by-issuer
cert-manager.io/issuer-name: issuer-kms
trust.cert-manager.io/hash: <hash_value>
labels:
controller.cert-manager.io/fao: "true"
trust.cert-manager.io/bundle: certificate-by-issuer
name: certificate-by-issuer
namespace: default
type: OpaqueFAQ
The full list of the fields supported on the Certificate resource can be found in the API reference documentation here.
Currently, not all fields are supported by the KMS PKI ACME server.
Make sure your Issuer or ClusterIssuer has enableDurationFeature: true set under the acme block (see Step 4). Without this flag, cert-manager defaults to 47 days regardless of the duration field in your Certificate resource.
This flag is disabled by default in cert-manager because public ACME servers like Let's Encrypt don't support custom durations. For more details, see the cert-manager v1.1 release notes.
Yes. cert-manager will automatically renew certificates according to the renewBefore threshold of expiry as
specified in the corresponding Certificate resource.
You can read more about the renewBefore field here.
How is this guide?
Last updated on