/images/resources/howtos/non-human-identities/spiffe.png

Integrate the Curity Identity Server with SPIFFE and SPIRE

On this page

The Secure Production Identity Framework For Everyone (SPIFFE) provides specifications for interoperable, strong identity documents for workloads. The SPIFFE Runtime Environment (SPIRE) is an implementation that you can deploy to a cloud native environment.

When you deploy SPIRE you can issue SPIFFE Verifiable Identity Documents (SVIDs) to backend workloads. SVIDs are verifiable X509 or JWT documents that serve as workload credentials. A workload can send a SVID to another workload, which can verify the SVID to authenticate the calling workload.

OAuth client workloads can authenticate at the Curity Identity Server using SVIDs. This tutorial explains how to run a basic deployment of SPIRE in Kubernetes, and reference its trust bundles in the Curity Identity Server. First, ensure that you have a Kubenetes test cluster, then follow the deployment steps.

Configure an Upstream Authority

In many deployments, SPIRE integrates with an upstream certificate authority that workloads trust, using the cert-manager tool. The following commands deploy cert-manager using its Helm chart:

bash
1234567
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait

The helm chart installs custom resources like Issuer, Certificate and ClusterIssuer that you need for the cert-manager to start issuing certificates. For a demo deployment you can create a long-lived, self-signed root authority with the following command.

bash
123456789101112131415161718192021222324252627282930313233343536
cat <<EOF | kubectl -n cert-manager apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: rootca-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: rootca
spec:
isCA: true
duration: 8760h
secretName: rootca-secret
commonName: curitydemo-root-ca
subject:
organizations:
- curitydemo
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: rootca-issuer
kind: Issuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: spire-ca-issuer
spec:
ca:
secretName: rootca-secret
EOF

Deploy SPIRE

The SPIRE Helm Charts provide a convenient way to deploy SPIRE. The following Helm values file demonstrates settings for a demo deployment that configures the upstream authority with the cert-manager. Among others, it sets the trust domain and refers to the spire-ca-issuer of the cert-manager from above for the upstream authority that signs SVIDs.

yaml
12345678910111213141516171818
global:
spire:
clusterName: curitydemo
trustDomain: curitydemo
caSubject:
country: SE
organization: curitydemo
commonName: curitydemo-intermediate-ca
spire-server:
replicaCount: 1
caKeyType: ec-p256
upstreamAuthority:
certManager:
enabled: true
issuerName: spire-ca-issuer
issuerKind: ClusterIssuer
issuerGroup: cert-manager.io

You can trigger the demo SPIRE deployment with the following commands, where spire-helm-values.yaml refers to the Helm values file from above.

bash
12345678910111213
helm repo add spire https://spiffe.github.io/helm-charts-hardened/
helm repo update
helm upgrade --install spire-crds spire-crds \
--namespace spire-server \
--create-namespace \
--repo https://spiffe.github.io/helm-charts-hardened/ \
helm upgrade --install spire spire \
--namespace spire-server \
--repo https://spiffe.github.io/helm-charts-hardened/ \
--values=spire-helm-values.yaml \
--wait

Once the deployment completes, run the command kubectl get pod -n spire-server and you will see a number of workloads.

text
1234567
NAME READY STATUS
spire-agent-9m2mr 1/1 Running
spire-agent-wmbg9 1/1 Running
spire-server-0 2/2 Running
spire-spiffe-csi-driver-f2mlv 2/2 Running
spire-spiffe-csi-driver-hmndn 2/2 Running
spire-spiffe-oidc-discovery-provider-7d86b894b4-ckqwr 2/2 Running

The list includes the main SPIRE server and SPIRE agent components that collaborate to issue SVIDs to workloads. It also contains a driver that enables workloads to get their SVIDs, and an OIDC discovery provider that hosts useful URLs for verifying SVIDs.

Integrate with a Service Mesh

In some use cases you may want to integrate SPIRE with a service mesh, so that the service mesh uses SPIRE X509 SVIDs for mutual TLS connections between workloads. If you use Istio, you can follow that provider's SPIRE Integration. To do so, use the following Helm values file to instruct Istio sidecars to integrate with SPIRE via the driver.

yaml
12345678910111213141516171818
meshConfig:
trustDomain: curitydemo
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

The SPIFFE Container Storage Interface (CSI) Driver mounts the Workload API socket from the SPIRE agent into the volume mount (at the mountPath). This socket allows Istio to download X509 SVIDs to the sidecars that the service mesh creates. Note that the trust domain for Istio (meshConfig.trustDomain) must match that of SPIRE as configured in the previous section.

