---
title: Deploying Zero Trust Workload Identity Manager operands
---

# Deploying Zero Trust Workload Identity Manager operands {#zero-trust-manager-configuration_{context}}

Deploy the Zero Trust Workload Identity Manager operands by creating their custom resources in a specific order. Adhering to the sequence ensures the successful installation of components, such as the Security Production Identity Framework for Everyone (SPIRE) Server, SPIRE Agent, and Secure Production Identity Framework For Everyone (SPIFFE) CSI driver.

You must deploy the operands in the following sequence to ensure successful installation:

- `ZeroTrustWorkloadIdentityManager` CR
- SPIRE Server
- SPIRE Agent
- SPIFFE CSI driver
- SPIRE OIDC discovery provider

## About the ZeroTrustWorkloadIdentityManager custom resource {#zero-trust-manager-ztwim-cr_zero-trust-manager-configuration}

The `ZeroTrustWorkloadIdentityManager` is the primary custom resource that initializes the SPIRE deployments. This primary resource defines the trust domain and cluster name to help ensure secure workload identity management.

Reference the complete YAML specification to correctly structure the `ZeroTrustWorkloadIdentityManager` CR. This example helps you identify required fields and immutable parameters for your configuration.

```yaml
apiVersion: operator.openshift.io/v1alpha1
kind: ZeroTrustWorkloadIdentityManager
metadata:
 name: cluster
 labels:
   app.kubernetes.io/name: zero-trust-workload-identity-manager
   app.kubernetes.io/managed-by: zero-trust-workload-identity-manager
spec:
  trustDomain: "example.com"
  clusterName: "production-cluster"
  bundleConfigMap: "spire-bundle"
```

where:

`spec.trustDomain`
:   Specifies the trust domain to be used for the SPIFFE identifiers. Must be a valid SPIFFE trust domain (lowercase alphanumeric, hyphens, and dots). Maximum length is 255 characters. After setting a value for the field, the field is immutable. Red Hat highly recommends to set this value to match your the base application URL (for example, `apps.mycluster.example.com`) of your OpenShift Container Platform cluster. Using a different value might cause automatically generated OpenShift Routes or federation endpoints to be created with incorrect or mismatched hostnames later in the configuration process.

`spec.clusterName`
:   Specifies the name that identifies this cluster within the trust domain. Must be a valid DNS-1123 subdomain with a maximum length of 63 characters. Once set, this field is immutable.

`spec.bundleConfigMap`
:   Specifies the name of the ConfigMap that stores the SPIRE trust bundle. This ConfigMap contains the root certificates for the trust domain and is created and maintained by the Operator. Must be a valid Kubernetes name with a maximum length of 253 characters. This field is optional (defaults to `spire-bundle`) and once set, is immutable.

## Deploying the SPIRE Server {#zero-trust-manager-spire-server-config_zero-trust-manager-configuration}

Deploy the SPIRE Server by configuring the `SpireServer` custom resource (CR). This establishes a central authority that manages and issues identities to the workloads in your cluster.

**Prerequisites**

- You have access to the cluster as a user with the `cluster-admin` role.
- You have installed Zero Trust Workload Identity Manager in the cluster.

**Procedure**

1. Create the `SpireServer` CR:

   1. Create a YAML file that defines the `SpireServer` CR, for example, `SpireServer.yaml`:

      The following is an example of a `SpireServer.yaml` file.

      ```yaml
      apiVersion: operator.openshift.io/v1alpha1
      kind: SpireServer
      metadata:
       name: cluster
      spec:
        logLevel: "info"
        logFormat: "text"
        jwtIssuer: "https://oidc-discovery.apps.cluster.example.com"
        caValidity: "24h"
        defaultX509Validity: "1h"
        defaultJWTValidity: "5m"
        jwtKeyType: "rsa-2048"
        caSubject:
          country: "US"
          organization: "Example Corporation"
          commonName: "SPIRE Server CA"
        persistence:
          size: "5Gi"
          accessMode: "ReadWriteOnce"
          storageClass: "gp3-csi"
        datastore:
          databaseType: "sqlite3"
          connectionString: "/run/spire/data/datastore.sqlite3"
          tlsSecretName: ""
          maxOpenConns: 100
          maxIdleConns: 10
          connMaxLifetime: 0
          disableMigration: "false"
      ```

      where:

      `metadata.name`
      :   Specifies that the value must be `cluster`.

