---
title: Zero Trust Workload Identity Manager OIDC federation
---

# Zero Trust Workload Identity Manager OIDC federation {#zero-trust-manager-oidc-federation}

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

## About the Entra ID OpenID Connect {#zero-trust-manager-entraid-oidc-about_zero-trust-manager-oidc-federation}

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.

### Configuring the external certificate for the managed OIDC discovery provider route {#zero-trust-manager-create-route-oidc_zero-trust-manager-oidc-federation}

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](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/security_and_compliance/index#cert-manager-operator-install).
- You have created a `ClusterIssuer` or `Issuer` configured with a publicly trusted CA service. For example, an Automated Certificate Management Environment (ACME) type `Issuer` with the "Let’s Encrypt ACME" service. For more information, see [Configuring an ACME issuer](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/security_and_compliance/index#cert-manager-operator-issuer-acme)

**Procedure**

1. Create a `Role` to provide the router service account permissions to read the referenced secret by running the following command:

   ```terminal
   $ oc create role secret-reader \
     --verb=get,list,watch \
     --resource=secrets \
     --resource-name=$TLS_SECRET_NAME \
     -n zero-trust-workload-identity-manager
   ```
2. Create a `RoleBinding` resource to bind the router service account with the newly created Role resource by running the following command:

   ```terminal
   $ oc create rolebinding secret-reader-binding \
     --role=secret-reader \
     --serviceaccount=openshift-ingress:router \
     -n zero-trust-workload-identity-manager
   ```
3. Configure the `SpireOIDCDIscoveryProvider` Custom Resource (CR) object to reference the Secret generated in the earlier step by running the following command:

   ```terminal
   $ oc patch SpireOIDCDiscoveryProvider cluster --type=merge -p='
   spec:
     externalSecretRef: ${TLS_SECRET_NAME}
   '
   ```

**Verification**

1. In the `SpireOIDCDiscoveryProvider` CR, check if the `ManageRouteReady` condition is set to `True` by running the following command:

   ```terminal
   $ oc wait --for=jsonpath='{.status.conditions[?(@.type=="ManagedRouteReady")].status}'=True SpireOIDCDiscoveryProvider/cluster --timeout=120s
   ```
2. Verify that the OIDC endpoint can be accessed securely through HTTPS by running the following command:

   ```terminal
   $ 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"
     ]
   }%
   ```

### Disabling a managed route {#zero-trust-manager-disabling-route_zero-trust-manager-oidc-federation}

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 `managedRoute` to `false` by running the following command:

  ```terminal
  $ oc patch SpireOIDCDiscoveryProvider cluster --type=merge -p='
  spec:
    managedRoute: "false"
  ```

### Using Entra ID with Microsoft Azure {#zero-trust-manager-configure-azure_zero-trust-manager-oidc-federation}

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**

1. Log in to Azure by running the following command:

   ```terminal
   $ az login
   ```
2. Configure variables for your Azure subscription and tenant by running the following commands:

   ```terminal
   $ export SUBSCRIPTION_ID=$(az account list --query "[?isDefault].id" -o tsv)
   ```

   ```terminal
   $ export TENANT_ID=$(az account list --query "[?isDefault].tenantId" -o tsv)
   ```

   ```terminal
   $ export LOCATION=centralus
   ```

   where:

   `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.

1. Define resource variable names by running the following commands:

   ```terminal
   $ export NAME=ztwim
   ```

   ```terminal
   $ export RESOURCE_GROUP="${NAME}-rg"
   ```

   ```terminal
   $ export STORAGE_ACCOUNT="${NAME}storage"
   ```

   ```terminal
   $ export STORAGE_CONTAINER="${NAME}storagecontainer"
   ```

   ```terminal
   $ 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.

1. Create the resource group by running the following command:

   ```terminal
   $ az group create \
     --name "${RESOURCE_GROUP}" \
     --location "${LOCATION}"
   ```

### Configuring Azure blob storage {#zero-trust-manager-configure-azure-blob_zero-trust-manager-oidc-federation}

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**

1. Create a new storage account that is used to store content by running the following command:

   ```terminal
   $ az storage account create \
     --name ${STORAGE_ACCOUNT} \
     --resource-group ${RESOURCE_GROUP} \
     --location ${LOCATION} \
     --encryption-services blob
   ```
2. Obtain the storage ID for the newly created storage account by running the following command:

   ```terminal
   $ export STORAGE_ACCOUNT_ID=$(az storage account show -n ${STORAGE_ACCOUNT} -g ${RESOURCE_GROUP} --query id --out tsv)
   ```
3. 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:

   ```terminal
   $ az storage container create \
     --account-name ${STORAGE_ACCOUNT} \
     --name ${STORAGE_CONTAINER} \
     --auth-mode login
   ```

### Configuring an Azure user managed identity {#zero-trust-manager-configure-azure-identity_zero-trust-manager-oidc-federation}

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**

1. 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:

   ```terminal
   $ az identity create \
     --name ${USER_ASSIGNED_IDENTITY_NAME} \
     --resource-group ${RESOURCE_GROUP}
   ```

   ```terminal
   $ export IDENTITY_CLIENT_ID=$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)
   ```
2. Retrieve the `CLIENT_ID` of an Azure user-assigned managed identity and save it as an environment variable by running the following command:

   ```terminal
   $ export IDENTITY_CLIENT_ID=$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)
   ```
3. Associate a role with the Service Principal associated with the User Managed Identity by running the following command:

   ```terminal
   $ az role assignment create \
     --role "Storage Blob Data Contributor" \
     --assignee "${IDENTITY_CLIENT_ID}" \
     --scope ${STORAGE_ACCOUNT_ID}
   ```

### Creating the demonstration application {#zero-trust-manager-create-demo-app_zero-trust-manager-oidc-federation}

Create the demonstration application to verify that the entire system functions correctly. This process validates the configuration of your application secrets and namespaces.

**Procedure**

1. Set the application name and namespace by running the following commands:

   ```terminal
   $ export APP_NAME=workload-app
   ```

   ```terminal
   $ export APP_NAMESPACE=demo
   ```
2. Create the namespace by running the following command:

   ```terminal
   $ oc create namespace $APP_NAMESPACE
   ```
3. Create the application Secret by running the following command:

   ```terminal
   $ 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
   ```

### Deploying the workload application {#zero-trust-manager-deploy-app_zero-trust-manager-oidc-federation}

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**

1. To deploy the application, copy the entire command block provided and paste it directly into your terminal. Press **Enter**.

   ```terminal
   $ 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**

1. Ensure that the `workload-app` pod is running successfully by running the following command:

   ```terminal
   $ oc get pods -n $APP_NAMESPACE
   ```

   ```terminal {title="Example output"}
   NAME                             READY     STATUS      RESTARTS      AGE
   workload-app-5f8b9d685b-abcde    1/1       Running     0             60s
   ```
2. Retrieve the SPIFFE JWT Token (SVID-JWT):

   1. Get the pod name dynamically by running the following command:

      ```terminal
      $ POD_NAME=$(oc get pods -n $APP_NAMESPACE -l app=$APP_NAME -o jsonpath='{.items[0].metadata.name}')
      ```
   2. Run the script inside the pod by running the following command:

      ```terminal
      $ oc exec -it $POD_NAME -n $APP_NAMESPACE -- \
        /opt/app-root/src/get-spiffe-token.py -a "api://AzureADTokenExchange"
      ```

### Configuring Azure with the SPIFFE identity federation {#zero-trust-manager-spiffe-identity-federation_zero-trust-manager-oidc-federation}

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:

  ```terminal
  $ 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
  ```

### Verifying that the application workload can access the content in the Azure Blob Storage {#zero-trust-manager-verify-blob-access_zero-trust-manager-oidc-federation}

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**

1. Retrieve a JWT token from the SPIFFE Workload API by running the following command:

   ```terminal
   $ oc rsh -n $APP_NAMESPACE deployment/$APP_NAME
   ```
2. Create and export an environment variable named `TOKEN` by running the following command:

   ```terminal
   $ export TOKEN=$(/opt/app-root/src/get-spiffe-token.py --audience=$AZURE_AUDIENCE)
   ```
3. Log in to Azure CLI included within the pod by running the following command:

   ```terminal
   $ az login --service-principal \
     -t ${AZURE_TENANT_ID} \
     -u ${AZURE_CLIENT_ID} \
     --federated-token ${TOKEN}
   ```
4. Create a new file with the application workload pod and upload the file to the Blob Storage by running the following command:

   ```terminal
   $ echo “Hello from OpenShift” > openshift-spire-federated-identities.txt
   ```
5. Upload a file to the Azure Blog Storage by running the following command:

   ```terminal
   $ 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:

  ```terminal
  $ az storage blob list \
    --account-name ${BLOB_STORE_ACCOUNT} \
    --container-name ${BLOB_STORE_CONTAINER} \
    --auth-mode login \
    -o table
  ```

## About Vault OpenID Connect {#zero-trust-manager-vault-oidc-about_zero-trust-manager-oidc-federation}

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.

### Installing Vault {#zero-trust-manager-install-vault-oidc_zero-trust-manager-oidc-federation}

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 [Configuring routes](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/ingress_and_load_balancing/routes#nw-configuring-routes)
- Helm is installed.
- A command-line JSON processor for easily reading the output from the Vault API.
- A HashiCorp Helm repository is added.

**Procedure**

1. Create the `vault-helm-value.yaml` file.

   ```yaml
   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 `openshift` field optimizes the deployment for OpenShift-specific security contexts.
   - The `tlsDisable` field disables TLS for Kubernetes objects created by the chart.
   - The `datastorage.enabled` field creates a 1Gi persistent volume to store Vault data.
   - The `standalone.enabled` field deploys a single Vault pod.
   - The `tls_disabled` field tells the Vault server to not use TLS.
2. Run the `helm install` command:

   ```terminal
   $ helm install vault hashicorp/vault \
     --create-namespace -n vault \
     --values ./vault-helm-value.yaml
   ```
3. Expose the Vault service by running the following command:

   ```terminal
   $ oc expose service vault -n vault
   ```
4. Set the `VAULT_ADDR` environment variable to retrieve the hostname from the new route and then export it by running the following command:

   ```terminal
   $ export VAULT_ADDR="http://$(oc get route vault -n vault -o jsonpath='{.spec.host}')"
   ```

   > [!NOTE]
   > `http://` is prepended because TLS is disabled.

**Verification**

- To ensure your Vault instance is running, run the following command:

  ```terminal
  $ curl -s $VAULT_ADDR/v1/sys/health | jq
  ```

  ```JSON {title="Example 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"
  }
  ```

### Initializing and unsealing Vault {#zero-trust-manager-initialize-vault-oidc_zero-trust-manager-oidc-federation}

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:

1. Initialize and unseal Vault
2. Enable the key-value (KV) secrets engine and store a test secret
3. Configure JSON Web Token (JWT) authentication with SPIRE
4. Deploy a demonstration application
5. 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**

1. Open a remote shell into the `vault` pod by running the following command:

   ```terminal
   $ oc rsh -n vault statefulset/vault
   ```
2. Initialize Vault to get your unseal key and root token by running the following command:

   ```terminal
   $ vault operator init -key-shares=1 -key-threshold=1 -format=json
   ```
3. Export the unseal key and root token you received from the earlier command by running the following commands:

   ```terminal
   $ export UNSEAL_KEY=<Your-Unseal-Key>
   ```

   ```terminal
   $ export ROOT_TOKEN=<Your-Root-Token>
   ```
4. Unseal Vault using your unseal key by running the following command:

   ```terminal
   $ vault operator unseal -format=json $UNSEAL_KEY
   ```
5. Exit the pod by entering `exit`.

**Verification**

- To verify that the Vault pod is ready, run the following command:

  ```terminal
  $ oc get pod -n vault
  ```

  ```terminal {title="Example output"}
  NAME        READY        STATUS      RESTARTS     AGE
  vault-0     1/1          Running     0            65d
  ```

### Enabling the key-value secrets engine and store a test secret {#zero-trust-manager-vault-enable-kv_zero-trust-manager-oidc-federation}

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**

1. Open another shell session in the `Vault` pod by running the following command:

   ```terminal
   $ oc rsh -n vault statefulset/vault
   ```
2. Export your root token again within this new session and log in by running the following command:

   ```terminal
   $ export ROOT_TOKEN=<Your-Root-Token>
   ```

   ```terminal
   $ vault login "${ROOT_TOKEN}"
   ```
3. Enable the KV secrets engine at the `secret/` path and create a test secret by running the following commands:

   ```terminal
   $ export NAME=ztwim
   ```

   ```terminal
   $ vault secrets enable -path=secret kv
   ```

   ```terminal
   $ vault kv put secret/$NAME version=v0.1.0
   ```

**Verification**

- To verify that the secret is stored correctly, run the following command:

  ```terminal
  $ vault kv get secret/$NAME
  ```

### Configuring JSON Web Token authentication with SPIRE {#zero-trust-manager-vault-authenticate-jwt_zero-trust-manager-oidc-federation}

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**

1. On your local machine, retrieve the SPIRE Certificate Authority (CA) bundle and save it to a file by running the following command:

   ```terminal
   $ oc get cm -n zero-trust-workload-identity-manager spire-bundle -o jsonpath='{ .data.bundle\.crt }' > oidc_provider_ca.pem
   ```
2. Back in the Vault pod shell, create a temporary file and paste the contents of `oidc_provider_ca.pem` into it by running the following command:

   ```terminal
   $ cat << EOF > /tmp/oidc_provider_ca.pem
   -----BEGIN CERTIFICATE-----
   <Paste-Your-Certificate-Content-Here>
   -----END CERTIFICATE-----
   EOF>
   ```
3. Set up the necessary environment variables for the JWT configuration by running the following commands:

   ```terminal
   $ export APP_DOMAIN=<Your-App-Domain>
   ```

   ```terminal
   $ export JWT_ISSUER_ENDPOINT="oidc-discovery.$APP_DOMAIN"
   ```

   ```terminal
   $ export OIDC_URL="https://$JWT_ISSUER_ENDPOINT"
   ```

   ```terminal
   $ export OIDC_CA_PEM="$(cat /tmp/oidc_provider_ca.pem)"
   ```
4. Crate a new environment variable by running the following command:

   ```terminal
   $ export ROLE="${NAME}-role"
   ```
5. Enable the JWT authentication method by running the following command:

   ```terminal
   $ vault auth enable jwt
   ```
6. Configure you ODIC authentication method by running the following command:

   ```terminal
   $ vault write auth/jwt/config \
     oidc_discovery_url=$OIDC_URL \
     oidc_discovery_ca_pem="$OIDC_CA_PEM" \
     default_role=$ROLE
   ```
7. Create a policy named `ztwim-policy` by running the following command:

   ```terminal
   $ export POLICY="${NAME}-policy"
   ```
8. Grant read access to the secret you created earlier by running the following command:

   ```terminal
   $ vault policy write $POLICY -<<EOF
   path "secret/$NAME" {
       capabilities = ["read"]
   }
   EOF
   ```
9. Create the following environment variables by running the following commands:

   ```terminal
   $ export APP_NAME=client
   ```

   ```terminal
   $ export APP_NAMESPACE=demo
   ```

   ```terminal
   $ export AUDIENCE=$APP_NAME
   ```
10. Create a JWT role that binds the policy to workload with a specific SPIFFE ID by running the following command:

    ```terminal
    $ 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
    ```

### Deploying a demonstration application {#zero-trust-manager-vault-deploy-demo_zero-trust-manager-oidc-federation}

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**

1. On your local machine, set the environment variables for your application by running the following commands:

   ```terminal
   $ export APP_NAME=client
   ```

   ```terminal
   $ export APP_NAMESPACE=demo
   ```

   ```terminal
   $ export AUDIENCE=$APP_NAME
   ```
2. Apply 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.

   ```terminal
   $ 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:

  ```terminal
  $ oc get deploy -n $APP_NAMESPACE
  ```

  ```terminal {title="Example output"}
  NAME             READY        UP-TO-DATE      AVAILABLE     AGE
  frontend-app     2/2          2               2             120d
  backend-api      3/3          3               3             120d
  ```

### Authenticating and retrieving the secret {#zero-trust-manager-vault-authenticate-secret_zero-trust-manager-oidc-federation}

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**

1. Fetch a JWT-SVID by running the following command inside the running client pod:

   ```terminal
   $ 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 $AUDIENCE
   ```
2. Copy the token from the output and export it as an environment variable on your local machine by running the following command:

   ```terminal
   $ export IDENTITY_TOKEN=<Your-JWT-Token>
   ```
3. Crate a new environment variable by running the following command:

   ```terminal
   $ export ROLE="${NAME}-role"
   ```
4. Use `curl` to send the JWT token to the Vault login endpoint to get a Vault client token by running the following command:

   ```terminal
   $ 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:

  ```terminal
  $ curl -s -H "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/secret/$NAME | jq
  ```

  You should see the contents of the secret (`"version": "v0.1.0"`) in the output, confirming the entire workflow is successful