To update the deployment from the Integrate the Curity Identity Server with an Istio Service Mesh tutorial to use X509 SPIRE identities for the Curity Identity Server, add an extra annotation of inject.istio.io/templates: 'sidecar,spire' to the admin and runtime workloads. The following example Helm values file demonstrates the approach.

yaml
123456789101112131415161718192021222324252627282930313232
replicaCount: 1
image:
repository: curity.azurecr.io/curity/idsvr
tag: latest
curity:
adminUiHttp: true
admin:
annotations:
inject.istio.io/templates: "sidecar,spire"
podLabels:
sidecar.istio.io/inject: 'true'
serviceAccount:
name: curity-idsvr-admin
runtime:
annotations:
inject.istio.io/templates: "sidecar,spire"
podLabels:
sidecar.istio.io/inject: 'true'
serviceAccount:
name: curity-idsvr-runtime
config:
uiEnabled: true
configuration:
- configMapRef:
name: idsvr-config
items:
- key: idsvr-config
path: curity-config.xml

View SPIRE Registrations

The SPIRE deployment uses the SPIRE Controller Manager to register workloads with SPIRE. Use the following command to view registration details.

bash
1
kubectl -n spire-server exec -t spire-server-0 -- ./bin/spire-server entry show

The following output shows registrations for workloads of the Curity Identity Server, each of which SPIRE assigns a SPIFFE ID.

text
1234567891011121314151617
Entry ID : curitydemo.fd3d3a57-bf2a-4f43-9bbd-8b1c3dc392e5
SPIFFE ID : spiffe://curitydemo/ns/curity/sa/curity-idsvr-admin
Parent ID : spiffe://curitydemo/spire/agent/k8s_psat/curitydemo/655a2f85-d01a-4801-ac40-a7f61d95376a
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:pod-uid:af7e7930-7704-4b22-b289-0f7549c2561b
Hint : default
Entry ID : curitydemo.2d79e928-fb22-4e8e-bb16-a1535b7ee6f8
SPIFFE ID : spiffe://curitydemo/ns/curity/sa/curity-idsvr-runtime
Parent ID : spiffe://curitydemo/spire/agent/k8s_psat/curitydemo/fc5d5cc0-46df-4446-96b0-2c30dea854e3
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:pod-uid:46ec0c14-b468-4486-92cb-43ca3b9dbe13
Hint : default

Deploy a Client Workload

SPIRE allows workloads to work directly with SVIDs and use them to authenticate with other workloads. To apply workload authentication to OAuth, deploy a workload that can download SVIDs and use them for OAuth client authentication.

A convenient way for a workload to download SVIDs, and renew them before they expire, is to use the SPIFFE Helper utility that integrates with the Workload API socket. You can download the SPIFFE helper binary with the following commands.

bash
123
VERSION='v0.11.0'
curl -s -L "https://github.com/spiffe/spiffe-helper/releases/download/$VERSION/spiffe-helper_${VERSION}_Linux-x86_64.tar.gz" > spiffe-helper.tar.gz
tar xf spiffe-helper.tar.gz

Then create a helper.conf file with the following settings, including daemon_mode = true, so that the container keeps running and renews SVIDs before they expire. The agent_address points to the expected path of the socket that the CSI driver mounts into the container where the SPIFFE helper utility is running. The audience of JWT SVIDs must point to the Curity Identity Server so that it accepts them for client authentication.

text
12345678910111212
daemon_mode = true
agent_address = "/spiffe-workload-api/socket"
cmd = ""
cmd_args = ""
cert_dir = "/svids"
renew_signal = ""
svid_file_name = "svid.pem"
svid_key_file_name = "svid_key.pem"
svid_bundle_file_name = "svid_bundle.pem"
jwt_svids = [{jwt_audience="https://login.curitydemo.example/oauth/v2/oauth-token", jwt_svid_file_name="jwt_svid.token"}]
jwt_bundle_file_name = "jwt_bundle.json"
add_intermediates_to_bundle = true

Next, create a Dockerfile for a workload that runs the SPIFFE helper utility to download SVIDs. Run a Docker build to produce an image such as client:1.0.

dockerfile
12345678
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y curl jq openssl
WORKDIR /work
COPY spiffe-helper /work/
COPY helper.conf /work/
CMD ["/work/spiffe-helper"]