`spec.logLevel`
:   Specifies the logging level for the SPIRE Server. The valid options are `debug`, `info`, `warn`, and `error`.

`spec.logFormat`
:   Specifies the logging format for the SPIRE Server. The valid options are `text` and `json`.

`spec.jwtIssuer`
:   Specifies the JWT issuer URL. Must be a valid HTTPS or HTTP URL with a maximum length of 512 characters.

`spec.caValidity`
:   Specifies the validity period (Time to Live (TTL)) for the SPIRE Server’s CA certificate. This determines how long the server’s root or intermediate certificate is valid. The format is a duration string (for example, `24h`, `168h`).

`spec.defaultX509Validity`
:   Specifies the default validity period (TTL) for X.509 SVIDs issued to workloads. This value is used if a specific TTL is not configured for a registration entry.

`spec.defaultJWTValidity`
:   Specifies thedefault validity period (TTL) for JWT SVIDs issued to workloads. This value is used if a specific TTL is not configured for a registration entry.

`spec.jwtKeyType`
:   Specifies the key type used for JWT signing. The valid options are `rsa-2048`, `rsa-4096`, `ec-p256`, and `ec-p384`. This field is optional.

`spec.caSubject.country`
:   Specifies the country for the SPIRE Server certificate authority (CA). Must be an ISO 3166-1 alpha-2 country code (2 characters).

`spec.caSubject.organization`
:   Specifies the organization for the SPIRE Server CA. Maximum length is 64 characters.

`spec.caSubject.commonName`
:   Specifies the common name for the SPIRE Server CA. Maximum length is 255 characters.

`spec.persistence.size`
:   Specifies the size of the persistent volume (for example, `1Gi`, `5Gi`). Once set, this field is immutable.

`spec.persistence.accessMode`
:   Specifies the access mode for the persistent volume. The valid options are `ReadWriteOnce`, `ReadWriteOncePod`, and `ReadWriteMany`. Once set, this field is immutable.

`spec.persistence.storageClass`
:   Specifies the storage class to be used for the PVC. Once set, this field is immutable.

`spec.datastore.databaseType`
:   Specifies the type of database to use for the datastore. The valid options are `sql`, `sqlite3`, `postgres`, `mysql`, `aws_postgresql`, and `aws_mysql`.

`spec.datastore.connectionString`
:   Specifies the connection string for the database. For PostgreSQL with SSL, include `sslmode` and certificate paths (for example, `dbname=spire user=spire host=postgres.example.com sslmode=verify-full`).

`spec.datastore.tlsSecretName`
:   Specifies the name of a Kubernetes Secret containing TLS certificates for database connections. The Secret will be mounted at `/run/spire/db/certs`. This field is optional.

`spec.datastore.maxOpenConns`
:   Specifies the maximum number of open database connections. Must be between 1 and 10000.

`spec.datastore.maxIdleConns`
:   Specifies the maximum number of idle database connections in the pool. Must be between 0 and 10000.

`spec.datastore.connMaxLifetime`
:   Specifies the maximum lifetime of a database connection in seconds. A value of 0 means connections are not closed due to age.

`spec.datastore.disableMigration`
:   Specifies whether to disable automatic database migration. The valid options are `true` and `false`.

    1. Apply the configuration by running the following command:

       ```terminal
       $ oc apply -f SpireServer.yaml
       ```

**Verification**

