Integrating SPIRE federation with multi-cluster Red Hat OpenShift Service Mesh¶
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.
Multi-cluster SPIFFE Runtime Environment integration with Red Hat OpenShift Service Mesh¶
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.
What gets federated¶
Federation happens at two layers, and both are required:
| Layer | What | How |
|---|---|---|
| SPIRE Federation | Trust bundles | SPIRE Servers exchange bundles via https_spiffe profile |
| Istio Federation | Service discovery and routing | Istiod discovers remote endpoints via remote secrets, routes traffic through east-west gateways |
Preparing the environment for multi-cluster SPIFFE Runtime Environment federation¶
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 the kubeconfig file paths for Cluster A and Cluster B by running the following commands:
-
Set the base domain environment variables for Cluster A and Cluster B by running the following commands:
-
Export the trust domain environment variables from the base domain of each cluster by running the following commands:
-
Define the cluster and network environment variables by running the following commands:
-
Export the federation endpoint URLs for Cluster A and Cluster B by running the following commands:
-
Set the JWT issuer environment variables for Cluster A and Cluster B by running the following commands:
Deploying SPIFFE Runtime Environment with federation on both clusters¶
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: -
Apply the YAML file on Cluster A by running the following command:
-
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:
-
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:
-
Create a YAML file that defines the
SpiffeCSIDriverCR on Cluster A: -
Apply the YAML file on Cluster A by running the following command:
-
Create a YAML file that defines the
SpireOIDCDiscoveryProviderCR on Cluster A: -
Apply the YAML file on Cluster A by running the following command:
-
-
Deploy SPIRE with federation enabled on Cluster B:
-
Create a YAML file that defines the
ZeroTrustWorkloadIdentityManagerCR on Cluster B: -
Apply the YAML file on Cluster B by running the following command:
-
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:
-
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:
-
Create a YAML file that defines the
SpiffeCSIDriverCR on Cluster B: -
Apply the YAML file on Cluster B by running the following command:
-
Create a YAML file that defines the
SpireOIDCDiscoveryProviderCR on Cluster B: -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
spire-serverStatefulSet to become ready on Cluster A by running the following command: -
Wait for the
spire-agentDaemonSet to become ready on Cluster A by running the following command: -
Wait for the
spire-spiffe-csi-driverDaemonSet to become ready on Cluster A by running the following command: -
Wait for the
spire-spiffe-oidc-discovery-providerdeployment to become available on Cluster A by running the following command: -
Wait for the
spire-serverStatefulSet to become ready on Cluster B by running the following command: -
Wait for the
spire-agentDaemonSet to become ready on Cluster B by running the following command: -
Wait for the
spire-spiffe-csi-driverDaemonSet to become ready on Cluster B by running the following command: -
Wait for the
spire-spiffe-oidc-discovery-providerdeployment to become available on Cluster B by running the following command:
Verification
-
Verify that the SDS configuration is available on Cluster A by running the following command:
-
Verify that the SDS configuration is available on Cluster B by running the following command:
Additional resources¶
- Installing the Zero Trust Workload Identity Manager
- Zero Trust Workload Identity Manager SPIRE federation
Configuring Red Hat OpenShift Service Mesh for multi-cluster SPIFFE Runtime Environment integration¶
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:
-
Verify that the federation routes are created on Cluster B by running the following command:
-
On 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/server -
Apply the YAML file on Cluster A by running the following command:
-
-
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/server -
Apply the YAML file on Cluster B by running the following command:
-
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}. -
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}. -
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. -
Verify 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.
Deploying the Red Hat OpenShift Service Mesh CNI on both clusters¶
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.11 on both clusters.
Procedure
-
Create the Red Hat OpenShift Service Mesh CNI namespace on Cluster A by running the following command:
-
Deploy the
IstioCNICR on Cluster A by running the following command:-
Create a YAML file that defines the
IstioCNICR on Cluster A: -
Apply the YAML file on Cluster A by running the following command:
-
-
Wait for the
istio-cni-nodeDaemonSet to be created on Cluster A by running the following command: -
Wait for the
IstioCNIDaemonSet to become ready on Cluster A by running the following command: -
Create the Red Hat OpenShift Service Mesh CNI namespace on Cluster B by running the following command:
-
Deploy the
IstioCNICR on Cluster B by running the following command:-
Create a YAML file that defines the
IstioCNICR on Cluster B: -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
istio-cni-nodeDaemonSet to be created on Cluster B by running the following command: -
Wait for the
IstioCNIDaemonSet to become ready on Cluster B by running the following command: -
Create 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:
-
-
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:
Warning
Do 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.
-
Deploying the Istio custom resource with the federation configuration¶
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.
Note
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:
-
Extract the OpenID Connect (OIDC) certificate on Cluster B by running the following command:
-
Get the bundle endpoint URL for Cluster A by running the following command:
-
Get the bundle endpoint URL for Cluster B by running the following command:
-
Create the
Istiocustom resource (CR) on Cluster A by running the following command: -
Apply 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: true -
Apply the YAML file on Cluster A by running the following command:
-
-
Create the
IstioCR on Cluster B by running the following command: -
Apply 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: true -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
istioddeployment to be created on Cluster A by running the following command: -
Wait for
Istiodto become ready on Cluster A by running the following command: -
Wait for the
istioddeployment to be created on Cluster B by running the following command: -
Wait for
Istiodto become ready on Cluster B by running the following command:
Verifying SPIRE integration with Istio on each cluster¶
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:
-
Prepare the verification namespace on both clusters by running the following commands:
-
Create the verification namespace on Cluster A:
-
Enable Istio injection for the verification namespace on Cluster A:
-
Create the verification namespace on Cluster B:
-
Enable Istio injection for the verification namespace on Cluster B:
-
-
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: 8080 -
Apply the YAML file on Cluster A by running the following command:
-
-
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: 8080 -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
httpbindeployment to become available on Cluster A by running the following command: -
Wait for the
httpbindeployment to become available on Cluster B by running the following command: -
Verify the Envoy sidecar certificate on Cluster A by running the following commands:
-
Get the
httpbinpod name on Cluster A: -
Export the Envoy sidecar certificate chain for the
httpbinpod on Cluster A: -
Confirm the certificate was issued by SPIRE on Cluster A:
-
-
Verify the Envoy sidecar certificate on Cluster B by running the following commands:
-
Get the
httpbinpod name on Cluster B: -
Export the Envoy sidecar certificate chain for the
httpbinpod on Cluster B: -
Confirm the certificate was issued by SPIRE on Cluster B:
If 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:
-
Remove the verification namespace from Cluster B:
-
Verifying workload mTLS with SPIRE-issued identities on each cluster¶
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:
-
Set the SPIFFE audience environment variable:
-
-
Prepare the test namespace on both clusters by running the following commands:
-
Create the test namespace on Cluster A:
-
Enable Istio injection for the test namespace on Cluster A:
-
Create the test namespace on Cluster B:
-
Enable Istio injection for the test namespace on Cluster B:
-
-
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: 8080 -
Apply the YAML file on Cluster A by running the following command:
-
-
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: 8080 -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
httpbindeployment to become available on both clusters by running the following commands:-
Wait for the
httpbindeployment on Cluster A: -
Wait for the
httpbindeployment on Cluster B:
-
-
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: IfNotPresent -
Apply the YAML file on Cluster A by running the following command:
-
-
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: IfNotPresent -
Apply the YAML file on Cluster B by running the following command:
-
-
Wait for the
curldeployment to become available on both clusters by running the following commands:-
Wait for the
curldeployment on Cluster A: -
Wait for the
curldeployment on Cluster B:
-
-
Verify that the
curlclient can reachhttpbinon both clusters before enablingSTRICTmTLS by running the following commands: -
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_MUTUAL -
Apply the YAML file on Cluster A by running the following command:
-
-
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_MUTUAL -
Apply the YAML file on Cluster B by running the following command:
-
-
Verify that the
curlclient can reachhttpbinon both clusters withSTRICTmTLS enabled by running the following commands:-
Verify connectivity on Cluster A:
-
Verify connectivity on Cluster B:
$ oc exec deploy/curl -n "${TPJ}" --kubeconfig="${CLUSTER_B_KUBECONFIG}" -it -- \ curl -s -o /dev/null -w "%{http_code}" http://httpbinIf 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:
-
Remove the test namespace from Cluster B:
-
Deploying east-west gateways¶
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:
-
Update the Istio Helm repository by running the following command:
-
Grant security context constraints (SCC) permissions on Cluster A by running the following command:
-
Grant security context constraints (SCC) permissions on Cluster B by running the following command:
-
Install the Istio gateway on Cluster A by running the following command:
-
Install the Istio gateway on Cluster B by running the following command:
-
Wait for the east-west gateway to become available on Cluster A by running the following command:
-
Wait for the east-west gateway to become available on Cluster B by running the following command:
-
Create 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: -
Apply the YAML file on Cluster A by running the following command:
-
-
Create the cross-network
GatewayCR on Cluster B by running the following command:-
Create a YAML file that defines the
GatewayCR on Cluster B: -
Apply the YAML file on Cluster B by running the following command:
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: -
Verify that the cross-network
Gatewayexists on Cluster B by running the following command:
Exchanging remote secrets¶
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:
-
Create an Istio remote secret on Cluster B by running the following command:
-
Verify that the remote cluster is synced on Cluster A by running the following command:
The output must show
${CLUSTER_B}with statussynced. -
Verify that the remote cluster is synced on Cluster B by running the following command:
The output must show
${CLUSTER_A}with statussynced.
Verifying cross-cluster service communication¶
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:
-
Create the
samplenamespace on Cluster A by running the following command: -
Enable Istio injection for the
samplenamespace on Cluster A by running the following command: -
Create the
samplenamespace on Cluster B by running the following command: -
Enable Istio injection for the
samplenamespace on Cluster B by running the following command: -
Install the Istio
HelloWorldServicein Cluster B by running the following command:-
Create a YAML file that defines the
HelloWorldServicein Cluster B: -
Apply the YAML file in Cluster B by running the following command:
-
-
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: 5000 -
Apply the YAML file in Cluster B by running the following command:
-
-
Install the Istio
HelloWorldServicein Cluster A by running the following command:-
Create a YAML file that defines the
HelloWorldServicein Cluster A: -
Apply the YAML file in Cluster A by running the following command:
-
-
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: true -
Apply the YAML file in Cluster A by running the following command:
-
-
Add the SPIRE injection template to the
sleepapplication in Cluster A by running the following command: -
Add the SPIRE injection template to the
HelloWorldapplication in Cluster B by running the following command: -
Wait for the
sleepdeployment to become available on Cluster A by running the following command: -
Wait for the
helloworld-v1deployment to become available on Cluster B by running the following command: -
Verify that the
sleeppod uses a SPIRE-issued identity by running the following command: -
Verify that the
sleeppod on Cluster A can reach thehelloworld.sampleservice by running the following command:
Additional resources