Back to Infisical

Kubernetes cert-manager

docs/documentation/platform/pki/k8s-cert-manager.mdx

0.159.2517.2 KB
Original Source

Concept

This guide demonstrates how to use Infisical 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 Infisical 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 Infisical 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 Infisical via ACME consists of the following steps:

  1. Create a certificate profile in Infisical with the ACME enrollment method 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 Infisical certificate profile.
  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 Infisical.

<Steps> <Step title="Create a certificate profile with ACME as the enrollment method in Infisical">
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 Infisical 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 Infisical 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 Infisical ACME server.
<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>
</Step> <Step title="Create the cert-manager Issuer connecting to Infisical 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 Infisical PKI collection's ACME server for certificate issuance.
```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 certificate profile (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/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 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
```
</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-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.
</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). 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.

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 Infisical:

  1. Navigate to your project in Infisical
  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
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-----
bash
kubectl apply -f infisical-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
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
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-infisical
    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 Infisical 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>