- Verify that the stateful set of SPIRE Server is ready and available by running the following command:

  ```terminal
  $ oc get statefulset -l app.kubernetes.io/name=spire-server -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME            READY   AGE
  spire-server    1/1     65s
  ```
- Verify that the status of the SPIRE Server pod is `Running` by running the following command:

  ```terminal
  $ oc get po -l app.kubernetes.io/name=spire-server -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME               READY   STATUS    RESTARTS        AGE
  spire-server-0     2/2     Running   1 (108s ago)    111s
  ```
- Verify that the persistent volume claim (PVC) is bound, by running the following command:

  ```terminal
  $ oc get pvc -l app.kubernetes.io/name=spire-server -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME                        STATUS    VOLUME                                     CAPACITY   ACCESS MODES  STORAGECLASS  VOLUMEATTRIBUTECLASS  AGE
  spire-data-spire-server-0   Bound     pvc-27a36535-18a1-4fde-ab6d-e7ee7d3c2744   5Gi        RW0           gp3-csi       <unset>               22m
  ```

## Deploying the SPIRE Agent {#zero-trust-manager-spire-agent-config_zero-trust-manager-configuration}

Use the `SpireAgent` custom resource to configure the SPIRE Agent `DaemonSet` on your nodes. This defines how the agent verifies workloads and manages identity attestation across your OpenShift Container Platform cluster.

**Prerequisites**

- You have access to the cluster as a user with the `cluster-admin` role.
- You have installed Zero Trust Workload Identity Manager in the cluster.

**Procedure**

1. Create the `SpireAgent` CR:

   1. Create a YAML file that defines the `SpireAgent` CR, for example, `SpireAgent.yaml`:

      The following is an example of a `SpireAgent.yaml` file.

      ```yaml
      apiVersion: operator.openshift.io/v1alpha1
      kind: SpireAgent
      metadata:
       name: cluster
      spec:
        socketPath: "/run/spire/agent-sockets"
        logLevel: "info"
        logFormat: "text"
        nodeAttestor:
          k8sPSATEnabled: "true"
        workloadAttestors:
          k8sEnabled: "true"
          workloadAttestorsVerification:
            type: "auto"
            hostCertBasePath: "/etc/kubernetes"
            hostCertFileName: "kubelet-ca.crt"
          disableContainerSelectors: "false"
          useNewContainerLocator: "true"
      ```

      where:

      `metadata.name`
      :   Specifies that the value must be `cluster`.

`spec.socketPath`
:   Specifies the directory on the host where the SPIRE agent socket is created. This directory is shared with the SPIFFE CSI driver via the `hostPath` volume. Must match the `SpiffeCSIDriver.spec.agentSocketPath` for workloads to access the socket. Must be an absolute path with a maximum length of 256 characters.

`spec.logLevel`
:   Specifies the logging level for the SPIRE Server. The valid options are `debug`, `info`, `warn`, and `error`.

`spec.logFormat`
:   Specifies the logging format for the SPIRE Server. The valid options are `text` and `json`.

`spec.nodeAttestor.k8sPSATEnabled`
:   Specifies whether Kubernetes Projected Service Account Token (PSAT) node attestation is enabled. When enabled, the SPIRE agent uses K8s PSATs to prove its identity to the SPIRE server during node attestation. The valid options are `true` and `false`.

`spec.workloadAttestors.k8sEnabled`
:   Specifies whether the Kubernetes workload attestor is enabled. When enabled, the SPIRE agent can verify workload identities using Kubernetes pod information and service account tokens. The valid options are `true` and `false`.

`spec.workloadAttestors.workloadAttestorsVerification.type`
:   Specifies the kubelet certificate verification mode. The valid options are `auto`, `hostCert`, and `skip`.

`spec.workloadAttestors.workloadAttestorsVerification.hostCertBasePath`
:   Specifies the directory containing the kubelet CA certificate. Required when type is `hostCert`. Optional when type is `auto` (defaults to /etc/kubernetes if not specified).

