> ## Documentation Index
> Fetch the complete documentation index at: https://infisical.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes with cert-manager (Infisical Issuer)

> Issue and renew Kubernetes TLS certificates with the Infisical cert-manager external issuer, authenticating with a machine identity.

Issue TLS certificates to your Kubernetes workloads using [cert-manager](https://cert-manager.io/) and the [Infisical Issuer](https://github.com/Infisical/infisical-issuer), an [external issuer](https://cert-manager.io/docs/configuration/external/) for cert-manager. Certificates are signed by Infisical through a [PKI Application](https://infisical.com/docs/documentation/platform/pki/applications/overview.md) and [Certificate Profile](https://infisical.com/docs/documentation/platform/pki/settings/profiles.md), stored in Kubernetes Secrets (including the CA chain), and renewed automatically before expiration.

> [!NOTE]
>
> This is the **Infisical Issuer** approach. If you would rather use the standard cert-manager ACME issuer with EAB credentials, see the [cert-manager (ACME) guide](https://infisical.com/docs/documentation/platform/pki/guides/applications/k8s-cert-manager-acme.md) instead.
>
> This guide assumes you have a PKI Application with [API enrollment](https://infisical.com/docs/documentation/platform/pki/applications/enrollment-methods/api) configured.

## When to use the Infisical Issuer instead of ACME

* You want to authenticate with a [machine identity](https://infisical.com/docs/documentation/platform/identities/machine-identities.md) ([Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth.md), [Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth.md) or [TLS Certificate Auth](https://infisical.com/docs/documentation/platform/identities/tls-cert-auth.md)) rather than ACME/EAB credentials.
* You want the **CA chain populated** in the Secret's `ca.crt` automatically (with the ACME issuer this isn't automatic and needs extra setup such as [trust-manager](https://cert-manager.io/docs/trust/trust-manager/)).
* You don't want ACME domain-ownership challenges (HTTP-01/DNS-01).
* You're issuing workload identity certificates (for example `spiffe://` URIs for service meshes).

## How it works

1. Install cert-manager and the Infisical Issuer controller in your Kubernetes cluster.
2. Create an `Issuer` (or `ClusterIssuer`) that authenticates to Infisical with a machine identity and points at your PKI Application and Profile.
3. Create `Certificate` resources that define the certificates you need.
4. cert-manager approves each request and the Infisical Issuer signs it through Infisical, storing the certificate, private key, and CA chain in a Secret.
5. Certificates are automatically renewed before expiration.

## Guide

Throughout this guide, replace `<namespace>` with the namespace where your workloads and certificates live. With a namespaced `Issuer`, its credentials `Secret`, the `Issuer`, and the `Certificate` all live in this namespace. To issue across namespaces from a single resource, use a [`ClusterIssuer`](#create-the-infisical-issuer) instead, whose `Secret` and service account live in the controller's namespace rather than alongside each workload.

**1. Set up your Certificate Management application and machine identity**

Before installing anything in Kubernetes, make sure you have the following in Infisical:

* A **PKI Application** with a **Certificate Profile** that uses **API enrollment**. Note the **Application name** (for example `web-services`) and the **Profile name** (for example `web-server`); you reference both by name when you create the `Issuer`.
* A **machine identity** added to the Application with the **operator** role, which lets it issue certificates.

Pick one authentication method for the identity:

* **Universal Auth**: use the identity's **Client ID** and **Client Secret**.
* **Kubernetes Auth**: the controller mints a short-lived token for a Kubernetes service account and presents it to Infisical. Configure it on the identity first, and note its **Identity ID**.
* **TLS Certificate Auth**: the controller logs in with a client certificate and presents it on every API call. Configure it on the identity first, and note its **Identity ID**. Enable **Bind Access Tokens to Client Certificate** on the identity to make a leaked token unusable without the certificate's private key.

**2. Install cert-manager**

Install cert-manager by following the official guide [here](https://cert-manager.io/docs/installation/) or by applying the manifest directly:

```bash theme={"dark"}
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml
```

**3. Install the Infisical Issuer**

Install the Infisical Issuer controller with Helm:

```bash theme={"dark"}
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm repo update
helm install infisical-pki-issuer infisical-helm-charts/infisical-pki-issuer \
  --namespace infisical-pki-issuer --create-namespace
```

This installs the controller and registers the `Issuer` and `ClusterIssuer` custom resources in the `infisical-issuer.infisical.com` API group. Confirm the controller is running:

```bash theme={"dark"}
kubectl get pods -n infisical-pki-issuer
```

**4. Allow cert-manager to approve Infisical Issuer requests**

cert-manager only signs a `CertificateRequest` once it has been **approved**. Its built-in approver approves requests for the standard cert-manager issuer types, but for an external issuer you must explicitly grant it permission to approve requests for the Infisical Issuer's signers. Apply the following once:

```yaml infisical-issuer-approver.yaml theme={"dark"}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: infisical-issuer-approver
rules:
  - apiGroups: ["cert-manager.io"]
    resources: ["signers"]
    verbs: ["approve"]
    resourceNames:
      - "issuers.infisical-issuer.infisical.com/*"
      - "clusterissuers.infisical-issuer.infisical.com/*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: infisical-issuer-approver
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: infisical-issuer-approver
subjects:
  - kind: ServiceAccount
    name: cert-manager
    namespace: cert-manager
```

```bash theme={"dark"}
kubectl apply -f infisical-issuer-approver.yaml
```

> [!NOTE]
>
> If you skip this step, `Certificate` resources stay pending because their `CertificateRequest` is never approved, so the Infisical Issuer never signs it. Adjust the `ServiceAccount` name and namespace if your cert-manager runs under different ones.

**5. Store your machine identity credentials in a Secret**

Create a Kubernetes `Secret` holding your machine identity credentials, in the namespace where you'll issue certificates.

> [tab] Universal Auth
>
> ```bash theme={"dark"}
> kubectl create secret generic infisical-credentials \
>   --namespace <namespace> \
>   --from-literal=clientId=<machine_identity_client_id> \
>   --from-literal=clientSecret=<machine_identity_client_secret>
> ```

> [tab] Kubernetes Auth
>
> Store the machine identity's **Identity ID** (Kubernetes Auth does not use a client secret):
>
> ```bash theme={"dark"}
> kubectl create secret generic infisical-identity \
>   --namespace <namespace> \
>   --from-literal=identityId=<machine_identity_id>
> ```
>
> The controller mints a short-lived token (via the Kubernetes `TokenRequest` API) for a service account you reference in the next step, then presents it to Infisical. Create that service account if it does not exist:
>
> ```bash theme={"dark"}
> kubectl create serviceaccount infisical-issuer-auth --namespace <namespace>
> ```
>
> The service account's name and namespace must be allowed by the identity's [Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth.md) configuration in Infisical (and the audience there must match), otherwise issuance fails authentication.

> [tab] TLS Certificate Auth
>
> Store the machine identity's **Identity ID** in a generic Secret, and the client certificate and private key in a `kubernetes.io/tls` Secret:
>
> ```bash theme={"dark"}
> kubectl create secret generic infisical-identity \
>   --namespace <namespace> \
>   --from-literal=identityId=<machine_identity_id>
>
> kubectl create secret tls infisical-client-cert \
>   --namespace <namespace> \
>   --cert=client.crt \
>   --key=client.key
> ```
>
> The certificate must be accepted by the identity's [TLS Certificate Auth](https://infisical.com/docs/documentation/platform/identities/tls-cert-auth.md) configuration in Infisical. `tls.crt` can also hold intermediates after the leaf.
>
> The certificate Secret can be managed by cert-manager itself. When the certificate in the Secret changes, the controller logs in again with the new certificate before its next API call.

**6. Create the Infisical Issuer**

Create an `Issuer` (or `ClusterIssuer`) referencing your Application and Profile by name, and the credentials Secret from the previous step.

> [tab] Universal Auth
>
> ```yaml issuer-infisical.yaml theme={"dark"}
> apiVersion: infisical-issuer.infisical.com/v1alpha1
> kind: Issuer
> metadata:
>   name: infisical-issuer
>   namespace: <namespace>
> spec:
>   # Base URL of your Infisical instance
>   # (https://app.infisical.com, https://eu.infisical.com, or your self-hosted URL)
>   url: https://app.infisical.com
>   # Name of the PKI Application to issue through
>   application: <application_name>
>   # Name of the Certificate Profile to issue with
>   profile: <profile_name>
>   authentication:
>     method: universal
>     universal:
>       clientIdRef:
>         name: infisical-credentials
>         namespace: <namespace>
>         key: clientId
>       clientSecretRef:
>         name: infisical-credentials
>         namespace: <namespace>
>         key: clientSecret
> ```

> [tab] Kubernetes Auth
>
> ```yaml issuer-infisical.yaml theme={"dark"}
> apiVersion: infisical-issuer.infisical.com/v1alpha1
> kind: Issuer
> metadata:
>   name: infisical-issuer
>   namespace: <namespace>
> spec:
>   url: https://app.infisical.com
>   application: <application_name>
>   profile: <profile_name>
>   authentication:
>     method: kubernetes
>     kubernetes:
>       # Secret holding the machine identity ID
>       identityIdRef:
>         name: infisical-identity
>         namespace: <namespace>
>         key: identityId
>       # Service account whose token is presented to Infisical
>       # (the one created in the previous step)
>       serviceAccountRef:
>         name: infisical-issuer-auth
>         namespace: <namespace>
> ```

> [tab] TLS Certificate Auth
>
> ```yaml issuer-infisical.yaml expandable theme={"dark"}
> apiVersion: infisical-issuer.infisical.com/v1alpha1
> kind: Issuer
> metadata:
>   name: infisical-issuer
>   namespace: <namespace>
> spec:
>   url: https://infisical.example.com
>   application: <application_name>
>   profile: <profile_name>
>   authentication:
>     method: tlsCert
>     tlsCert:
>       # Secret holding the machine identity ID
>       identityIdRef:
>         name: infisical-identity
>         namespace: <namespace>
>         key: identityId
>       # Client certificate and private key presented to Infisical
>       clientCertificateRef:
>         name: infisical-client-cert
>         namespace: <namespace>
>         key: tls.crt
>       clientKeyRef:
>         name: infisical-client-cert
>         namespace: <namespace>
>         key: tls.key
> ```
>
> `url` must use `https://`, since the client certificate is presented over TLS. On Infisical Cloud, set `url` to port `8443`, for example `https://app.infisical.com:8443`. On a self-hosted instance, the proxy in front of Infisical must forward the client certificate on every API request. See [Security requirements for self-hosted deployments](https://infisical.com/docs/documentation/platform/identities/tls-cert-auth.md#security-requirements-for-self-hosted-deployments).

```bash theme={"dark"}
kubectl apply -f issuer-infisical.yaml
```

Confirm the issuer is ready. The controller authenticates with Infisical and checks that the Application and Profile exist, then records the result on the issuer's `Ready` condition:

```bash theme={"dark"}
kubectl get issuers.infisical-issuer.infisical.com infisical-issuer \
  -n <namespace> \
  -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
```

```bash theme={"dark"}
True
```

If this is not `True`, run `kubectl describe issuers.infisical-issuer.infisical.com infisical-issuer -n <namespace>` and read the `Ready` condition message for the reason (for example a missing secret, or an unknown Application or Profile).

> [!NOTE]
>
> * An `Issuer` is namespace-scoped: certificates can only be issued from an `Issuer` in the same namespace as the `Certificate`. Its referenced Secrets (and service account) must live in that same namespace; cross-namespace references are rejected.
> * To issue across namespaces with one resource, create a `ClusterIssuer` instead. The spec is identical except `kind: ClusterIssuer` and no `metadata.namespace`. Because a `ClusterIssuer` has no namespace of its own, its referenced Secrets and service account must live in the controller's namespace (the one the issuer is installed into, configurable with the controller's `--cluster-resource-namespace` flag), and each reference's `namespace` must match it.
> * For a self-hosted Infisical that uses a private CA, add a `tls` block so the controller trusts it:
>   ```yaml theme={"dark"}
>   spec:
>     tls:
>       caCertificate:
>         name: <secret_with_ca_cert>
>         namespace: <namespace>
>         key: ca.crt
>   ```

**7. Create the Certificate**

Request a certificate by creating a cert-manager `Certificate` that references the Infisical Issuer. The `issuerRef.group` must be `infisical-issuer.infisical.com`.

```yaml certificate.yaml theme={"dark"}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: <namespace>
spec:
  secretName: example-com-tls
  commonName: example.com
  dnsNames:
    - example.com
  # Optional. If omitted, the Certificate Profile's default TTL is used.
  duration: 720h
  # cert-manager renews before expiry
  renewBefore: 240h
  privateKey:
    algorithm: RSA
    size: 2048
  issuerRef:
    name: infisical-issuer
    kind: Issuer
    group: infisical-issuer.infisical.com
```

```bash theme={"dark"}
kubectl apply -f certificate.yaml
```

Check that it was issued:

```bash theme={"dark"}
kubectl get certificate -n <namespace>
```

```bash theme={"dark"}
NAME          READY   SECRET            AGE
example-com   True    example-com-tls   15s
```

> [!NOTE]
>
> Workload identity certificates work too. For a SPIFFE identity (for example with istio-csr), set a `uris` SAN and make sure the Certificate Profile policy allows URI SANs:
>
> ```yaml theme={"dark"}
> spec:
>   uris:
>     - spiffe://cluster.local/ns/default/sa/my-workload
> ```

**8. Use the certificate**

The certificate and key are stored in the Secret named by `secretName`:

```bash theme={"dark"}
kubectl get secret example-com-tls -n <namespace>
```

```bash theme={"dark"}
NAME              TYPE                DATA   AGE
example-com-tls   kubernetes.io/tls   3      1m
```

The Secret contains three keys: `tls.crt` (the issued certificate plus any intermediates), `tls.key` (the private key), and `ca.crt` (the root CA). You can decode the certificate with `openssl`:

```bash theme={"dark"}
kubectl get secret example-com-tls -n <namespace> \
  -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
```

The Secret is now ready to be mounted by your workloads (Ingresses, Deployments, service meshes, and so on).

## FAQ

> [accordion] Which authentication method should I use?
>
> All three authenticate as an Infisical machine identity:
>
> * **Universal Auth** is the simplest: store a Client ID and Client Secret in a Secret. Good for any cluster.
> * **Kubernetes Auth** avoids storing a long-lived secret: the controller mints a short-lived token for a Kubernetes service account, and Infisical validates it against your cluster. Prefer this when you want to bind issuance to a service account rather than a static secret. It requires configuring [Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth.md) on the identity first.
> * **TLS Certificate Auth** authenticates with a client certificate. With **Bind Access Tokens to Client Certificate** enabled on the identity, a leaked access token is unusable without the certificate's private key. It requires configuring [TLS Certificate Auth](https://infisical.com/docs/documentation/platform/identities/tls-cert-auth.md) on the identity, and a proxy in front of Infisical that forwards the client certificate on every API request.

> [accordion] Why is my certificate duration different from what I requested?
>
> Infisical issues certificates in whole-hour granularity, so the requested `duration` is rounded down to whole hours (with a one-hour minimum). The effective lifetime is also clamped to the maximum validity allowed by the Certificate Profile's policy. If you omit `duration` entirely, the Profile's default TTL is used.

> [accordion] My CertificateRequest is stuck waiting for approval.
>
> cert-manager must be allowed to approve requests for the Infisical Issuer's signers. Make sure you applied the approver `ClusterRole` and `ClusterRoleBinding` from Step 4, and that the `ServiceAccount` subject matches your cert-manager installation (name and namespace).

> [accordion] Can certificates be renewed automatically?
>
> Yes. cert-manager renews certificates according to the `renewBefore` threshold on the `Certificate`. A renewal is just a new request that the Infisical Issuer signs, so no extra configuration is needed.

## What's next?

> [card] [cert-manager (ACME)](https://infisical.com/docs/documentation/platform/pki/guides/applications/k8s-cert-manager-acme.md)
>
> Use the standard cert-manager ACME issuer with EAB instead.

> [card] [Certificate Profiles](https://infisical.com/docs/documentation/platform/pki/settings/profiles.md)
>
> Configure the policy and defaults for issued certificates.

> [card] [Certificate Syncs](https://infisical.com/docs/documentation/platform/pki/applications/certificate-syncs/overview)
>
> Push certificates to AWS, Azure, and more.

> [card] [Alerting](https://infisical.com/docs/documentation/platform/pki/applications/alerting/overview)
>
> Get notified when certificates are about to expire.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.