Chapter 10. Zero Trust Workload Identity Manager
10.1. Zero Trust Workload Identity Manager overview Copy linkLink copied to clipboard!
The Zero Trust Workload Identity Manager is an OpenShift Container Platform Operator that manages the lifecycle of SPIFFE Runtime Environment (SPIRE) components. It enables workload identity management based on the Secure Production Identity Framework for Everyone (SPIFFE) standard, providing cryptographically verifiable identities (SVIDs) to workloads running in OpenShift Container Platform clusters.
The following are components of the Zero Trust Workload Identity Manager architecture:
10.1.1. SPIFFE Copy linkLink copied to clipboard!
Establish trust between software workloads in distributed systems with Secure Production Identity Framework for Everyone (SPIFFE). SPIFFE assigns unique IDs to workloads, allowing workloads to verify identities and communicate securely. This ensures secure authentication across dynamic environments.
The SPIFFE IDs are contained in the SPIFFE Verifiable Identity Document (SVID). SVIDs are used by workloads to verify their identity to other workloads so that the workloads can communicate with each other. The two main SVID formats are:
- X.509-SVIDs: X.509 certificates where the SPIFFE ID is embedded in the Subject Alternative Name (SAN) field.
-
JWT-SVIDs: JSON Web Tokens (JWTs) where the SPIFFE ID is included as the
subclaim.
For more information, see SPIFFE Overview.
10.1.2. SPIRE Server Copy linkLink copied to clipboard!
The SPIRE Server is the central management component of SPIRE that issues SPIFFE identities and maintains the registration database for a trust domain.
10.1.3. SPIRE Agent Copy linkLink copied to clipboard!
The SPIRE Agent performs workload attestation to ensure that workloads receive a verified identity when requesting authentication through the SPIFFE Workload API. The agent uses configured workload attestor plugins to verify these identities.
SPIRE and the SPIRE Agent perform node attestation via node plugins. The plugins are used to verify the identity of the node on which the agent is running. For more information, see About the SPIRE Agent.
10.1.4. Attestation Copy linkLink copied to clipboard!
The attestation process verifies the identity of nodes and workloads before issuing SPIFFE IDs. By comparing attributes against defined selectors, this process ensures that only legitimate entities within the trust domain receive cryptographic credentials.
The two main types of attestation in SPIFFE/SPIRE are:
- Node attestation: verifies the identity of a machine or a node on a system, before a SPIRE Agent running on that node can be trusted to request identities for workloads.
- Workload attestation: verifies the identity of an application or service running on an attested node before the SPIRE Agent on that node can provide it with a SPIFFE ID and SVID.
For more information, see Attestation.
10.2. Zero Trust Workload Identity Manager components Copy linkLink copied to clipboard!
Review the components available in the initial release of Zero Trust Workload Identity Manager to understand the architecture. These components provide the foundation for identifying and securing your workloads.
10.2.1. SPIFFE CSI Driver Copy linkLink copied to clipboard!
The SPIFFE Container Storage Interface (CSI) driver helps pods securely obtain their SPIFFE Verifiable Identity Document (SVID) by delivering the Workload API socket. By using Kubernetes ephemeral inline volumes, the driver simplifies how applications request temporary storage for identity management.
When the pod starts, the Kubelet calls the SPIFFE CSI driver to provision and mount a volume into the pod’s containers. The SPIFFE CSI driver mounts a directory that contains the SPIFFE Workload API into the pod. Applications in the pod then communicate with the Workload API to obtain their SVIDs. The driver guarantees that each SVID is unique.
10.2.2. SPIRE OpenID Connect Discovery Provider Copy linkLink copied to clipboard!
Use the SPIRE OpenID Connect (OIDC) Discovery Provider to integrate SPIRE workload identities with OIDC-compliant systems. This component exposes endpoints for token verification. It helps ensure compatibility between SPIRE-issued credentials and external APIs requiring standard OIDC tokens.
While SPIRE primarily issues identities for workloads, additional workload-related claims can be embedded into JWT-SVIDs through the configuration of SPIRE, which these claims to be included in the token and verified by OIDC-compliant clients.
10.2.3. SPIRE Controller Manager Copy linkLink copied to clipboard!
Use the SPIRE Controller Manager to automate workload registration with custom resource definitions (CRDs). The manager monitors pods and CRDs to create, update, or delete entries on the SPIRE Server. This process helps ensure that your SPIRE entries accurately reflect your active resources.
The SPIRE Controller Manager is designed to be deployed on the same pod as the SPIRE Server. The manager communicates with the SPIRE Server API using a private UNIX Domain Socket within a shared volume.
10.2.4. SPIRE Server and Agent telemetry Copy linkLink copied to clipboard!
Use the SPIRE Controller Manager to register workloads by using custom resource definitions (CRDs). The manager monitors pods and CRDs for changes and triggers a reconciliation process. This process creates, updates, or deletes SPIRE Server entries to help ensure they match your configuration.
10.2.5. About the Zero Trust Workload Identity Manager workflow Copy linkLink copied to clipboard!
Understand the high-level workflow of Zero Trust Workload Identity Manager to help you manage secure identities. This process relies on SPIRE components and custom resource definitions (CRDs) to validate nodes and workloads.
The following is a high-level workflow of the Zero Trust Workload Identity Manager within the Red Hat OpenShift cluster.
- The SPIRE, SPIRE Agent, SPIFFE CSI Driver, and the SPIRE OIDC Discovery Provider operands are deployed and managed by Zero Trust Workload Identity Manager via associated customer resource definitions (CRDs).
- Watches are then registered for relevant Kubernetes resources and the necessary SPIRE CRDs are applied to the cluster.
-
The CR for the ZeroTrustWorkloadIdentityManager resource named
clusteris deployed and managed by a controller. To deploy the SPIRE Server, SPIRE Agent, SPIFFE CSI Driver, and SPIRE OIDC Discovery Provider, you need to create a custom resource of a each certain type and name it
cluster. The custom resource types are as follows:-
SPIRE Server -
SpireServer -
SPIRE Agent -
SpireAgent -
SPIFFE CSI Driver -
SpiffeCSIDriver -
SPIRE OIDC discovery provider -
SpireOIDCDiscoveryProvider
-
SPIRE Server -
- When a node starts, the SPIRE Agent initializes, and connects to the SPIRE Server.
- The SPIRE Agent begins the node attestation process. The agent collects information on the node’s identity such as label name and namespace. The agent securely provides the information it gathered through the attestation to the SPIRE Server.
- The SPIRE Server then evaluates this information against its configured attestation policies and registration entries. If successful, the server generates an agent SVID and the Trust Bundle (CA Certificate) and securely sends this back to the SPIRE Agent.
- A workload starts on the node and needs a secure identity. The workload connects to the agent’s Workload API and requests a SVID.
- The SPIRE Agent receives the request and begins a workload attestation to gather information about the workload.
- After the SPIRE Agent gathers the information, the information is sent to the SPIRE Server and the server checks its configured registration entries.
- The SPIRE Agent receives the workload SVID and Trust Bundle and passes it on to the workload. The workload can now present their SVIDs to other SPIFFE-aware devices to communicate with them.
10.3. Zero Trust Workload Identity Manager release notes Copy linkLink copied to clipboard!
The Zero Trust Workload Identity Manager leverages Secure Production Identity Framework for Everyone (SPIFFE) and the SPIFFE Runtime Environment (SPIRE) to provide a comprehensive identity management solution for distributed systems.
These release notes track the development of Zero Trust Workload Identity Manager.
10.3.1. Zero Trust Workload Identity Manager 1.1.0 Copy linkLink copied to clipboard!
Issued: 30 Jun 2026
This release adds integration and operational capabilities for workloads that use external certificate authorities, service mesh deployments, or file-based Transport Layer Security (TLS) credentials. The release includes a supported SPIFFE Helper container image, SPIRE UpstreamAuthority plugins for cert-manager and HashiCorp Vault, and Red Hat OpenShift Service Mesh integration with SPIRE for single-cluster and federated multi-cluster mutual Transport Layer Security (mTLS).
The following advisories are available for Zero Trust Workload Identity Manager:
Zero Trust Workload Identity Manager supports the following components and versions:
| Component | Version |
|---|---|
| Zero Trust Workload Identity Manager | 1.1.0 |
| SPIRE Server | 1.14.7 |
| SPIRE Agent | 1.14.7 |
| SPIRE Controller Manager | 0.6.4 |
| SPIRE OIDC Discovery Provider | 1.14.7 |
| SPIFFE CSI Driver | 0.2.8 |
10.3.1.1. New features and enhancements Copy linkLink copied to clipboard!
- Supported SPIFFE Helper container image
- Zero Trust Workload Identity Manager now provides a supported SPIFFE Helper container image for workloads that cannot use the SPIFFE Workload API directly but can read Transport Layer Security (TLS) credentials from a shared volume. The image is based on upstream SPIFFE Helper. The configuration file format, command-line flags, and Workload API behavior remain compatible.
- SPIRE UpstreamAuthority plugins for external certificate authorities
Zero Trust Workload Identity Manager now supports SPIRE Server UpstreamAuthority plugins that obtain intermediate signing certificates from external certificate management systems while preserving Secure Production Identity Framework for Everyone (SPIFFE) identity standards.
Supported plugins:
- cert-manager UpstreamAuthority plugin: integrates SPIRE Server with cert-manager Operator for Red Hat OpenShift.
- Vault UpstreamAuthority plugin: integrates SPIRE Server with the HashiCorp Vault Public Key Infrastructure (PKI) secrets engine.
- cert-manager UpstreamAuthority plugin
-
The cert-manager UpstreamAuthority plugin connects SPIRE Server to cert-manager Operator for Red Hat OpenShift for automated intermediate certificate provisioning. SPIRE Server creates a
CertificateRequestcustom resource, then the configuredIssuerorClusterIssuersigns the request, and then the SPIRE Server uses the signed intermediate certificate to issue workload identities. - Vault UpstreamAuthority plugin
- The Vault UpstreamAuthority plugin connects SPIRE Server to the HashiCorp Vault PKI secrets engine for automated intermediate certificate authority (CA) certificate signing. Use this plugin when PKI is centralized in Vault and SPIRE must obtain intermediate signing certificates through Vault policies and authentication.
- Single-cluster Service Mesh integration with SPIRE
- Zero Trust Workload Identity Manager now supports single-cluster integration with Red Hat OpenShift Service Mesh. SPIRE replaces Istio’s built-in certificate authority (CA) with SPIFFE-compliant identities and short-lived certificates that rotate automatically for workload mutual Transport Layer Security (mTLS).
- Multi-cluster Service Mesh integration with SPIRE federation
Zero Trust Workload Identity Manager now supports multi-cluster integration with Red Hat OpenShift Service Mesh through SPIRE federation, enabling cross-cluster mutual Transport Layer Security (mTLS) authentication and zero trust workload identity across separate OpenShift Container Platform clusters.
Cross-cluster trust requires federation at two layers:
-
SPIRE federation: SPIRE Servers exchange trust bundles through the
https_spiffeprofile. - Istio federation: Istio discovers remote endpoints through remote secrets and routes traffic through East-West Gateways.
-
SPIRE federation: SPIRE Servers exchange trust bundles through the
10.3.1.2. Deprecated features Copy linkLink copied to clipboard!
- Custom SCC
spire-spiffe-csi-driver Starting in Zero Trust Workload Identity Manager 1.1.0, the SPIFFE CSI Driver no longer uses the custom
SecurityContextConstraints(SCC)spire-spiffe-csi-driver.Zero Trust Workload Identity Manager now grants the CSI
ServiceAccountaccess to the platformprivilegedSCC through aRoleBindingnamespace.Zero Trust Workload Identity Manager uses only the existing OpenShift
privilegedSCC. Zero Trust Workload Identity Manager does not create, modify, update, or delete theprivilegedSCC.- Action required after upgrade
Zero Trust Workload Identity Manager does not remove the legacy custom SCC
spire-spiffe-csi-driver. After you upgrade Zero Trust Workload Identity Manager to 1.1.0 from the OpenShift OperatorHub catalog, remove it manually once CSI is healthy on the platform SCC.For more information, see Manually delete the custom security context constraints.
10.3.1.3. Fixed issues Copy linkLink copied to clipboard!
- Managed route TLS secrets no longer require manual RBAC configuration
Before this update, when you configured a managed route with an
externalSecretRefTLS certificate on theSpireOIDCDiscoveryProviderorSpireServercustom resource (CR), Zero Trust Workload Identity Manager did not create theRoleBindingthat grants the OpenShift Ingress router service account permission to read the referenced Secret. As a consequence, route reconciliation failed with aManagedRouteUpdateFailedcondition, and you had to manually createsecret-reader,Role, andRoleBindingCRs. With this release, Zero Trust Workload Identity Manager automatically reconciles the requiredRoleandRoleBindingCRs so that the router service account can access the Secrets referenced by theexternalSecretRefTLS certificate. Managed routes that use externally provided TLS certificates now reconcile without additional manual RBAC steps.
- Pre-existing operand resources are no longer overwritten at installation
Before this update, when you installed Zero Trust Workload Identity Manager on a cluster that already contained Kubernetes resources with the same names as operand objects, Zero Trust Workload Identity Manager reconciled and overwrote those resources during the initial installation without reporting a conflict. As a consequence, manually created or third-party resources could be modified unexpectedly before you had a chance to resolve naming collisions. With this release, Zero Trust Workload Identity Manager checks the
app.kubernetes.io/managed-bylabel before updating any existing resource during installation. If a matching resource is not managed by Zero Trust Workload Identity Manager, Zero Trust Workload Identity Manager sets aResourceConflictstatus condition and stops reconciliation instead of overwriting the resource.
- Unmanaged cluster resources are protected during reconciliation
Before this update, after Zero Trust Workload Identity Manager was already running, operand controllers could still update Kubernetes resources with matching names even when those resources were not owned by Zero Trust Workload Identity Manager. This could occur when a name collision appeared later or when the
app.kubernetes.io/managed-by: zero-trust-workload-identity-managerlabel was removed from a resource that Zero Trust Workload Identity Manager had previously managed. With this release, each operand controller verifies themanaged-bylabel on every reconcile cycle before applying updates. When the label is absent, Zero Trust Workload Identity Manager sets aResourceConflictstatus condition on the custom resource and skips the update to the conflicting object. Operand updates proceed only for resources that Zero Trust Workload Identity Manager currently manages.
- Duplicate health check port names resolved in SPIRE Server StatefulSet
Before this update, the
spire-serverandspire-controller-managercontainers in the SPIRE ServerStatefulSetfield both declared a port namedhealthzon different container ports. Kubernetes treated this as a duplicate port name within the pod, issued a warning, and the services or probes that selected the ports by name could target the wrong container. With this release, Zero Trust Workload Identity Manager assigns unique port names, such asserver-healthzandctrlmgr-healthz, and updates the liveness and readiness probes to reference those names. Health checks and monitoring configurations now resolve to the intended container.
- Create-only mode disabled status now updates on the main custom resource
Before this update, after you disabled create-only mode by setting
CREATE_ONLY_MODEtofalsein the Operator subscription, theCreateOnlyModecondition on the mainZeroTrustWorkloadIdentityManagerCR could remainTruewith the reasonCreateOnlyModeEnabled. Because Zero Trust Workload Identity Manager set that condition from operand status instead of from theCREATE_ONLY_MODEenvironment variable in the subscription, the main CR status did not reflect that create-only mode was disabled even though the operand reconciliation had resumed. With this release, Zero Trust Workload Identity Manager sets theCreateOnlyModecondition on the main CR directly from theCREATE_ONLY_MODEenvironment variable and updates it toFalsewith reasonCreateOnlyModeDisabledwhen create-only mode is turned off.
- Create-only mode status updates reconcile reliably
Before this update, Zero Trust Workload Identity Manager controllers wrote the custom resource (CR) status twice during each reconciliation cycle. The CR status was written at the start through the
SetInitialReconciliationStatuscycle and again at the end through the status manager. Because the informer cache could return a staleResourceVersionafter the first write, the deferred status update was rejected withHTTP 409 Conflict. Status conditions, includingCreateOnlyMode, could fail to transition to the expected state even after you changed configuration in the Operator subscription. With this release, controllers apply the status in a single update at the end of reconciliation, and status update retry logic now complies with theRetryOnConflictcontract. CR status updates, including create-only mode transitions, are now completed.
10.3.2. Zero Trust Workload Identity Manager 1.0.1 Copy linkLink copied to clipboard!
Issued: 17 May 2026
This release fixes some Common Vulnerabilities and Exposures (CVEs).
The following advisories are available for the Zero Trust Workload Identity Manager:
10.3.2.1. CVEs Copy linkLink copied to clipboard!
10.3.3. Zero Trust Workload Identity Manager 1.0.0 (General Availability) Copy linkLink copied to clipboard!
Issued: 12 December 2025
This release introduces capabilities for enterprise readiness, security, and operational flexibility. The release includes SPIRE federation for cross-cluster identity, PostgreSQL support for production persistence, and enhanced security through stricter constraints and API validation.
The following advisories are available for the Zero Trust Workload Identity Manager:
Zero Trust Workload Identity Manager supports the following components and versions:
| Component | Version |
|---|---|
| SPIRE Server | 1.13.3 |
| SPIRE Agent | 1.13.3 |
| SPIRE Controller Manager | 0.6.3 |
| SPIRE OIDC Discovery Provider | 1.13.3 |
| SPIFFE CSI Driver | 0.2.8 |
10.3.3.1. New features and enhancements Copy linkLink copied to clipboard!
- SPIRE federation support
The Operator now includes support for SPIRE federation, enabling workloads across distinct trust domains to securely communicate and authenticate with each other.
Key capabilities:
-
Configuration of bundle endpoints using
https_spiffe(TLS) orhttps_web(Web PKI) profiles. -
Automatic certificate management via the ACME protocol. For example,
Let’s Encrypt. - Automatic OpenShift Container Platform route creation for federation endpoints.
- Ability to configure relationships with multiple federated trust domains.
-
Configuration of bundle endpoints using
Customer action required:
-
Review the
federationconfiguration within theSpireServercustom resource (CR). - Ensure proper DNS resolution and network connectivity to federated trust domains.
-
Review the
- PostgreSQL database support
SPIRE Server now supports PostgreSQL as an external database backend, accommodating production deployments that necessitate enterprise-grade data persistence and high availability.
-
Supported Types:
sqlite3(default),postgres,mysql. Customer action required:
- For production, evaluation of migration from SQLite to PostgreSQL is recommended.
- Creation and configuration of Kubernetes Secrets for database TLS certificates and credentials are required.
-
Supported Types:
- Configurable agent socket path and Container Storage Interface (CSI) plugin name
The SPIRE Agent socket path and the SPIFFE CSI Driver plugin name are now configurable, providing operational flexibility for environments with specific directory requirements or co-existence with multiple SPIFFE deployments.
Key configuration points:
-
SpireAgent.spec.socketPath -
SpiffeCSIDriver.spec.agentSocketPath -
SpiffeCSIDriver.spec.pluginName
-
Customer action required:
-
Ensure consistency between
socketPathin theSpireAgentCR andagentSocketPathin theSpiffeCSIDriverCR.
-
Ensure consistency between
- Workload attestors verification API
A new API has been introduced to configure kubelet certificate verification for workload attestation, enhancing security and supporting various OpenShift Container Platform configurations.
Verification types:
-
auto(default): Verification utilizes OpenShift Container Platform defaults (/etc/kubernetes/kubelet-ca.crt). -
hostCert: Uses a custom CA certificate path. -
skip: Skips TLS verification (not recommended for production use).
-
- Configurable Certificate Authority and JSON Web Token key types
Administrators can now configure the cryptographic key types used for the SPIRE Server Certificate Authority (CA) and JSON Web Token (JWT) signing, ensuring compliance with organizational security policies.
-
Supported Key Types:
rsa-2048(default),rsa-4096,ec-p256,ec-p384. Customer action required:
- Review organizational security policies to determine required key types.
-
Supported Key Types:
- Custom namespace deployment
- The Operator and all associated operands can now be deployed within a custom namespace, providing flexibility for organizations with specific namespace governance requirements.
- Proxy-aware Operator and operands
- The Operator and all managed operands are now proxy-aware and automatically inherit cluster-wide proxy settings when configured.
- Enhanced Security Context Constraints
- SPIRE Agent and SPIFFE CSI Driver now run with Security Context Constraints (SCC) that prevent root user execution, though privileged container mode remains enabled for necessary host-level operations.
-
The Operator and all operand containers are configured with the
ReadOnlyRootFilesystemset totrue.
- Enhanced API validation
Comprehensive Common Expression Language (CEL) validation has been integrated into all Custom Resource Definitions (CRDs) to prevent configuration errors during admission control.
Key validations:
-
All Operator CRDs are enforced as singletons (must be named
cluster). -
Immutable Fields: Fields including
trustDomain,clusterName,bundleConfigMap,federation,bundleEndpointprofile, and allPersistencesettings (size,accessMode, andstorageClass) are now immutable after initial creation.
-
All Operator CRDs are enforced as singletons (must be named
Customer action required:
- Review existing CR configurations to ensure compliance with the new validation rules.
- Common configuration consolidation
-
Standard configuration options (
labels,resources,affinity,tolerations,nodeSelector) are now standardized across all operand CRs via a sharedCommonConfigstructure.
-
Standard configuration options (
- Configuring log level and log format for the operands
This release introduces flexible logging controls to improve observability and debugging across the platform:
-
SPIRE Components: Users can now configure the
logLevel(debug, info, warn, error) andlogFormat(text, JSON) independently forSpireServer,SpireAgent, andSpireOIDCDiscoveryProviderdirectly within their CR specifications. The defaults are set to "info" for thelogLeveland "text" for thelogFormat. -
Operator: The Operator’s log verbosity is now configurable via the
OPERATOR_LOG_LEVELenvironment variable using klog’stextlogger.
-
SPIRE Components: Users can now configure the
- Refactor for create-only mode
-
By setting the
CREATE_ONLY_MODEenvironment variable, users can prevent the Operator from reconciling updates. This allows for manual resource modification without interference. If this mode is disabled, the Operator resumes enforcing the state and overwrites any manual changes.
10.3.3.2. Status and observability improvements Copy linkLink copied to clipboard!
- Enhanced status reporting
- The main CR now aggregates status information from all operand CRs.
- New status conditions include Upgradeable (indicating a safe upgrade path) and Progressing (detailing deployment progress).
- Operator metrics
- Operator metrics are now exposed and secured with appropriate RBAC configuration.
- Integration is supported with the OpenShift Container Platform monitoring stack.
10.3.3.3. Fixed issues Copy linkLink copied to clipboard!
- Enhanced Security Context Constraints for SPIRE Agent
Before this update, the SPIRE Agent and SPIFFE CSI Driver containers were running as root user, leading to potential security violations. With this release, Security Context Constraints (SCC) have been configured to ensure these components no longer run as root. While privileged container mode is still required for necessary capabilities, this change reduces potential security risks for the user.
(SPIRE-60)
- SpireServer updates now propagate without Operator restart
Before this update, the Operator failed to trigger reconciliation after updating the operand CR spec. As a consequence, user updates to
SpireServerCR resources were not propagated to theStatefulSet, causing reconciliation to fail and changes to be ignored, leading to inconsistent resource allocation. With this release, the race condition between the manager and reconciler’s cache to trigger reconciliation after CR updates has been fixed. As a result, post installation patch operations onSpireServerCRs reliably trigger reconciliation, ensuring updated values are applied to the StatefulSet without manual Operator restart.(SPIRE-68)
- Removed unnecessary security context constraint for OpenID Connect discovery provider
Before this update, the system unnecessarily created a custom security context constraint (SCC) for the OpenID Connect (OIDC) discovery provider, which increased the security footprint and configuration complexity even though the deployment did not require it. With this release, the custom SCC creation logic has been removed, resulting in a configuration where the OIDC discovery provider operates successfully without the extra security constraints.
- Fixed ConfigMap Reconciliation for SPIRE Controller Manager
Before this update, Spire-controller manager ConfigMap reconciliation failed due to an unhandled edge case in the previous implementation. As a consequence, users experienced configuration inconsistencies. With this release, the Spire-controller manager ConfigMap reconciliation issue has been resolved. As a result, end users now experience seamless Spire-controller manager configuration.
- OIDC discovery provider now restarts automatically on configuration changes
Before this update, the SPIRE OIDC discovery provider failed to automatically restart following
configmapchanges, leading to persistent authentication failures. With this release, updates to the CR now trigger an automatic pod restart, ensuring thatconfigmapchanges are applied immediately.
- Corrected update rollback for DaemonSets, Deployments, and StatefulSets
Before this update,
daemonset,deployment, andstatefulsetswere not properly reverted to their original form in all valid scenarios due to an oversight in the update logic. As a consequence, user data loss or inconsistency occurred in valid scenarios. With this release, the update logic has been corrected, ensuring all valid scenarios revert to their original form.Other bug fixes included:
- Fixed issues related to continuous reconciliation and unnecessary updates.
- Eliminated requeue logic for user input validation errors.
10.3.4. Zero Trust Workload Identity Manager 0.2.0 (General Availability) Copy linkLink copied to clipboard!
Issued: 09 August 2025
The following advisories are available for the Zero Trust Workload Identity Manager:
10.3.4.1. New features and enhancements Copy linkLink copied to clipboard!
- Support for the managed OIDC Discovery Provider Route
-
The Operator exposes the
SPIREOIDCDiscoveryProviderspec through OpenShift Container Platform Routes under the domain*.apps.<cluster_domain>for the selected default installation. -
The
managedRouteandexternalSecretReffields have been added to thespireOidcDiscoveryProviderspec. -
The
managedRoutefield is boolean and is set totrueby default. If set tofalse, the Operator stops managing the route and the existing route will not be deleted automatically. If set back totrue, the Operator resumes managing the route. If a route does not exist, the Operator creates a new one. If a route already exists, the Operator will override the user configuration if a conflict exists. -
The
externalSecretRefreferences an externally managed Secret that has the TLS certificate for theoidc-discovery-providerRoute host. When provided, this populates the route’s.Spec.TLS.ExternalCertificatefield. For more information, see Creating a route with externally managed certificate
-
The Operator exposes the
- Enabling the custom Certificate Authority Time-To-Live for the SPIRE bundle
The following Time-To-Live (TTL) fields have been added to the
SpireServercustom resource definition (CRD) API for SPIRE Server certificate management:-
CAValidity(default: 24h) -
DefaultX509Validity(default: 1h) -
DefaultJWTValidity(default: 5m)
-
- The default values can be replaced in the server configuration with user-configurable options that give users the flexibility to customize certificate and SPIFFE Verifiable Identity Document (SVID) lifetimes based on their security requirements.
- Enabling Manual User Configurations
-
The Operator controller switches to
create-onlymode once theztwim.openshift.io/create-only=trueannotation is present on the Operator’s APIs. This allows resource creation while skipping the updates. A user can update the resources manually to test their configuration. This annotation supports APIs such asSpireServer,SpireAgents,SpiffeCSIDriver,SpireOIDCDiscoveryProvider, andZeroTrustWorkloadIdentityManager. - When the annotation is applied, all derived resources including resources created and managed by the Operator are created but not updated.
- After the annotation is removed and the pod restarts, the Operator tries to come back to the required state. The annotation is applied only once during start or a restart.
-
The Operator controller switches to
10.3.4.2. Fixed issues Copy linkLink copied to clipboard!
- JSON Web Token Issuer field now requires a valid URL
Before this update, the
JwtIssuerfield for both theSpireServerand theSpireOidcDiscoveryProvidercustom resources did not require the input to be a URL, frequently causing configuration errors. With this release, the validation has been updated, and users must now manually enter a valid issuer URL in theJwtIssuerfield for both custom resources. As a result, misconfigurations caused by a malformed issuer values are prevented, ensuring a stable and reliable setup.
10.3.5. Zero Trust Workload Identity Manager 0.1.0 (General Availability) Copy linkLink copied to clipboard!
Issued: 16 June 2025
The following advisories are available for the Zero Trust Workload Identity Manager:
10.4. Installing the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
Install Zero Trust Workload Identity Manager to help ensure secure communication between your workloads. You can install the Zero Trust Workload Identity Manager by using either the web console or CLI.
If you install the Operator into a custom namespace (for example, my-custom-namespace), all managed operand resources are deployed within that same namespace. All secrets and ConfigMaps referenced by the Custom Resources (CRs) must also exist in that custom namespace.
The Operator installation is not supported in the openshift-* namespaces and the default namespace.
10.4.1. Installing the Zero Trust Workload Identity Manager by using the web console Copy linkLink copied to clipboard!
Use the Software Catalog in the OpenShift Container Platform web console to install the Zero Trust Workload Identity Manager. This process streamlines deployment and helps ensure the Operator is installed in the correct namespace with the appropriate installation mode.
A minimum of 1Gi persistent volume is required to install the SPIRE Server.
Prerequisites
-
You have access to the cluster with
cluster-adminprivileges. - You have access to the OpenShift Container Platform web console.
Procedure
- Log in to the OpenShift Container Platform web console.
-
Go to Ecosystem
Software Catalog. - Search for Zero Trust Workload Identity Manager.
On the Install Operator page:
-
Update the Update channel, if necessary. The channel defaults to
stable-v1, which installs the lateststable-v1release of the Zero Trust Workload Identity Manager. Choose the Installed Namespace for the Operator. The default Operator namespace is
zero-trust-workload-identity-manager.If the
zero-trust-workload-identity-managernamespace does not exist, it is created for you.NoteThe Operator and operands are deployed in the same namespace.
Select an Update Approval strategy
- The Automatic strategy allows Operator Lifecycle Manager (OLM) to automatically update the Operator when a new version is available.
- The Manual strategy requires a user with appropriate credentials to approve the Operator update.
-
Update the Update channel, if necessary. The channel defaults to
- Click Install.
Verification
Navigate to Ecosystem
Installed Operators. -
Verify that Zero Trust Workload Identity Manager is listed with a Status of Succeeded in the
zero-trust-workload-identity-managernamespace. Verify that Zero Trust Workload Identity Manager controller manager deployment is ready and available by running the following command:
$ oc get deployment -l name=zero-trust-workload-identity-manager -n zero-trust-workload-identity-managerExample output
NAME READY UP-TO-DATE AVAILABLE AGE zero-trust-workload-identity-manager-controller-manager 1/1 1 1 3h36m
-
Verify that Zero Trust Workload Identity Manager is listed with a Status of Succeeded in the
To check the Operator logs, run the following command:
$ oc logs -f deployment/zero-trust-workload-identity-manager-controller-manager -n zero-trust-workload-identity-manager
10.4.2. Installing the Zero Trust Workload Identity Manager by using the CLI Copy linkLink copied to clipboard!
Install the Zero Trust Workload Identity Manager by using the command-line interface (CLI) to create the required project, OperatorGroup, and Subscription objects. You can then deploy the Operator components necessary for managing workload identities on your OpenShift Container Platform cluster.
Prerequisites
-
You have access to the cluster with
cluster-adminprivileges.
A minimum of 1Gi persistent volume is required to install the SPIRE Server.
Procedure
Create a new project named
zero-trust-workload-identity-managerby running the following command:$ oc new-project zero-trust-workload-identity-managerCreate an
OperatorGroupobject:Create a YAML file, for example,
operatorGroup.yaml, with the following content:apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: openshift-zero-trust-workload-identity-manager namespace: zero-trust-workload-identity-manager spec: upgradeStrategy: DefaultCreate the
OperatorGroupobject by running the following command:$ oc create -f operatorGroup.yaml
Create a
Subscriptionobject:Create a YAML file, for example,
subscription.yaml, that defines theSubscriptionobject:apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: openshift-zero-trust-workload-identity-manager namespace: zero-trust-workload-identity-manager spec: channel: stable-v1 name: openshift-zero-trust-workload-identity-manager source: redhat-operators sourceNamespace: openshift-marketplace installPlanApproval: AutomaticCreate the
Subscriptionobject by running the following command:$ oc create -f subscription.yaml
Verification
Verify that the OLM subscription is created by running the following command:
$ oc get subscription -n zero-trust-workload-identity-managerExample output
NAME PACKAGE SOURCE CHANNEL openshift-zero-trust-workload-identity-manager zero-trust-workload-identity-manager redhat-operators stable-v1Verify whether the Operator is successfully installed by running the following command:
$ oc get csv -n zero-trust-workload-identity-managerExample output
NAME DISPLAY VERSION PHASE zero-trust-workload-identity-manager.v1.0.0 Zero Trust Workload Identity Manager 1.0.0 SucceededVerify that the Zero Trust Workload Identity Manager controller manager is ready by running the following command:
$ oc get deployment -l name=zero-trust-workload-identity-manager -n zero-trust-workload-identity-managerExample output
NAME READY UP-TO-DATE AVAILABLE AGE zero-trust-workload-identity-manager-controller-manager 1/1 1 1 43m
10.5. Deploying Zero Trust Workload Identity Manager operands Copy linkLink copied to clipboard!
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:
-
ZeroTrustWorkloadIdentityManagerCR - SPIRE Server
- SPIRE Agent
- SPIFFE CSI driver
- SPIRE OIDC discovery provider
10.5.1. About the ZeroTrustWorkloadIdentityManager custom resource Copy linkLink copied to clipboard!
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.
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.
10.5.2. Deploying the SPIRE Server Copy linkLink copied to clipboard!
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-adminrole. - You have installed Zero Trust Workload Identity Manager in the cluster.
Procedure
Create the
SpireServerCR:Create a YAML file that defines the
SpireServerCR, for example,SpireServer.yaml:The following is an example of a
SpireServer.yamlfile.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, anderror. spec.logFormat-
Specifies the logging format for the SPIRE Server. The valid options are
textandjson. 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.wtKeyType-
Specifies the key type used for JWT signing. The valid options are
rsa-2048,rsa-4096,ec-p256, andec-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, andReadWriteMany. 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, andaws_mysql. spec.datastore.connectionString-
Specifies the connection string for the database. For PostgreSQL with SSL, include
sslmodeand 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
trueandfalse.
Apply the configuration by running the following command:
$ oc apply -f SpireServer.yaml
Verification
Verify that the stateful set of SPIRE Server is ready and available by running the following command:
$ oc get statefulset -l app.kubernetes.io/name=server -n zero-trust-workload-identity-managerExample output
NAME READY AGE spire-server 1/1 65sVerify that the status of the SPIRE Server pod is
Runningby running the following command:$ oc get po -l app.kubernetes.io/name=server -n zero-trust-workload-identity-managerExample output
NAME READY STATUS RESTARTS AGE spire-server-0 2/2 Running 1 (108s ago) 111sVerify that the persistent volume claim (PVC) is bound, by running the following command:
$ oc get pvc -l app.kubernetes.io/name=server -n zero-trust-workload-identity-managerExample 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
10.5.3. Deploying the SPIRE Agent Copy linkLink copied to clipboard!
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-adminrole. - You have installed Zero Trust Workload Identity Manager in the cluster.
Procedure
Create the
SpireAgentCR:Create a YAML file that defines the
SpireAgentCR, for example,SpireAgent.yaml:The following is an example of a
SpireAgent.yamlfile.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
hostPathvolume. Must match theSpiffeCSIDriver.spec.agentSocketPathfor 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, anderror. spec.logFormat-
Specifies the logging format for the SPIRE Server. The valid options are
textandjson. 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
trueandfalse. 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
trueandfalse. spec.workloadAttestors.workloadAttestorsVerification.type-
Specifies the kubelet certificate verification mode. The valid options are
auto,hostCert, andskip. spec.workloadAttestors.workloadAttestorsVerification.hostCertBasePath-
Specifies the directory containing the kubelet CA certificate. Required when type is
hostCert. Optional when type isauto(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 ishostCert. Optional when type isauto. Defaults tokubelet-ca.crtif not specified. spec.workloadAttestors.disableContainerSelectors-
Specifies whether to disable container selectors in the Kubernetes workload attestor. Set to
trueif usingholdApplicationUntilProxyStartsin Istio. The valid options aretrueandfalse. spec.workloadAttestors.useNewContainerLocator-
Specifies enabling the new container locator algorithm that has support for cgroups v2. The valid options are
trueandfalse.
Apply the configuration by running the following command:
$ oc apply -f SpireAgent.yaml
Verification
Verify that the daemon set of the SPIRE Agent is ready and available by running the following command:
$ oc get daemonset -l app.kubernetes.io/name=agent -n zero-trust-workload-identity-managerExample output
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE spire-agent 3 3 3 3 3 <none> 10mVerify that the status of SPIRE Agent pods is
Runningby running the following command:$ oc get po -l app.kubernetes.io/name=agent -n zero-trust-workload-identity-managerExample 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
10.5.4. Deploying the SPIFFE Container Storage Interface driver Copy linkLink copied to clipboard!
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-adminrole. - You have installed Zero Trust Workload Identity Manager in the cluster.
Procedure
Create the
SpiffeCSIDriverCR:Create a YAML file that defines the
SpiffeCSIDriverCR object, for example,SpiffeCSIDriver.yaml:Example
SpiffeCSIDriver.yamlapiVersion: 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
hostPathvolume. Must be an absolute path with a maximum length of 256 characters. This value must matchSpireAgent.spec.socketPathfor 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
VolumeMountconfigurations. 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.
Apply the configuration by running the following command:
$ 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:
$ oc get daemonset -l app.kubernetes.io/name=spiffe-csi-driver -n zero-trust-workload-identity-managerExample output
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE spire-spiffe-csi-driver 3 3 3 3 3 <none> 114sVerify that the status of SPIFFE Container Storage Interface (CSI) Driver pods is
Runningby running the following command:$ oc get po -l app.kubernetes.io/name=spiffe-csi-driver -n zero-trust-workload-identity-managerExample 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
10.5.5. Deploying the SPIRE OpenID Connect Discovery Provider Copy linkLink copied to clipboard!
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-adminrole. - You have installed Zero Trust Workload Identity Manager in the cluster.
Procedure
Create the
SpireOIDCDiscoveryProviderCR:Create a YAML file that defines the
SpireOIDCDiscoveryProviderCR, for example,SpireOIDCDiscoveryProvider.yaml:Example
SpireOIDCDiscoveryProviderYAMLaapiVersion: 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, anderror. spec.logFormat-
Specifies the logging format for the SPIRE Server. The valid options are
textandjson. spec.csiDriverName-
Specifies the name of the CSI driver to use for mounting the Workload API socket. This must match the
SpiffeCSIDriver.spec.pluginNamevalue 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.jwtIssuervalue. 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
trueto have the Operator automatically create and maintain an OpenShift route for OIDC discovery endpoints (*.apps.). Set tofalsefor 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.
Apply the configuration by running the following command:
$ oc apply -f SpireOIDCDiscoveryProvider.yaml
Verification
Verify that the deployment of OIDC Discovery Provider is ready and available by running the following command:
$ oc get deployment -l app.kubernetes.io/name=spiffe-oidc-discovery-provider -n zero-trust-workload-identity-managerExample output
NAME READY UP-TO-DATE AVAILABLE AGE spire-spiffe-oidc-discovery-provider 1/1 1 1 2m58sVerify that the status of OIDC Discovery Provider pods is
Runningby running the following command:$ oc get po -l app.kubernetes.io/name=spiffe-oidc-discovery-provider -n zero-trust-workload-identity-managerExample output
NAME READY STATUS RESTARTS AGE spire-spiffe-oidc-discovery-provider-64586d599f-lcc94 2/2 Running 0 7m15s
10.5.6. Verify the health of the operands Copy linkLink copied to clipboard!
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:
oc get ZeroTrustWorkloadIdentityManager cluster -o yamlExample 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.
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.
10.5.7. Manually delete the custom security context constraints Copy linkLink copied to clipboard!
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
Confirm the SPIFFE CSI driver is ready by running the following command:
$ oc get spiffecsidriver cluster -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'The expected output includes
DaemonSetAvailable=TrueandReady=True.Confirm that the CSI
DaemonSetis available by running the following command:$ oc get ds spire-spiffe-csi-driver -n zero-trust-workload-identity-managerConfirm the CSI pods are running and are using the
privilegedSCC by running the following commands:$ oc get pods -n zero-trust-workload-identity-manager -l app.kubernetes.io/name=spiffe-csi-driver$ 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.After all checks have passed, delete the legacy custom SCC by running the following commands:
$ oc get scc spire-spiffe-csi-driver$ oc delete scc spire-spiffe-csi-driverIf the SCC is already absent, no action is needed.
10.6. Configuring the egress proxy for the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
Operator Lifecycle Manager (OLM) automatically configures managed Operators with proxy settings when you use a cluster-wide egress proxy. To support proxying HTTPS connections, you can inject certificate authority (CA) certificates into the Zero Trust Workload Identity Manager.
10.6.1. Injecting a custom CA certificate for the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
Inject certificate authority (CA) certificates into the Zero Trust Workload Identity Manager to support proxying HTTPS connections. This configuration helps ensure that the Identity Manager can communicate securely when you enable a cluster-wide proxy.
Prerequisites
-
You have access to the cluster as a user with the
cluster-adminrole. - You have enabled the cluster-wide proxy for OpenShift Container Platform.
- You have installed Zero Trust Workload Identity Manager 1.0.0 or later.
- You have deployed the SPIRE Server, SPIRE Agent, SPIFFE CSI Driver, and the SPIRE OIDC Discovery Provider operands in the cluster.
Procedure
Create a config map in the
zero-trust-workload-identity-managernamespace by running the following command:$ oc create configmap trusted-ca -n zero-trust-workload-identity-managerInject the CA bundle that is trusted by OpenShift Container Platform into the config map by running the following command:
$ oc label cm trusted-ca config.openshift.io/inject-trusted-cabundle=true -n zero-trust-workload-identity-managerUpdate the subscription for the Zero Trust Workload Identity Manager to use the config map by running the following command:
$ oc -n zero-trust-workload-identity-manager patch subscription openshift-zero-trust-workload-identity-manager --type='merge' -p '{"spec":{"config":{"env":[{"name":"TRUSTED_CA_BUNDLE_CONFIGMAP","value":"trusted-ca"}]}}}'
Verification
Verify that the operands have finished rolling out by running the following command:
$ oc rollout status deployment/zero-trust-workload-identity-manager-controller-manager -n zero-trust-workload-identity-manager && \ $ oc rollout status statefulset/spireserver -n zero-trust-workload-identity-manager && \ $ oc rollout status daemonset/spire-agent -n zero-trust-workload-identity-manager && \ $ oc rollout status deployment/spire-spiffe-oidc-discovery-provider -n zero-trust-workload-identity-managerExample output
deployment "zero-trust-workload-identity-manager-controller-manager" successfully rolled out statefulset "spire-server" successfully rolled out daemonset "spire-agent" successfully rolled out deployment "spire-spiffe-oidc-discovery-provider" successfully rolled outVerify that the CA bundle was mounted as a volume by running the following command:
$ oc get deployment zero-trust-workload-identity-manager -n zero-trust-workload-identity-manager -o=jsonpath={.spec.template.spec.'containers[0].volumeMounts'}$ oc get statefulset spire-server -n zero-trust-workload-identity-manager -o jsonpath='{.spec.template.spec.containers[*].volumeMounts[?(@.name=="trusted-ca-bundle")]}'$ oc get daemonset spire-agent -n zero-trust-workload-identity-manager -o jsonpath='{.spec.template.spec.containers[*].volumeMounts[?(@.name=="trusted-ca-bundle")]}'$ oc get daemonset spire-spiffe-csi-driver -n zero-trust-workload-identity-manager -o jsonpath='{.spec.template.spec.containers[*].volumeMounts[?(@.name=="trusted-ca-bundle")]}'Example output
[{{"mountPath":"/etc/pki/ca-trust/extracted/pem","name":"trusted-ca-bundle","readOnly":true}]Verify that the source of the CA bundle is the
trusted-caconfig map by running the following command:$ oc get deployment zero-trust-workload-identity-manager -n zero-trust-workload-identity-manager -o=jsonpath={.spec.template.spec.volumes}$ oc get statefulset spire-server -n zero-trust-workload-identity-manager -o=jsonpath='{.spec.template.spec.volumes}' | jq '.[] | select(.name=="trusted-ca-bundle")'$ oc get daemonset spire-agent -n zero-trust-workload-identity-manager -o=jsonpath='{.spec.template.spec.volumes}' | jq '.[] | select(.name=="trusted-ca-bundle")'$ oc get deployment spire-spiffe-oidc-discovery-provider -n zero-trust-workload-identity-manager -o=jsonpath='{.spec.template.spec.volumes}' | jq '.[] | select(.name=="trusted-ca-bundle")'Example output
{ "configMap": { "defaultMode": 420, "items": [ { "key": "ca-bundle.crt", "path": "tls-ca-bundle.pem" } ], "name": "trusted-ca" }, "name": "trusted-ca-bundle" }
10.7. Zero Trust Workload Identity Manager OIDC federation Copy linkLink copied to clipboard!
Ensure that your workloads can receive verifiable JSON Web Tokens (JWT-SVIDs) and allow external systems, such as cloud providers, to retrieve public keys from the discovery endpoint. Configure Zero Trust Workload Identity Manager to act as an OpenID Connect (OIDC) provider through the SPIRE server.
The following providers are verified to work with SPIRE OIDC federation:
- Azure Entra ID
- Vault
10.7.1. About the Entra ID OpenID Connect Copy linkLink copied to clipboard!
Integrate Entra ID OpenID Connect (OIDC) with SPIRE to provide workloads with automatic, short-lived cryptographic identities. This configuration allows you to securely authenticate services without maintaining static secrets.
10.7.1.1. Configuring the external certificate for the managed OIDC discovery provider route Copy linkLink copied to clipboard!
Configure the managed OIDC discovery provider route to use an externally managed TLS certificate. By referencing a TLS secret, you can secure the OIDC endpoint with your own certificate credentials.
Prerequisites
- You have installed Zero Trust Workload Identity Manager 0.2.0 or later.
- You have deployed the SPIRE Server, SPIRE Agent, SPIFFEE CSI Driver, and the SPIRE OIDC Discovery Provider operands in the cluster.
- You have installed the cert-manager Operator for Red Hat OpenShift. For more information, Installing the cert-manager Operator for Red Hat OpenShift.
-
You have created a
ClusterIssuerorIssuerconfigured with a publicly trusted CA service. For example, an Automated Certificate Management Environment (ACME) typeIssuerwith the "Let’s Encrypt ACME" service. For more information, see Configuring an ACME issuer
Procedure
Create a
Roleto provide the router service account permissions to read the referenced secret by running the following command:$ oc create role secret-reader \ --verb=get,list,watch \ --resource=secrets \ --resource-name=$TLS_SECRET_NAME \ -n zero-trust-workload-identity-managerCreate a
RoleBindingresource to bind the router service account with the newly created Role resource by running the following command:$ oc create rolebinding secret-reader-binding \ --role=secret-reader \ --serviceaccount=openshift-ingress:router \ -n zero-trust-workload-identity-managerConfigure the
SpireOIDCDIscoveryProviderCustom Resource (CR) object to reference the Secret generated in the earlier step by running the following command:$ oc patch SpireOIDCDiscoveryProvider cluster --type=merge -p=' spec: externalSecretRef: ${TLS_SECRET_NAME} '
Verification
In the
SpireOIDCDiscoveryProviderCR, check if theManageRouteReadycondition is set toTrueby running the following command:$ oc wait --for=jsonpath='{.status.conditions[?(@.type=="ManagedRouteReady")].status}'=True SpireOIDCDiscoveryProvider/cluster --timeout=120sVerify that the OIDC endpoint can be accessed securely through HTTPS by running the following command:
$ curl https://$JWT_ISSUER_ENDPOINT/.well-known/openid-configuration { "issuer": "https://$JWT_ISSUER_ENDPOINT", "jwks_uri": "https://$JWT_ISSUER_ENDPOINT/keys", "authorization_endpoint": "", "response_types_supported": [ "id_token" ], "subject_types_supported": [], "id_token_signing_alg_values_supported": [ "RS256", "ES256", "ES384" ] }%
10.7.1.2. Disabling a managed route Copy linkLink copied to clipboard!
If you want to fully control the behavior of exposing the OIDC Discovery Provider service, you can disable the managed route based on your requirements.
Procedure
To manually configure the OIDC Discovery Provider, set
managedRoutetofalseby running the following command:$ oc patch SpireOIDCDiscoveryProvider cluster --type=merge -p=' spec: managedRoute: "false"
10.7.1.3. Using Entra ID with Microsoft Azure Copy linkLink copied to clipboard!
Configure your Microsoft Azure environment to enable Entra ID integration with Azure. By defining variables and creating a resource group, you establish the infrastructure needed to securely manage workload identities.
Prerequisites
- You have configured the SPIRE OIDC Discovery Provider Route to serve the TLS certificates from a publicly trusted CA.
Procedure
Log in to Azure by running the following command:
$ az loginConfigure variables for your Azure subscription and tenant by running the following commands:
$ export SUBSCRIPTION_ID=$(az account list --query "[?isDefault].id" -o tsv)$ export TENANT_ID=$(az account list --query "[?isDefault].tenantId" -o tsv)$ export LOCATION=centraluswhere:
SUBSCRIPTION_ID- Specifies your unique subscription identifier.
TENANT_ID- Specifies the ID for your Azure Active Directory instance.
LOCATION- The Azure region where your resource is created.
Define resource variable names by running the following commands:
$ export NAME=ztwim$ export RESOURCE_GROUP="${NAME}-rg"$ export STORAGE_ACCOUNT="${NAME}storage"$ export STORAGE_CONTAINER="${NAME}storagecontainer"$ export USER_ASSIGNED_IDENTITY_NAME="${NAME}-identity"where:
NAME- Specifies A base name for all resources.
RESOURCE_GROUP- Specifies the name of the resource group.
STORAGE_ACCOUNT- Specifies the name for the storage account.
STORAGE_CONTAINER- Specifies the name for the storage container.
USER_ASSIGNED_IDENTITY_NAME- Specifies the name for a managed identity.
Create the resource group by running the following command:
$ az group create \ --name "${RESOURCE_GROUP}" \ --location "${LOCATION}"
10.7.1.4. Configuring Azure blob storage Copy linkLink copied to clipboard!
Create a new Microsoft Azure storage account and container to provide a dedicated location for your content. Configuring this storage ensures that the Zero Trust Workload Identity Manager can successfully store and retrieve blobs for your environment.
Procedure
Create a new storage account that is used to store content by running the following command:
$ az storage account create \ --name ${STORAGE_ACCOUNT} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --encryption-services blobObtain the storage ID for the newly created storage account by running the following command:
$ export STORAGE_ACCOUNT_ID=$(az storage account show -n ${STORAGE_ACCOUNT} -g ${RESOURCE_GROUP} --query id --out tsv)Create a storage container inside the newly created storage account to provide a location to support the storage of blobs by running the following command:
$ az storage container create \ --account-name ${STORAGE_ACCOUNT} \ --name ${STORAGE_CONTAINER} \ --auth-mode login
10.7.1.5. Configuring an Azure user managed identity Copy linkLink copied to clipboard!
Create a user-assigned managed identity in Azure to manage access control for your resources. You must also obtain the Client ID to associate roles with the service principal.
Procedure
Create a new User Managed Identity and then obtain the Client ID of the related Service Principal associated with the User Managed Identity by running the following command:
$ az identity create \ --name ${USER_ASSIGNED_IDENTITY_NAME} \ --resource-group ${RESOURCE_GROUP}$ export IDENTITY_CLIENT_ID=$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)Retrieve the
CLIENT_IDof an Azure user-assigned managed identity and save it as an environment variable by running the following command:$ export IDENTITY_CLIENT_ID=$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)Associate a role with the Service Principal associated with the User Managed Identity by running the following command:
$ az role assignment create \ --role "Storage Blob Data Contributor" \ --assignee "${IDENTITY_CLIENT_ID}" \ --scope ${STORAGE_ACCOUNT_ID}
10.7.1.6. Creating the demonstration application Copy linkLink copied to clipboard!
Create the demonstration application to verify that the entire system functions correctly. This process validates the configuration of your application secrets and namespaces.
Procedure
Set the application name and namespace by running the following commands:
$ export APP_NAME=workload-app$ export APP_NAMESPACE=demoCreate the namespace by running the following command:
$ oc create namespace $APP_NAMESPACECreate the application Secret by running the following command:
$ oc apply -f - << EOF apiVersion: v1 kind: Secret metadata: name: $APP_NAME namespace: $APP_NAMESPACE stringData: AAD_AUTHORITY: https://login.microsoftonline.com/ AZURE_AUDIENCE: "api://AzureADTokenExchange" AZURE_TENANT_ID: "${TENANT_ID}" AZURE_CLIENT_ID: "${IDENTITY_CLIENT_ID}" BLOB_STORE_ACCOUNT: "${STORAGE_ACCOUNT}" BLOB_STORE_CONTAINER: "${STORAGE_CONTAINER}" EOF
10.7.1.7. Deploying the workload application Copy linkLink copied to clipboard!
Deploy the workload application to your cluster to validate the Zero Trust Workload Identity Manager environment. This application confirms that the SPIFFE Workload API is functioning and can successfully retrieve JWT tokens.
Prerequisites
- The demonstration application has been created and deployed.
Procedure
To deploy the application, copy the entire command block provided and paste it directly into your terminal. Press Enter.
$ oc apply -f - << EOF apiVersion: v1 kind: ServiceAccount metadata: name: $APP_NAME namespace: $APP_NAMESPACE --- kind: Deployment apiVersion: apps/v1 metadata: name: $APP_NAME namespace: $APP_NAMESPACE spec: selector: matchLabels: app: $APP_NAME template: metadata: labels: app: $APP_NAME deployment: $APP_NAME spec: serviceAccountName: $APP_NAME containers: - name: $APP_NAME image: "registry.redhat.io/ubi9/python-311:latest" command: - /bin/bash - "-c" - | #!/bin/bash pip install spiffe azure-cli cat << EOF > /opt/app-root/src/get-spiffe-token.py #!/opt/app-root/bin/python from spiffe import JwtSource import argparse parser = argparse.ArgumentParser(description='Retrieve SPIFFE Token.') parser.add_argument("-a", "--audience", help="The audience to include in the token", required=True) args = parser.parse_args() with JwtSource() as source: jwt_svid = source.fetch_svid(audience={args.audience}) print(jwt_svid.token) EOF chmod +x /opt/app-root/src/get-spiffe-token.py while true; do sleep 10; done envFrom: - secretRef: name: $APP_NAME env: - name: SPIFFE_ENDPOINT_SOCKET value: unix:///run/spire/sockets/spire-agent.sock securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: false runAsNonRoot: true seccompProfile: type: RuntimeDefault ports: - containerPort: 8080 protocol: TCP volumeMounts: - name: spiffe-workload-api mountPath: /run/spire/sockets readOnly: true volumes: - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true EOF
Verification
Ensure that the
workload-apppod is running successfully by running the following command:$ oc get pods -n $APP_NAMESPACEExample output
NAME READY STATUS RESTARTS AGE workload-app-5f8b9d685b-abcde 1/1 Running 0 60sRetrieve the SPIFFE JWT Token (SVID-JWT):
Get the pod name dynamically by running the following command:
$ POD_NAME=$(oc get pods -n $APP_NAMESPACE -l app=$APP_NAME -o jsonpath='{.items[0].metadata.name}')Run the script inside the pod by running the following command:
$ oc exec -it $POD_NAME -n $APP_NAMESPACE -- \ /opt/app-root/src/get-spiffe-token.py -a "api://AzureADTokenExchange"
10.7.1.8. Configuring Azure with the SPIFFE identity federation Copy linkLink copied to clipboard!
Configure Microsoft Azure with SPIFFE identity federation to enable password-free, automated authentication for the demonstration application. This federates the User Managed Identity with the SPIFFE identity associated with your workload application.
Procedure
Federate the identities between the User Managed Identity and the SPIFFE identity associated with the workload application by running the following command:
$ az identity federated-credential create \ --name ${NAME} \ --identity-name ${USER_ASSIGNED_IDENTITY_NAME} \ --resource-group ${RESOURCE_GROUP} \ --issuer https://$JWT_ISSUER_ENDPOINT \ --subject spiffe://$APP_DOMAIN/ns/$APP_NAMESPACE/sa/$APP_NAME \ --audience api://AzureADTokenExchange
10.7.1.9. Verifying that the application workload can access the content in the Azure Blob Storage Copy linkLink copied to clipboard!
Verify that your application workload can connect to the Azure Blob Storage. By uploading a test file, you validate the authentication token and ensure that the workload has the correct permissions.
Prerequisites
- An Azure Blob Storage has been created.
Procedure
Retrieve a JWT token from the SPIFFE Workload API by running the following command:
$ oc rsh -n $APP_NAMESPACE deployment/$APP_NAMECreate and export an environment variable named
TOKENby running the following command:$ export TOKEN=$(/opt/app-root/src/get-spiffe-token.py --audience=$AZURE_AUDIENCE)Log in to Azure CLI included within the pod by running the following command:
$ az login --service-principal \ -t ${AZURE_TENANT_ID} \ -u ${AZURE_CLIENT_ID} \ --federated-token ${TOKEN}Create a new file with the application workload pod and upload the file to the Blob Storage by running the following command:
$ echo “Hello from OpenShift” > openshift-spire-federated-identities.txtUpload a file to the Azure Blog Storage by running the following command:
$ az storage blob upload \ --account-name ${BLOB_STORE_ACCOUNT} \ --container-name ${BLOB_STORE_CONTAINER} \ --name openshift-spire-federated-identities.txt \ --file openshift-spire-federated-identities.txt \ --auth-mode login
Verification
Confirm the file uploaded successfully by listing the files contained by running the following command:
$ az storage blob list \ --account-name ${BLOB_STORE_ACCOUNT} \ --container-name ${BLOB_STORE_CONTAINER} \ --auth-mode login \ -o table
10.7.2. About Vault OpenID Connect Copy linkLink copied to clipboard!
Use Vault OpenID Connect (OIDC) with SPIRE to securely authenticate workloads. Vault uses SPIRE as a trusted OIDC provider to validate workload identities. This configuration enables workloads to receive short-lived tokens to access secrets and perform actions within Vault.
10.7.2.1. Installing Vault Copy linkLink copied to clipboard!
Install HashiCorp Vault to serve as an OpenID Connect (OIDC) provider. This establishes the necessary infrastructure to manage workload identities securely in your Zero Trust Workload Identity Manager environment.
Prerequisites
- Configure a route. For more information, see Route configuration
- Helm is installed.
- A command-line JSON processor for easily reading the output from the Vault API.
- A HashiCorp Helm repository is added.
Procedure
Create the
vault-helm-value.yamlfile.global: enabled: true openshift: true tlsDisable: true injector: enabled: false server: ui: enabled: true image: repository: docker.io/hashicorp/vault tag: "1.19.0" dataStorage: enabled: true size: 1Gi standalone: enabled: true config: | listener "tcp" { tls_disable = 1 address = "[::]:8200" cluster_address = "[::]:8201" } storage "file" { path = "/vault/data" } extraEnvironmentVars: {}-
The
openshiftfield optimizes the deployment for OpenShift-specific security contexts. -
The
tlsDisablefield disables TLS for Kubernetes objects created by the chart. -
The
datastorage.enabledfield creates a 1Gi persistent volume to store Vault data. -
The
standalone.enabledfield deploys a single Vault pod. -
The
tls_disabledfield tells the Vault server to not use TLS.
-
The
Run the
helm installcommand:$ helm install vault hashicorp/vault \ --create-namespace -n vault \ --values ./vault-helm-value.yamlExpose the Vault service by running the following command:
$ oc expose service vault -n vaultSet the
VAULT_ADDRenvironment variable to retrieve the hostname from the new route and then export it by running the following command:$ export VAULT_ADDR="http://$(oc get route vault -n vault -o jsonpath='{.spec.host}')"Notehttp://is prepended because TLS is disabled.
Verification
To ensure your Vault instance is running, run the following command:
$ curl -s $VAULT_ADDR/v1/sys/health | jqExample output
{ "initialized": true, "sealed": true, "standby": true, "performance_standby": false, "replication_performance_mode": "disabled", "replication_dr_mode": "disabled", "server_time_utc": 1663786574, "version": "1.19.0", "cluster_name": "vault-cluster-a1b2c3d4", "cluster_id": "5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b" }
10.7.2.2. Initializing and unsealing Vault Copy linkLink copied to clipboard!
To prepare a newly installed Vault server for operation, initialize and unseal it. This process loads the primary encryption key into memory so that Vault can decrypt data and protect other encryption keys.
The steps to initialize a Vault server are:
- Initialize and unseal Vault
- Enable the key-value (KV) secrets engine and store a test secret
- Configure JSON Web Token (JWT) authentication with SPIRE
- Deploy a demonstration application
- Authenticate and retrieve the secret
Prerequisites
- Ensure that Vault is running.
- Ensure that Vault is not initialized. You can only initialize a Vault server once.
Procedure
Open a remote shell into the
vaultpod by running the following command:$ oc rsh -n vault statefulset/vaultInitialize Vault to get your unseal key and root token by running the following command:
$ vault operator init -key-shares=1 -key-threshold=1 -format=jsonExport the unseal key and root token you received from the earlier command by running the following commands:
$ export UNSEAL_KEY=<Your-Unseal-Key>$ export ROOT_TOKEN=<Your-Root-Token>Unseal Vault using your unseal key by running the following command:
$ vault operator unseal -format=json $UNSEAL_KEY-
Exit the pod by entering
exit.
Verification
To verify that the Vault pod is ready, run the following command:
$ oc get pod -n vaultExample output
NAME READY STATUS RESTARTS AGE vault-0 1/1 Running 0 65d
10.7.2.3. Enabling the key-value secrets engine and store a test secret Copy linkLink copied to clipboard!
Enable the key-value secrets engine to create a secure, centralized location for managing credentials. You can also store a test secret to verify that the engine is working.
Prerequisites
- Make sure that Vault is initialized and unsealed.
Procedure
Open another shell session in the
Vaultpod by running the following command:$ oc rsh -n vault statefulset/vaultExport your root token again within this new session and log in by running the following command:
$ export ROOT_TOKEN=<Your-Root-Token>$ vault login "${ROOT_TOKEN}"Enable the KV secrets engine at the
secret/path and create a test secret by running the following commands:$ export NAME=ztwim$ vault secrets enable -path=secret kv$ vault kv put secret/$NAME version=v0.1.0
Verification
To verify that the secret is stored correctly, run the following command:
$ vault kv get secret/$NAME
10.7.2.4. Configuring JSON Web Token authentication with SPIRE Copy linkLink copied to clipboard!
To help your applications securely log in to Vault using SPIFFE identities, configure JSON Web Token (JWT) authentication.
Prerequisites
- Make sure that Vault is initialized and unsealed.
- Ensure that a test secret is stored in the key-value secrets engine.
Procedure
On your local machine, retrieve the SPIRE Certificate Authority (CA) bundle and save it to a file by running the following command:
$ oc get cm -n zero-trust-workload-identity-manager spire-bundle -o jsonpath='{ .data.bundle\.crt }' > oidc_provider_ca.pemBack in the Vault pod shell, create a temporary file and paste the contents of
oidc_provider_ca.peminto it by running the following command:$ cat << EOF > /tmp/oidc_provider_ca.pem -----BEGIN CERTIFICATE----- <Paste-Your-Certificate-Content-Here> -----END CERTIFICATE----- EOF>Set up the necessary environment variables for the JWT configuration by running the following commands:
$ export APP_DOMAIN=<Your-App-Domain>$ export JWT_ISSUER_ENDPOINT="oidc-discovery.$APP_DOMAIN"$ export OIDC_URL="https://$JWT_ISSUER_ENDPOINT"$ export OIDC_CA_PEM="$(cat /tmp/oidc_provider_ca.pem)"Crate a new environment variable by running the following command:
$ export ROLE="${NAME}-role"Enable the JWT authentication method by running the following command:
$ vault auth enable jwtConfigure you ODIC authentication method by running the following command:
$ vault write auth/jwt/config \ oidc_discovery_url=$OIDC_URL \ oidc_discovery_ca_pem="$OIDC_CA_PEM" \ default_role=$ROLECreate a policy named
ztwim-policyby running the following command:$ export POLICY="${NAME}-policy"Grant read access to the secret you created earlier by running the following command:
$ vault policy write $POLICY -<<EOF path "secret/$NAME" { capabilities = ["read"] } EOFCreate the following environment variables by running the following commands:
$ export APP_NAME=client$ export APP_NAMESPACE=demo$ export AUDIENCE=$APP_NAMECreate a JWT role that binds the policy to workload with a specific SPIFFE ID by running the following command:
$ vault write auth/jwt/role/$ROLE -<<EOF { "role_type": "jwt", "user_claim": "sub", "bound_audiences": "$AUDIENCE", "bound_claims_type": "glob", "bound_claims": { "sub": "spiffe://$APP_DOMAIN/ns/$APP_NAMESPACE/sa/$APP_NAME" }, "token_ttl": "24h", "token_policies": "$POLICY" } EOF
10.7.2.5. Deploying a demonstration application Copy linkLink copied to clipboard!
Deploy a demonstration application to create a simple client that uses its SPIFFE identity to authenticate with Vault. By doing this you can verify that the client can successfully authenticate using the configured identity.
Procedure
On your local machine, set the environment variables for your application by running the following commands:
$ export APP_NAME=client$ export APP_NAMESPACE=demo$ export AUDIENCE=$APP_NAMEApply the Kubernetes manifest to create the namespace, service account, and deployment for the demo app by running the following command. This deployment mounts the SPIFFE CSI driver socket.
$ oc apply -f - <<EOF # ... (paste the full YAML from your provided code here) ... EOF>>
Verification
Verify that the client deployment is ready by running the following command:
$ oc get deploy -n $APP_NAMESPACEExample output
NAME READY UP-TO-DATE AVAILABLE AGE frontend-app 2/2 2 2 120d backend-api 3/3 3 3 120d
10.7.2.6. Authenticating and retrieving the secret Copy linkLink copied to clipboard!
Use the demonstration application to fetch a JWT token from the SPIFFE Workload API. Use the token to authenticate with Vault so that you can securely retrieve the secret and verify the workflow.
Procedure
Fetch a JWT-SVID by running the following command inside the running client pod:
$ oc -n $APP_NAMESPACE exec -it $(oc get pod -o=jsonpath='{.items[*].metadata.name}' -l app=$APP_NAME -n $APP_NAMESPACE) \ -- /opt/spire/bin/spire-agent api fetch jwt \ -socketPath /run/spire/sockets/spire-agent.sock \ -audience $AUDIENCECopy the token from the output and export it as an environment variable on your local machine by running the following command:
$ export IDENTITY_TOKEN=<Your-JWT-Token>Crate a new environment variable by running the following command:
$ export ROLE="${NAME}-role"Use
curlto send the JWT token to the Vault login endpoint to get a Vault client token by running the following command:$ VAULT_TOKEN=$(curl -s --request POST --data '{ "jwt": "'"${IDENTITY_TOKEN}"'", "role": "'"${ROLE}"'"}' "${VAULT_ADDR}"/v1/auth/jwt/login | jq -r '.auth.client_token')
Verification
Use the newly acquired Vault token to read the secret from the KV store by running the following command:
$ curl -s -H "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/secret/$NAME | jqYou should see the contents of the secret (
"version": "v0.1.0") in the output, confirming the entire workflow is successful
10.8. Zero Trust Workload Identity Manager SPIRE federation Copy linkLink copied to clipboard!
Configure SPIRE federation to enable workloads in different trust domains to securely authenticate each other across clusters, cloud providers, and organizational boundaries. By establishing trust relationships between separate SPIRE deployments, you can build a zero-trust architecture that spans multiple environments without compromising security or sharing secrets.
Federation works by securely sharing trust bundles between SPIRE servers through dedicated federation endpoints. Each SPIRE deployment maintains its own trust domain and cryptographic identity, while being able to verify identities from federated trust domains. This approach enables cross-cluster communication, multi-cloud deployments, and secure integration with external partners.
Setting up SPIRE federation involves the following high-level steps:
-
Choose an authentication profile: Select either
https_spiffeorhttps_web. - Configure the bundle endpoints: Each cluster exposes its trust bundle through a federation endpoint secured by the chosen authentication profile.
- Bootstrap the initial trust: Manually fetch and configure the initial trust bundle from each remote cluster.
-
Establish federation relationships: Create
ClusterFederatedTrustDomainresources to define which clusters trust each other. - Configure automatic synchronization: The SPIRE Controller Manager automatically keeps trust bundles synchronized after initial setup.
10.8.1. Understanding bundle endpoint profiles Copy linkLink copied to clipboard!
The bundle endpoint profile determines how your cluster exposes its trust bundle to other SPIRE deployments and how it authenticates remote clusters accessing the bundle. Choose the profile that best matches your security requirements and infrastructure.
The Zero Trust Workload Identity Manager supports two authentication profiles for federation:
- https_spiffe
- Uses SPIFFE-based TLS authentication. The SPIRE server presents its own SVID (SPIFFE Verifiable Identity Document) to authenticate itself to remote SPIRE servers. This profile provides strong cryptographic identity verification and is ideal for federation between SPIRE deployments.
- https_web
- Uses standard Web PKI (X.509 certificates from public or private certificate Authorities). This profile supports both automatic certificate management via ACME (Let’s Encrypt) and manual certificate management using tools like cert-manager.
The following table summarizes the key differences between the two profiles:
| Criteria | https_spiffe | https_web |
|---|---|---|
| Authentication method | SPIFFE SVID (TLS) | X.509 certificate from CA |
| Certificate management | Automatic (SPIRE-managed) | ACME (automatic) or manual |
| Trust model | SPIFFE trust domain | Web PKI / CA trust |
| Best for | Internal SPIRE-to-SPIRE federation | External federation, public endpoints |
| Security level | Very high (cryptographic identity) | High (CA-based trust) |
| Setup complexity | Medium (requires SPIFFE IDs) | Low (ACME) to Medium (manual certs) |
After enablement, federation cannot be disabled. The bundle endpoint profile is immutable once configured. Changing the profile or disabling federation requires reinstallation of the system. However, peer configurations (federatesWith) remain dynamic and can be added or removed at any time. Plan your profile selection carefully based on your long-term federation requirements.
10.8.2. Federation configuration examples Copy linkLink copied to clipboard!
The following examples demonstrate different SPIRE federation configurations. Use these as templates when setting up federation between your clusters.
- Example 1: Using ACME for automatic certificate management
The following example shows how to configure federation using Let’s Encrypt for automatic certificate provisioning and renewal:
apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_web refreshHint: 300 httpsWeb: acme: directoryUrl: https://acme-v02.api.letsencrypt.org/directory domainName: federation.apps.cluster1.example.com email: admin@example.com tosAccepted: "true" federatesWith: - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_web - trustDomain: cluster3.example.com bundleEndpointUrl: https://federation.apps.cluster3.example.com bundleEndpointProfile: https_web managedRoute: "true"-
The
profilefield useshttps_webprofile for Web PKI certificate-based authentication. -
The
directoryURLfield is used for the production directory. For testing, use staging URLhttps://acme-staging-v02.api.letsencrypt.org/directory
-
The
- Example 2: Using manual certificate management with cert-manager
The following example shows how to configure federation using externally managed certificates:
apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_web refreshHint: 300 httpsWeb: servingCert: fileSyncInterval: 86400 externalSecretRef: spire-server-federation-tls federatesWith: - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_web - trustDomain: cluster3.example.com bundleEndpointUrl: https://federation.apps.cluster3.example.com bundleEndpointProfile: https_web managedRoute: "true"-
The
fileSyncIntervalfield checks for certificate updates every 24 hours. -
The
externalSecretReffield is the name of the Kubernetes Secret containingtls.crtandtls.key
-
The
- Example 3: Using https_spiffe profile for SPIRE-to-SPIRE federation
The following example shows how to configure federation using SPIFFE-based TLS authentication:
apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_spiffe refreshHint: 300 federatesWith: - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_spiffe endpointSpiffeId: spiffe://cluster2.example.com/spire/server - trustDomain: cluster3.example.com bundleEndpointUrl: https://federation.apps.cluster3.example.com bundleEndpointProfile: https_spiffe endpointSpiffeId: spiffe://cluster3.example.com/spire/server managedRoute: "true"-
The
profilefield useshttps_spiffeprofile for SPIFFE-based TLS authentication. -
The
endpointSiffeIdfield contains the SPIFFE ID of the remote SPIRE server, required for identity validation.
-
The
- Example 4: Mixed federation with multiple authentication profiles
The following example shows a cluster federating with multiple remote clusters using different authentication profiles:
apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: internal-cluster.example.com federation: bundleEndpoint: profile: https_spiffe refreshHint: 300 federatesWith: # Internal cluster using SPIFFE TLS - trustDomain: dev-cluster.example.com bundleEndpointUrl: https://federation.apps.dev-cluster.example.com bundleEndpointProfile: https_spiffe endpointSpiffeId: spiffe://dev-cluster.example.com/spire/server # External partner using Web PKI - trustDomain: partner.example.com bundleEndpointUrl: https://federation.partner.example.com bundleEndpointProfile: https_web # Another external partner using Web PKI - trustDomain: vendor.example.com bundleEndpointUrl: https://spire-federation.vendor.example.com bundleEndpointProfile: https_web managedRoute: "true"-
The
profilefield cluster exposes its bundle usinghttps_spiffeprofile. -
The
bundleEndpointProfilefield cluster exposes its bundle usinghttps_spiffeprofile.
-
The
10.8.3. Configuring SPIRE federation with the https_spiffe profile Copy linkLink copied to clipboard!
The Zero Trust Workload Identity Manager includes SPIRE Federation support, allowing multiple independent SPIRE deployments to establish trust relationships. This procedure demonstrates how to configure federation using the https_spiffe profile, which uses SPIFFE-based TLS authentication between SPIRE servers.
Prerequisites
-
You have installed the OpenShift CLI (
oc). - You have installed the Zero Trust Workload Identity Manager on all clusters that will participate in the federation.
-
You have
cluster-adminprivileges on all participating clusters. - You have network connectivity between the clusters you intend to federate.
Procedure
Configure the
SpireServercustom resource on each cluster to enable federation with thehttps_spiffeprofile. Thehttps_spiffeprofile uses SPIFFE-based TLS authentication, where SPIRE servers authenticate to each other using their own SPIFFE Verifiable Identity Documents (SVIDs).apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_spiffe refreshHint: 300 managedRoute: "true"-
The
spec.trustDomainfield sets a unique trust domain for each cluster. -
The
spec.federation.bundleEnpoint.profilefield uses thehttps_spiffeprofile for SPIFFE-based TLS authentication. -
The
spec.federation.bundleEndpoint.refreshHintfield suggests intervals (in seconds) for remote servers to refresh the trust bundle. Range: 60-3600 seconds. -
The
spec.federation.managedRoutefield enables automatic route creation by the Operator.
-
The
Apply the configuration changes by running the following command:
$ oc apply -f spire-server.yamlCheck the status of the SPIRE Server by entering the following command. Wait for the
Readystatus to be returned.$ oc get spireserver cluster -wVerify that the federation route has been created:
$ oc get route -n zero-trust-workload-identity-manager | grep federationExample output
NAME HOST/PORT PATH SERVICES PORT TERMINATION spire-server-federation federation.apps.cluster1.example.com spire-server 8443 passthroughFetch the trust bundle from each remote cluster’s federation endpoint:
$ curl -k https://federation.apps.cluster2.example.com > cluster2-bundle.jsonNoteFor
https_spiffeprofile, you might need to use the-kflag if the certificate is not trusted by your system’s CA bundle:The response contains the trust bundle in JSON Web Key Set (JWKS) format:
Example trust bundle
{ "keys": [ { "use": "x509-svid", "kty": "RSA", "n": "...", "e": "AQAB", "x5c": ["..."] } ], "spiffe_sequence": 1, "refresh_hint": 300 }Create
ClusterFederatedTrustDomainresources for each remote trust domain.On Cluster 1, create a resource to federate with Cluster 2:
apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: federation-to-cluster2 spec: trustDomain: <CLUSTER2_APPS_DOMAIN> bundleEndpointURL: https://federation.<CLUSTER2_APPS_DOMAIN> bundleEndpointProfile: type: https_spiffe endpointSPIFFEID: spiffe://<CLUSTER2_APPS_DOMAIN>/spire/server className: zero-trust-workload-identity-manager-spire trustDomainBundle: | { "keys": [ { "use": "x509-svid", "kty": "RSA", "n": "...", "e": "AQAB", "x5c": ["..."] } ], "spiffe_sequence": 1 }where
<CLUSTER2_APPS_DOMAIN>- Specifies the trust domain of the external cluster you are federating with.
spec.bundleEndpointProfile.endpointSPIFFEID-
Specifies the SPIFFE ID of the remote SPIRE server. Required for
https_spiffeprofile to validate the remote server’s identity. spec.trustDomainBundle- Specifies the complete trust bundle JSON that you fetched in the previous step.
spec.className-
Specifies the name of a class to watch CRs for. Spire-controller-manager watches the resource only if
spec.classNameis set tozero-trust-workload-identity-manager-spire.
Apply the
ClusterFederatedTrustDomainresource by running the following command:$ oc apply -f clusterfederatedtrustdomain.yaml-
Repeat steps 5-7 on each cluster for every remote cluster it should federate with. For bidirectional federation, each cluster needs a
ClusterFederatedTrustDomainresource for every other cluster. Update the
SpireServerresource on each cluster to add thefederatesWithconfiguration:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_spiffe refreshHint: 300 federatesWith: - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_spiffe endpointSpiffeId: spiffe://cluster2.example.com/spire/server - trustDomain: cluster3.example.com bundleEndpointUrl: https://federation.apps.cluster3.example.com bundleEndpointProfile: https_spiffe endpointSpiffeId: spiffe://cluster3.example.com/spire/server managedRoute: "true"-
The
spec.federation.federatesWithfield lists all remote trust domains this cluster should federate with.
-
The
Apply the updated configuration by running the following command:
$ oc apply -f spireserver.yaml
Verification
Verify that the
ClusterFederatedTrustDomainresources have been created by running the following command:$ oc get clusterfederatedtrustdomainsExample output
NAME TRUST DOMAIN ENDPOINT URL AGE cluster2-federation cluster2.example.com https://federation.apps.cluster2.example.com 5m cluster3-federation cluster3.example.com https://federation.apps.cluster3.example.com 5mCheck the status of a
ClusterFederatedTrustDomainto ensure bundle synchronization is working by running the following command:$ oc describe clusterfederatedtrustdomain cluster2-federationLook for successful status conditions indicating that the trust bundle has been synchronized.
Verify that the federation endpoint is accessible by running the following command:
$ curl https://federation.apps.cluster1.example.comYou should receive a JSON response containing the trust bundle.
Check the SPIRE Server logs to confirm federation is active by running the following command:
$ oc logs -n zero-trust-workload-identity-manager \ statefulset/spire-server -c spire-server --tail=50Look for log messages indicating successful bundle synchronization with federated trust domains.
10.8.4. Using SPIRE federation with Automatic Certificate Management Environment protocol Copy linkLink copied to clipboard!
Using SPIRE federation with Automatic Certificate Management Environment (ACME) protocol provides automatic certificate provisioning from Let’s Encrypt. ACME also enables automatic certificate renewal before expiration, eliminating manual certificate management overhead.
Prerequisites
- You have installed the Zero Trust Workload Identity Manager on all clusters that will participate in the federation.
-
You have installed the OpenShift CLI (
oc). -
You have
cluster-adminprivileges on all participating clusters. - Your federation endpoints must be publicly accessible for Let’s Encrypt HTTP-01 challenge validation.
- You have network connectivity between all federated clusters.
Procedure
Configure the
SpireServercustom resource on each cluster to enable federation with ACME certificate management.Create or update your
SpireServerresource with the federation configuration:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_web refreshHint: 300 httpsWeb: acme: directoryUrl: https://acme-v02.api.letsencrypt.org/directory domainName: federation.apps.cluster1.example.com email: admin@example.com tosAccepted: "true" managedRoute: "true"-
The
spec.trustDomainfield sets a unique trust domain for each cluster (for example,cluster1.example.com,cluster2.example.com). -
The
spec.federation.bundleEndpoint.profilefield uses thehttps_webprofile for ACME-based certificate management. -
The
spec.federation.bundleEndpoint.httpsWeb.acme.directoryUrlfield contains the Let’s Encrypt production directory URL. For testing, use:https://acme-staging-v02.api.letsencrypt.org/directory. -
The
spec.federation.bundleEndpoint.httpsWeb.acme.domainNamefield is the domain name where your federation endpoint is accessible. This automatically sets tofederation.<cluster-apps-domain>ifmanagedRouteis set to "true". -
The
spec.federation.bundleEndpoint.httpsWeb.acme.emailfield is your email address for ACME account registration and certificate expiration notifications. -
The
spec.federation.bundleEndpoint.httpsWeb.acme.tosAcceptedfield accepts the Let’s Encrypt Terms of Service. -
The
spec.federation.managedRoutefield enables an automatic route creation by the operator for the federation bundle endpoint.
-
The
Apply the configuration to each cluster by running the following command:
$ oc apply -f spireserver.yamlCheck the status of the SPIRE Server by entering the following command. Wait for the
Readystatus to be returned before proceeding to the next step.$ oc get spireserver cluster -wExample output
NAME STATUS AGE cluster Ready 5mVerify that the federation route has been created by running the following command:
$ oc get route -n zero-trust-workload-identity-manager | grep federationExample output
NAME HOST/PORT PATH SERVICES PORT TERMINATION spire-server-federation federation.apps.cluster1.example.com spire-server 8443 passthroughOn each cluster, fetch the trust bundle from the federation endpoint by running the following command:
$ curl https://federation.apps.cluster1.example.com > cluster1-bundle.jsonThe response contains the trust bundle in JSON Web Key Set (JWKS) format:
Example trust bundle
{ "keys": [ { "use": "x509-svid", "kty": "RSA", "n": "...", "e": "AQAB", "x5c": ["..."] } ], "spiffe_sequence": 1, "refresh_hint": 300 }Create
ClusterFederatedTrustDomainresources to establish federation relationships.On Cluster 1, create resources to federate with Cluster 2 and Cluster 3:
apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: cluster2-federation spec: trustDomain: cluster2.example.com bundleEndpointURL: https://federation.apps.cluster2.example.com bundleEndpointProfile: type: https_web className: zero-trust-workload-identity-manager-spire trustDomainBundle: | { "keys": [...], "spiffe_sequence": 1 } --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: cluster3-federation spec: trustDomain: cluster3.example.com bundleEndpointURL: https://federation.apps.cluster3.example.com bundleEndpointProfile: type: https_web className: zero-trust-workload-identity-manager-spire trustDomainBundle: | { "keys": [...], "spiffe_sequence": 1 }-
The
spec.trustDomainBundlefield contains the complete trust bundle JSON that you fetched usingcurlin step 5. -
The
spec.classNamefield contains the name of a class to watch CRs for. Spire-controller-manager watches the resource only ifspec.classNameis set tozero-trust-workload-identity-manager-spire.
-
The
Apply the
ClusterFederatedTrustDomainresources by running the following command:$ oc apply -f cluster-federated-trust-domains.yaml-
Repeat steps 6 and 7 on each cluster to establish bidirectional federation. Each cluster needs
ClusterFederatedTrustDomainresources for every other cluster it federates with. Update the
SpireServerresource on each cluster to add thefederatesWithconfiguration:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: # ... existing configuration ... federation: bundleEndpoint: # ... existing bundleEndpoint configuration ... federatesWith: - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_web - trustDomain: cluster3.example.com bundleEndpointUrl: https://federation.apps.cluster3.example.com bundleEndpointProfile: https_web managedRoute: "true"-
The
spec.federation.federatesWithfield lists all remote trust domains this cluster should federate with.
-
The
Apply the updated configuration by running the following command:
$ oc apply -f spireserver.yaml
Verification
Verify that the
ClusterFederatedTrustDomainresources have been created by running the following command:$ oc get clusterfederatedtrustdomainsExample output
NAME TRUST DOMAIN ENDPOINT URL AGE cluster2-federation cluster2.example.com https://federation.apps.cluster2.example.com 5m cluster3-federation cluster3.example.com https://federation.apps.cluster3.example.com 5mCheck the status of a
ClusterFederatedTrustDomainto ensure bundle synchronization is working by running the following command:$ oc describe clusterfederatedtrustdomain cluster2-federationLook for
Successfulstatus conditions indicating that the trust bundle has been synchronized.Verify that the federation endpoint is accessible and serving the trust bundle by running the following command:
$ curl https://federation.apps.cluster1.example.comYou should receive a JSON response containing the trust bundle.
Check the SPIRE Server logs to confirm federation is active by running the following command:
$ oc logs -n zero-trust-workload-identity-manager deployment/spire-server -c spire-server --tail=50Look for log messages indicating successful bundle synchronization with federated trust domains.
Verify that all SPIRE components are running by running the following command:
$ oc get pods -n zero-trust-workload-identity-managerExample output
NAME READY STATUS RESTARTS AGE spire-agent-abcde 1/1 Running 0 10m spire-server-0 2/2 Running 0 10m- Optional: Test cross-cluster workload authentication by deploying workloads with SPIFFE identities on different clusters and verifying they can authenticate to each other using the federated trust.
10.8.5. Using SPIRE federation with manual certificate management Copy linkLink copied to clipboard!
You can use SPIRE federation with custom certificate management using cert-manager or other certificate providers. This approach provides flexibility for organizations that require control over certificate issuance, support for internal certificate authorities (CAs), or integration with existing certificate management infrastructure.
Prerequisites
- You have installed the Zero Trust Workload Identity Manager on all clusters that will participate in the federation.
-
You have installed the OpenShift CLI (
oc). -
You have
cluster-adminprivileges on all participating clusters. - You have installed the cert-manager Operator for Red Hat OpenShift. For more information, see cert-manager Operator for Red Hat OpenShift.
- Your federation endpoints must be publicly accessible for certificate validation.
- You have network connectivity between all federated clusters.
Procedure
Install the cert-manager Operator on the cluster where you want to use externally managed certificates.
Create a namespace and install the operator:
apiVersion: v1 kind: Namespace metadata: name: cert-manager-operator --- apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: openshift-cert-manager-operator namespace: cert-manager-operator spec: upgradeStrategy: Default --- apiVersion: operators.coreos.com/stable-v1 kind: Subscription metadata: name: openshift-cert-manager-operator namespace: cert-manager-operator spec: source: redhat-operators sourceNamespace: openshift-marketplace name: openshift-cert-manager-operator channel: stable-v1Apply the cert-manager installation by running the following command:
$ oc apply -f cert-manager-install.yamlCheck the status of the cert-manager Operator by entering the following command:
$ oc get pods -n cert-managerAll cert-manager pods should be in
Runningstatus.Create an Issuer for certificate provisioning.
For Let’s Encrypt with HTTP-01 challenge:
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-http01 namespace: zero-trust-workload-identity-manager spec: acme: server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-account-key solvers: - http01: ingress: ingressClassName: openshift-defaultAlternatively, for an internal CA:
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: internal-ca namespace: zero-trust-workload-identity-manager spec: ca: secretName: internal-ca-key-pairApply the Issuer by running the following command:
$ oc apply -f issuer.yamlDetermine the federation endpoint domain name.
The federation route follows a predictable naming pattern if
managedRouteis set totrue. Get your cluster’s application domain by running the following command:$ CLUSTER_DOMAIN=$(oc get ingresses.config/cluster -o jsonpath='{.spec.domain}') $ FEDERATION_DOMAIN="federation.${CLUSTER_DOMAIN}" $ echo "Federation domain will be: $FEDERATION_DOMAIN"Example output
Federation domain will be: federation.apps.cluster1.example.comNoteThe federation route is created automatically if
managedRouteis set totruewhen you apply theSpireServerconfiguration in a later step. The route name isspire-server-federationand the hostname isfederation.<cluster-apps-domain>.Create a Certificate resource to request a TLS certificate.
Use the federation domain determined in the previous step:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: spire-server-federation-tls namespace: zero-trust-workload-identity-manager spec: secretName: spire-server-federation-tls duration: 2160h renewBefore: 360h commonName: federation.apps.cluster1.example.com dnsNames: - federation.apps.cluster1.example.com usages: - server auth - digital signature - key encipherment issuerRef: kind: Issuer name: letsencrypt-http01-
The
secretNamefield must match theexternalSecretRefvalue in SpireServer. -
The
durationfield shows how long a certificate is valid. Certificates are valid for 90 days. -
The
renewBeforefield shows how many days a certificate must be renewed before it expires. Renew a certificate 15 days before expiration. -
The
commonNamefield must be replaced with your actual federation domain from the previous step. -
The
dnsNamesfield must match thecommonNameand the actual routehostnamethat was created. -
The
namefield must reference the Issuer that was created earlier.
-
The
Apply the Certificate resource by running the following command:
$ oc apply -f certificate.yamlMonitor the certificate issuance by running the following command:
$ oc get certificate spire-server-federation-tls \ -n zero-trust-workload-identity-manager -wExample output when ready
NAME READY SECRET AGE spire-server-federation-tls True spire-server-federation-tls 2mCreate RBAC permissions for the OpenShift Ingress Router to access the certificate secret.
Create a Role by running the following command:
$ oc create role secret-reader \ --verb=get,list,watch \ --resource=secrets \ --resource-name=spire-server-federation-tls \ -n zero-trust-workload-identity-managerCreate a
RoleBindingby running the following command:$ oc create rolebinding secret-reader-binding \ --role=secret-reader \ --serviceaccount=openshift-ingress:router \ -n zero-trust-workload-identity-managerConfigure the
SpireServercustom resource to use manual certificate management.Now that the certificate is ready, configure the SpireServer to reference it:
apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster1.example.com federation: bundleEndpoint: profile: https_web refreshHint: 300 httpsWeb: servingCert: fileSyncInterval: 86400 externalSecretRef: spire-server-federation-tls managedRoute: "true"-
The
profilefield must usehttps_webprofile for certificate-based authentication. -
The
fileSyncIntervalfield checks for certificate updates every 24 hours (86400 seconds). Range: 3600-7776000 seconds. -
The
externalSecretReffield is the name of the secret containing the TLS certificate and private key. Must match the certificate secret created in the previous steps.
-
The
Apply the configuration by running the following command:
$ oc apply -f spireserver.yamlWait for the SPIRE Server to be ready:
$ oc get spireserver cluster -n zero-trust-workload-identity-manager -wWait until the status shows
Ready.Verify that the federation route was created by running the following command:
$ oc get route spire-server-federation -n zero-trust-workload-identity-managerExample output
NAME HOST/PORT PATH SERVICES PORT TERMINATION spire-server-federation federation.apps.cluster1.example.com spire-server 8443 reencryptVerify that the route hostname matches the domain name used in your certificate.
Verify that the federation endpoint is accessible by running the following command:
$ curl https://$(oc get route spire-server-federation \ -n zero-trust-workload-identity-manager \ -o jsonpath='{.spec.host}')You should receive a JSON response containing the trust bundle.
Fetch the trust bundle from each federation endpoint that you want to federate with.
For each remote cluster, fetch its trust bundle by running the following commands:
$ curl https://federation.apps.cluster1.example.com > cluster1-bundle.json $ curl https://federation.apps.cluster2.example.com > cluster2-bundle.jsonThe trust bundle is in JSON Web Key Set (JWKS) format:
Example trust bundle
{ "keys": [ { "use": "x509-svid", "kty": "RSA", "n": "xGOzB...", "e": "AQAB", "x5c": ["MIIC..."] } ], "spiffe_sequence": 1, "refresh_hint": 300 }Create
ClusterFederatedTrustDomainresources for each remote trust domain you want to federate with:apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: cluster1-federation spec: trustDomain: cluster1.example.com bundleEndpointURL: https://federation.apps.cluster1.example.com bundleEndpointProfile: type: https_web className: zero-trust-workload-identity-manager-spire trustDomainBundle: | { "keys": [ { "use": "x509-svid", "kty": "RSA", "n": "xGOzB...", "e": "AQAB", "x5c": ["MIIC..."] } ], "spiffe_sequence": 1 } --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: cluster2-federation spec: trustDomain: cluster2.example.com bundleEndpointURL: https://federation.apps.cluster2.example.com bundleEndpointProfile: type: https_web className: zero-trust-workload-identity-manager-spire trustDomainBundle: | { "keys": [...], "spiffe_sequence": 1 }-
The
trustDomainBundlefield contains the complete trust bundle JSON that you fetched in the previous step. -
The
spec.classNamefield contains the name of a class to watch CRs for. Spire-controller-manager watches the resource only ifspec.classNameis set tozero-trust-workload-identity-manager-spire.
-
The
Apply the
ClusterFederatedTrustDomainresources by running the following command:$ oc apply -f clusterfederatedtrustdomains.yamlUpdate the
SpireServerresource to add thefederatesWithconfiguration:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: trustDomain: cluster3.example.com federation: bundleEndpoint: profile: https_web refreshHint: 300 httpsWeb: servingCert: fileSyncInterval: 86400 externalSecretRef: spire-server-federation-tls federatesWith: - trustDomain: cluster1.example.com bundleEndpointUrl: https://federation.apps.cluster1.example.com bundleEndpointProfile: https_web - trustDomain: cluster2.example.com bundleEndpointUrl: https://federation.apps.cluster2.example.com bundleEndpointProfile: https_web managedRoute: "true"-
The
federatesWithfield lists all remote trust domains this cluster should federate with.
-
The
Apply the updated configuration by running the following command:
$ oc apply -f spireserver.yamlRepeat steps 1-15 on each cluster that participates in the federation, ensuring that:
- Each cluster has cert-manager installed and configured
-
Each cluster has its own certificate created and ready before applying the
SpireServerconfiguration - Each cluster has the RBAC for the ingress router configured
-
Each cluster has
ClusterFederatedTrustDomainresources for every other cluster it federates with -
Each cluster’s
SpireServerhas the completefederatesWithlist
Verification
Verify that the certificate has been issued successfully by running the following command:
$ oc get certificate spire-server-federation-tls \ -n zero-trust-workload-identity-managerExample output
NAME READY SECRET AGE spire-server-federation-tls True spire-server-federation-tls 5mCheck the certificate details and expiration by running the following command:
$ oc get secret spire-server-federation-tls \ -n zero-trust-workload-identity-manager \ -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -datesExample output
notBefore=Dec 16 10:00:00 2025 GMT notAfter=Mar 16 10:00:00 2026 GMTVerify that the RBAC permissions are configured correctly by running the following command:
$ oc get role,rolebinding -n zero-trust-workload-identity-manager \ | grep secret-readerExample output
role.rbac.authorization.k8s.io/secret-reader rolebinding.rbac.authorization.k8s.io/secret-reader-bindingVerify the RoleBinding references the correct ServiceAccount by running the following command:
$ oc describe rolebinding secret-reader-binding \ -n zero-trust-workload-identity-managerExample output
Name: secret-reader-binding Namespace: zero-trust-workload-identity-manager Role: Kind: Role Name: secret-reader Subjects: Kind Name Namespace ---- ---- --------- ServiceAccount router openshift-ingressVerify that the
ClusterFederatedTrustDomainresources have been created by running the following command:$ oc get clusterfederatedtrustdomainsExample output
NAME TRUST DOMAIN ENDPOINT URL AGE cluster1-federation cluster1.example.com https://federation.apps.cluster1.example.com 5m cluster2-federation cluster2.example.com https://federation.apps.cluster2.example.com 5mCheck the status of a
ClusterFederatedTrustDomainto ensure bundle synchronization is working by running the following command:$ oc describe clusterfederatedtrustdomain cluster1-federationLook for successful status conditions indicating that the trust bundle has been synchronized.
Verify that the federation endpoint is accessible and using the correct certificate by running the following command:
$ curl -v https://$(oc get route spire-server-federation \ -n zero-trust-workload-identity-manager \ -o jsonpath='{.spec.host}')In the output, verify that the certificate presented is issued by your configured CA (Let’s Encrypt or internal CA).
Check the SPIRE Server logs to confirm that by running the following command:
- Federation is active with remote trust domains
- Trust bundles are being synchronized
The bundle endpoint is serving correctly
$ oc logs -n zero-trust-workload-identity-manager \ statefulset/spire-server -c spire-server --tail=100Look for log messages indicating successful federation bundle synchronization.
Verify that all SPIRE components are running by running the following command:
$ oc get pods -n zero-trust-workload-identity-managerExample output
NAME READY STATUS RESTARTS AGE spire-agent-abc123 1/1 Running 0 10m spire-server-0 2/2 Running 0 10m- Optional: Test cross-cluster workload authentication by deploying workloads with SPIFFE identities on different clusters and verifying they can authenticate to each other using the federated trust.
10.8.6. Federation configuration field reference Copy linkLink copied to clipboard!
This reference provides detailed information about all configuration fields available for SPIRE federation in the SpireServer custom resource. Use this reference when customizing your federation setup.
- Top-level federation fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| object | Yes | N/A | Configuration for this cluster’s federation endpoint that exposes the trust bundle to remote clusters. |
|
| array | No |
| List of remote trust domains to federate with. |
|
| string | No |
|
Enable or disable automatic OpenShift Route creation. Set to |
- bundleEndpoint configuration fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| string (enum) | Yes |
|
Authentication profile for the bundle endpoint. Valid values: |
|
| integer | No |
| Suggested interval (in seconds) for remote servers to refresh the trust bundle. Valid range: 60-3600. |
|
| object | Conditional | N/A |
Required when |
- httpsWeb configuration fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| object | Conditional | N/A |
ACME configuration for automatic certificate management. Mutually exclusive with |
|
| object | Conditional | N/A |
Manual certificate configuration. Mutually exclusive with |
- ACME configuration fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| string | Yes | N/A |
ACME directory URL. For Let’s Encrypt production: |
|
| string | Yes | N/A | Fully qualified domain name for the certificate. Typically the federation endpoint hostname. |
|
| string | Yes | N/A | Email address for ACME account registration and certificate expiration notifications. |
|
| string | No |
|
Accept the ACME provider’s Terms of Service. Must be |
- servingCert configuration fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| integer | No |
| Interval (in seconds) to check for certificate updates. Valid range: 3600-7776000 (1 hour to 90 days). |
|
| string | Yes | N/A |
Name of the Kubernetes Secret containing the TLS certificate ( |
- federatesWith configuration fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| string | Yes | N/A |
Trust domain name of the remote SPIRE deployment (for example, |
|
| string | Yes | N/A |
HTTPS URL of the remote federation endpoint (for example, |
|
| string (enum) | Yes | N/A |
Authentication profile of the remote endpoint. Valid values: |
|
| string | Conditional | N/A |
SPIFFE ID of the remote SPIRE server (for example, |
- Field validation rules
The following validation rules are enforced by the operator:
-
Profile immutability: The
bundleEndpoint.profilefield cannot be changed after initial configuration. Changing it requires deleting and recreating theSpireServerresource (re-installation of the system). -
Mutual exclusivity: Within
httpsWeb, only one ofacmeorservingCertcan be specified. -
Conditional requirements: When
profileishttps_web, thehttpsWebobject must be present with eitheracmeorservingCertconfigured. -
SPIFFE ID requirement: When
bundleEndpointProfileishttps_spiffein thefederatesWithlist, theendpointSpiffeIdfield is required. -
Array limits: The
federatesWitharray supports a maximum of 50 entries. Numeric ranges:
-
refreshHint: 60-3600 seconds -
fileSyncInterval: 3600-7776000 seconds
-
-
Profile immutability: The
10.9. Using the SPIFFE Helper container image Copy linkLink copied to clipboard!
SPIFFE Helper writes Transport Layer Security (TLS) certificates to disk for applications that cannot use the SPIFFE Workload API. Use it to eliminate manual certificate management and reduce the risk of expired certificates.
10.9.1. SPIFFE Helper container image Copy linkLink copied to clipboard!
Use SPIFFE Helper with the Zero Trust Workload Identity Manager when your application cannot call the workload API directly but can read TLS certificates from a shared volume.
SPIFFE Helper is a utility that connects to the SPIFFE Workload API, fetches identity credentials, writes them to files on disk, and optionally notifies a workload when X.509 material is renewed.
The Zero Trust Workload Identity Manager provides a supported SPIFFE Helper container image based on the upstream SPIFFE Helper. The configuration file format, command-line flags, and workload API behavior are compatible with upstream.
SPIFFE Helper performs the following tasks:
- Connects to the SPIFFE Workload API, usually through the SPIFFE Runtime Environment Agent socket exposed by the SPIFFE CSI driver.
- Fetches X.509 SPIFFE Verifiable Identity Document (SVID)s, JSON Web Token (JWT) SVIDs, and JWT bundles from SPIFFE Runtime Environment.
-
Writes credentials to files under a configured directory such as
cert_dir. - Optionally notifies a workload when X.509 authentication credentials are renewed in daemon mode.
10.9.2. SPIFFE Helper modes Copy linkLink copied to clipboard!
SPIFFE Helper fetches credentials from the SPIFFE Workload API and writes them to disk for workloads that cannot call the API directly. On OpenShift Container Platform, a pod typically uses non-daemon mode in an init container for initial certificates and daemon mode in a sidecar to rotate them before expiring.
SPIFFE Helper runs in one of two modes:
-
Non-daemon mode (
-daemon-mode=falseordaemon_mode = false): Fetches credentials once, writes files, and exits. Use this mode in an init container to bootstrap TLS before the main application starts. In this mode,cmdandrenew_signalare ignored. -
Daemon mode (default): Stays running until the pod is terminated or an unrecoverable error occurs. Watches the Workload API and renews credentials before they expire. Supports
cmd,pid_file_name,renew_signal, and optional HTTP health checks. SPIFFE Helper does not background itself like a traditional Unix daemon.
On OpenShift Container Platform, a typical pod uses both modes:
-
An init container runs SPIFFE Helper in non-daemon mode to populate a shared
emptyDirvolume with initial TLS material. - A sidecar container runs SPIFFE Helper in daemon mode to rotate certificates before they expire.
- The main application container mounts the shared certificate directory and uses the files for TLS.
The SPIFFE workload API is exposed through the SPIFFE container storage interface (CSI), which mounts the Workload API socket into the pod at a path such as /spiffe-workload-api/spire-agent.sock.
The following deployment excerpt shows the init container and sidecar pattern on OpenShift Container Platform:
initContainers:
- name: spiffe-helper-init
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>
args:
- '-config'
- /etc/spiffe-helper/helper.conf
- '-daemon-mode=false'
volumeMounts:
- name: spiffe-workload-api
readOnly: true
mountPath: /spiffe-workload-api
- name: postgresql-certs
mountPath: /opt/postgresql-certs
- name: spiffe-helper
mountPath: /etc/spiffe-helper
containers:
- name: spiffe-helper
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>
args:
- '-config'
- /etc/spiffe-helper/helper.conf
volumeMounts:
- name: spiffe-workload-api
readOnly: true
mountPath: /spiffe-workload-api
- name: postgresql-certs
mountPath: /opt/postgresql-certs
- name: spiffe-helper
mountPath: /etc/spiffe-helper
volumes:
- name: spiffe-workload-api
csi:
driver: csi.spiffe.io
readOnly: true
- name: postgresql-certs
emptyDir:
medium: Memory
- name: spiffe-helper
configMap:
name: spiffe-helper
Replace <version> with the tag that matches your Zero Trust Workload Identity Manager installation.
10.9.3. SPIFFE Helper credential types Copy linkLink copied to clipboard!
SPIFFE Helper supports X.509 SPIFFE Verifiable Identity Document (SVID), JSON Web Token (JWT) bundle, and JWT SVID outputs configured in the SPIFFE Helper configuration file and written under cert_dir for workloads that cannot use the workload API directly.
At least one complete set must be specified:
-
X.509 SVID — Requires
svid_file_name,svid_key_file_name, andsvid_bundle_file_name. SPIFFE Helper writes Privacy-Enhanced Mail (PEM) certificate, PEM private key, and PEM trust bundle files undercert_dir. -
JWT bundle — Requires
jwt_bundle_file_name. SPIFFE Helper writes a JSON bundle file. -
JWT SVIDs — Requires one or more
jwt_svidsblocks withjwt_audience,jwt_svid_file_name, and related settings. SPIFFE Helper writes base64-encoded token files.
The cert_dir directory must exist before SPIFFE Helper starts.
The following helper.conf excerpt configures X.509 SVID output for a PostgreSQL server:
agent_address = "/spiffe-workload-api/spire-agent.sock"
cert_dir = "/opt/postgresql-certs"
svid_file_name = "svid.pem"
svid_key_file_name = "svid.key"
svid_bundle_file_name = "svid_bundle.pem"
The following helper.conf excerpt configures JWT SVID output:
agent_address = "/spiffe-workload-api/spire-agent.sock"
cert_dir = "/opt/jwt-certs"
jwt_svids = [{
jwt_audience = "your-audience"
jwt_svid_file_name = "jwt_svid.token"
}]
For JWT bundle output, set jwt_bundle_file_name instead of the X.509 or JWT SVID file names.
10.9.4. Deploying PostgreSQL with SPIFFE Helper Copy linkLink copied to clipboard!
Deploy a PostgreSQL server and client that use SPIFFE Helper to fetch X.509 SPIFFE Verifiable Identity Document (SVID) from the SPIFFE Workload API and write them to disk for mutual Transport Layer Security (mTLS).
The manifests in this procedure use the Zero Trust Workload Identity Manager SPIFFE Helper image.
When certificates renew, the server must reload PostgreSQL TLS configuration. Containers in the same pod use separate process namespaces, so the SPIFFE Helper sidecar cannot signal the PostgreSQL container directly. Instead, SPIFFE Helper uses the managed-child pattern. This pattern in daemon mode it runs cmd (/usr/bin/psql) with cmd_args to execute SELECT pg_reload_conf(); as a child process inside the helper container whenever X.509 material is updated.
The stock SPIFFE Helper image does not include the psql client required for that reload command. Build a custom image that extends the Zero Trust Workload Identity Manager image and adds the PostgreSQL client package.
Prerequisites
-
You have access to the cluster with
cluster-adminprivileges. - You have installed the Zero Trust Workload Identity Manager.
-
The
ZeroTrustWorkloadIdentityManagercustom resource reportsReady.
Procedure
Create a
Containerfilewith the following contents:FROM registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version> USER 0 RUN dnf install -y postgresql && \ dnf clean all USER 65534Replace
<version>with the tag that matches your Zero Trust Workload Identity Manager installation.Build and push a custom SPIFFE Helper image for the PostgreSQL server by running the following command.
$ podman build -f Containerfile \ -t <registry>/spiffe-helper-postgresql:latest .$ podman push <registry>/spiffe-helper-postgresql:latestReplace
<registry>with your container registry.Create the PostgreSQL server resources by using the following example:
$ oc apply -f - << 'EOF' --- kind: Namespace apiVersion: v1 metadata: name: postgresql-spiffe --- kind: ServiceAccount apiVersion: v1 metadata: name: postgresql-spiffe namespace: postgresql-spiffe --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: postgresql-spiffe spec: className: zero-trust-workload-identity-manager-spire dnsNameTemplates: - postgresql-spiffe.postgresql-spiffe.svc namespaceSelector: matchLabels: kubernetes.io/metadata.name: postgresql-spiffe podSelector: matchLabels: app: postgresql-spiffe spiffeIDTemplate: 'spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}' --- kind: ConfigMap apiVersion: v1 metadata: name: postgres-config namespace: postgresql-spiffe data: pg_hba.conf: | # TYPE DATABASE USER ADDRESS METHOD local all all trust host postgres postgres 127.0.0.1/32 trust hostnossl postgres postgres 127.0.0.1/32 trust hostnossl all all 0.0.0.0/0 reject hostssl all all 0.0.0.0/0 cert postgresql.conf: | listen_addresses '*' ssl = on ssl_cert_file = '/opt/postgresql-certs/svid.pem' ssl_key_file = '/opt/postgresql-certs/svid.key' ssl_ca_file = '/opt/postgresql-certs/svid_bundle.pem' --- kind: ConfigMap apiVersion: v1 metadata: name: postgresql-init-db namespace: postgresql-spiffe data: initdb.sh: | # Copy configuration files cp /opt/pg_hba/pg_hba.conf /var/lib/pgsql/data/userdata # Create postgresql resources psql -U postgres <<!!EOF CREATE DATABASE $SPIFFE_DATABASE; CREATE USER $SPIFFE_USER WITH encrypted password '$SPIFFE_PASSWORD'; GRANT ALL privileges ON database $SPIFFE_DATABASE TO $SPIFFE_USER; \c $SPIFFE_DATABASE; CREATE TABLE test_table ( id bigserial primary key, name VARCHAR(255) NOT NULL, text VARCHAR(255) NOT NULL ); GRANT ALL privileges ON table test_table TO $SPIFFE_USER; GRANT ALL privileges ON sequence test_table_id_seq TO $SPIFFE_USER; !!EOF --- kind: ConfigMap apiVersion: v1 metadata: name: spiffe-helper namespace: postgresql-spiffe data: helper.conf: | agent_address = "/spiffe-workload-api/spire-agent.sock" cmd = "/usr/bin/psql" cmd_args = "-U postgres -h 127.0.0.1 -c \"SELECT pg_reload_conf();\"" cert_dir = "/opt/postgresql-certs" renew_signal = "" svid_file_name = "svid.pem" svid_key_file_name = "svid.key" svid_bundle_file_name = "svid_bundle.pem" --- kind: Service apiVersion: v1 metadata: name: postgresql-spiffe namespace: postgresql-spiffe spec: ports: - protocol: TCP port: 5432 targetPort: 5432 type: ClusterIP selector: app: postgresql-spiffe --- kind: Deployment apiVersion: apps/v1 metadata: name: postgresql-spiffe namespace: postgresql-spiffe labels: app: postgresql-spiffe spec: replicas: 1 selector: matchLabels: app: postgresql-spiffe template: metadata: labels: app: postgresql-spiffe spec: serviceAccountName: postgresql-spiffe securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault initContainers: - name: spiffe-helper-init image: <registry>/spiffe-helper-postgresql:latest args: - '-config' - /etc/spiffe-helper/helper.conf - '-daemon-mode=false' volumeMounts: - name: spiffe-workload-api readOnly: true mountPath: /spiffe-workload-api - name: postgresql-certs mountPath: /opt/postgresql-certs - name: spiffe-helper mountPath: /etc/spiffe-helper securityContext: capabilities: drop: - ALL containers: - name: postgresql-spiffe image: registry.redhat.io/rhel9/postgresql-16:latest env: - name: POSTGRESQL_USER value: user - name: POSTGRESQL_PASSWORD value: wearenotusingthissoIdontworryaboutit - name: SPIFFE_USER value: postgresql_spiffe - name: SPIFFE_PASSWORD value: somealsonotusedpassword - name: SPIFFE_DATABASE value: testdb - name: POSTGRESQL_DATABASE value: sampledb ports: - name: postgresql containerPort: 5432 protocol: TCP volumeMounts: - name: postgresql-certs mountPath: /opt/postgresql-certs - name: pg-hba mountPath: /opt/pg_hba - name: postgresql-init-db mountPath: /opt/app-root/src/postgresql-init - name: postgres-config mountPath: /opt/app-root/src/postgresql-cfg securityContext: capabilities: drop: - ALL - name: spiffe-helper image: <registry>/spiffe-helper-postgresql:latest args: - '-config' - /etc/spiffe-helper/helper.conf volumeMounts: - name: spiffe-workload-api readOnly: true mountPath: /spiffe-workload-api - name: postgresql-certs mountPath: /opt/postgresql-certs - name: spiffe-helper mountPath: /etc/spiffe-helper securityContext: capabilities: drop: - ALL volumes: - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true - name: postgresql-certs emptyDir: medium: Memory - name: spiffe-helper configMap: name: spiffe-helper - name: postgresql-init-db configMap: name: postgresql-init-db - name: pg-hba configMap: name: postgres-config items: - key: pg_hba.conf path: pg_hba.conf - name: postgres-config configMap: name: postgres-config items: - key: postgresql.conf path: postgresql.conf EOFReplace both
<registry>occurrences in the deployment with your container registry.Confirm that the SPIFFE Helper configuration matches the PostgreSQL TLS settings by running the following command:
$ oc get configmap spiffe-helper -n postgresql-spiffe \ -o jsonpath='{.data.helper\.conf}{"\n"}'The
cert_dirandsvid_*file names must match thessl_*_filepaths inpostgresql.conf. The serverhelper.confmust include settings similar to the following example:agent_address = "/spiffe-workload-api/spire-agent.sock" cmd = "/usr/bin/psql" cmd_args = "-U postgres -h 127.0.0.1 -c \"SELECT pg_reload_conf();\"" cert_dir = "/opt/postgresql-certs" renew_signal = "" svid_file_name = "svid.pem" svid_key_file_name = "svid.key" svid_bundle_file_name = "svid_bundle.pem"Create the PostgreSQL client by using the following example. The client uses the stock SPIFFE Helper image from the Zero Trust Workload Identity Manager. Only the server deployment requires the custom image with the
psqlclient.$ oc apply -f - << 'EOF' --- kind: Namespace apiVersion: v1 metadata: name: postgresql-spiffe-client --- kind: ServiceAccount apiVersion: v1 metadata: name: postgresql-spiffe-client namespace: postgresql-spiffe-client --- kind: ConfigMap apiVersion: v1 metadata: name: spiffe-helper namespace: postgresql-spiffe-client data: helper.conf: | agent_address = "/spiffe-workload-api/spire-agent.sock" cmd = "" cmd_args = "" cert_dir = "/opt/postgresql-certs" renew_signal = "" svid_file_name = "svid.pem" svid_key_file_name = "svid.key" svid_bundle_file_name = "svid_bundle.pem" --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: postgresql-spiffe-client spec: className: zero-trust-workload-identity-manager-spire dnsNameTemplates: - postgresql-spiffe namespaceSelector: matchLabels: kubernetes.io/metadata.name: postgresql-spiffe-client podSelector: matchLabels: app: postgresql-spiffe-client spiffeIDTemplate: 'spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}' --- kind: Deployment apiVersion: apps/v1 metadata: name: postgresql-spiffe-client namespace: postgresql-spiffe-client labels: app: postgresql-spiffe-client spec: replicas: 1 selector: matchLabels: app: postgresql-spiffe-client template: metadata: labels: app: postgresql-spiffe-client spec: serviceAccountName: postgresql-spiffe-client securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault containers: - name: postgresql-spiffe-client image: registry.redhat.io/rhel9/postgresql-16:latest command: - /bin/bash - '-c' - sleep infinity volumeMounts: - name: postgresql-certs mountPath: /opt/postgresql-certs securityContext: capabilities: drop: - ALL - name: spiffe-helper image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9: args: - '-config' - /etc/spiffe-helper/helper.conf volumeMounts: - name: spiffe-workload-api readOnly: true mountPath: /spiffe-workload-api - name: postgresql-certs mountPath: /opt/postgresql-certs - name: spiffe-helper mountPath: /etc/spiffe-helper securityContext: capabilities: drop: - ALL volumes: - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true - name: postgresql-certs emptyDir: medium: Memory - name: spiffe-helper configMap: name: spiffe-helper EOFReplace
<version>with the tag that matches your Zero Trust Workload Identity Manager installation.Confirm that pods are running in both namespaces by running the following commands:
$ oc get pods -n postgresql-spiffe$ oc get pods -n postgresql-spiffe-client
Verification
Inspect the server certificate by running the following command:
$ oc rsh -n postgresql-spiffe -c postgresql-spiffe \ deployment/postgresql-spiffe \ cat /opt/postgresql-certs/svid.pem | openssl x509 -noout -textThe server certificate should include the service hostname
postgresql-spiffe.postgresql-spiffe.svc.Inspect the client certificate by running the following command:
$ oc rsh -n postgresql-spiffe-client -c postgresql-spiffe-client \ deployment/postgresql-spiffe-client \ cat /opt/postgresql-certs/svid.pem | openssl x509 -noout -textThe certificate common name (CN) field should be
postgresql_spiffe.Connect to PostgreSQL from the client pod by running the following commands:
$ oc rsh -n postgresql-spiffe-client -c postgresql-spiffe-client \ deployment/postgresql-spiffe-client$ psql "host=postgresql-spiffe.postgresql-spiffe.svc port=5432 \ user=postgresql_spiffe dbname=testdb sslmode=verify-full \ sslcert=/opt/postgresql-certs/svid.pem \ sslkey=/opt/postgresql-certs/svid.key \ sslrootcert=/opt/postgresql-certs/svid_bundle.pem"If the deployment succeeded,
psqlconnects to the database.
10.9.5. SPIFFE Helper image reference Copy linkLink copied to clipboard!
Reference information for the SPIFFE Helper container image included with the Zero Trust Workload Identity Manager, including image location, command-line flags, configuration options, and volume requirements.
10.9.5.1. Image location Copy linkLink copied to clipboard!
The Zero Trust Workload Identity Manager provides a SPIFFE Helper image at the following location:
registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>
Replace <version> with the tag that matches your Zero Trust Workload Identity Manager installation. Some workloads require additional tools in the helper image.
10.9.5.2. Command-line flags Copy linkLink copied to clipboard!
| Flag | Description |
|---|---|
|
| Path to the SPIFFE Helper configuration file. Required. |
|
|
Controls operating mode. |
|
| Prints version information and exits. |
Examples of command-line flags
$ spiffe-helper -config /etc/spiffe-helper/helper.conf
$ spiffe-helper -config /etc/spiffe-helper/helper.conf -daemon-mode=false
10.9.5.3. Configuration file options Copy linkLink copied to clipboard!
SPIFFE Helper reads a configuration file. The following table describes the most common options for X.509 workloads on OpenShift Container Platform.
| Option | Description |
|---|---|
|
|
Path to the SPIFFE Runtime Environment Agent Workload API socket. On Linux, SPIFFE Helper connects with |
|
| Directory where SPIFFE Helper writes credential files. The directory must exist before SPIFFE Helper starts. |
|
|
When |
|
| File names for the X.509 SVID certificate, private key, and trust bundle. All three are required to enable X.509 output. |
|
| File name for a JWT bundle JSON file. |
|
| Block defining JWT SVID audiences and output file names. |
|
| Executable path for the managed-child pattern. On the first successful X.509 write, SPIFFE Helper starts this process. Ignored in non-daemon mode. |
|
|
Space-separated arguments for |
|
|
Path to a file containing one integer PID for the external-process pattern. Requires |
|
|
POSIX signal name sent on renewal, for example |
|
| Optional HTTP liveness and readiness endpoints. Available in daemon mode only. |
10.9.5.4. Volume and mount requirements Copy linkLink copied to clipboard!
When deploying SPIFFE Helper on OpenShift Container Platform, mount the following resources:
| Volume | Mount path | Access |
|---|---|---|
| SPIFFE Workload API (CSI) |
| Read-only |
|
SPIFFE Helper configuration ( |
Path passed to | Read-only |
|
Certificate directory ( |
Path matching | Read/write for the helper; read-only for the application container when possible |
CSI volume example
apiVersion: apps/v1
metadata:
name: postgresql-spiffe-client
namespace: postgresql-spiffe-client
labels:
app: postgresql-spiffe-client
# ...
volumes:
- name: spiffe-workload-api
csi:
driver: csi.spiffe.io
readOnly: true
Init and sidecar container example
apiVersion: apps/v1
metadata:
name: postgresql-spiffe
namespace: postgresql-spiffe
labels:
app: postgresql-spiffe
# ...
initContainers:
- name: spiffe-helper-init
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>
args:
- '-config'
- /etc/spiffe-helper/helper.conf
- '-daemon-mode=false'
volumeMounts:
- name: spiffe-workload-api
readOnly: true
mountPath: /spiffe-workload-api
- name: postgresql-certs
mountPath: /opt/postgresql-certs
- name: spiffe-helper
mountPath: /etc/spiffe-helper
containers:
- name: spiffe-helper
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>
args:
- '-config'
- /etc/spiffe-helper/helper.conf
volumeMounts:
- name: spiffe-workload-api
readOnly: true
mountPath: /spiffe-workload-api
- name: postgresql-certs
mountPath: /opt/postgresql-certs
- name: spiffe-helper
mountPath: /etc/spiffe-helper
SPIFFE Helper configuration example
agent_address = "/spiffe-workload-api/spire-agent.sock"
cert_dir = "/opt/postgresql-certs"
svid_file_name = "svid.pem"
svid_key_file_name = "svid.key"
svid_bundle_file_name = "svid_bundle.pem"
cmd = "/usr/bin/psql"
cmd_args = "-U postgres -h 127.0.0.1 -c \"SELECT pg_reload_conf();\""
renew_signal = ""
Replace <version> with the tag that matches your Zero Trust Workload Identity Manager installation.
10.9.5.5. Quick reference Copy linkLink copied to clipboard!
| Question | Answer |
|---|---|
|
What happens if both | SPIFFE Helper writes files, then runs managed-child logic, then PID-file logic. There is no priority between them. |
|
Is |
Yes. Validation fails if |
| Does SPIFFE Helper watch files for the application? | No. The application watches disk, polls, reads at connection time, or handles signals. |
| What happens in non-daemon mode? |
SPIFFE Helper fetches once, writes files, and exits. No watching, |
| What triggers workload notification? | Only X.509 updates in daemon mode. JWT-only refreshes write files only. |
| Can SPIFFE Helper signal another container in the same pod? |
Not by default. Containers use separate process namespaces unless |
10.9.6. Troubleshooting SPIFFE Helper deployments Copy linkLink copied to clipboard!
Resolve common issues when deploying the SPIFFE Helper image with Zero Trust Workload Identity Manager, and use the diagnostic oc commands to verify readiness, sidecars, and on-disk SVID certificates.
10.9.6.1. Common issues Copy linkLink copied to clipboard!
- Certificate output directory is empty
-
SPIFFE Helper cannot reach the Workload API, cannot write to
cert_dir, or the workload is not registered with SPIFFE Runtime Environment. Confirm theZeroTrustWorkloadIdentityManagercustom resource reportsReady, the CSI volume is mounted at the path inagent_address, and a matchingClusterSPIFFEIDexists for the pod namespace and labels. Verifycert_direxists before SPIFFE Helper starts; anemptyDirvolume satisfies this requirement. - Init container exits but TLS files are missing
-
The init container must run with
-daemon-mode=false. Check init container logs. If SPIFFE Helper reports configuration warnings about ignoredcmdorrenew_signal, that is expected in non-daemon mode. Ensure the certificate volume is shared with the main application container. - Sidecar runs but certificates are not renewed
-
Confirm the sidecar container does not set
-daemon-mode=false. Check sidecar logs for Workload API or write errors. If writes fail, SPIFFE Helper logs the error and does not runcmdor PID-file notification logic. - Application does not pick up renewed certificates
-
SPIFFE Helper overwrites files in
cert_dir; it does not notify your application unless you configurecmd,pid_file_name, or the application watches or polls the directory. For sidecar deployments with emptycmd, the application must reload TLS from disk. For PostgreSQL, configure the managed-child pattern withpsqlandpg_reload_conf()in the server sidecar. cmdorrenew_signalappears to have no effect-
In non-daemon mode, SPIFFE Helper ignores
cmdandrenew_signal. In daemon mode,cmdand PID-file logic run only after a successful X.509 write. JWT bundle or JWT SVID updates do not trigger them. Ifrenew_signalis empty whilecmdis set, the child starts on first write but later renewals send no signal. - Cannot signal the application container from the helper sidecar
-
Containers in the same pod use separate process namespaces by default.
pid_file_nameandcmdcannot signal another container unless you setshareProcessNamespace: trueor run the reload command as a child of SPIFFE Helper. The PostgreSQL example usescmdto runpsqllocally instead of signaling the database container. - Configuration validation fails for
pid_file_name -
renew_signalis required whenpid_file_nameis set.pid_file_nameis not valid in non-daemon mode. - Workload API socket is not accessible
-
Verify the CSI volume is mounted and the socket path matches
agent_address, typically/spiffe-workload-api/spire-agent.sock. Confirm the Zero Trust Workload Identity Manager operands are running and the pod service account matches theClusterSPIFFEIDselectors. - Permission denied when reading certificate files
-
Check file ownership and permissions on the shared volume. Consider setting
fsGroupin the podsecurityContextso the application container can read files written by SPIFFE Helper. Mount the certificate directory read-only in the application container when possible. - Image pull errors for the SPIFFE Helper image
-
Confirm your cluster can pull
registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9:<version>. If you use a custom image, verify the registry credentials and image reference in the deployment manifest. - PostgreSQL mTLS connection fails after deployment
-
Confirm SPIFFE Helper wrote files to
/opt/postgresql-certs, the client certificate CN matches the PostgreSQL user, andpg_hba.confrequires certificate authentication. Compare server and client SVIDs withopenssl x509 -noout -text.
Use the following oc commands when investigating SPIFFE Helper deployments and workloads such as PostgreSQL.
10.9.6.2. Verify Zero Trust Workload Identity Manager readiness Copy linkLink copied to clipboard!
$ oc get ZeroTrustWorkloadIdentityManager cluster \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
$ oc get clusterspiffeid
10.9.6.3. Inspect SPIFFE Helper pods Copy linkLink copied to clipboard!
$ oc get pods -n postgresql-spiffe
$ oc get pods -n postgresql-spiffe-client
10.9.6.4. Review SPIFFE Helper logs Copy linkLink copied to clipboard!
$ oc logs -n postgresql-spiffe deployment/postgresql-spiffe -c spiffe-helper-init
$ oc logs -n postgresql-spiffe deployment/postgresql-spiffe -c spiffe-helper
$ oc logs -n postgresql-spiffe-client deployment/postgresql-spiffe-client -c spiffe-helper
10.9.6.5. Verify on-disk SVID certificates Copy linkLink copied to clipboard!
$ oc rsh -n postgresql-spiffe deployment/postgresql-spiffe -c postgresql-spiffe \
-- ls -la /opt/postgresql-certs
$ oc rsh -n postgresql-spiffe deployment/postgresql-spiffe -c postgresql-spiffe -- \
cat /opt/postgresql-certs/svid.pem | openssl x509 -noout -dates -subject
10.10. Integrating Red Hat OpenShift Service Mesh with Zero Trust Workload Identity Manager in a single-cluster Copy linkLink copied to clipboard!
Deploy and configure SPIFFE Runtime Environment as the certificate authority (CA) for Red Hat OpenShift Service Mesh workloads, replacing the Istio built-in CA with SPIFFE-compliant identities and automatically rotated short-lived certificates.
10.10.1. SPIRE integration with Red Hat OpenShift Service Mesh Copy linkLink copied to clipboard!
Red Hat OpenShift Service Mesh integrates with Zero Trust Workload Identity Manager so Envoy sidecars obtain mTLS certificates from Secure Production Identity Framework for Everyone (SPIFFE) instead of Istio’s built-in CA, enabling cryptographically verified workload identities.
SPIRE provides cryptographic workload identities based on the Secure Production Identity Framework for Everyone (SPIFFE) standard. This integration enables a zero-trust security model where workload identities are cryptographically verified rather than relying on network-based authentication.
10.10.1.1. Component overview Copy linkLink copied to clipboard!
The following table summarizes the main components in a single-cluster SPIFFE and Red Hat OpenShift Service Mesh integration and what each one does.
| Component | Purpose |
|---|---|
| Zero Trust Workload Identity Manager | Manages SPIRE deployment on OpenShift Container Platform |
| SPIRE Server | Certificate Authority; issues SVIDs |
| SPIRE Agent | Runs on each node; provides SDS API to workloads |
| SPIFFE CSI Driver | Mounts SPIRE socket into pods |
| ClusterSPIFFEID | Registers which pods get which identities |
| Red Hat OpenShift Service Mesh Operator | Manages Istio deployment |
| Istiod | Istio control plane |
| Envoy Sidecar | Proxy in each pod; uses SPIRE for certificates |
10.10.2. SPIRE integration architecture components Copy linkLink copied to clipboard!
Learn about the key components in the SPIRE integration architecture and how they work together to enable zero-trust workload identity and automated certificate management for secure mTLS connections in Red Hat OpenShift Service Mesh.
- Zero Trust Workload Identity Manager
- Manages the SPIRE deployment lifecycle on OpenShift Container Platform, including custom resources for SPIRE Server, SPIRE Agent, and related components.
- SPIRE Server
- Acts as the certificate authority that issues SPIFFE Verifiable Identity Documents (SVIDs) to authenticated workloads.
- SPIRE Agent
- Runs as a DaemonSet on each cluster node, providing the Envoy Secret Discovery Service (SDS) API to workloads on that node.
- SPIFFE CSI Driver
- Mounts the SPIRE Agent UNIX domain socket into pods, enabling secure communication between Envoy sidecars and the SPIRE Agent.
- Red Hat OpenShift Service Mesh
-
Manages the Istio deployment through the
servicemeshoperator3Operator. - Istiod
- The Istio control plane that configures Envoy proxies but delegates certificate issuance to SPIRE.
- Envoy sidecar
- The proxy injected into each workload pod that uses SPIRE-issued certificates for mTLS connections.
10.10.3. Deploying SPIRE operands for Red Hat OpenShift Service Mesh integration Copy linkLink copied to clipboard!
Deploy SPIRE operands by creating the ZeroTrustWorkloadIdentityManager custom resource (CR) and related SPIRE operand CRs together. A running SPIRE deployment is required before you configure Red Hat OpenShift Service Mesh to use SPIRE-issued certificates for workload mTLS.
Prerequisites
- You have installed Zero Trust Workload Identity Manager.
-
The OpenShift CLI (
oc) is configured with access to the cluster. -
You have permissions to create custom resources in the
zero-trust-workload-identity-managernamespace.
Procedure
Set the environment variables by running the following commands:
$ export TRUST_DOMAIN=ocp.one $ export ZTWIM_NS=zero-trust-workload-identity-manager $ export JWT_ISSUER="https://oidc-discovery.$(oc get ingresses.config/cluster -o jsonpath={.spec.domain})"Deploy all SPIRE operand CRs, including the
ZeroTrustWorkloadIdentityManagerCR:Create the
ZeroTrustWorkloadIdentityManagerCR:$ oc apply -f - <<EOF 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: ${TRUST_DOMAIN} clusterName: "" bundleConfigMap: "spire-bundle" EOFCreate the
SpireServerCR:$ cat <<EOF | oc apply -f - apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: logLevel: "info" logFormat: "text" jwtIssuer: $JWT_ISSUER caValidity: "24h" defaultX509Validity: "1h" defaultJWTValidity: "5m" caKeytype: “rsa-2048” jwtKeyType: "rsa-2048" keyManager: “” caSubject: country: "US" organization: "RH" commonName: "SPIRE Server CA" persistence: size: "5Gi" accessMode: "ReadWriteOnce" datastore: databaseType: "sqlite3" connectionString: "/run/spire/data/datastore.sqlite3" tlsSecretName: "" maxOpenConns: 100 maxIdleConns: 10 connMaxLifetime: 0 disableMigration: "false" EOFWait for the SPIRE Server to become ready by running the following commands:
$ until oc get statefulset/spire-server -n "${ZTWIM_NS}" &> /dev/null; do sleep 3; done$ kubectl rollout status statefulset/spire-server -n "${ZTWIM_NS}" --timeout=300sCreate the
SpireAgentCR:$ cat <<EOF | oc apply -f - 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" EOFWait for the SPIRE Agent to become ready by running the following commands:
$ until oc get daemonset/spire-agent -n "${ZTWIM_NS}" &> /dev/null; do sleep 3; done$ kubectl rollout status daemonset/spire-agent -n "${ZTWIM_NS}" --timeout=300sDeploy the
SpiffeCSIDriverCR:$ cat <<EOF | oc apply -f - apiVersion: operator.openshift.io/v1alpha1 kind: SpiffeCSIDriver metadata: name: cluster spec: agentSocketPath: '/run/spire/agent-sockets' pluginName: "csi.spiffe.io" EOFWait for the SPIFFE CSI Driver to become ready by running the following commands:
$ until oc get daemonset/spire-spiffe-csi-driver -n "${ZTWIM_NS}" &> /dev/null; do sleep 3; done$ kubectl rollout status daemonset/spire-spiffe-csi-driver -n "${ZTWIM_NS}" --timeout=300sDeploy the
SpireOIDCDiscoveryProviderCR:$ export OIDC_DISCOVERY_CONFIG_MAP=spire-spiffe-oidc-discovery-provider$ cat <<EOF | oc apply -f - apiVersion: operator.openshift.io/v1alpha1 kind: SpireOIDCDiscoveryProvider metadata: name: cluster spec: logLevel: "info" logFormat: "text" csiDriverName: "csi.spiffe.io" jwtIssuer: $JWT_ISSUER replicaCount: 1 managedRoute: "true" EOFWait for the OIDC Discovery Provider to be created by running the following commands:
$ until oc get deployment spire-spiffe-oidc-discovery-provider -n "${ZTWIM_NS}" &> /dev/null; do sleep 3; done$ oc wait --for=condition=Available deployment/spire-spiffe-oidc-discovery-provider -n "${ZTWIM_NS}" --timeout=300s
Verification
Verify that Zero Trust Workload Identity Manager is installed:
Deploy the client workload and try to fetch a workload SVID:
$ cat <<EOF | oc apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: ztwim-client namespace: default labels: app: ztwim-client spec: selector: matchLabels: app: ztwim-client template: metadata: labels: app: ztwim-client spec: containers: - name: client image: ghcr.io/spiffe/spire-agent:1.5.1 command: ["/opt/spire/bin/spire-agent"] args: [ "api", "watch", "-socketPath", "/run/spire/sockets/spire-agent.sock" ] volumeMounts: - mountPath: /run/spire/sockets name: spiffe-workload-api readOnly: true volumes: - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true EOFWait for the client deployment to become ready by running the following command:
$ until oc get deployment ztwim-client -n default &> /dev/null; do sleep 3; done$ oc wait --for=condition=Available deployment/ztwim-client -n default --timeout=300s$ sleep 5
Verify that the x509 SVID is available by running the following command:
$ oc exec -it \ "$(oc get \ pods -o=jsonpath='{.items[0].metadata.name}' \ -l app=ztwim-client \ -n default \ )" -n default -- \ /opt/spire/bin/spire-agent \ api fetch -socketPath /run/spire/sockets/spire-agent.sockThe expected output is an SVID like the following example:
Received 1 svid after 29.636075ms SPIFFE ID: spiffe://ocp.one/ns/default/sa/default SVID Valid After: 2025-10-21 14:04:03 +0000 UTC SVID Valid Until: 2025-10-21 15:04:13 +0000 UTC CA #1 Valid After: 2025-10-21 07:38:03 +0000 UTC CA #1 Valid Until: 2025-10-22 07:38:13 +0000 UTCVerify that the JSON Web Token (JWT) SVID is available by running the following command:
$ oc exec -it \ "$(oc get \ pods -o=jsonpath='{.items[0].metadata.name}' \ -l app=ztwim-client \ -n default \ )" -n default -- \ /opt/spire/bin/spire-agent \ api fetch jwt -audience=sample-aud -socketPath /run/spire/sockets/spire-agent.sockThe expected output is a JWT SVID like the following example:
token(spiffe://ocp.one/ns/default/sa/default): eyJhbGciOiJSUzI1NiIsImtpZCI6Ij....IsIm bundle(spiffe://ocp.one): { "keys": [ { "kty": "RSA", "kid": "6k9PfhrAdfajT6jvLvR6bdomFvQxMeGf", "n": "wEYTV0ri4OOcdgEVgzN0...KhUEGf0NKxnuaeGQ", "e": "AQAB" } ] }Remove the client workload by running the following command:
$ oc delete deployment ztwim-client -n default
10.10.4. Deploying Red Hat OpenShift Service Mesh for SPIRE integration Copy linkLink copied to clipboard!
Deploy Red Hat OpenShift Service Mesh by creating the IstioCNI and Istio CRs with SPIRE integration settings so Envoy sidecars obtain SPIRE-issued certificates for workload mTLS after the SPIRE stack is running.
Prerequisites
- You have installed Zero Trust Workload Identity Manager.
-
The OpenShift CLI (
oc) is configured with access to the cluster. -
You have permissions to create namespaces and custom resources in the
istio-cniandistio-systemnamespaces. -
You have permissions to read secrets in the
zero-trust-workload-identity-managernamespace.
Procedure
Set the Istio environment variables by running the following commands:
$ export ZTWIM_NS=zero-trust-workload-identity-manager $ export TRUST_DOMAIN=ocp.one $ export JWT_ISSUER="https://oidc-discovery.$(oc get ingresses.config/cluster -o jsonpath={.spec.domain})" $ export OSSM_NS=istio-system $ export OSSM_CNI=istio-cni $ export VERIFY_NS=verify-ossm-ztwim $ export EXTRA_ROOT_CA="$(oc get secret oidc-serving-cert \ -n ${ZTWIM_NS} -o json | \ jq -r '.data."tls.crt"' | \ base64 -d | \ sed 's/^/ /')"Create the
IstioCNICR to deploy Istio CNI by running the following commands:$ oc new-project "${OSSM_CNI}" 2>/dev/null || oc project "${OSSM_CNI}"$ oc apply -f - <<EOF apiVersion: sailoperator.io/v1 kind: IstioCNI metadata: name: default spec: version: <version> namespace: ${OSSM_CNI} EOFwhere:
spec.version-
Replace
<version>with the Istio version supported by your Red Hat OpenShift Service Mesh Operator. You can find supported versions by runningoc get IstioCNI -o jsonpath='{.items[*].spec.version}'after the Operator is installed.
Wait for Istio CNI to become ready by running the following commands:
$ until oc get daemonset/istio-cni-node -n "${OSSM_CNI}" &> /dev/null; do sleep 3; done$ kubectl rollout status daemonset/istio-cni-node -n "${OSSM_CNI}" --timeout=300sThe
untilloop waits for the Red Hat OpenShift Service Mesh Operator to create theistio-cni-nodeDaemonSet. Theoc rollout statuscommand waits for the DaemonSet pods to become ready.Install the Istio CR with SPIRE integration by running the following commands:
$ oc new-project "${OSSM_NS}" 2>/dev/null$ cat <<EOF | oc apply -f - apiVersion: sailoperator.io/v1 kind: Istio metadata: name: default spec: namespace: istio-system updateStrategy: type: InPlace values: pilot: jwksResolverExtraRootCA: | ${EXTRA_ROOT_CA} env: PILOT_JWT_ENABLE_REMOTE_JWKS: "true" meshConfig: trustDomain: $TRUST_DOMAIN defaultConfig: proxyMetadata: WORKLOAD_IDENTITY_SOCKET_FILE: "spire-agent.sock" sidecarInjectorWebhook: templates: spire: | spec: initContainers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: true spireGateway: | spec: containers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: true EOFWait for all of the resources to become ready by running the following commands:
$ until oc get deployment istiod -n "${OSSM_NS}" &> /dev/null; do sleep 3; done$ oc wait --for=condition=Available deployment/istiod -n "${OSSM_NS}" --timeout=300s
Verification
Verify that Istio is integrated with SPIRE:
Create a test workload with the
spireinjection template by running the following commands:$ oc new-project "${VERIFY_NS}" 2>/dev/nullEnable the sidecar injection by running the following command:
$ oc label namespace "${VERIFY_NS}" istio-injection=enabledCreate the
httpbinworkload:$ cat <<EOF | oc apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: ${VERIFY_NS} spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "test-audience" labels: app: httpbin version: v1 spec: containers: - image: docker.io/mccutchen/go-httpbin:v2.15.0 imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8080 EOFWait for all of the resources to become ready by running the following commands:
$ until oc get deployment httpbin -n "${VERIFY_NS}" &> /dev/null; do sleep 3; done$ oc wait --for=condition=Available deployment/httpbin -n "${VERIFY_NS}" --timeout=300sVerify the SPIRE workload identity by running the following command:
$ HTTPBIN_POD=$(oc get pod -l app=httpbin -n "${VERIFY_NS}" -o jsonpath="{.items[0].metadata.name}") $ istioctl proxy-config secret "$HTTPBIN_POD" \ -n "${VERIFY_NS}" -o json \ | jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' \ | base64 --decode > chain.pem openssl x509 -in chain.pem -text | grep SPIREExample output
Issuer: C=US, O=RH, CN=<APP_DOMAIN>/serialNumber=... Subject: C=US, O=SPIREIf you see
SPIREin bothIssuerandSubject, the integration is working. Envoy is getting its certificates from SPIRE, not from Istio’s built-in CA.Remove the namespace by running the following command:
$ oc delete namespace "${VERIFY_NS}"
10.10.5. Additional resources Copy linkLink copied to clipboard!
10.11. Integrating SPIRE federation with multi-cluster Red Hat OpenShift Service Mesh Copy linkLink copied to clipboard!
Configure SPIFFE Runtime Environment (SPIRE) federation across multiple OpenShift Container Platform clusters to enable cross-cluster mutual TLS (mTLS) authentication and zero trust workload identity in a multi-cluster service mesh deployment.
10.11.1. Multi-cluster SPIFFE Runtime Environment integration with Red Hat OpenShift Service Mesh Copy linkLink copied to clipboard!
Understand how SPIFFE Runtime Environment (SPIRE) federation integrates with multi-cluster Red Hat OpenShift Service Mesh. Cross-cluster mutual TLS (mTLS) lets workloads on separate clusters authenticate each other under a unified zero-trust identity framework.
Multi-cluster SPIRE integration extends single-cluster SPIRE capabilities to enable workloads in different clusters to authenticate each other using Secure Production Identity Framework for Everyone (SPIFFE) identities. This eliminates the need for separate certificate authorities per cluster and enables true cross-cluster zero-trust architecture.
10.11.1.1. What gets federated Copy linkLink copied to clipboard!
Federation happens at two layers, and both are required:
| Layer | What | How |
|---|---|---|
| SPIRE Federation | Trust bundles |
SPIRE Servers exchange bundles via |
| Istio Federation | Service discovery and routing |
|
10.11.2. Preparing the environment for multi-cluster SPIFFE Runtime Environment federation Copy linkLink copied to clipboard!
Export kubeconfig paths, trust domains, federation endpoints, and JWT issuer URLs for Cluster A and Cluster B before you deploy federated SPIFFE Runtime Environment (SPIRE) operands on both clusters.
Prerequisites
- You have two OpenShift Container Platform clusters (4.x) with network connectivity between them.
- You have installed Zero Trust Workload Identity Manager on both clusters.
-
The OpenShift CLI (
oc) is configured with access to both clusters. -
You have installed the
istioctlCLI tool. -
You have installed
helm. This is used for gateway deployment. - You have Istio version 1.29.2 or later.
Procedure
Export the namespace variables by running the following commands:
$ export ZTWIM_NS=zero-trust-workload-identity-manager$ export OSSM_NS=istio-system$ export OSSM_CNI=istio-cniExport the kubeconfig file paths for Cluster A and Cluster B by running the following commands:
$ export CLUSTER_A_KUBECONFIG="/path/to/cluster-a/kubeconfig"$ export CLUSTER_B_KUBECONFIG="/path/to/cluster-b/kubeconfig"Set the base domain environment variables for Cluster A and Cluster B by running the following commands:
$ export CLUSTER_A_BASE_DOMAIN=$(oc get ingresses.config/cluster \ -o jsonpath='{.spec.domain}' --kubeconfig "${CLUSTER_A_KUBECONFIG}")$ export CLUSTER_B_BASE_DOMAIN=$(oc get ingresses.config/cluster \ -o jsonpath='{.spec.domain}' --kubeconfig "${CLUSTER_B_KUBECONFIG}")Export the trust domain environment variables from the base domain of each cluster by running the following commands:
$ export CLUSTER_A_TRUST_DOMAIN="${CLUSTER_A_BASE_DOMAIN}"$ export CLUSTER_B_TRUST_DOMAIN="${CLUSTER_B_BASE_DOMAIN}"Define the cluster and network environment variables by running the following commands:
$ export CLUSTER_A=cluster-a$ export CLUSTER_B=cluster-b$ export NETWORK_A=network-a$ export NETWORK_B=network-bExport the federation endpoint URLs for Cluster A and Cluster B by running the following commands:
$ export FEDERATION_ENDPOINT_A="https://federation.${CLUSTER_A_BASE_DOMAIN}"$ export FEDERATION_ENDPOINT_B="https://federation.${CLUSTER_B_BASE_DOMAIN}"Set the JWT issuer environment variables for Cluster A and Cluster B by running the following commands:
$ export JWT_ISSUER_A="https://oidc-discovery.${CLUSTER_A_BASE_DOMAIN}"$ export JWT_ISSUER_B="https://oidc-discovery.${CLUSTER_B_BASE_DOMAIN}"
10.11.3. Deploying SPIFFE Runtime Environment with federation on both clusters Copy linkLink copied to clipboard!
Deploy SPIFFE Runtime Environment (SPIRE) operand custom resources (CRs) with federation enabled on Cluster A and Cluster B, wait for operands to become ready, and verify SDS configuration.
Prerequisites
- You have completed preparing the environment for multi-cluster SPIRE federation. For more information, see "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" procedure are set.
Procedure
Deploy SPIRE with federation enabled on Cluster A:
Create a YAML file that defines the
ZeroTrustWorkloadIdentityManagerCR on Cluster A:apiVersion: operator.openshift.io/v1alpha1 kind: ZeroTrustWorkloadIdentityManager metadata: name: cluster spec: trustDomain: ${CLUSTER_A_TRUST_DOMAIN} clusterName: ${CLUSTER_A} bundleConfigMap: "spire-bundle"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireServerCR on Cluster A:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: logLevel: "info" logFormat: "text" jwtIssuer: $JWT_ISSUER_A caValidity: "24h" defaultX509Validity: "1h" defaultJWTValidity: "5m" caKeytype: "rsa-2048" jwtKeyType: "rsa-2048" keyManager: "" caSubject: country: "US" organization: "RH" commonName: "SPIRE Server CA" persistence: size: "5Gi" accessMode: "ReadWriteOnce" datastore: databaseType: "sqlite3" connectionString: "/run/spire/data/datastore.sqlite3" tlsSecretName: "" maxOpenConns: 100 maxIdleConns: 10 connMaxLifetime: 0 disableMigration: "false" federation: bundleEndpoint: profile: "https_spiffe"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireAgentCR on Cluster A: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" useNewContainerLocator: "true"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpiffeCSIDriverCR on Cluster A:apiVersion: operator.openshift.io/v1alpha1 kind: SpiffeCSIDriver metadata: name: cluster spec: agentSocketPath: "/run/spire/agent-sockets" pluginName: csi.spiffe.ioApply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireOIDCDiscoveryProviderCR on Cluster A:apiVersion: operator.openshift.io/v1alpha1 kind: SpireOIDCDiscoveryProvider metadata: name: cluster spec: logLevel: "info" logFormat: "text" csiDriverName: "csi.spiffe.io" jwtIssuer: $JWT_ISSUER_A replicaCount: 1 managedRoute: "true"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Deploy SPIRE with federation enabled on Cluster B:
Create a YAML file that defines the
ZeroTrustWorkloadIdentityManagerCR on Cluster B:apiVersion: operator.openshift.io/v1alpha1 kind: ZeroTrustWorkloadIdentityManager metadata: name: cluster spec: trustDomain: ${CLUSTER_B_TRUST_DOMAIN} clusterName: ${CLUSTER_B} bundleConfigMap: "spire-bundle"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireServerCR on Cluster B:apiVersion: operator.openshift.io/v1alpha1 kind: SpireServer metadata: name: cluster spec: logLevel: "info" logFormat: "text" jwtIssuer: $JWT_ISSUER_B caValidity: "24h" defaultX509Validity: "1h" defaultJWTValidity: "5m" caKeytype: "rsa-2048" jwtKeyType: "rsa-2048" keyManager: "" caSubject: country: "US" organization: "RH" commonName: "SPIRE Server CA" persistence: size: "5Gi" accessMode: "ReadWriteOnce" datastore: databaseType: "sqlite3" connectionString: "/run/spire/data/datastore.sqlite3" tlsSecretName: "" maxOpenConns: 100 maxIdleConns: 10 connMaxLifetime: 0 disableMigration: "false" federation: bundleEndpoint: profile: "https_spiffe"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireAgentCR on Cluster B: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" useNewContainerLocator: "true"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpiffeCSIDriverCR on Cluster B:apiVersion: operator.openshift.io/v1alpha1 kind: SpiffeCSIDriver metadata: name: cluster spec: agentSocketPath: "/run/spire/agent-sockets" pluginName: csi.spiffe.ioApply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>Create a YAML file that defines the
SpireOIDCDiscoveryProviderCR on Cluster B:apiVersion: operator.openshift.io/v1alpha1 kind: SpireOIDCDiscoveryProvider metadata: name: cluster spec: logLevel: "info" logFormat: "text" csiDriverName: "csi.spiffe.io" jwtIssuer: $JWT_ISSUER_B replicaCount: 1 managedRoute: "true"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
spire-serverStatefulSet to become ready on Cluster A by running the following command:$ oc rollout status statefulset/spire-server --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-agentDaemonSet to become ready on Cluster A by running the following command:$ oc rollout status daemonset/spire-agent --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-spiffe-csi-driverDaemonSet to become ready on Cluster A by running the following command:$ oc rollout status daemonset/spire-spiffe-csi-driver --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-spiffe-oidc-discovery-providerdeployment to become available on Cluster A by running the following command:$ oc wait --for=condition=Available deployment/spire-spiffe-oidc-discovery-provider \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-serverStatefulSet to become ready on Cluster B by running the following command:$ oc rollout status statefulset/spire-server --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-agentDaemonSet to become ready on Cluster B by running the following command:$ oc rollout status daemonset/spire-agent --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-spiffe-csi-driverDaemonSet to become ready on Cluster B by running the following command:$ oc rollout status daemonset/spire-spiffe-csi-driver --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300sWait for the
spire-spiffe-oidc-discovery-providerdeployment to become available on Cluster B by running the following command:$ oc wait --for=condition=Available deployment/spire-spiffe-oidc-discovery-provider \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} --timeout=300s
Verification
Verify that the SDS configuration is available on Cluster A by running the following command:
$ oc get cm spire-agent --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n "${ZTWIM_NS}" \ -o jsonpath='{.data.agent\.conf}' | grep -A5 '"sds"'Verify that the SDS configuration is available on Cluster B by running the following command:
$ oc get cm spire-agent --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n "${ZTWIM_NS}" \ -o jsonpath='{.data.agent\.conf}' | grep -A5 '"sds"'Example output
"sds": { "default_all_bundles_name": "ROOTCA", "default_bundle_name": "null" },
10.11.5. Configuring Red Hat OpenShift Service Mesh for multi-cluster SPIFFE Runtime Environment integration Copy linkLink copied to clipboard!
Configure Red Hat OpenShift Service Mesh on each cluster with federation settings, east-west gateways, and remote secrets to enable cross-cluster service communication by using SPIRE-issued certificates.
Prerequisites
- You deployed SPIFFE Runtime Environment (SPIRE) with federation for multi-cluster integration. For more information, see "Deploying SPIFFE Runtime Environment with federation on both clusters".
Procedure
Verify that the federation routes are created on Cluster A by running the following command:
$ oc get route -n ${ZTWIM_NS} --kubeconfig="${CLUSTER_A_KUBECONFIG}" | grep federationVerify that the federation routes are created on Cluster B by running the following command:
$ oc get route -n ${ZTWIM_NS} --kubeconfig="${CLUSTER_B_KUBECONFIG}" | grep federationOn Cluster A, create a
ClusterFederatedTrustDomainobject pointing to Cluster B by running the following command:Create a YAML file that defines the
ClusterFederatedTrustDomainCR on Cluster A:apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: federation-to-cluster-b spec: trustDomain: ${CLUSTER_B_TRUST_DOMAIN} bundleEndpointURL: ${FEDERATION_ENDPOINT_B} bundleEndpointProfile: type: https_spiffe endpointSPIFFEID: spiffe://${CLUSTER_B_TRUST_DOMAIN}/spire/serverApply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
On Cluster B, create a
ClusterFederatedTrustDomainobject pointing to Cluster A by running the following command:Create a YAML file that defines the
ClusterFederatedTrustDomainCR on Cluster B:apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterFederatedTrustDomain metadata: name: federation-to-cluster-a spec: trustDomain: ${CLUSTER_A_TRUST_DOMAIN} bundleEndpointURL: ${FEDERATION_ENDPOINT_A} bundleEndpointProfile: type: https_spiffe endpointSPIFFEID: spiffe://${CLUSTER_A_TRUST_DOMAIN}/spire/serverApply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Verification
Verify that the SPIRE Server on Cluster A has the trust bundle from Cluster B by running the following command:
$ oc exec --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} spire-server-0 -c spire-server -- \ spire-server bundle list -socketPath /tmp/spire-server/private/api.sock -format spiffe 2>&1 | head -5The output must show public keys for
${CLUSTER_B_TRUST_DOMAIN}.Example output
{ "trust_domains": { "${CLUSTER_B_TRUST_DOMAIN}": { "keys": [Verify that the SPIRE Server on Cluster B has the trust bundle from Cluster A by running the following command:
$ oc exec --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} spire-server-0 -c spire-server -- \ spire-server bundle list -socketPath /tmp/spire-server/private/api.sock -format spiffe 2>&1 | head -5The output must show public keys for
${CLUSTER_A_TRUST_DOMAIN}.Example output
{ "trust_domains": { "${CLUSTER_A_TRUST_DOMAIN}": { "keys": [Verify that the federation endpoint on Cluster A is reachable by running the following command:
$ curl -sk "${FEDERATION_ENDPOINT_A}" | python3 -c "import sys,json; print(f'Keys: {len(json.load(sys.stdin).get(\"keys\",[]))}')"The output must show at least one
x509-svidkey and onejwt-svidkey.Example output
Keys: 2Verify that the federation endpoint on Cluster B is reachable by running the following command:
$ curl -sk "${FEDERATION_ENDPOINT_B}" | python3 -c "import sys,json; print(f'Keys: {len(json.load(sys.stdin).get(\"keys\",[]))}')"The output must show at least one
x509-svidkey and onejwt-svidkey.Example output
Keys: 2
10.11.6. Deploying the Red Hat OpenShift Service Mesh CNI on both clusters Copy linkLink copied to clipboard!
Deploy the IstioCNI CR and federated ClusterSPIFFEID resources on Cluster A and Cluster B. This configures Red Hat OpenShift Service Mesh CNI networking and federated SPIFFE trust for cross-cluster mesh workloads.
Prerequisites
- You have configured Red Hat OpenShift Service Mesh for multi-cluster integration. For more information, see "Configuring Red Hat OpenShift Service Mesh for multi-cluster SPIFFE Runtime Environment integration".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
- You have installed Red Hat OpenShift Service Mesh 2.6.12 on both clusters.
Procedure
Create the Red Hat OpenShift Service Mesh CNI namespace on Cluster A by running the following command:
$ oc new-project "${OSSM_CNI}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" 2>/dev/null || trueDeploy the
IstioCNICR on Cluster A by running the following command:Create a YAML file that defines the
IstioCNICR on Cluster A:apiVersion: sailoperator.io/v1 kind: IstioCNI metadata: name: default spec: namespace: ${OSSM_CNI}Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Wait for the
istio-cni-nodeDaemonSet to be created on Cluster A by running the following command:$ until oc get daemonset/istio-cni-node --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n "${OSSM_CNI}" &> /dev/null; do sleep 3 doneWait for the
IstioCNIDaemonSet to become ready on Cluster A by running the following command:$ oc rollout status daemonset/istio-cni-node --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n "${OSSM_CNI}" --timeout=300sCreate the Red Hat OpenShift Service Mesh CNI namespace on Cluster B by running the following command:
$ oc new-project "${OSSM_CNI}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" 2>/dev/null || trueDeploy the
IstioCNICR on Cluster B by running the following command:Create a YAML file that defines the
IstioCNICR on Cluster B:apiVersion: sailoperator.io/v1 kind: IstioCNI metadata: name: default spec: namespace: ${OSSM_CNI}Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
istio-cni-nodeDaemonSet to be created on Cluster B by running the following command:$ until oc get daemonset/istio-cni-node --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n "${OSSM_CNI}" &> /dev/null; do sleep 3 doneWait for the
IstioCNIDaemonSet to become ready on Cluster B by running the following command:$ oc rollout status daemonset/istio-cni-node --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n "${OSSM_CNI}" --timeout=300sCreate the federated
ClusterSPIFFEIDresources on Cluster A by running the following command:Create a YAML file that defines the federated
ClusterSPIFFEIDresources on Cluster A:apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: sample-federation spec: className: zero-trust-workload-identity-manager-spire spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}" namespaceSelector: matchLabels: kubernetes.io/metadata.name: sample federatesWith: - "${CLUSTER_B_TRUST_DOMAIN}" --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: istio-system-federation spec: className: zero-trust-workload-identity-manager-spire spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}" namespaceSelector: matchLabels: kubernetes.io/metadata.name: istio-system federatesWith: - "${CLUSTER_B_TRUST_DOMAIN}"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Create federated
ClusterSPIFFEIDresources on Cluster B by running the following command:Create a YAML file that defines the federated
ClusterSPIFFEIDresources on Cluster B:apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: sample-federation spec: className: zero-trust-workload-identity-manager-spire spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}" namespaceSelector: matchLabels: kubernetes.io/metadata.name: sample federatesWith: - "${CLUSTER_A_TRUST_DOMAIN}" --- apiVersion: spire.spiffe.io/v1alpha1 kind: ClusterSPIFFEID metadata: name: istio-system-federation spec: className: zero-trust-workload-identity-manager-spire spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}" namespaceSelector: matchLabels: kubernetes.io/metadata.name: istio-system federatesWith: - "${CLUSTER_A_TRUST_DOMAIN}"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>ImportantDo not patch the default
ClusterSPIFFEID(zero-trust-workload-identity-manager-spire-default). The Zero Trust Workload Identity Manager reconciles and reverts manual changes. Instead, create customClusterSPIFFEIDresources for the specific namespaces.
10.11.7. Deploying the Istio custom resource with the federation configuration Copy linkLink copied to clipboard!
Deploy the Istio custom resource on Cluster A and Cluster B with SPIFFE Runtime Environment (SPIRE) federation and multi-cluster Red Hat OpenShift Service Mesh settings. This configures Istiod to obtain workload certificates from SPIRE and to trust SPIFFE bundles from both clusters for cross-cluster mTLS.
The Istio CR must include the following fields and values:
-
A
meshConfig.trustDomainvalue that matches the SPIRE trust domain. -
A
meshConfig.caCertificatesvalue with bundle URLs for both clusters. This handles cross-trust-domain validation. -
A
WORKLOAD_IDENTITY_SOCKET_FILEvalue for SPIRE SDS integration. -
A
jwksResolverExtraRootCAvalue for OIDC validation. -
A multi-cluster configuration that includes
meshID,clusterName, andnetwork. - A SPIRE injection template configuration.
Do not use meshConfig.trustDomainAliases. Use meshConfig.caCertificates with spiffeBundleUrl instead.
Prerequisites
- You have completed deploying the Istio Container Network Interface (CNI) on both clusters. For more information, see "Deploying Red Hat OpenShift Service Mesh CNI on both clusters".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
Procedure
Extract the OpenID Connect (OIDC) certificate on Cluster A by running the following command:
$ export EXTRA_ROOT_CA_A="$(oc get secret oidc-serving-cert \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${ZTWIM_NS} -o json | \ jq -r '.data."tls.crt"' | base64 -d | sed 's/^/ /')"Extract the OpenID Connect (OIDC) certificate on Cluster B by running the following command:
$ export EXTRA_ROOT_CA_B="$(oc get secret oidc-serving-cert \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${ZTWIM_NS} -o json | \ jq -r '.data."tls.crt"' | base64 -d | sed 's/^/ /')"Get the bundle endpoint URL for Cluster A by running the following command:
$ export BUNDLE_URL_A="${FEDERATION_ENDPOINT_A}"Get the bundle endpoint URL for Cluster B by running the following command:
$ export BUNDLE_URL_B="${FEDERATION_ENDPOINT_B}"Create the
Istiocustom resource (CR) on Cluster A by running the following command:$ oc new-project "${OSSM_NS}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" 2>/dev/null || trueApply the
IstioCR on Cluster A by running the following command:Create a YAML file that defines the
IstioCR on Cluster A:apiVersion: sailoperator.io/v1 kind: Istio metadata: name: default spec: namespace: istio-system updateStrategy: type: InPlace values: meshConfig: trustDomain: ${CLUSTER_A_TRUST_DOMAIN} defaultConfig: proxyMetadata: WORKLOAD_IDENTITY_SOCKET_FILE: "spire-agent.sock" caCertificates: - spiffeBundleUrl: ${BUNDLE_URL_A} trustDomains: - ${CLUSTER_A_TRUST_DOMAIN} - spiffeBundleUrl: ${BUNDLE_URL_B} trustDomains: - ${CLUSTER_B_TRUST_DOMAIN} global: meshID: mesh1 multiCluster: clusterName: ${CLUSTER_A} network: ${NETWORK_A} pilot: jwksResolverExtraRootCA: | ${EXTRA_ROOT_CA_A} env: ENABLE_CA_SERVER: "true" sidecarInjectorWebhook: templates: spire: | spec: initContainers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: true spireGw: | spec: containers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: trueApply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Create the
IstioCR on Cluster B by running the following command:$ oc new-project "${OSSM_NS}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" 2>/dev/null || trueApply the
IstioCR on Cluster B by running the following command:Create a YAML file that defines the
IstioCR on Cluster B:apiVersion: sailoperator.io/v1 kind: Istio metadata: name: default spec: namespace: istio-system updateStrategy: type: InPlace values: meshConfig: trustDomain: ${CLUSTER_B_TRUST_DOMAIN} defaultConfig: proxyMetadata: WORKLOAD_IDENTITY_SOCKET_FILE: "spire-agent.sock" caCertificates: - spiffeBundleUrl: ${BUNDLE_URL_B} trustDomains: - ${CLUSTER_B_TRUST_DOMAIN} - spiffeBundleUrl: ${BUNDLE_URL_A} trustDomains: - ${CLUSTER_A_TRUST_DOMAIN} global: meshID: mesh1 multiCluster: clusterName: ${CLUSTER_B} network: ${NETWORK_B} pilot: jwksResolverExtraRootCA: | ${EXTRA_ROOT_CA_B} env: ENABLE_CA_SERVER: "true" sidecarInjectorWebhook: templates: spire: | spec: initContainers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: true spireGw: | spec: containers: - name: istio-proxy volumeMounts: - name: workload-socket mountPath: /run/secrets/workload-spiffe-uds readOnly: true volumes: - name: workload-socket csi: driver: "csi.spiffe.io" readOnly: trueApply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
istioddeployment to be created on Cluster A by running the following command:$ until oc get deployment istiod --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n "${OSSM_NS}" &> /dev/null; do sleep 3 doneWait for
Istiodto become ready on Cluster A by running the following command:$ oc wait --for=condition=Available deployment/istiod \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n "${OSSM_NS}" --timeout=300sWait for the
istioddeployment to be created on Cluster B by running the following command:$ until oc get deployment istiod --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n "${OSSM_NS}" &> /dev/null; do sleep 3 doneWait for
Istiodto become ready on Cluster B by running the following command:$ oc wait --for=condition=Available deployment/istiod \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n "${OSSM_NS}" --timeout=300s
10.11.8. Verifying SPIRE integration with Istio on each cluster Copy linkLink copied to clipboard!
Verify that Red Hat OpenShift Service Mesh on Cluster A and Cluster B obtains workload certificates from SPIFFE Runtime Environment (SPIRE). This confirms Istio sidecars use SPIRE-issued identities rather than the built-in Istio certificate authority (CA) before you proceed with cross-cluster mesh verification.
Prerequisites
- You have deployed the Istio custom resource (CR) with the federation configuration. For more information, see "Deploying the Istio custom resource with the federation configuration".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
- Istiod is running and ready on Cluster A and Cluster B.
Procedure
Set the verification namespace variable by running the following command:
$ export VERIFY_NS=verify-ossm-ztwimPrepare the verification namespace on both clusters by running the following commands:
Create the verification namespace on Cluster A:
$ oc create namespace ${VERIFY_NS} --kubeconfig="${CLUSTER_A_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the verification namespace on Cluster A:
$ oc label namespace ${VERIFY_NS} istio-injection=enabled \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" --overwriteCreate the verification namespace on Cluster B:
$ oc create namespace ${VERIFY_NS} --kubeconfig="${CLUSTER_B_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the verification namespace on Cluster B:
$ oc label namespace ${VERIFY_NS} istio-injection=enabled \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" --overwrite
Deploy the
httpbinworkload on Cluster A by running the following command:Create a YAML file that defines the
httpbinDeploymenton Cluster A:apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: ${VERIFY_NS} spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "test-audience" labels: app: httpbin version: v1 spec: containers: - image: docker.io/mccutchen/go-httpbin:v2.15.0 imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8080Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Deploy the
httpbinworkload on Cluster B by running the following command:Create a YAML file that defines the
httpbinDeploymenton Cluster B:apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: ${VERIFY_NS} spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "test-audience" labels: app: httpbin version: v1 spec: containers: - image: docker.io/mccutchen/go-httpbin:v2.15.0 imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8080Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
httpbindeployment to become available on Cluster A by running the following command:$ oc rollout status deployment/httpbin \ -n "${VERIFY_NS}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" --timeout=300sWait for the
httpbindeployment to become available on Cluster B by running the following command:$ oc rollout status deployment/httpbin \ -n "${VERIFY_NS}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" --timeout=300sVerify the Envoy sidecar certificate on Cluster A by running the following commands:
Get the
httpbinpod name on Cluster A:$ HTTPBIN_POD=$(oc get pod -l app=httpbin -n "${VERIFY_NS}" \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" -o jsonpath="{.items[0].metadata.name}")Export the Envoy sidecar certificate chain for the
httpbinpod on Cluster A:$ istioctl --kubeconfig="${CLUSTER_A_KUBECONFIG}" proxy-config secret "${HTTPBIN_POD}" \ -n "${VERIFY_NS}" -o json \ | jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' \ | base64 --decode > chain-a.pemConfirm the certificate was issued by SPIRE on Cluster A:
$ openssl x509 -in chain-a.pem -text | grep SPIRE
Verify the Envoy sidecar certificate on Cluster B by running the following commands:
Get the
httpbinpod name on Cluster B:$ HTTPBIN_POD=$(oc get pod -l app=httpbin -n "${VERIFY_NS}" \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" -o jsonpath="{.items[0].metadata.name}")Export the Envoy sidecar certificate chain for the
httpbinpod on Cluster B:$ istioctl --kubeconfig="${CLUSTER_B_KUBECONFIG}" proxy-config secret "${HTTPBIN_POD}" \ -n "${VERIFY_NS}" -o json \ | jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' \ | base64 --decode > chain-b.pemConfirm the certificate was issued by SPIRE on Cluster B:
$ openssl x509 -in chain-b.pem -text | grep SPIREExample output
Issuer: C=US, O=RH, CN=<APP_DOMAIN>/serialNumber=... Subject: C=US, O=SPIREIf you see
SPIREin bothIssuerandSubjecton each cluster, Red Hat OpenShift Service Mesh is obtaining workload certificates from SPIRE rather than the Istio built-in CA.
Remove the verification namespace from both clusters by running the following commands:
Remove the verification namespace from Cluster A:
$ oc delete namespace ${VERIFY_NS} --kubeconfig="${CLUSTER_A_KUBECONFIG}" --ignore-not-foundRemove the verification namespace from Cluster B:
$ oc delete namespace ${VERIFY_NS} --kubeconfig="${CLUSTER_B_KUBECONFIG}" --ignore-not-found
10.11.9. Verifying workload mTLS with SPIRE-issued identities on each cluster Copy linkLink copied to clipboard!
Deploy httpbin and curl test workloads with SPIFFE Runtime Environment (SPIRE) sidecar injection on both clusters, enable STRICT mTLS with ISTIO_MUTUAL, and verify HTTP connectivity on each cluster. This confirms workloads use SPIRE-issued certificates under STRICT mTLS.
Prerequisites
- You have verified that SPIRE is integrated with Istio on each cluster. For more information, see "Verifying SPIRE integration with Istio on each cluster".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
- Istiod is running and ready on both clusters.
Procedure
Set the test environment variables by running the following commands:
Set the test namespace environment variable:
$ export TPJ=test-ossm-with-ztwimSet the SPIFFE audience environment variable:
$ export SPIFFE_AUDIENCE="sky-computing-demo"
Prepare the test namespace on both clusters by running the following commands:
Create the test namespace on Cluster A:
$ oc create namespace ${TPJ} --kubeconfig="${CLUSTER_A_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the test namespace on Cluster A:
$ oc label namespace ${TPJ} istio-injection=enabled \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" --overwriteCreate the test namespace on Cluster B:
$ oc create namespace ${TPJ} --kubeconfig="${CLUSTER_B_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the test namespace on Cluster B:
$ oc label namespace ${TPJ} istio-injection=enabled \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" --overwrite
Create the
httpbinserver on Cluster A by running the following command:Create a YAML file that defines the
httpbinServiceAccount,Service, andDeploymenton Cluster A:apiVersion: v1 kind: ServiceAccount metadata: name: httpbin namespace: ${TPJ} --- apiVersion: v1 kind: Service metadata: name: httpbin namespace: ${TPJ} labels: app: httpbin service: httpbin spec: ports: - name: http-ex-spiffe port: 443 targetPort: 8080 - name: http port: 80 targetPort: 8080 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: ${TPJ} spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "${SPIFFE_AUDIENCE}" labels: app: httpbin version: v1 spec: serviceAccountName: httpbin containers: - image: docker.io/mccutchen/go-httpbin:v2.15.0 imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8080Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Create the
httpbinserver on Cluster B by running the following command:Create a YAML file that defines the
httpbinServiceAccount,Service, andDeploymenton Cluster B:apiVersion: v1 kind: ServiceAccount metadata: name: httpbin namespace: ${TPJ} --- apiVersion: v1 kind: Service metadata: name: httpbin namespace: ${TPJ} labels: app: httpbin service: httpbin spec: ports: - name: http-ex-spiffe port: 443 targetPort: 8080 - name: http port: 80 targetPort: 8080 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: ${TPJ} spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "${SPIFFE_AUDIENCE}" labels: app: httpbin version: v1 spec: serviceAccountName: httpbin containers: - image: docker.io/mccutchen/go-httpbin:v2.15.0 imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8080Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
httpbindeployment to become available on both clusters by running the following commands:Wait for the
httpbindeployment on Cluster A:$ oc rollout status deployment/httpbin \ -n "${TPJ}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" --timeout=300sWait for the
httpbindeployment on Cluster B:$ oc rollout status deployment/httpbin \ -n "${TPJ}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" --timeout=300s
Create the
curlclient on Cluster A by running the following command:Create a YAML file that defines the
curlServiceAccount,Service, andDeploymenton Cluster A:apiVersion: v1 kind: ServiceAccount metadata: name: curl namespace: ${TPJ} --- apiVersion: v1 kind: Service metadata: name: curl namespace: ${TPJ} labels: app: curl service: curl spec: ports: - port: 80 name: http selector: app: curl --- apiVersion: apps/v1 kind: Deployment metadata: name: curl namespace: ${TPJ} spec: replicas: 1 selector: matchLabels: app: curl template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "${SPIFFE_AUDIENCE}" labels: app: curl spec: terminationGracePeriodSeconds: 0 serviceAccountName: curl containers: - name: curl image: curlimages/curl:8.16.0 command: - /bin/sh - -c - sleep inf imagePullPolicy: IfNotPresentApply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Create the
curlclient on Cluster B by running the following command:Create a YAML file that defines the
curlServiceAccount,Service, andDeploymenton Cluster B:apiVersion: v1 kind: ServiceAccount metadata: name: curl namespace: ${TPJ} --- apiVersion: v1 kind: Service metadata: name: curl namespace: ${TPJ} labels: app: curl service: curl spec: ports: - port: 80 name: http selector: app: curl --- apiVersion: apps/v1 kind: Deployment metadata: name: curl namespace: ${TPJ} spec: replicas: 1 selector: matchLabels: app: curl template: metadata: annotations: inject.istio.io/templates: "sidecar,spire" spiffe.io/audience: "${SPIFFE_AUDIENCE}" labels: app: curl spec: terminationGracePeriodSeconds: 0 serviceAccountName: curl containers: - name: curl image: curlimages/curl:8.16.0 command: - /bin/sh - -c - sleep inf imagePullPolicy: IfNotPresentApply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Wait for the
curldeployment to become available on both clusters by running the following commands:Wait for the
curldeployment on Cluster A:$ oc rollout status deployment/curl \ -n "${TPJ}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" --timeout=300sWait for the
curldeployment on Cluster B:$ oc rollout status deployment/curl \ -n "${TPJ}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" --timeout=300s
Verify that the
curlclient can reachhttpbinon both clusters before enablingSTRICTmTLS by running the following commands:Verify connectivity on Cluster A:
$ oc exec deploy/curl -n "${TPJ}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" -it -- \ curl -s -o /dev/null -w "%{http_code}" http://httpbinVerify connectivity on Cluster B:
$ oc exec deploy/curl -n "${TPJ}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" -it -- \ curl -s -o /dev/null -w "%{http_code}" http://httpbinExample output
200You must receive an HTTP
200status code on each cluster.
Enable
STRICTmTLS between the services on Cluster A by running the following command:Create a YAML file that defines the
PeerAuthenticationandDestinationRuleresources on Cluster A:apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: ${TPJ} spec: mtls: mode: STRICT --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: curl namespace: ${TPJ} spec: host: curl trafficPolicy: tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: httpbin namespace: ${TPJ} spec: host: httpbin trafficPolicy: tls: mode: ISTIO_MUTUALApply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Enable
STRICTmTLS between the services on Cluster B by running the following command:Create a YAML file that defines the
PeerAuthenticationandDestinationRuleresources on Cluster B:apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: ${TPJ} spec: mtls: mode: STRICT --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: curl namespace: ${TPJ} spec: host: curl trafficPolicy: tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: httpbin namespace: ${TPJ} spec: host: httpbin trafficPolicy: tls: mode: ISTIO_MUTUALApply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>
Verify that the
curlclient can reachhttpbinon both clusters withSTRICTmTLS enabled by running the following commands:Verify connectivity on Cluster A:
$ oc exec deploy/curl -n "${TPJ}" --kubeconfig="${CLUSTER_A_KUBECONFIG}" -it -- \ curl -s -o /dev/null -w "%{http_code}" http://httpbinVerify connectivity on Cluster B:
$ oc exec deploy/curl -n "${TPJ}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" -it -- \ curl -s -o /dev/null -w "%{http_code}" http://httpbinExample output
200If you receive an HTTP
200status code on each cluster, Red Hat OpenShift Service Mesh workloads are communicating underSTRICTmTLS using SPIRE-issued identities.
Remove the test namespace from both clusters by running the following commands:
Remove the test namespace from Cluster A:
$ oc delete namespace ${TPJ} --kubeconfig="${CLUSTER_A_KUBECONFIG}" --ignore-not-foundRemove the test namespace from Cluster B:
$ oc delete namespace ${TPJ} --kubeconfig="${CLUSTER_B_KUBECONFIG}" --ignore-not-found
10.11.10. Deploying east-west gateways Copy linkLink copied to clipboard!
Deploy SPIRE-enabled east-west gateways on both clusters using Helm. Red Hat OpenShift Service Mesh uses east-west gateways to connect cluster networks and enable secure cross-cluster communication in a multi-cluster mesh.
Prerequisites
- You deployed the Istio custom resource with the federation configuration. For more information, see "Deploying the Istio custom resource with the federation configuration".
- The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
-
Federated
ClusterSPIFFEIDresources exist on both clusters.
Procedure
Add the Istio Helm repository by running the following command:
$ helm repo add istio https://istio-release.storage.googleapis.com/chartsUpdate the Istio Helm repository by running the following command:
$ helm repo updateGrant security context constraints (SCC) permissions on Cluster A by running the following command:
$ oc adm policy add-scc-to-user anyuid \ -z istio-eastwestgateway -n istio-system --kubeconfig="${CLUSTER_A_KUBECONFIG}"Grant security context constraints (SCC) permissions on Cluster B by running the following command:
$ oc adm policy add-scc-to-user anyuid \ -z istio-eastwestgateway -n istio-system --kubeconfig="${CLUSTER_B_KUBECONFIG}"Install the Istio gateway on Cluster A by running the following command:
$ helm upgrade --install istio-eastwestgateway istio/gateway \ -n istio-system \ --set-json 'podAnnotations={"inject.istio.io/templates":"gateway,spireGw"}' \ --set name=istio-eastwestgateway \ --set networkGateway="${NETWORK_A}" \ --kubeconfig="${CLUSTER_A_KUBECONFIG}"Install the Istio gateway on Cluster B by running the following command:
$ helm upgrade --install istio-eastwestgateway istio/gateway \ -n istio-system \ --set-json 'podAnnotations={"inject.istio.io/templates":"gateway,spireGw"}' \ --set name=istio-eastwestgateway \ --set networkGateway="${NETWORK_B}" \ --kubeconfig="${CLUSTER_B_KUBECONFIG}"Wait for the east-west gateway to become available on Cluster A by running the following command:
$ oc wait --for=condition=Available deployment/istio-eastwestgateway \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n istio-system --timeout=300sWait for the east-west gateway to become available on Cluster B by running the following command:
$ oc wait --for=condition=Available deployment/istio-eastwestgateway \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n istio-system --timeout=300sCreate the cross-network
Gatewaycustom resource (CR) on Cluster A by running the following command:Create a YAML file that defines the
GatewayCR on Cluster A:apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: cross-network-gateway namespace: istio-system spec: selector: istio: eastwestgateway servers: - port: number: 15443 name: tls protocol: TLS tls: mode: AUTO_PASSTHROUGH hosts: - "*.local"Apply the YAML file on Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f <filename>
Create the cross-network
GatewayCR on Cluster B by running the following command:Create a YAML file that defines the
GatewayCR on Cluster B:apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: cross-network-gateway namespace: istio-system spec: selector: istio: eastwestgateway servers: - port: number: 15443 name: tls protocol: TLS tls: mode: AUTO_PASSTHROUGH hosts: - "*.local"Apply the YAML file on Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f <filename>The
GatewayCRs configure the east-west gateway deployment to accept cross-cluster TLS traffic on port 15443 usingAUTO_PASSTHROUGHmode. This preserves SPIRE-issued certificates for end-to-end mTLS.
Verification
Verify that the cross-network
Gatewayexists on Cluster A by running the following command:$ oc get gateway cross-network-gateway -n istio-system \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" \ -o jsonpath='{.spec.servers[0].tls.mode}{"\n"}'Example output
AUTO_PASSTHROUGHVerify that the cross-network
Gatewayexists on Cluster B by running the following command:$ oc get gateway cross-network-gateway -n istio-system \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" \ -o jsonpath='{.spec.servers[0].tls.mode}{"\n"}'Example output
AUTO_PASSTHROUGH
10.11.11. Exchanging remote secrets Copy linkLink copied to clipboard!
Create remote secrets on both clusters so Istiod can discover services in the peer cluster and route cross-cluster traffic through the east-west gateways.
Prerequisites
-
You have deployed the east-west gateway, including the cross-network
GatewayCR on both clusters. For more information, see "Deploying east-west gateways". - The environment variables from the "Preparing the environment for multi-cluster SPIFFE Runtime Environment federation" and "Deploying SPIFFE Runtime Environment with federation on both clusters" procedures are set.
-
The
istioctlCLI is available and configured for both clusters.
Procedure
Create an Istio remote secret on Cluster A by running the following command:
$ istioctl create-remote-secret \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" \ --name="${CLUSTER_A}" \ --istioNamespace=istio-system | \ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -f -Create an Istio remote secret on Cluster B by running the following command:
$ istioctl create-remote-secret \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" \ --name="${CLUSTER_B}" \ --istioNamespace=istio-system | \ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -f -Verify that the remote cluster is synced on Cluster A by running the following command:
$ istioctl remote-clusters --kubeconfig="${CLUSTER_A_KUBECONFIG}"The output must show
${CLUSTER_B}with statussynced.Example output
NAME STATUS SECRET cluster-b synced istio-remote-secret-cluster-bVerify that the remote cluster is synced on Cluster B by running the following command:
$ istioctl remote-clusters --kubeconfig="${CLUSTER_B_KUBECONFIG}"The output must show
${CLUSTER_A}with statussynced.Example output
NAME STATUS SECRET cluster-a synced istio-remote-secret-cluster-a
10.11.12. Verifying cross-cluster service communication Copy linkLink copied to clipboard!
Verify cross-cluster service communication between Red Hat OpenShift Service Mesh clusters using sample workloads. This confirms SPIRE-issued identities and federated mesh routing enable end-to-end cross-cluster communication.
Prerequisites
-
You have deployed east-west gateways and created the cross-network
GatewayCR on both clusters. - You have exchanged remote secrets between clusters.
Procedure
Set the sample namespace environment variable by running the following command:
$ export SAMPLE_NS=sampleCreate the
samplenamespace on Cluster A by running the following command:$ oc create namespace ${SAMPLE_NS} --kubeconfig="${CLUSTER_A_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the
samplenamespace on Cluster A by running the following command:$ oc label namespace ${SAMPLE_NS} istio-injection=enabled \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" --overwriteCreate the
samplenamespace on Cluster B by running the following command:$ oc create namespace ${SAMPLE_NS} --kubeconfig="${CLUSTER_B_KUBECONFIG}" 2>/dev/null || trueEnable Istio injection for the
samplenamespace on Cluster B by running the following command:$ oc label namespace ${SAMPLE_NS} istio-injection=enabled \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" --overwriteInstall the Istio
HelloWorldServicein Cluster B by running the following command:Create a YAML file that defines the
HelloWorldServicein Cluster B:apiVersion: v1 kind: Service metadata: name: helloworld labels: app: helloworld service: helloworld spec: ports: - port: 5000 name: http selector: app: helloworldApply the YAML file in Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${SAMPLE_NS} -f <filename>
Install the
helloworld-v1Deploymentin Cluster B by running the following command:Create a YAML file that defines the
helloworld-v1Deploymentin Cluster B:apiVersion: apps/v1 kind: Deployment metadata: name: helloworld-v1 labels: app: helloworld version: v1 spec: replicas: 1 selector: matchLabels: app: helloworld version: v1 template: metadata: labels: app: helloworld version: v1 spec: containers: - name: helloworld image: registry.istio.io/release/examples-helloworld-v1:1.0 resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent ports: - containerPort: 5000Apply the YAML file in Cluster B by running the following command:
$ oc apply --kubeconfig="${CLUSTER_B_KUBECONFIG}" -n ${SAMPLE_NS} -f <filename>
Install the Istio
HelloWorldServicein Cluster A by running the following command:Create a YAML file that defines the
HelloWorldServicein Cluster A:apiVersion: v1 kind: Service metadata: name: helloworld labels: app: helloworld service: helloworld spec: ports: - port: 5000 name: http selector: app: helloworldApply the YAML file in Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${SAMPLE_NS} -f <filename>
Install the
sleepclient in Cluster A by running the following command:Create a YAML file that defines the
sleepServiceAccount,Service, andDeploymentin Cluster A:apiVersion: v1 kind: ServiceAccount metadata: name: sleep --- apiVersion: v1 kind: Service metadata: name: sleep labels: app: sleep service: sleep spec: ports: - port: 80 name: http selector: app: sleep --- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: terminationGracePeriodSeconds: 0 serviceAccountName: sleep containers: - name: sleep image: docker.io/curlimages/curl:8.16.0 command: ["/bin/sleep", "infinity"] imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /etc/sleep/tls name: secret-volume volumes: - name: secret-volume secret: secretName: sleep-secret optional: trueApply the YAML file in Cluster A by running the following command:
$ oc apply --kubeconfig="${CLUSTER_A_KUBECONFIG}" -n ${SAMPLE_NS} -f <filename>
Add the SPIRE injection template to the
sleepapplication in Cluster A by running the following command:$ oc patch deploy sleep \ -n ${SAMPLE_NS} \ --type='merge' \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" \ -p '{"spec": {"template": {"metadata": {"annotations": {"inject.istio.io/templates": "sidecar,spire"}}}}}'Add the SPIRE injection template to the
HelloWorldapplication in Cluster B by running the following command:$ oc patch deploy helloworld-v1 \ -n ${SAMPLE_NS} \ --type='merge' \ --kubeconfig="${CLUSTER_B_KUBECONFIG}" \ -p '{"spec": {"template": {"metadata": {"annotations": {"inject.istio.io/templates": "sidecar,spire"}}}}}'Wait for the
sleepdeployment to become available on Cluster A by running the following command:$ oc rollout status deploy/sleep --kubeconfig "${CLUSTER_A_KUBECONFIG}" -n ${SAMPLE_NS} --timeout=300sWait for the
helloworld-v1deployment to become available on Cluster B by running the following command:$ oc rollout status deploy/helloworld-v1 --kubeconfig "${CLUSTER_B_KUBECONFIG}" -n ${SAMPLE_NS} --timeout=300sVerify that the
sleeppod uses a SPIRE-issued identity by running the following command:$ oc exec deploy/sleep -n ${SAMPLE_NS} --kubeconfig="${CLUSTER_A_KUBECONFIG}" -c istio-proxy -- \ curl -s localhost:15000/certs | jq -r '.certificates[0].cert_chain[0].subject_alt_names[0].uri'Example output
spiffe://${CLUSTER_A_TRUST_DOMAIN}/ns/sample/sa/sleepVerify that the
sleeppod on Cluster A can reach thehelloworld.sampleservice by running the following command:$ oc exec deploy/sleep \ -n ${SAMPLE_NS} \ --kubeconfig="${CLUSTER_A_KUBECONFIG}" \ -- curl -sS helloworld.sample:5000/helloExample output
Hello version: v1, instance: helloworld-v1-5859666d7-pcb8v
10.12. Enabling create-only mode for the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
To pause Operator reconciliation, enable create-only mode by setting an environment variable in the subscription object. By setting this value, you can perform manual configurations or debug the operator without the controller overwriting your changes.
The following scenarios are examples of when the create-only mode might be of use:
Manual Customization Required: You need to customize operator-managed resources (ConfigMaps, Deployments, DaemonSets, etc.) with specific configurations that differ from the operator’s defaults
Day 2 Operations: After initial deployment, you want to prevent the operator from overwriting their manual changes during subsequent reconciliation cycles
Configuration Drift Prevention: You want to maintain control over certain resource configurations while still benefiting from the operator’s lifecycle management
10.12.1. Pausing Operator reconciliation Copy linkLink copied to clipboard!
Pause reconciliation of the operands by enabling create-only mode. This setting prevents the Operator from automatically reverting your manual changes to the desired state. You can enable this mode by updating the Operator’s subscription object.
When create-only mode is disabled, the Operator overwrites the resources if any conflicts exist.
Prerequisites
- You have installed Zero Trust Workload Identity Manager on your machine.
- You have installed the SPIRE Servers, Agents, SPIFFE Container Storage Interface (CSI), and an OpenID Connect (OIDC) Discovery Provider and are in running status.
Procedure
To pause reconciling the operands resources managed by the Operator, add the environment variable
CREATE_ONLY_MODE:truein the subscription object by running the following command:$ oc -n $OPERATOR_NAMESPACE patch subscription openshift-zero-trust-workload-identity-manager --type='merge' -p '{"spec":{"config":{"env":[{"name":"CREATE_ONLY_MODE","value":"true"}]}}}'
Verification
Check the status of the
SpireServerresource to confirm that thecreate-onlymode is active. Thestatusmust betrueand thereasonmust beCreateOnlyModeEnabled.$ oc get SpireServer cluster -o yamlThe following is an example that confirms that the 'create-only' mode is active.
status:
conditions:
- lastTransitionTime: "2025-12-23T11:36:58Z"
message: All components are ready
reason: Ready
status: "True"
type: Ready
- lastTransitionTime: "2025-12-23T11:36:58Z"
message: All operand CRs are ready
reason: Ready
status: "True"
type: OperandsAvailable
- lastTransitionTime: "2025-12-23T11:36:58Z"
message: create-only mode enabled
reason: CreateOnlyModeEnabled
status: "True"
type: CreateOnlyMode
The Operator updates the upgradeable condition to false in the operatorCondition resource. You might not be able to upgrade the Operator when in create-only mode.
10.12.2. Resuming Operator reconciliation Copy linkLink copied to clipboard!
To resume Operator reconciliation after manual configuration or debugging, disable the create-only mode. This allows the controller to resume managing resources and applying the desired state. You can disable this mode by setting the environment variable in the subscription object.
Prerequisites
-
You have enabled
create-onlymode on the Zero Trust Workload Identity Manager. - You have completed your manual configuration or debugging tasks.
Procedure
To restart reconciling the Operator-managed resources, add the environment variable
CREATE_ONLY_MODE:falsein the subscription object by running the following command:$ oc -n $OPERATOR_NAMESPACE patch subscription openshift-zero-trust-workload-identity-manager --type='merge' -p '{"spec":{"config":{"env":[{"name":"CREATE_ONLY_MODE","value":"false"}]}}}'
Verification
Check the status of the
SpireServerresource to confirm thatcreate-onlymode is disabled by running the following command:$ oc get SpireServer cluster -o yamlExample output
status: conditions: - lastTransitionTime: "2025-12-23T11:40:00Z" message: create-only mode disabled reason: CreateOnlyModeDisabled status: "False" type: CreateOnlyMode
10.13. SPIRE UpstreamAuthority plugins for Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
To integrate SPIFFE Runtime Environment (SPIRE) with your existing certificate management infrastructure and keep Secure Production Identity Framework for Everyone (SPIFFE) (SPIFFE) identity standards, configure SPIRE Server with UpstreamAuthority plugins. These plugins obtain intermediate signing certificates from external certificate authorities.
You can configure SPIRE Server to use one of the following UpstreamAuthority plugins:
- cert-manager UpstreamAuthority plugin
- Integrates SPIRE with cert-manager Operator for Red Hat OpenShift running in Kubernetes or OpenShift Container Platform clusters. The cert-manager Operator for Red Hat OpenShift instance can use various issuer types to provide signing certificates for SPIRE intermediate CAs.
- Vault UpstreamAuthority plugin
- Integrates SPIRE with the HashiCorp Vault Public Key Infrastructure (PKI) secrets engine. This plugin supports many Vault authentication methods and enables SPIRE to use Vault’s security features for certificate management.
Choose the plugin that matches your certificate management infrastructure. You can configure only one UpstreamAuthority plugin at a time. The SpireServer CR rejects configurations that specify both certManager and vault simultaneously.
10.13.1. About the cert-manager upstream authority plugin Copy linkLink copied to clipboard!
The cert-manager Operator for Red Hat OpenShift upstream authority plugin connects SPIRE Server to cert-manager Operator for Red Hat OpenShift for automated intermediate certificate provisioning.
When you configure this plugin, SPIRE Server creates a CertificateRequest resource in the cluster. The configured Issuer or ClusterIssuer signs the request and the certificate. The SPIRE Server then uses the signed intermediate certificate to issue workload identities.
10.13.1.1. How the cert-manager plugin works Copy linkLink copied to clipboard!
- SPIRE Server generates a certificate signing request for an intermediate signing certificate.
-
The plugin creates a
CertificateRequestin the configured namespace. -
The
CertificateRequestreferences the configured Issuer or ClusterIssuer. - cert-manager Operator for Red Hat OpenShift signs the request.
-
SPIRE Server retrieves the signed certificate and CA bundle from the
CertificateRequest.
10.13.1.2. Requirements Copy linkLink copied to clipboard!
- cert-manager Operator for Red Hat OpenShift
- cert-manager Operator for Red Hat OpenShift must be installed and running in the cluster.
- Issuer
-
You must configure an
IssuerorClusterIssuerthat can sign intermediate CA certificates. - Permissions
-
On OpenShift Container Platform, Zero Trust Workload Identity Manager grants the SPIRE Server
ServiceAccountpermission to manageCertificateRequestresources whenspec.upstreamAuthority.certManageris configured. Prepare the Issuer and namespace before you configure theSpireServerCR. - Supported issuers
-
The
Issuermust support signing certificate requests for intermediate CAs.
10.13.2. Preparing cert-manager for SPIRE Server Copy linkLink copied to clipboard!
Install cert-manager Operator for Red Hat OpenShift and create an Issuer or ClusterIssuer that can sign SPIRE intermediate certificates. After you complete this procedure, configure spec.upstreamAuthority.certManager on the SpireServer CR.
Prerequisites
-
You are logged in to the cluster with the
cluster-adminrole. - You have installed Zero Trust Workload Identity Manager or plan to install it after cert-manager Operator for Red Hat OpenShift is ready.
Procedure
Create a self-signed bootstrap
ClusterIssuer:Save the following manifest as
selfsigned-bootstrap.yaml:apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-bootstrap spec: selfSigned: {}Apply the
ClusterIssuerby running the following command:$ oc apply -f selfsigned-bootstrap.yaml
Use the bootstrap
ClusterIssuerto issue a root CA certificate into a Secret:Save the following manifest as
spire-root-ca.yaml:apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: spire-root-ca namespace: cert-manager spec: isCA: true secretName: spire-root-ca-secret issuerRef: name: selfsigned-bootstrap kind: ClusterIssuer commonName: "SPIRE Root CA" duration: 87600hApply the
Certificateby running the following command:$ oc apply -f spire-root-ca.yaml
Create a CA
Issuerthat references the root CA Secret:Save the following manifest as
spire-ca-issuer.yaml:apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: spire-ca namespace: cert-manager spec: ca: secretName: spire-root-ca-secretApply the
Issuerby running the following command:$ oc apply -f spire-ca-issuer.yaml
Verification
Confirm that the bootstrap
ClusterIssuer, root CACertificate, and signingIssuerare ready by running the following commands:$ oc get clusterissuer selfsigned-bootstrap$ oc get certificate spire-root-ca -n cert-manager$ oc get issuer spire-ca -n cert-manager
10.13.3. Configuring the cert-manager upstream authority plugin Copy linkLink copied to clipboard!
Configure SPIRE Server to obtain intermediate signing certificates from cert-manager Operator for Red Hat OpenShift by setting spec.upstreamAuthority.certManager on the SpireServer custom resource. Zero Trust Workload Identity Manager generates SPIRE Server configuration and reconciles the SPIRE Server `StatefulSet.
Prerequisites
-
You have installed Zero Trust Workload Identity Manager and deployed a
SpireServerCR. -
You have completed preparing cert-manager Operator for Red Hat OpenShift for SPIRE Server use, including creating an
IssuerorClusterIssuer.
Procedure
Export the current
SpireServerCR to a file by running the following command:$ oc get spireserver cluster -o yaml > SpireServer-cert-manager.yamlIn the
SpireServer-cert-manager.yaml, add theupstreamAuthoritysection underspec::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" upstreamAuthority: certManager: namespace: cert-manager issuerName: spire-ca issuerKind: Issuer issuerGroup: cert-manager.iowhere:
spec.upstreamAuthority.certManager.namespace-
Specifies the namespace where SPIRE Server creates
CertificateRequestresources. For a namespace-scopedIssuer, this must match theIssuernamespace. For aClusterIssuer, any namespace is valid. spec.upstreamAuthority.certManager.issuerName-
Specifies the name of the
IssuerorClusterIssuer. spec.upstreamAuthority.certManager.issuerKind-
Specifies the
IssuerorClusterIssuer. The default isIssuer. spec.upstreamAuthority.certManager.issuerGroupSpecifies the API group of the issuer. The default is
cert-manager.io.NoteOn OpenShift Container Platform, SPIRE Server uses its in-cluster
ServiceAccount. Zero Trust Workload Identity Manager grantsCertificateRequestpermissions whenspec.upstreamAuthority.certManageris configured.
Apply the updated CR by running the following command:
$ oc apply -f SpireServer-cert-manager.yamlWait for Zero Trust Workload Identity Manager to reconcile the SPIRE Server by running the following command:
$ oc rollout status statefulset/spire-server -n zero-trust-workload-identity-manager
Verification
Verify that SPIRE Server is healthy and that logs show the
UpstreamAuthorityplugin loaded by running the following commands:$ oc exec -n zero-trust-workload-identity-manager statefulset/spire-server -c spire-server -- \ /opt/spire/bin/spire-server healthcheck
$ oc logs statefulset/spire-server -n zero-trust-workload-identity-manager -c spire-server --tail=50
Confirm that a cert-manager-signed intermediate certificate is present by running the following command:
$ oc exec -n zero-trust-workload-identity-manager statefulset/spire-server -c spire-server -- \ /opt/spire/bin/spire-server bundle show
10.13.4. cert-manager upstream authority plugin reference Copy linkLink copied to clipboard!
This reference describes spec.upstreamAuthority.certManager fields on the SpireServer custom resource and how Zero Trust Workload Identity Manager uses them to request intermediate certificates from cert-manager Operator for Red Hat OpenShift. Use it when you configure or troubleshoot the cert-manager Operator for Red Hat OpenShift UpstreamAuthority plugin and need field descriptions or defaults.
10.13.4.1. SpireServer CR fields Copy linkLink copied to clipboard!
Configure cert-manager Operator for Red Hat OpenShift upstream authority under spec.upstreamAuthority.certManager. Zero Trust Workload Identity Manager generates the SPIRE Server configuration from these fields.
| Field | Description |
|---|---|
|
|
Required. Namespace where SPIRE Server creates |
|
| Required. Name of the Issuer or ClusterIssuer. |
|
|
Optional. |
|
|
Optional. API group of the issuer. The default is |
On OpenShift Container Platform, SPIRE Server uses its in-cluster ServiceAccount. Zero Trust Workload Identity Manager grants permissions to create, get, list, and delete CertificateRequest resources when spec.upstreamAuthority.certManager is configured.
10.13.5. Troubleshooting the cert-manager Operator for Red Hat OpenShift upstream authority plugin Copy linkLink copied to clipboard!
Resolve the most common cert-manager Operator for Red Hat OpenShift upstream authority failures on OpenShift Container Platform.
10.13.5.1. Quick reference Copy linkLink copied to clipboard!
| Symptom or error | Likely cause | Section |
|---|---|---|
|
|
Missing permissions or wrong | |
|
|
Wrong | |
|
Issuer not | Issuer misconfiguration | |
|
| Approval policy or approver configuration | |
| UpstreamAuthority fails to load in SPIRE Server logs |
Invalid or incomplete |
10.13.5.2. Permission and issuer errors Copy linkLink copied to clipboard!
Permission errors
-
Confirm
spec.upstreamAuthority.certManager.namespacematches the namespace where SPIRE Server createsCertificateRequestresources. -
After you configure
spec.upstreamAuthority.certManager, verify that Zero Trust Workload Identity Manager updated the SPIRE Server role-based access control (RBAC) and the SPIRE Server pod restarted.
Issuer errors
-
Verify
issuerNameandissuerKindon theSpireServerCR match an existing Issuer or ClusterIssuer. -
For a namespace-scoped
Issuer, the Issuer must exist in the namespace referenced byCertificateRequestresources or in the namespace where the issuer is defined, depending on your issuer configuration. Check that the Issuer reports
READY=True:$ oc get issuer,clusterissuer -A $ oc describe issuer spire-ca -n cert-manager
10.13.5.3. SPIRE Server errors Copy linkLink copied to clipboard!
SPIRE Server errors
-
Confirm that
namespace,issuerName, andissuerKindare set underspec.upstreamAuthority.certManager. - Review SPIRE Server logs by running the following command:
$ oc logs statefulset/spire-server -n zero-trust-workload-identity-manager -c spire-server --tail=100
10.13.6. About the SPIRE Vault UpstreamAuthority plugin Copy linkLink copied to clipboard!
The Vault UpstreamAuthority plugin connects SPIRE Server to the HashiCorp Vault PKI secrets engine for automated intermediate CA certificate signing. Use this plugin when you centralize PKI in Vault and want SPIRE to obtain intermediate signing certificates through Vault policies and authentication method.
The SPIRE Vault UpstreamAuthority plugin provides the following features:
- Integration with HashiCorp Vault PKI secrets engine as an upstream certificate authority
-
Support for
Kubernetes authVault authentication methods - Automatic signing of SPIRE intermediate CA certificates
- Vault Enterprise namespace support
- Secure certificate management using Vault’s security features
The Vault UpstreamAuthority plugin does not support the PublishJWTKey remote procedure call (RPC) and is not appropriate for use in nested SPIRE topologies where JSON Web Token SPIFFE Verifiable Identity Document (SVID) (JWT-SVIDs) are used.
10.13.6.1. Supported authentication method Copy linkLink copied to clipboard!
The Vault UpstreamAuthority plugin supports only the following authentication methods for connecting to Vault:
- Kubernetes authentication
- Uses Kubernetes service account tokens to authenticate to Vault.
10.13.6.2. Prerequisites and requirements Copy linkLink copied to clipboard!
Before configuring the Vault UpstreamAuthority plugin, ensure the following requirements are met:
- Vault PKI secrets engine
-
A running HashiCorp Vault instance with the PKI secrets engine enabled at a configured mount point (default:
pki). The PKI secrets engine must have a root CA certificate configured for signing intermediate certificates. - Vault policy
-
A Vault policy that grants the
updatecapability for thepki/root/sign-intermediateendpoint, attached to the credentials SPIRE Server uses. - Authentication credentials
- Valid credentials for one of the supported authentication methods, associated with the Vault signing policy.
- TTL configuration
-
SPIRE Server
ca_ttlmust not exceed the Vault PKI secrets engine maximum lease TTL. - Network connectivity
- SPIRE Server must reach the Vault server over the network and trust the Vault TLS certificate when TLS is enabled.
- Kubernetes RBAC (when using Kubernetes authentication)
-
A
ServiceAccountfor SPIRE Server bound to the Vault Kubernetes auth role. The SPIRE ServerServiceAccountrequires no additional RBAC. However, the VaultServiceAccountmust havesystem:auth-delegatorpermissions to validate projected tokens via the KubernetesTokenReviewAPI.
10.13.7. Configuring the SPIRE Vault UpstreamAuthority plugin Copy linkLink copied to clipboard!
Configure SPIRE Server to obtain intermediate signing certificates from HashiCorp Vault by setting spec.upstreamAuthority.vault on the SpireServer custom resource (CR). Zero Trust Workload Identity Manager generates the SPIRE Server configuration, mounts Vault credentials into the SPIRE Server pod, and reconciles the SPIRE Server StatefulSet.
On OpenShift Container Platform, the SpireServer CR supports Vault authentication only through the Kubernetes auth method. Zero Trust Workload Identity Manager mounts a projected SPIRE Server ServiceAccount token and, when configured, a Vault CA certificate Secret at fixed paths inside the pod.
Prerequisites
-
You have installed Zero Trust Workload Identity Manager and deployed a
SpireServerCR. -
You have a running HashiCorp Vault instance with the PKI secrets engine enabled at your mount point (default:
pki) and a root CA configured to sign intermediate certificates. -
You have a Vault policy that grants the
updatecapability on<pki_mount>/root/sign-intermediate. -
You have configured the Vault Kubernetes authentication method and created a Vault role bound to the SPIRE Server
ServiceAccount. For example,spire-serverin thezero-trust-workload-identity-managernamespace. -
The value of
spec.caValidityon theSpireServerCR is less than or equal to the maximum lease Time to Live (TTL) configured on the Vault PKI secrets engine. -
If Vault uses a private or custom TLS certificate authority, you have a PEM-encoded CA certificate available to store in a Kubernetes
Secret.
Procedure
Export the current
SpireServerCR to a file by running the following command:$ oc get spireserver cluster -o yaml > SpireServer-vault.yamlIn
SpireServer-vault.yaml, add theupstreamAuthoritysection from the following example underspec::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" upstreamAuthority: vault: vaultAddr: "https://vault.example.com:8200" pkiMountPoint: "pki" caCertSecretRef: name: vault-ca-cert key: ca.crt k8sAuth: k8sAuthMountPoint: "kubernetes" k8sAuthRoleName: "spire-server" audience: "vault" vaultNamespace: "vault-namespace" # optionalNoteFor in-cluster Vault over HTTP, set
vaultAddrto the in-cluster service URL, such ashttp://vault.vault.svc:8200, and omitcaCertSecretRef.Include
caCertSecretRefonly when Vault TLS is signed by a custom CA. Omit it when Vault uses a public CA.where:
spec.caValidity-
Specifies the SPIRE Server CA validity. Must be less than or equal to the Vault PKI
max_lease_ttl. Zero Trust Workload Identity Manager maps this value to SPIREca_ttl. spec.upstreamAuthority.vault.vaultAddr- Specifies the URL of the Vault server.
spec.upstreamAuthority.vault.pkiMountPoint-
Specifies the Vault PKI secrets engine mount path. Default:
pki. spec.upstreamAuthority.vault.caCertSecretRef-
Optional. Specifies the
Secretreference when Vault TLS is signed by a custom CA. Zero Trust Workload Identity Manager mounts the Secret at/run/spire/upstream-ca/ca.crt. spec.upstreamAuthority.vault.k8sAuth.k8sAuthRoleName- Specifies the Vault Kubernetes auth role name.
spec.upstreamAuthority.vault.k8sAuth.k8sAuthMountPoint-
Specifies the Vault Kubernetes auth mount path. The default is
kubernetes. spec.upstreamAuthority.vault.k8sAuth.audience-
Specifies the projected
ServiceAccounttoken audience. This must match the Vault role. The default isvault. spec.upstreamAuthority.vault.vaultNamespace- Optional. Specifies the Vault namespace name.
Apply the updated CR by running the following command:
$ oc apply -f SpireServer-vault.yamlWait for Zero Trust Workload Identity Manager to reconcile the SPIRE Server by running the following command:
$ oc rollout status statefulset/spire-server -n zero-trust-workload-identity-manager
Verification
Verify that SPIRE Server is healthy and that logs show the UpstreamAuthority plugin loaded by running the following commands:
$ oc exec -n zero-trust-workload-identity-manager statefulset/spire-server -c spire-server -- \ /opt/spire/bin/spire-server healthcheck $ oc logs statefulset/spire-server -n zero-trust-workload-identity-manager -c spire-server --tail=50Example output
Server is healthy. time="2026-04-07T10:15:30Z" level=info msg="Upstream authority loaded" subsystem_name=ca time="2026-04-07T10:15:31Z" level=info msg="Server CA activated" certificate_fingerprint="ABC123..."Confirm that a Vault-signed intermediate certificate is present by running the following command:
$ oc exec -n zero-trust-workload-identity-manager statefulset/spire-server -c spire-server -- \ /opt/spire/bin/spire-server bundle show
10.13.8. SPIRE Vault UpstreamAuthority plugin reference Copy linkLink copied to clipboard!
Reference for spec.upstreamAuthority.vault on the SpireServer CR and the Vault signing policy SPIRE Server requires.
10.13.8.1. SpireServer CR fields Copy linkLink copied to clipboard!
Configure Vault upstream authority under spec.upstreamAuthority.vault. Zero Trust Workload Identity Manager generates SPIRE Server configuration from these fields.
| Field | Description |
|---|---|
|
| Required. Vault server URL. Use HTTPS for external endpoints; HTTP is permitted for in-cluster services. |
|
|
PKI secrets engine mount path. Default: |
|
|
Optional Secret reference in the |
|
|
Optional. Accepts any Vault server certificate when |
|
| Optional. Vault Enterprise namespace. |
|
|
Vault Kubernetes auth mount path. Default: |
|
|
Required. Vault role bound to the |
|
|
Token audience for the projected ServiceAccount token. Default: |
On OpenShift Container Platform, the SpireServer CR supports Vault Kubernetes authentication only. Zero Trust Workload Identity Manager mounts a projected ServiceAccount token at /var/run/secrets/tokens/vault.
Ensure that spec.caValidity does not exceed the Vault PKI max_lease_ttl.
10.13.8.2. Required Vault policy Copy linkLink copied to clipboard!
The Vault Kubernetes auth role must include a policy with update on the intermediate signing path:
path "pki/root/sign-intermediate" {
capabilities = ["update"]
}
Replace pki with your PKI mount point when different.
10.13.9. Troubleshooting SPIRE Vault UpstreamAuthority plugin Copy linkLink copied to clipboard!
Resolve the most common SPIRE Vault upstream authority failures on OpenShift Container Platform.
10.13.9.1. Quick reference Copy linkLink copied to clipboard!
| Symptom or error | Likely cause | Section |
|---|---|---|
| Connection refused, timeout, or unreachable Vault |
Wrong | |
|
|
Missing or invalid | |
|
Vault | Vault role, ServiceAccount binding, or signing policy misconfiguration | |
|
|
|
10.13.9.2. Connection and TLS errors Copy linkLink copied to clipboard!
Connection failures
-
Verify
spec.upstreamAuthority.vault.vaultAddron theSpireServerCR. -
Confirm the SPIRE Server pod can reach Vault from the
zero-trust-workload-identity-managernamespace. -
For in-cluster Vault, use the Kubernetes service URL, such as
http://vault.vault.svc:8200.
TLS failures
-
When Vault uses a custom CA, set
caCertSecretRefto a Secret in thezero-trust-workload-identity-managernamespace with a PEM-encoded CA certificate. -
Confirm the
Secretname and key matchcaCertSecretRef. -
Omit
caCertSecretRefonly for a public CA or in-cluster HTTP. -
Do not use
insecureSkipVerify: truein production.
10.13.9.3. Authentication and signing errors Copy linkLink copied to clipboard!
SPIRE Server uses Vault Kubernetes authentication with the spire-server ServiceAccount.
Check the following:
-
k8sAuth.k8sAuthRoleNameon theSpireServerCR matches the Vault Kubernetes auth role. -
The Vault role binds to ServiceAccount
spire-serverin namespacezero-trust-workload-identity-manager. -
The role policy grants
updateon<pki_mount>/root/sign-intermediate. -
pkiMountPointmatches the PKI secrets engine mount in Vault. -
k8sAuth.audiencematches the Vault role when you changed the default fromvault.
Review SPIRE Server logs:
$ oc logs statefulset/spire-server -n zero-trust-workload-identity-manager -c spire-server --tail=100
10.13.9.4. TTL mismatch errors Copy linkLink copied to clipboard!
spec.caValidity on the SpireServer CR must be less than or equal to the Vault PKI max_lease_ttl.
-
Reduce
caValidityon theSpireServerCR, or increase the Vault PKI limit withvault secrets tune -max-lease-ttl=….
For Vault Enterprise namespace errors, set spec.upstreamAuthority.vault.vaultNamespace to the correct namespace path.
10.14. Monitoring Zero Trust Workload Identity Managergit Copy linkLink copied to clipboard!
Track the performance of the Zero Trust Workload Identity Manager by collecting metrics. Configure monitoring to collect metrics from the Security Production Identity Framework for Everyone (SPIRE) Server and SPIRE Agent components.
10.14.1. Enabling user workload monitoring Copy linkLink copied to clipboard!
Enable user workload monitoring to track metrics for your user-defined projects. Configuring this feature allows you to observe application performance and helps you maintain the health of your services.
Prerequisites
-
You have access to the cluster as a user with the
cluster-admincluster role.
Procedure
Create the
cluster-monitoring-config.yamlfile to define and configure theConfigMap:apiVersion: v1 kind: ConfigMap metadata: name: cluster-monitoring-config namespace: openshift-monitoring data: config.yaml: | enableUserWorkload: trueApply the
ConfigMapby running the following command:$ oc apply -f cluster-monitoring-config.yaml
Verification
Verify that the monitoring components for user workloads are running in the
openshift-user-workload-monitoringnamespace:$ oc -n openshift-user-workload-monitoring get podExample output
NAME READY STATUS RESTARTS AGE prometheus-operator-6cb6bd9588-dtzxq 2/2 Running 0 50s prometheus-user-workload-0 6/6 Running 0 48s prometheus-user-workload-1 6/6 Running 0 48s thanos-ruler-user-workload-0 4/4 Running 0 42s thanos-ruler-user-workload-1 4/4 Running 0 42s
The status of the pods such as prometheus-operator, prometheus-user-workload, and thanos-ruler-user-workload must be Running.
10.14.2. Configuring metrics collection for SPIRE Server by using a ServiceMonitor Copy linkLink copied to clipboard!
To collect custom metrics from the SPIRE Server, create a ServiceMonitor custom resource (CR). This configuration enables the Prometheus Operator to scrape metrics from the default endpoint, which helps you monitor your SPIRE deployment.
The SPIRE Server operand exposes metrics by default on port 9402 at the /metrics endpoint. You can configure metrics collection for the SPIRE Server by creating a ServiceMonitor custom resource (CR) that enables the Prometheus Operator to collect custom metrics.
Prerequisites
-
You have access to the cluster as a user with the
cluster-admincluster role. - You have installed the Zero Trust Workload Identity Manager.
- You have deployed the SPIRE Server operand in the cluster.
- You have enabled the user workload monitoring.
Procedure
Create the
ServiceMonitorCR:Create the YAML file that defines the
ServiceMonitorCR:Example
servicemonitor-spire-serverfileapiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: app.kubernetes.io/name: server app.kubernetes.io/instance: spire name: spire-server-metrics namespace: zero-trust-workload-identity-manager spec: endpoints: - port: metrics interval: 30s path: /metrics selector: matchLabels: app.kubernetes.io/name: server app.kubernetes.io/instance: spire namespaceSelector: matchNames: - zero-trust-workload-identity-managerCreate the
ServiceMonitorCR by running the following command:$ oc create -f servicemonitor-spire-server.yamlAfter the
ServiceMonitorCR is created, the user workload Prometheus instance begins metrics collection from the SPIRE Server. The collected metrics are labeled withjob="spire-server".
Verification
-
In the OpenShift Container Platform web console, navigate to Observe
Targets. In the Label filter field, enter the following label to filter the metrics targets:
$ service=zero-trust-workload-identity-manager-metrics-service-
Confirm that the Status column shows
Upfor thespire-server-metricsentry.
10.14.3. Configuring metrics collection for SPIRE Agent by using a Service Monitor Copy linkLink copied to clipboard!
Configure metrics collection for the SPIRE Agent by creating a ServiceMonitor custom resource (CR). This enables the Prometheus Operator to collect custom metrics that the SPIRE Agent exposes on the default port.
The SPIRE Agent operand exposes metrics by default on port 9402 at the /metrics endpoint. You can configure metrics collection for the SPIRE Agent by creating a ServiceMonitor custom resource (CR), which enables the Prometheus Operator to collect custom metrics.
Prerequisites
-
You have access to the cluster as a user with the
cluster-admincluster role. - You have installed the Zero Trust Workload Identity Manager.
- You have deployed the SPIRE Agent operand in the cluster.
- You have enabled the user workload monitoring.
Procedure
Create the
ServiceMonitorCR:Create the YAML file that defines the
ServiceMonitorCR:Example
servicemonitor-spire-agent.yamlfileapiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: app.kubernetes.io/name: agent app.kubernetes.io/instance: spire name: spire-agent-metrics namespace: zero-trust-workload-identity-manager spec: endpoints: - port: metrics interval: 30s path: /metrics selector: matchLabels: app.kubernetes.io/name: agent app.kubernetes.io/instance: spire namespaceSelector: matchNames: - zero-trust-workload-identity-managerCreate the
ServiceMonitorCR by running the following command:$ oc create -f servicemonitor-spire-agent.yamlAfter the
ServiceMonitorCR is created, the user workload Prometheus instance begins metrics collection from the SPIRE Agent. The collected metrics are labeled withjob="spire-agent".
Verification
-
In the OpenShift Container Platform web console, navigate to Observe
Targets. In the Label filter field, enter the following label to filter the metrics targets:
$ service=spire-agent-
Confirm that the Status column shows
Upfor thespire-agent-metricsentry.
10.14.4. Configuring metrics collection for the Operator by using a ServiceMonitor Copy linkLink copied to clipboard!
The Zero Trust Workload Identity Manager exposes metrics by default on port 8443 at the /metrics service endpoint. You can configure metrics collection for the Operator by creating a ServiceMonitor custom resource (CR) that enables the Prometheus Operator to collect custom metrics. For more information, see "Configuring user workload monitoring".
The SPIRE Server operand exposes metrics by default on port 9402 at the /metrics endpoint. You can configure metrics collection for the SPIRE Server by creating a ServiceMonitor custom resource (CR) that enables the Prometheus Operator to collect custom metrics.
Prerequisites
-
You have access to the cluster as a user with the
cluster-admincluster role. - You have installed the Zero Trust Workload Identity Manager.
- You have enabled the user workload monitoring.
Procedure
Configure the Operator to use HTTP or HTTPS protocols for the metrics server.
Update the subscription object for Zero Trust Workload Identity Manager to configure the HTTP protocol by running the following command:
$ oc -n zero-trust-workload-identity-manager patch subscription zero-trust-workload-identity-manager-subscription --type='merge' -p '{"spec":{"config":{"env":[{"name":"METRICS_BIND_ADDRESS","value":":8080"}, {"name": "METRICS_SECURE", "value": "false"}]}}}'Verify the Zero Trust Workload Identity Manager pod is redeployed and that the configured values for
METRICS_BIND_ADDRESSandMETRICS_SECUREis updated by running the following command:$ oc set env --list deployment/zero-trust-workload-identity-manager-controller-manager -n zero-trust-workload-identity-manager | grep -e METRICS_BIND_ADDRESS -e METRICS_SECURE -e containerExample output
deployments/zero-trust-workload-identity-manager-controller-manager, container manager METRICS_BIND_ADDRESS=:8080 METRICS_SECURE=false
Create the
Secretresource withkubernetes.io/service-account.nameannotation to inject the token required for authenticating with the metrics server.Create the
secret-zero-trust-workload-identity-manager.yamlYAML file:apiVersion: v1 kind: Secret metadata: labels: name: zero-trust-workload-identity-manager name: zero-trust-workload-identity-manager-metrics-auth namespace: zero-trust-workload-identity-manager annotations: kubernetes.io/service-account.name: zero-trust-workload-identity-manager-controller-manager type: kubernetes.io/service-account-tokenCreate the
Secretresource by running the following command:$ oc apply -f secret-zero-trust-workload-identity-manager.yaml
Create the
ClusterRoleBindingresource required for granting permissions to access the metrics.Create the
clusterrolebinding-zero-trust-workload-identity-manager.yamlYAML file:apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: name: zero-trust-workload-identity-manager name: zero-trust-workload-identity-manager-allow-metrics-access roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: zero-trust-workload-identity-manager-metrics-reader subjects: - kind: ServiceAccount name: zero-trust-workload-identity-manager-controller-manager namespace: zero-trust-workload-identity-managerCreate the
ClusterRoleBindingresource by running the following command:$ oc apply -f clusterrolebinding-zero-trust-workload-identity-manager.yaml
Create the following
ServiceMonitorCR if the metrics server is configured to usehttp.Create the
servicemonitor-zero-trust-workload-identity-manager-http.yamlYAML file:apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: name: zero-trust-workload-identity-manager name: zero-trust-workload-identity-manager-metrics-monitor namespace: zero-trust-workload-identity-manager spec: endpoints: - authorization: credentials: name: zero-trust-workload-identity-manager-metrics-auth key: token type: Bearer interval: 60s path: /metrics port: metrics-http scheme: http scrapeTimeout: 30s namespaceSelector: matchNames: - zero-trust-workload-identity-manager selector: matchLabels: name: zero-trust-workload-identity-managerCreate the
ServiceMonitorCR by running the following command:$ oc apply -f servicemonitor-zero-trust-workload-identity-manager-http.yaml
Create the following
ServiceMonitorCR if the metrics server is configured to usehttps.Create the
servicemonitor-zero-trust-workload-identity-manager-https.yamlYAML file:apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: name: zero-trust-workload-identity-manager name: zero-trust-workload-identity-manager-metrics-monitor namespace: zero-trust-workload-identity-manager spec: endpoints: - authorization: credentials: name: zero-trust-workload-identity-manager-metrics-auth key: token type: Bearer interval: 60s path: /metrics port: metrics-https scheme: https scrapeTimeout: 30s tlsConfig: ca: configMap: name: openshift-service-ca.crt key: service-ca.crt serverName: zero-trust-workload-identity-manager-metrics-service.zero-trust-workload-identity-manager.svc.cluster.local namespaceSelector: matchNames: - zero-trust-workload-identity-manager selector: matchLabels: name: zero-trust-workload-identity-managerCreate the
ServiceMonitorCR by running the following command:$ oc apply -f servicemonitor-zero-trust-workload-identity-manager-https.yamlAfter the
ServiceMonitorCR is created, the user workload Prometheus instance begins metrics collection from the SPIRE Server. The collected metrics are labeled withjob="zero-trust-workload-identity-manager-metrics-service".
Verification
-
In the OpenShift Container Platform web console, navigate to Observe
Targets. In the Label filter field, enter the following label to filter the metrics targets:
$ service=zero-trust-workload-identity-manager-metrics-service-
Confirm that the Status column shows
Upfor thezero-trust-workload-identity-managerentry.
10.14.5. Querying metrics for the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
Query SPIRE Agent and SPIRE Server metrics using the OpenShift Container Platform web console or the command line. This helps you monitor the performance of SPIRE components that match specific job labels.
Prerequisites
-
You have access to the cluster as a user with the
cluster-adminrole. - You have installed the Zero Trust Workload Identity Manager.
- You have deployed the SPIRE Server and SPIRE Agent operands in the cluster.
-
You have enabled monitoring and metrics collection by creating
ServiceMonitorobjects.
Procedure
-
In the OpenShift Container Platform web console, navigate to Observe
Metrics. In the query field, enter the following PromQL expression to query SPIRE Server metrics:
{job="spire-server"}In the query field, enter the following PromQL expression to query SPIRE Agent metrics.
{job="spire-agent"}
10.14.6. Zero Trust Workload Identity Manager monitoring available metrics Copy linkLink copied to clipboard!
Monitor the health and performance of Zero Trust Workload Identity Manager components by reviewing exposed metrics. This reference describes controller, certificate, and runtime metrics that help you maintain system health and troubleshoot errors.
The Zero Trust Workload Identity Manager exposes the following metrics:
- Controller runtime metrics
-
controller_runtime_active_workers: Number of currently used workers per controller -
controller_runtime_max_concurrent_reconciles: Maximum number of concurrent reconciles per controller -
controller_runtime_reconcile_errors_total: Total number of reconciliation errors per controller -
controller_runtime_reconcile_time_seconds: Length of time per reconciliation per controller -
controller_runtime_reconcile_total: Total number of reconciliations per controller
-
- Certificate watcher metrics
-
certwatcher_read_certificate_errors_total: Total number of certificate read errors -
certwatcher_read_certificate_total: Total number of certificates read
-
- Go runtime metrics
Standard Go runtime metrics including:
-
go_gc_duration_seconds: Garbage collection duration -
go_goroutines: Number of goroutines -
go_memstats_*: Memory statistics -
process_*: Process statistics
-
- Custom Operator metrics
The operator also exposes custom metrics related to:
- SPIRE Server status and health
- SPIRE Agent deployment status
- SPIFFE CSI Driver status
- OIDC Discovery Provider status
- Workload identity management operations
10.15. Uninstalling the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
To remove the Zero Trust Workload Identity Manager from OpenShift Container Platform, uninstall the Operator and delete its related resources. This process removes the component from your cluster.
10.15.1. Uninstalling the Zero Trust Workload Identity Manager Copy linkLink copied to clipboard!
To remove the Zero Trust Workload Identity Manager from your cluster, uninstall the Operator using the web console. This helps you clean up resources and delete the service from your environment.
Prerequisites
-
You have access to the cluster with
cluster-adminprivileges. - You have access to the OpenShift Container Platform web console.
- The Zero Trust Workload Identity Manager is installed.
Procedure
- Log in to the OpenShift Container Platform web console.
Uninstall the Zero Trust Workload Identity Manager.
-
Go to Ecosystem
Installed Operators. - Click the Options menu next to the Zero Trust Workload Identity Manager entry, and then click Uninstall Operator.
- In the confirmation dialog, click Uninstall.
-
Go to Ecosystem
Verification
Verify that the Zero Trust Workload Identity Manager Operator is uninstalled.
$ oc get csv -n openshift-zero-trust-workload-identityExample output
No resources found in openshift-zero-trust-workload-identity namespace.
10.15.2. Uninstalling Zero Trust Workload Identity Manager resources by using the CLI Copy linkLink copied to clipboard!
Remove Zero Trust Workload Identity Manager resources from your cluster using the CLI. This deletes the remaining operands and definitions to help ensure a clean environment after you uninstall the product.
Prerequisites
-
You have access to the cluster with
cluster-adminprivileges.
Procedure
Uninstall the operands by running each of the following commands:
Delete the
SpireOIDCDiscoveryProvidercluster by running the following command:$ oc delete SpireOIDCDiscoveryProvider clusterDelete the
SpiffeCSIDrivercluster by running the following command:$ oc delete SpiffeCSIDriver cluster -lDelete the
SpireAgentcluster by running the following command:$ oc delete SpireAgent clusterDelete the
SpireServercluster by running the following command:$ oc delete SpireServer clusterDelete the
ZeroTrustWorkloadIdentityManagercluster by running the following command:$ oc delete ZeroTrustWorkloadIdentityManager clusterDelete the persistent volume claim (PVC) by running the following command:
$ oc delete pvc -l=app.kubernetes.io/name=spire-serverDelete the service by running the following command:
$ oc delete service -l=app.kubernetes.io/name=zero-trust-workload-identity-manager -n zero-trust-workload-identity-managerDelete the namespace by running the following command:
$ oc delete ns zero-trust-workload-identity-managerDelete the cluster role by running the following command:
$ oc delete clusterrole -l=app.kubernetes.io/name=zero-trust-workload-identity-managerDelete the admission webhook configuration by running the following command:
$ oc delete validatingwebhookconfigurations -l=app.kubernetes.io/name=zero-trust-workload-identity-manager
Delete the custom resource definitions (CRDs) by running each of the following commands:
Delete the SPIRE Server CRD by running the following command:
$ oc delete crd spireservers.operator.openshift.ioDelete the SPIRE Agent CRD by running the following command:
$ oc delete crd spireagents.operator.openshift.ioDelete the SPIFFEE CSI Drivers CRD by running the following command:
$ oc delete crd spiffecsidrivers.operator.openshift.ioDelete the SPIRE OIDC Discovery Provider CRD by running the following command:
$ oc delete crd spireoidcdiscoveryproviders.operator.openshift.ioDelete the SPIRE and SPIFFE cluster federated trust domains CRD by running the following command:
$ oc delete crd clusterfederatedtrustdomains.spire.spiffe.ioDelete the cluster SPIFFE IDs CRD by running the following command:
$ oc delete crd clusterspiffeids.spire.spiffe.ioDelete the SPIRE and SPIFFE cluster static entries CRD by running the following command:
$ oc delete crd clusterstaticentries.spire.spiffe.ioDelete the Zero Trust Workload Identity Manager CRD by running the following command:
$ oc delete crd zerotrustworkloadidentitymanagers.operator.openshift.io
Verification
To verify that the resources have been deleted, replace each oc delete command with oc get, and then run the command. If no resources are returned, the deletion was successful.