`spec.workloadAttestors.workloadAttestorsVerification.hostCertFileName`
:   Specifies the file name for the kubelet’s CA certificate. When combined with `hostCertBasePath`, forms the full path. Required when type is `hostCert`. Optional when type is `auto`. Defaults to `kubelet-ca.crt` if not specified.

`spec.workloadAttestors.disableContainerSelectors`
:   Specifies whether to disable container selectors in the Kubernetes workload attestor. Set to `true` if using `holdApplicationUntilProxyStarts` in Istio. The valid options are `true` and `false`.

`spec.workloadAttestors.useNewContainerLocator`
:   Specifies enabling the new container locator algorithm that has support for cgroups v2. The valid options are `true` and `false`.

    1. Apply the configuration by running the following command:

       ```terminal
       $ oc apply -f SpireAgent.yaml
       ```

**Verification**

- Verify that the daemon set of the SPIRE Agent is ready and available by running the following command:

  ```terminal
  $ oc get daemonset -l app.kubernetes.io/name=spire-agent -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
  spire-agent   3         3         3       3            3           <none>          10m
  ```
- Verify that the status of SPIRE Agent pods is `Running` by running the following command:

  ```terminal
  $ oc get po -l app.kubernetes.io/name=spire-agent -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME                READY   STATUS    RESTARTS   AGE
  spire-agent-dp4jb   1/1     Running   0          12m
  spire-agent-nvwjm   1/1     Running   0          12m
  spire-agent-vtvlk   1/1     Running   0          12m
  ```

## Deploying the SPIFFE Container Storage Interface driver {#zero-trust-manager-spire-csidriver-config_zero-trust-manager-configuration}

Configure the Container Storage Interface (CSI) driver using the `SpiffeCSIDriver` CR. This configuration mounts SPIFFE sockets directly into workload pods, which allows your applications to access the SPIFFE Workload API securely.

**Prerequisites**

- You have access to the cluster as a user with the `cluster-admin` role.
- You have installed Zero Trust Workload Identity Manager in the cluster.

**Procedure**

1. Create the `SpiffeCSIDriver` CR:

   1. Create a YAML file that defines the `SpiffeCSIDriver` CR object, for example, `SpiffeCSIDriver.yaml`:

      ```yaml {title="Example SpiffeCSIDriver.yaml"}
      apiVersion: operator.openshift.io/v1alpha1
      kind: SpiffeCSIDriver
      metadata:
       name: cluster
      spec:
        agentSocketPath: "/run/spire/agent-sockets"
        pluginName: "csi.spiffe.io"
      ```

      where:

      `metadata.name`
      :   Specifies that the name must be `cluster`.

`spec.agentSocketPath`
:   Specifies the path to the directory containing the SPIRE agent’s Workload API socket. This directory is bind-mounted into workload containers by the CSI driver. The directory is shared between the SPIRE agent and CSI driver via a `hostPath` volume. Must be an absolute path with a maximum length of 256 characters. This value must match `SpireAgent.spec.socketPath` for workloads to access the socket.

`spec.pluginName`
:   Specifies the name of the CSI plugin. This sets the CSI driver name that is deployed to the cluster and used in `VolumeMount` configurations. Must match the driver name referenced in the workload pods. Must be a valid domain name format (for example, `csi.spiffe.io`) with a maximum length of 127 characters.

    1. Apply the configuration by running the following command:

       ```terminal
       $ oc apply -f SpiffeCSIDriver.yaml
       ```

**Verification**

- Verify that the daemon set of the SPIFFE CSI driver is ready and available by running the following command:

  ```terminal
  $ oc get daemonset -l app.kubernetes.io/name=spiffe-csi-driver -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
  spire-spiffe-csi-driver   3         3         3       3            3           <none>          114s
  ```
- Verify that the status of SPIFFE Container Storage Interface (CSI) Driver pods is `Running` by running the following command:

  ```terminal
  $ oc get po -l app.kubernetes.io/name=spiffe-csi-driver -n zero-trust-workload-identity-manager
  ```

  ```terminal {title="Example output"}
  NAME                            READY   STATUS    RESTARTS   AGE
  spire-spiffe-csi-driver-gpwcp   2/2     Running   0          2m37s
  spire-spiffe-csi-driver-rrbrd   2/2     Running   0          2m37s
  spire-spiffe-csi-driver-w6s6q   2/2     Running   0          2m37s
  ```

## Deploying the SPIRE OpenID Connect Discovery Provider {#zero-trust-manager-oidc-config_zero-trust-manager-configuration}

Deploy the SPIRE OpenID Connect (OIDC) Discovery Provider by configuring the `SpireOIDCDiscoveryProvider` CR. This allows you to define the trust domain and JSON web token (JWT) issuer for your cluster.

**Prerequisites**

- You have access to the cluster as a user with the `cluster-admin` role.
- You have installed Zero Trust Workload Identity Manager in the cluster.

**Procedure**

1. Create the `SpireOIDCDiscoveryProvider` CR:

   1. Create a YAML file that defines the `SpireOIDCDiscoveryProvider` CR, for example, `SpireOIDCDiscoveryProvider.yaml`:

      ```yaml {title="Example SpireOIDCDiscoveryProvider YAML"}
      apiVersion: operator.openshift.io/v1alpha1
      kind: SpireOIDCDiscoveryProvider
      metadata:
       name: cluster
      spec:
        logLevel: "info"
        logFormat: "text"
        csiDriverName: "csi.spiffe.io"
        jwtIssuer: "https://oidc-discovery.apps.cluster.example.com"
        replicaCount: 1
        managedRoute: "true"
        externalSecretRef: ""
      ```

      where:

      `metadata.name`
      :   Specifies that the value must be `cluster`.

`spec.logLevel`
:   Specifies the logging level for the SPIRE Server. The valid options are `debug`, `info`, `warn`, and `error`.

`spec.logFormat`
:   Specifies the logging format for the SPIRE Server. The valid options are `text` and `json`.

`spec.csiDriverName`
:   Specifies the name of the CSI driver to use for mounting the Workload API socket. This must match the `SpiffeCSIDriver.spec.pluginName` value for the OIDC provider to access SPIFFE identities. Must be a valid DNS subdomain format (for example, `csi.spiffe.io`) with a maximum length of 127 characters.

`spec.jwtIssuer`
:   Specifies the JWT issuer URL. Must be a valid HTTPS or HTTP URL with a maximum length of 512 characters. This value must match the `SpireServer.spec.jwtIssuer` value.

`spec.replicaCount`
:   Specifies the number of replicas for the OIDC Discovery Provider deployment. Must be between 1 and 5.

`spec.managedRoute`
:   Specifies  whether the Operator automatically creates an OpenShift route for the OIDC Discovery Provider endpoints. Set to `true` to have the Operator automatically create and maintain an OpenShift route for OIDC discovery endpoints (`*.apps.`). Set to `false` for administrators to manually configure routes or ingress.

`spec.externalSecretRef`
:   Specifies a reference to an externally managed secret that contains the TLS certificate for the OIDC Discovery Provider route host. Must be a valid Kubernetes secret reference name with a maximum length of 253 characters. This field is optional.

    1. Apply the configuration by running the following command:

       ```terminal
       $ oc apply -f SpireOIDCDiscoveryProvider.yaml
       ```

**Verification**

1. Verify that the deployment of OIDC Discovery Provider is ready and available by running the following command:

   ```terminal
   $ oc get deployment -l app.kubernetes.io/name=spiffe-oidc-discovery-provider -n zero-trust-workload-identity-manager
   ```

   ```terminal {title="Example output"}
   NAME                                    READY  UP-TO-DATE  AVAILABLE  AGE
   spire-spiffe-oidc-discovery-provider    1/1    1           1          2m58s
   ```
