The test OAuth client is a basic pod that contains the curl command line tool, so can make HTTP requests.
The client presents its service account token as an OAuth client credential.
The client uses a sidecar that is instructed to use an Istio sidecar with a SPIRE identity.
This enables it to call the authorization server using mutual TLS.
It also loads a service account token to present to the OAuth token endpoint:
apiVersion: v1
kind: ServiceAccount
metadata:
name: oauthclient
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: oauthclient
spec:
replicas: 1
selector:
matchLabels:
app: oauthclient
template:
metadata:
labels:
app: oauthclient
sidecar.istio.io/inject: 'true'
spire-managed-identity: 'true'
annotations:
inject.istio.io/templates: 'sidecar,spire'
spec:
serviceAccountName: oauthclient
containers:
- name: oauthclient
image: oauthclient:v1
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: client-assertion-location
volumes:
- name: client-assertion-location
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: 'https://login.democluster.example'
Once the client is deployed, get a remote shell to its container:
OAUTHCLIENT=$(kubectl -n applications get pods --selector='app=oauthclient' -o=name)
kubectl -n applications exec -it $OAUTHCLIENT -- bash
From the container, run the following command to see its service account token:
cat /var/run/secrets/kubernetes.io/serviceaccount/token
In a JWT viewer, this is the payload:
{
"aud": [
"https://login.democluster.example"
],
"exp": 1724344795,
"iat": 1724337595,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "c39a0c1f-e5c9-4617-bc1e-055605c7b491",
"kubernetes.io": {
"namespace": "applications",
"node": {
"name": "example-worker",
"uid": "8c948d13-bd4b-4f47-aa67-0c369b885ff1"
},
"pod": {
"name": "oauthclient-5f9d7465d8-g4j88",
"uid": "5729ec3d-7e40-430f-b043-1e76534f0f29"
},
"serviceaccount": {
"name": "oauthclient",
"uid": "78e4dd18-ce09-4095-abbd-13c0f8513fe2"
}
},
"nbf": 1724337595,
"sub": "system:serviceaccount:applications:oauthclient"
}
Then run a client credentials request over plain HTTP, supplying the above JWT as a client assertion.
The client's sidecar calls to the authorization server's sidecar, which upgrades the connection to use mutual TLS.
The result should be a response that contains OAuth tokens:
SERVICE_ACCOUNT_TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
curl -X POST http://curity-idsvr-runtime-svc.authorizationserver:8443/oauth/v2/oauth-token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'client_id=oauthclient' \
-d "client_assertion=$SERVICE_ACCOUNT_TOKEN" \
-d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
-d 'scope=products'
At the time of writing, the default authorization server implements client assertions according to the OpenID Connect Core specification.
This leads to the error explained in Best Current Practice for OAuth 2.0 Client Authentication in Workload Environments.
There is an active product request to enable usage of the RFC7523 behavior, which will fix this issue.
The same pattern can be implemented by SPIRE-issued JWT SVIDs if you prefer.
The SPIFFE helper can be configured with properties like these:
jwt_svids = [{jwt_audience="https://login.democluster.example/oauth/v2/oauth-anonymous", jwt_svid_file_name="jwt_svid.token"}]
jwt_bundle_file_name = "jwt_bundle.json"
A workload then receives JWT SVID documents with payloads of this form:
{
"aud": [
"https://login.democluster.example"
],
"exp": 1722005575,
"iat": 1722001975,
"iss": "https://oidc-discovery.democluster.internal",
"sub": "spiffe://democluster.internal/ns/applications/sa/oauthclient"
}
The authorization server can use the internal SPIRE JWKS URI to validate JWT SVIDs:
curl -i -k https://spire-spiffe-oidc-discovery-provider.spire-server/keys
The dbclient workload is configured to download JWT SVIDs.
Therefore you can use it to view some downloaded JWT SVID documents.