docs/documentation/platform/pki/guides/applications/k8s-cert-manager.mdx
Issue TLS certificates to your Kubernetes workloads using cert-manager and Infisical. Certificates are requested via ACME, stored in Kubernetes Secrets, and renewed automatically before expiration.
<Info> This guide assumes you have an Application with [ACME enrollment](/documentation/platform/pki/applications/enrollment-methods/acme) configured. </Info>Issuer that connects to Infisical's ACME serverCertificate resources that define what certificates you needFor complete details, see the official cert-manager documentation and ACME configuration.
- **ACME Directory URL**
- **EAB Key ID (KID)**
- **EAB Secret**
If you haven't configured ACME enrollment yet, follow the [ACME enrollment guide](/documentation/platform/pki/applications/enrollment-methods/acme).
<Note>
Currently, ACME enrollment uses dedicated EAB credentials. Support for [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) is planned.
</Note>
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
```
<Tabs>
<Tab title="kubectl command">
```bash
kubectl create secret generic infisical-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: infisical-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>
```yaml issuer-infisical.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: issuer-infisical
namespace: <namespace_you_want_to_issue_certificates_in>
spec:
acme:
# ACME server URL from your Infisical Application's ACME enrollment (Step 1)
server: <acme_server_url>
# Email address for ACME account
# (any valid email works; currently ignored by Infisical)
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: infisical-acme-eab-secret
key: eabSecret
privateKeySecretRef:
name: issuer-infisical-account-key
solvers:
- http01:
ingress:
# Replace with your actual ingress class if different
className: nginx
```
```
kubectl apply -f issuer-infisical.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-infisical 21h
```
<Note>
- Currently, the [ACME enrollment method](/documentation/platform/pki/applications/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 enrollment 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>
Finally, request a certificate from Infisical 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-infisical
```
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-infisical` 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 Infisical 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-infisical Certificate is up to date and has not expired 20h
```
```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-infisical
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.
If your application requires ca.crt (e.g., for mTLS), use trust-manager to inject it automatically.
1. Install trust-manager
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
2. Create a CA certificate secret
Download the CA certificate chain from Certificate Manager → Settings → Certificate Authorities (select your CA → Download CA Certificate Chain), then create:
apiVersion: v1
kind: Secret
metadata:
name: infisical-ca-cert
namespace: cert-manager
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
<paste_the_downloaded_certificate_chain_here>
-----END CERTIFICATE-----
3. Create a trust-manager Bundle
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: certificate-by-issuer
spec:
sources:
- secret:
name: infisical-ca-cert
key: ca.crt
target:
secret:
key: ca.crt
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
The Bundle metadata.name must match your Certificate's secretName. Update namespaceSelector to target your namespace(s).
4. Verify
kubectl get secret certificate-by-issuer -o yaml
You should now see ca.crt, tls.crt, and tls.key in the secret data.
<Note>
Currently, not all fields are supported by the Infisical PKI ACME server.
</Note>
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/).
You can read more about the `renewBefore` field [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).