2. Verify that the status of OIDC Discovery Provider pods is `Running` by running the following command:

   ```terminal
   $ oc get po -l app.kubernetes.io/name=spiffe-oidc-discovery-provider -n zero-trust-workload-identity-manager
   ```

   ```terminal {title="Example output"}
   NAME                                                    READY   STATUS    RESTARTS   AGE
   spire-spiffe-oidc-discovery-provider-64586d599f-lcc94   2/2     Running   0          7m15s
   ```

## Verify the health of the operands {#zero-trust-manager-verify-operands_zero-trust-manager-configuration}

View the status fields to verify the operational health of managed components. This information helps you confirm that the SPIRE Server, SPIRE Agent, SPIFFE CSI driver, and the SPIRE OIDC discovery provider operands are ready and functioning correctly.

- To verify the operands, run the following command:

  ```terminal
  oc get ZeroTrustWorkloadIdentityManager cluster -o yaml
  ```

  ```yaml {title="Example output"}
  status:
    conditions:
    - lastTransitionTime: "2025-12-16T10:59:06Z"
      message: All components are ready
      reason: Ready
      status: "True"
      type: Ready
    - lastTransitionTime: "2025-12-16T10:59:06Z"
      message: All operand CRs are ready
      reason: Ready
      status: "True"
      type: OperandsAvailable
    operands:
    - kind: SpireServer
      message: Ready
      name: cluster
      ready: "true"
    - kind: SpireAgent
      message: Ready
      name: cluster
      ready: "true"
    - kind: SpiffeCSIDriver
      message: Ready
      name: cluster
      ready: "true"
    - kind: SpireOIDCDiscoveryProvider
      message: Ready
      name: cluster
      ready: "true"
     # ...
  ```

This status is reflected when all operands are healthy and stable.

> [!IMPORTANT]
> The Operator adds the owner reference for the `ZeroTrustWorkloadIdentityManager` CR on the other operands' CRs. This causes the operands' resources to be deleted once the `ZeroTrustWorkloadIdentityManager` CRs are deleted.

## Manually delete the custom security context constraints {#zero-trust-manager-manually-delete-scc_zero-trust-manager-configuration}

You can delete the `spire-spiffe-csi-driver` custom SCC when the SPIFFE CSI driver is ready and its pods are using the `privileged` security context constraint (SCC).

**Prerequisites**

- You have upgraded Zero Trust Workload Identity Manager to 1.1.0 from the **OperatorHub** catalog.

**Procedure**

1. Confirm the SPIFFE CSI driver is ready by running the following command:

   ```terminal
   $ oc get spiffecsidriver cluster -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
   ```

   The expected output includes `DaemonSetAvailable=True` and `Ready=True`.
2. Confirm that the CSI `DaemonSet` is available by running the following command:

   ```terminal
   $ oc get ds spire-spiffe-csi-driver -n zero-trust-workload-identity-manager
   ```
3. Confirm the CSI pods are running and are using the `privileged` SCC by running the following commands:

   ```terminal
   $ oc get pods -n zero-trust-workload-identity-manager -l app.kubernetes.io/name=spiffe-csi-driver
   ```

   ```terminal
   $ oc get pod -n zero-trust-workload-identity-manager -l app.kubernetes.io/name=spiffe-csi-driver \
       -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.openshift\.io/scc}{"\n"}{end}'
   ```

   Every pod should show `privileged`.
4. After all checks have passed, delete the legacy custom SCC by running the following commands:

   ```terminal
   $ oc get scc spire-spiffe-csi-driver
   ```

   ```terminal
   $ oc delete scc spire-spiffe-csi-driver
   ```

   If the SCC is already absent, no action is needed.