The following example command deploys the Docker image in a Kubernetes deployment. The workload's container mounts the socket for the workload API via the SPIFFE CSI driver. This is the socket that the SPIFFE helper utility uses to get the SPIRE identities for the workload.

bash
123456789101112131415161718192021222324252627282930313233343536373838
kubectl create namespace applications
cat <<EOF | kubectl -n applications apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: workload-client
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: workload-client
spec:
replicas: 1
selector:
matchLabels:
app: workload-client
template:
metadata:
labels:
app: workload-client
spec:
serviceAccountName: workload-client
containers:
- name: workload-client
image: client:1.0
volumeMounts:
- name: spiffe-workload-api
mountPath: /spiffe-workload-api
readOnly: true
- name: svids
mountPath: /svids
volumes:
- name: spiffe-workload-api
csi:
driver: 'csi.spiffe.io'
readOnly: true
- name: svids
emptyDir: {}

Next, use a command such as the following, to get a command shell in the workload.

bash
12
POD=$(kubectl -n applications get pods --selector='app=workload-client' -o=name)
kubectl -n applications exec -it "$POD" -- bash

The SPIFFE helper should populate the following files in the /svids folder.

FileDescription
jwt_svid.tokenA JWT SVID workload identity.
jwt_bundle.jsonA trust store in JSON Web Key (JWK) format.
svid_key.pemThe private key for the X509 SVID workload identity.
svid.pemThe certificate for the X509 SVID workload identity.
svid_bundle.pemA trust store with SPIRE's root and intermediate certificate authorities.

Run OAuth Flows

The Harden OAuth Client Credentials with SPIFFE JWT SVIDs tutorial explains how to configure the Curity Identity Server to enable workloads to use JWT SVIDs as OAuth client credentials. To do so, the workload can send a request of the following form.

bash
1234567
JWT_ASSERTION="$(cat /svids/jwt_svid.token)"
curl -s -X POST http://curity-idsvr-runtime-svc.curity:8443/oauth/v2/oauth-token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d "client_assertion=$JWT_ASSERTION" \
-d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
-d 'scope=reports'

The Harden OAuth Client Credentials with SPIFFE X509 SVIDs tutorial explains how to configure the Curity Identity Server to enable workloads to use X509 SVIDs as OAuth client credentials. To do so, the workload can send a request of the following form.

bash
12345
curl -s -X POST http://curity-idsvr-runtime-svc.curity:8443/oauth/v2/oauth-token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=x509_certificate_client' \
-d 'grant_type=client_credentials' \
-d 'scope=reports'

However, by default OAuth flows may experience errors due to X509 trust failures. To resolve them you must configure trust stores during the deployment of the Curity Identity Server.

Configure Trust for JWT SVIDs

When the Curity Identity Server receives a JWT SVID as an OAuth client credential, it calls the SPIFFE JWKS URI. From a command shell in the client workload you can run the following command to manually call this URL and retrieve the public keys with which SPIFFE signs JWT SVIDs.

bash
1
curl -s --cacert /svids/svid_bundle.pem https://spire-spiffe-oidc-discovery-provider.spire-server/keys | jq

Depending on your SPIRE root certificate, you may need to configure a server trust store in the Curity Identity Server, so that it trusts the SPIFFE JWKS URI's certificate. In this tutorial's example deployment you need to trust the self-signed root CA of the cert-manager. To do so, produce a configuration parameter at deployment time.

bash
1
export WORKLOAD_ROOT_CA="$(kubectl -n cert-manager get secret/rootca-secret -o jsonpath='{.data.ca\.crt}')"

Then, reference the parameter in the configuration XML that your deployment uses. The following example is for the cert-manager root certificate from this tutorial.

xml
12345678910111213141516
<config xmlns="http://tail-f.com/ns/config/1.0">
<facilities xmlns="https://curity.se/ns/conf/base">
<crypto>
<ssl>
<server-truststore>
<server-certificate>
<id>workload_root_ca</id>
<curve-name>P-256</curve-name>
<type>elliptic-curve</type>
<keystore>#{WORKLOAD_ROOT_CA}</keystore>
</server-certificate>
</server-truststore>
</ssl>
</crypto>
</facilities>
</config>

Configure Trust for X509 SVIDs

To verify X509 SVIDs, the Curity Identity Server uses client trust stores. Deployments must manage the configuration of SPIRE certificate authorities. For this tutorial's example deployment to trust X509 SVIDs, you need to include two client trust stores in the configuration XML that your deployment uses, one for the root CA and one for the intermediate CA.

