mirror of
https://github.com/hanzoai/kms.git
synced 2026-10-30 14:57:36 +00:00
- Fix 381 files where branding script corrupted variable names (e.g. isHanzo KMSTemplate -> isKmsTemplate) - Rebrand from "Hanzo KMS" to just "KMS" for white-label support (allows Lux KMS, Hanzo KMS, etc. via configuration) - 856 files, purely string replacements, no logic changes
402 lines
17 KiB
Plaintext
402 lines
17 KiB
Plaintext
---
|
|
title: "Kubernetes cert-manager"
|
|
description: "Learn how to automatically provision and manage TLS certificates in Kubernetes using KMS"
|
|
---
|
|
|
|
## Concept
|
|
|
|
This guide demonstrates how to use KMS to issue TLS certificates back to your Kubernetes environment using [cert-manager](https://cert-manager.io/).
|
|
|
|
It uses the [ACME issuer type](https://cert-manager.io/docs/configuration/acme/) to request and renew certificates automatically from KMS
|
|
using the [ACME enrollment method](/documentation/platform/pki/enrollment-methods/acme) configured on a [certificate profile](/documentation/platform/pki/certificates/profiles). 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 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](https://cert-manager.io/docs/) for a complete overview.
|
|
For the ACME-specific configuration, refer to the [ACME section](https://cert-manager.io/docs/configuration/acme/).
|
|
|
|
## Workflow
|
|
|
|
A typical workflow for using cert-manager with KMS via ACME consists of the following steps:
|
|
|
|
1. Create a [certificate profile](/documentation/platform/pki/certificates/profiles) in KMS with the [ACME enrollment method](/documentation/platform/pki/enrollment-methods/acme) configured on it.
|
|
2. Install `cert-manager` in your Kubernetes cluster.
|
|
3. Create a Kubernetes `Secret` containing the EAB (External Account Binding) credentials for the ACME certificate profile.
|
|
4. Create an `Issuer` or `ClusterIssuer` resource that connects to the desired KMS [certificate profile](/documentation/platform/pki/certificates/profiles).
|
|
5. Create a `Certificate` resource defining the certificate you wish to issue and the target `Secret` where the certificate and private key will be stored.
|
|
6. Use the resulting Kubernetes `Secret` in your Ingresses or other resources.
|
|
|
|
## Guide
|
|
|
|
The following steps show how to install cert-manager (using `kubectl`) and obtain certificates from KMS.
|
|
|
|
<Steps>
|
|
<Step title="Create a certificate profile with ACME as the enrollment method in KMS">
|
|
|
|
Follow the instructions [here](/documentation/platform/pki/enrollment-methods/acme) 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.
|
|
|
|
<Note>
|
|
Currently, the KMS ACME enrollment method only supports authentication via dedicated EAB credentials generated per certificate profile.
|
|
|
|
Support for [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) is planned for the near future.
|
|
</Note>
|
|
|
|
</Step>
|
|
<Step title="Install cert-manager">
|
|
|
|
Install cert-manager in your Kubernetes cluster by following the official guide [here](https://cert-manager.io/docs/installation/) or by applying the manifest directly:
|
|
|
|
```bash
|
|
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml
|
|
```
|
|
|
|
</Step>
|
|
<Step title="Create a Kubernetes Secret for the KMS ACME EAB credentials">
|
|
Create a Kubernetes `Secret` that contains the **EAB Secret (HMAC key)** obtained in step 1.
|
|
The cert-manager uses this secret to authenticate with the KMS ACME server.
|
|
|
|
<Tabs>
|
|
<Tab title="kubectl command">
|
|
```bash
|
|
kubectl create secret generic kms-acme-eab-secret \
|
|
--namespace <namespace_you_want_to_issue_certificates_in> \
|
|
--from-literal=eabSecret=<eab_secret>
|
|
```
|
|
</Tab>
|
|
<Tab title="Configuration file">
|
|
```yaml acme-eab-secret.yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: kms-acme-eab-secret
|
|
namespace: <namespace_you_want_to_issue_certificates_in>
|
|
data:
|
|
eabSecret: <eab_secret>
|
|
```
|
|
|
|
```bash
|
|
kubectl apply -f acme-eab-secret.yaml
|
|
```
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
</Step>
|
|
<Step title="Create the cert-manager Issuer connecting to KMS ACME server">
|
|
Next, 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.
|
|
|
|
```yaml issuer-kms.yaml
|
|
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 KMS certificate profile (Step 1)
|
|
server: <acme_server_url>
|
|
# Email address for ACME account
|
|
# (any valid email works; currently ignored by 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: nginx
|
|
```
|
|
|
|
```
|
|
kubectl apply -f issuer-kms.yaml
|
|
```
|
|
|
|
You can check that the issuer was created successfully by running the following command:
|
|
|
|
```bash
|
|
kubectl get issuers.cert-manager.io -n <namespace_of_issuer> -o wide
|
|
```
|
|
|
|
```bash
|
|
NAME AGE
|
|
issuer-kms 21h
|
|
```
|
|
|
|
<Note>
|
|
- Currently, the [ACME enrollment method](/documentation/platform/pki/enrollment-methods/acme) only supports the [HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge) method. Support for the [DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#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 `Issuer` is namespace-scoped. Certificates can only be issued using an `Issuer` that exists in the same namespace as the `Certificate` resource.
|
|
- If you need to issue certificates across multiple namespaces with a single resource, create a `ClusterIssuer` instead. The configuration is identical except `kind: ClusterIssuer` and no `metadata.namespace`.
|
|
- More details: https://cert-manager.io/docs/configuration/acme/
|
|
</Note>
|
|
|
|
</Step>
|
|
<Step title="Create the Certificate">
|
|
|
|
Finally, request a certificate from 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.
|
|
|
|
```yaml certificate-issuer.yaml
|
|
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-kms
|
|
```
|
|
|
|
The 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](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
|
|
|
<Note>
|
|
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.
|
|
</Note>
|
|
|
|
<Info>
|
|
cert-manager does not currently support specifying a `pathLen` in the Certificate resource. When issuing CA certificates with `isCA: true`, ensure your KMS certificate policy does not set a **Maximum Allowed Path Length** restriction, otherwise the request will fail validation.
|
|
</Info>
|
|
|
|
You can check that the certificate was created successfully by running the following command:
|
|
|
|
```bash
|
|
kubectl get certificates -n <namespace_of_your_certificate> -o wide
|
|
```
|
|
|
|
```bash
|
|
NAME READY SECRET ISSUER STATUS AGE
|
|
certificate-by-issuer True certificate-by-issuer issuer-kms Certificate is up to date and has not expired 20h
|
|
```
|
|
|
|
</Step>
|
|
<Step title="Use Certificate in Kubernetes Secret">
|
|
Since 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:
|
|
|
|
```bash
|
|
kubectl get secret certificate-by-issuer -n <namespace_of_your_certificate>
|
|
```
|
|
|
|
```bash
|
|
NAME TYPE DATA AGE
|
|
certificate-by-issuer kubernetes.io/tls 2 26h
|
|
```
|
|
|
|
We can `describe` the secret to get more information about it:
|
|
|
|
```bash
|
|
kubectl describe secret certificate-by-issuer -n default
|
|
```
|
|
|
|
```bash
|
|
Name: 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 bytes
|
|
```
|
|
|
|
Here, `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`:
|
|
|
|
```bash
|
|
kubectl get secret certificate-by-issuer -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
|
|
```
|
|
|
|
In any case, the certificate is ready to be used as Kubernetes Secret by your Kubernetes resources.
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## 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](https://github.com/cert-manager/cert-manager/issues/1571)). 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](https://cert-manager.io/docs/trust/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.
|
|
|
|
```bash
|
|
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=true
|
|
```
|
|
|
|
<Note>
|
|
The `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](https://cert-manager.io/docs/trust/trust-manager/installation/) for more details.
|
|
</Note>
|
|
|
|
### Creating the CA Certificate Secret
|
|
|
|
Create a Kubernetes Secret containing your CA certificate chain. To obtain the certificate chain from KMS:
|
|
|
|
1. Navigate to your project in KMS
|
|
2. Go to the **Certificate Authorities** tab
|
|
3. Select the CA that is issuing your certificates
|
|
4. 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.
|
|
|
|
```yaml kms-ca-cert.yaml
|
|
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-----
|
|
```
|
|
|
|
```bash
|
|
kubectl apply -f kms-ca-cert.yaml
|
|
```
|
|
|
|
### Creating the trust-manager Bundle
|
|
|
|
Create a trust-manager `Bundle` resource that copies the CA certificate from the source secret into your certificate secrets.
|
|
|
|
```yaml trust-bundle.yaml
|
|
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: default
|
|
```
|
|
|
|
```bash
|
|
kubectl apply -f trust-bundle.yaml
|
|
```
|
|
|
|
<Note>
|
|
- The `metadata.name` of the Bundle must match the `secretName` specified in your `Certificate` resource.
|
|
- Update the `namespaceSelector` to match the namespace(s) where your certificate secrets are located.
|
|
- The Bundle is cluster-scoped and can target secrets across multiple namespaces using `namespaceSelector`.
|
|
</Note>
|
|
|
|
### Verifying the CA Certificate Injection
|
|
|
|
After applying the Bundle, verify that the `ca.crt` field has been added to your certificate secret:
|
|
|
|
```bash
|
|
kubectl get secret certificate-by-issuer -o yaml
|
|
```
|
|
|
|
You should see output similar to:
|
|
|
|
```yaml
|
|
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: Opaque
|
|
```
|
|
|
|
## FAQ
|
|
|
|
<AccordionGroup>
|
|
<Accordion title="What fields can be configured on the Certificate resource?">
|
|
The full list of the fields supported on the `Certificate` resource can be found in the API reference documentation [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
|
|
|
<Note>
|
|
Currently, not all fields are supported by the KMS PKI ACME server.
|
|
</Note>
|
|
|
|
</Accordion>
|
|
<Accordion title="Why is my certificate duration different from what I specified?">
|
|
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](https://cert-manager.io/docs/releases/release-notes/release-notes-1.1/).
|
|
|
|
</Accordion>
|
|
<Accordion title="Can certificates be renewed automatically?">
|
|
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](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
|
|
|
</Accordion>
|
|
</AccordionGroup>
|