xml
123456789101112131415161718192021222324
<config xmlns="http://tail-f.com/ns/config/1.0">
<facilities xmlns="https://curity.se/ns/conf/base">
<crypto>
<ssl>
<client-truststore>
<client-certificate>
<id>workload_root_ca</id>
<curve-name>P-256</curve-name>
<type>elliptic-curve</type>
<keystore>#{WORKLOAD_ROOT_CA}</keystore>
</client-certificate>
</client-truststore>
<client-truststore>
<client-certificate>
<id>workload_intermediate_ca</id>
<curve-name>P-256</curve-name>
<type>elliptic-curve</type>
<keystore>#{WORKLOAD_INTERMEDIATE_CA}</keystore>
</client-certificate>
</client-truststore>
</ssl>
</crypto>
</facilities>
</config>

The example deployment uses short lived SPIRE intermediate certificate authorities. To ensure that the Curity Identity Server correctly validates trust chains for X509 SVIDs, the deployment must ensure that the Curity Identity Server's admin and runtime workloads meet the following requirements.

  • During the initial deployment, configuration must use an up to date client trust store.
  • If auto-healing or auto-scaling creates new pods, they must use an up to date client trust store.
  • When SPIRE renews intermediate certificates, pods must receive a client trust store update.

To do so, you can use the following techniques, which adds some complexity to the deployment.

  • Use an Init Container that configures an up to date trust store whenever a pod starts.
  • Use a Sidecar Container that uses the RESTCONF API to update trust when X509 SVIDs are renewed.

An init container could run the SPIFFE helper utility with daemon_mode = false to get an up to date SPIFFE trust bundle and then use the technique from the Set Environment Variables in an Init Container article. To do so, you could add extra entries to the Helm chart for both admin and runtime workloads.

yaml
12345678910111213141516171819202122232425262727
curity:
adminUiHttp: true
admin:
...
extraEnv:
- name: WORKLOAD_INTERMEDIATE_CA
valueFrom:
fileKeyRef:
path: .env
volumeName: startup-environment-variables
key: WORKLOAD_INTERMEDIATE_CA
optional: true
initContainers:
- name: trust-init
image: trust_init:1.0
volumeMounts:
- name: spiffe-workload-api
mountPath: /spiffe-workload-api
readOnly: true
- name: startup-environment-variables
extraVolumes:
- name: spiffe-workload-api
csi:
driver: 'csi.spiffe.io'
readOnly: true
- name: startup-environment-variables
emptyDir: {}

Only the admin workload needs to run a sidecar container. It could run the SPIFFE helper with daemon_mode = true to keep SPIFFE trust bundles up to date. It could then use the RESTCONF API to update the admin container. The admin sidecar can use the loopback hostname to call the RESTCONF API. The admin container would then push the configuration update to all runtime workloads.

bash
1234567891011
RESTCONF_BASE_URL='http://127.0.0.1:6749/admin/api/restconf/data'
ADMIN_USER='admin'
ADMIN_PASSWORD='Password1'
awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > "/tmp/cert." c ".pem"}' < /svids/svid_bundle.pem
WORKLOAD_INTERMEDIATE_CA=$(cat /tmp/cert.2.pem | openssl base64 | tr -d '\n')
curl -s -X POST "$RESTCONF_BASE_URL/base:facilities/crypto/add-ssl-client-truststore" \
-u "$ADMIN_USER:$ADMIN_PASSWORD" \
-H 'Content-Type: application/yang-data+json' \
-d "{\"id\":\"workload_intermediate_ca\",\"keystore\":\"$WORKLOAD_INTERMEDIATE_CA\"}"

Example Deployment

The GitHub repository link at the top of this page provides example Kubernetes deployments for a local computer that integrate SPIRE with the Curity Identity Server. The deployments enable you to succesfully run the OAuth flows from the following tutorials.

Conclusion

Cloud native components can work together in a Kubernetes cluster, to divide security responsibilities. SPIFFE and SPIRE enable many possible use cases for strong and interoperable client authentication. The Curity Identity Server can use SPIFFE SVIDs as an OAuth client authentication mechnism, then issue access tokens to workloads, so that workloads get least privilege access to APIs.

Newsletter

Join our Newsletter

Get the latest on identity management, API Security and authentication straight to your inbox.

Newsletter

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial