Workload Identity lets your workloads authenticate to MonoCloud as configured clients, using cryptographic identities issued through SPIFFE (their SPIFFE SVIDs) - instead of long-lived client secrets.
A workload running in your infrastructure, typically issued an identity by SPIRE, presents its SVID to MonoCloud's token endpoint. MonoCloud verifies the SVID against the issuing trust domain and, if the SPIFFE ID is associated with a client, issues an access token for the requested APIs.
There are no shared secrets to distribute, rotate, or leak. The workload proves its identity with a short-lived, automatically rotated SVID.
MonoCloud supports both SVID formats defined by SPIFFE:
cnf claim, so it cannot be replayed by another party.Workload Identity is an add-on capability. Trust stores and SPIFFE-based client authentication are available only when the Workload Identity add-on is enabled for your tenant.
| Term | Meaning |
|---|---|
| Trust domain | The root of trust for a set of workloads, written as a hostname such as example.org. Every SPIFFE ID belongs to exactly one trust domain. |
| SPIFFE ID | A URI that names a workload, in the form spiffe://<trust-domain>/<path>, for example spiffe://example.org/ns/billing/sa/worker. |
| SVID | A SPIFFE Verifiable Identity Document - the credential that carries a SPIFFE ID. It can be an X.509 SVID (a certificate with the SPIFFE ID in its URI SAN) or a JWT SVID (a signed JWT with the SPIFFE ID as sub). |
| Trust bundle | The set of public keys for a trust domain that relying parties use to verify its SVIDs: X.509 authorities for X.509 SVIDs and JWT authorities for JWT SVIDs. |
| Bundle endpoint | An HTTPS URL that serves the trust bundle in SPIFFE bundle format. MonoCloud fetches the bundle from this endpoint. |
| Trust store | The MonoCloud object that federates one trust domain. It holds the bundle endpoint and SVID validation settings, and MonoCloud uses it to verify incoming SVIDs. |
The end-to-end model is:
Before MonoCloud can verify your workloads' SVIDs, it needs your trust domain's bundle. MonoCloud retrieves the bundle by performing an HTTPS GET against a bundle endpoint URL that you supply.
MonoCloud fetches the bundle when you create the trust store, then refreshes it according to the bundle's refresh hint and MonoCloud's cache rules. Authentication uses the current cached bundle. If MonoCloud needs to refresh the bundle and the endpoint cannot be reached within the configured timeout, authentication is rejected.
There are two common ways to expose your SPIRE trust domain's bundle to MonoCloud. Both result in a single HTTPS URL that you paste into the trust store.
**MonoCloud validates the bundle endpoint's TLS certificate against the public web PKI. If you expose a SPIRE bundle endpoint directly, configure SPIRE to serve it using itshttps_webprofile, with a certificate from a publicly trusted CA. Thehttps_spiffeprofile, where the endpoint's TLS certificate is itself an SVID, is not supported.
A trust bundle contains public verification material, not workload private keys. It must be integrity-protected and served over HTTPS, but it does not need to be confidential.
SPIRE Server can serve its own trust bundle over HTTPS through its built-in bundle endpoint. Configure the federation.bundle_endpoint block with the https_web profile so the endpoint presents a publicly trusted TLS certificate, for example one obtained automatically through ACME / Let's Encrypt:
server {
trust_domain = "example.org"
# ...
federation {
bundle_endpoint {
address = "0.0.0.0"
port = 8443
acme {
domain_name = "spire.example.org"
email = "admin@example.org"
tos_accepted = true
}
}
}
}
MonoCloud then fetches the bundle from an endpoint such as: https://spire.example.org:8443
This keeps SPIRE as the single source of truth, but it requires the SPIRE bundle endpoint to be reachable from MonoCloud over the public internet with a publicly trusted certificate.
The exact configuration schema for SPIRE's bundle endpoint can vary between SPIRE versions. The block above is representative. Consult the SPIRE federation documentation for your installed version.
If you would rather not expose your SPIRE server publicly, use a SPIRE Server bundle publisher plugin to push the bundle to external storage whenever it rotates, then serve that object over HTTPS.
The aws_s3 bundle publisher writes the bundle to an S3 object. Publish it in SPIFFE format so MonoCloud can parse it:
server {
trust_domain = "example.org"
# ...
}
BundlePublisher "aws_s3" {
plugin_data {
region = "us-east-1"
bucket = "example-trust-bundle"
object_key = "spiffe-bundle.json"
# Publish SPIFFE JWK Set format, not SPIRE's PEM format.
format = "spiffe"
}
}
Front the object with HTTPS and a publicly trusted certificate - for example, an S3 bucket behind CloudFront or another CDN - then point MonoCloud at that URL: https://bundles.example.org/spiffe-bundle.json.
This decouples MonoCloud from your SPIRE server. MonoCloud only reaches the static object, while SPIRE pushes a fresh bundle whenever its signing material rotates.
SPIRE also offers publishers for other object stores, such as GCP Cloud Storage. Any option works as long as the published object is served over HTTPS in SPIFFE format.
Whichever option you choose, the URL must return a SPIFFE bundle encoded as an RFC 7517 JWK Set. This is not an ordinary OAuth JWKS: the keys must include SPIFFE-specific use values such as x509-svid and jwt-svid.
"use": "x509-svid" are the X.509 authorities used to build and verify X.509 SVID certificate chains. These keys carry the trust domain's certificates in x5c."use": "jwt-svid" are the JWT authorities used to verify JWT SVID signatures.A few practical requirements:
spiffe://<trust-domain> URI SAN of those certificates. You never enter the trust domain manually. A bundle with no X.509 authority carrying a SPIFFE ID SAN is rejected.spiffe_refresh_hint value in seconds. MonoCloud uses it to decide how long to cache the bundle, so set it to match your rotation cadence.spire-server bundle show -format spiffe.A trust store federates one trust domain. Creating one tells MonoCloud to fetch and trust SVIDs issued by that domain.
mtls_additional_endpoint_aliases extension of the OpenID Connect discovery document.When you save, MonoCloud fetches the bundle from the endpoint, parses it as a SPIFFE JWK Set, and discovers the trust domain from the X.509 authorities' SPIFFE ID SAN. The discovered trust domain becomes the trust store's name.
The first trust store you create (MutualTLS or Workload Identity) automatically becomes the tenant default.
The dialog asks only for the bundle endpoint because everything else is derived. The Trust Domain is read from the bundle, and the signing keys come from the bundle itself.
MonoCloud publishes the default trust store's mTLS endpoints in the standardmtls_endpoint_aliasesfield. Additional trust stores are published inmtls_additional_endpoint_aliases, a MonoCloud-specific discovery extension. Clients that need a non-default trust store must read this extension and use the corresponding endpoint.
Each trust store has SVID validation settings, editable from its Settings tab. They apply to every SVID verified through that trust store.
| Setting | Default | Description |
|---|---|---|
| Validate Certificate Use | On | Enforce the X.509 SVID leaf-certificate requirements. It must be usable for digital signatures, must not be a CA, and must not have certificate-signing or CRL-signing key usage. |
| Validate Certificate Expiry | On | Reject the SVID unless the current time falls within its Not Before / Not After validity period. |
| Cache Authentication Result / Authentication Cache Duration | 300s | Cache successful certificate-authentication results to avoid repeating cryptographic checks. Range: 0-3600 seconds. |
| Bundle Fetch Timeout | 10s | Maximum time to wait when downloading the bundle from the endpoint. If MonoCloud needs to refresh the bundle and the endpoint does not respond in time, authentication is rejected. Range: 3-30 seconds. |
From a trust store's Banned SVIDs tab, you can block an individual workload identity that is still trusted by the trust domain - for example, a compromised workload that you cannot immediately deprovision in SPIRE.
Click Ban SVID, enter the full SPIFFE ID such as: spiffe://example.org/ns/billing/sa/worker. You can also add an optional reason for banning the SVID.
Banned SVIDs are rejected at authentication time regardless of whether their SVID is otherwise valid.
You can also manage trust stores with the MonoCloud Management API. To create a SPIFFE trust store:
POST /truststores/spiffe/
Content-Type: application/json
{
"spiffe_bundle_endpoint": "https://bundles.example.org/spiffe-bundle.json",
"show_in_discovery_document": false,
"options": {
"validate_certificate_use": true,
"validate_validity_period": true,
"certificate_auth_cache_duration": 300,
"bundle_fetch_timeout": 10
}
}
A successful call returns 201 Created with the trust store, including the discovered trust domain.
Creating or enabling a SPIFFE trust store requires the Workload Identity add-on. Without it, the API returns402 Payment Required. The number of trust stores you can create is governed by your add-on quantity.
A trust store tells MonoCloud which SVIDs are authentic. It does not, by itself, specify which client a workload may authenticate as.
You make that association by adding a secret to the client whose value is the workload's SPIFFE ID. The client is what the access token is issued for, the SPIFFE ID secret is how the workload proves it may act as that client.
There is one secret type per SVID format. Both take a SPIFFE ID as their value and both require the Workload Identity add-on:
| Secret type in the dashboard | Used by | Value |
|---|---|---|
| SPIFFE ID (X.509-SVID) | The X.509 SVID mutual-TLS flow | The workload's SPIFFE ID |
| SPIFFE ID (JWT-SVID) | The JWT SVID client-assertion flow | The workload's SPIFFE ID |
To add one in the dashboard:
A client can hold several SPIFFE ID secrets. You can add secrets of both types to the same client if it uses both flows.
The value can match either an exact identity or a path prefix using a trailing /* wildcard:
spiffe://example.org/ns/billing/sa/worker matches only that exact workload.spiffe://example.org/ns/billing/* matches workloads below that path, such as spiffe://example.org/ns/billing/sa/worker.At authentication time, MonoCloud compares the SPIFFE ID from the presented SVID against the client's SPIFFE ID secrets of the matching type. If none match, authentication fails.
Workload Identity applies to both application and agent clients, so either client type can authenticate with an SVID.
If you omitclient_idon the JWT SVID flow, MonoCloud finds the client by looking up which client holds a matchingSPIFFE ID (JWT-SVID)secret. Each SPIFFE ID should normally be associated with a single client. See Requesting tokens with JWT SVIDs.
In this flow the workload presents its X.509 SVID as the client certificate over mutual TLS to MonoCloud's mTLS token endpoint.
Find the correct endpoint in your tenant's OpenID Connect discovery document. For the default trust store, use the corresponding token_endpoint value in mtls_endpoint_aliases. For an additional trust store, use the corresponding endpoint from MonoCloud's mtls_additional_endpoint_aliases extension.
This flow is normally combined with the client_credentials grant for service-to-service access.
curl -X POST https://{tenant}.{region}.mtls.monocloud.com/connect/token \
--cert svid.pem \
--key svid.key \
-d grant_type=client_credentials \
-d client_id=billing-worker \
-d scope=billing \
-d resource=https://api.example.com/billing
You can include client_id to select a specific client. It is optional when the SPIFFE ID maps to exactly one client, but required when the same SPIFFE ID is associated with more than one client.
The client_id is required in this flow. The SVID is presented at the TLS layer, while client_id tells MonoCloud which client to issue the token for.
MonoCloud verifies the SVID against the matching trust store before issuing a token. It checks that:
x509-svid authorities from the bundle - either directly, or through one or more intermediate CAs the workload presents alongside the leaf,MonoCloud then confirms that the SPIFFE ID matches one of the client's SPIFFE ID (X.509-SVID) secrets.
On success, MonoCloud issues an access token that is bound to the certificate used on the TLS connection through the cnf claim. See Proof of possession.
In a nested SPIRE topology, downstream SPIRE servers issue SVIDs from their own intermediate signing CAs, which chain up to a single root.
The federated trust bundle holds only that root authority; the downstream intermediate CAs are not published in the bundle - they travel with each X.509 SVID during the TLS handshake.
MonoCloud supports this topology. It builds the full path leaf -> intermediate(s) -> root.
It anchors validation on the bundle's root authority, so a workload issued by a downstream SPIRE server validates without you adding that server's intermediate CA to the bundle.
For this to work, the workload must present its complete X.509-SVID chain as the client certificate: the leaf first, followed by any intermediate CAs.
This is what the SPIFFE Workload API returns for an X.509 SVID, so a PEM built from it already has the certificates in the correct order:
-----BEGIN CERTIFICATE-----
<leaf SVID - carries the SPIFFE ID>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<downstream / intermediate CA>
-----END CERTIFICATE-----
A few things to keep in mind:
In this flow, the workload obtains a JWT SVID through its SPIFFE Workload API and presents it as a client assertion on a regular, non-mTLS request to the token endpoint.
Find the token endpoint in your tenant's OpenID Connect discovery document.
First request a JWT SVID with MonoCloud's issuer URL as its audience:
SVID_JWT=$(spire-agent api fetch jwt \
-audience https://{tenant}.{region}.monocloud.com \
-socketPath /run/spire/sockets/agent.sock \
-output json | jq -r '.svids[0].svid')
Then present that SVID as the client assertion:
curl -X POST https://{tenant}.{region}.monocloud.com/connect/token \
-d grant_type=client_credentials \
-d scope=billing \
-d resource=https://api.example.com/billing \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe \
-d client_assertion="$SVID_JWT"
The single most important requirement is the audience:
A JWT SVID used to authenticate to MonoCloud must have anaudclaim equal to your tenant's issuer URL, and that must be its only audience. This is stricter thanprivate_key_jwt. A JWT SVID whose audience is the token endpoint, or that carries any additional audience, is rejected. Take the issuer value from your tenant's OpenID configuration.
A JWT SVID is a standard SPIFFE JWT with the workload's SPIFFE ID as its sub:
{
"sub": "spiffe://example.org/ns/billing/sa/worker",
"aud": "https://{tenant}.{region}.monocloud.com",
"exp": 1735689600
}
MonoCloud:
sub and looks up the matching trust store,jwt-svid authorities,RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, or ES512,sub to be a valid workload SPIFFE ID with a non-root path,client_id is optional on this flow.
If you omit it, MonoCloud resolves the client from the SPIFFE ID in sub. If more than one client is associated with that SPIFFE ID the request is ambiguous and you must send client_id to disambiguate.
Access tokens issued through the JWT SVID flow are bearer tokens associated with the client. They are not key-bound and carry nocnfclaim. Use the X.509 SVID flow when you need proof-of-possession, sender-constrained tokens.
cnf claimWhen MonoCloud issues a token through the X.509 SVID flow, it binds the token to the client certificate used on the TLS connection, following RFC 8705 (OAuth 2.0 Mutual-TLS).
The token carries a confirmation (cnf) claim containing the SHA-256 thumbprint of that certificate:
{
"iss": "https://{tenant}.{region}.monocloud.com",
"client_id": "billing-worker",
"aud": "https://api.example.com/billing",
...
"cnf": {
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
The same cnf claim is returned for the token at the introspection endpoint, so resource servers that use reference (opaque) tokens can retrieve it.
Certificate-bound tokens are issued only through the mTLS token endpoint.
A cnf-bound access token may only be used by the holder of the matching certificate.
A resource server that accepts these tokens must, in addition to validating the token itself:
x5t#S256 value inside the token's cnf claim.This ensures a leaked or exfiltrated token is useless to anyone who does not also hold the corresponding SVID private key.
Because SVIDs are short-lived and rotate frequently, each certificate-bound token is tied to the specific SVID that requested it. After rotation, the workload requests a new token with its new SVID.
JWT SVID tokens have nocnfclaim and are validated as ordinary bearer tokens. Reserve resource-server proof-of-possession checks for the X.509 SVID flow.
Once a workload authenticates with an SVID, its SPIFFE identity is available to API Access Policies, so you can authorize token issuance based on workload identity, not just the client.
For requests authenticated with either SVID format, the workload's SPIFFE ID is exposed as context.spiffe_id:
permit (
principal,
action == Action::"IssueAccessToken",
resource == Api::"https://api.example.com/billing"
)
when {
context has spiffe_id &&
context.spiffe_id == "spiffe://example.org/ns/billing/sa/worker"
};
For X.509 SVIDs, which authenticate over mTLS, you can additionally match on the certificate that was validated, including the trust store that validated it and the SPIFFE ID in its URI SAN:
permit (
principal,
action == Action::"IssueAccessToken",
resource == Api::"https://api.example.com/billing"
)
when {
context has certificate &&
context.certificate.san_uris.contains("spiffe://example.org/ns/billing/sa/worker")
};
context.spiffe_id and context.certificate are optional, so guard them with context has .... See the API Access Policies guide for the complete context reference.
https_spiffe profile.spiffe:// URI SAN. Publish with SPIRE's spiffe format, not the PEM format.402 Payment Required when creating a trust store or credential: The Workload Identity add-on is required for trust stores and SPIFFE credentials. Reaching your trust-store quota also returns 402; the limit is governed by your add-on quantity.x509-svid authorities, and that it satisfies Validate Certificate Use and Validate Certificate Expiry. Also confirm that the client has a matching SPIFFE ID (X.509-SVID) secret and that the SPIFFE ID is not banned.aud must be exactly your tenant's issuer URL and nothing else. An audience set to the token endpoint, or any extra audience, fails validation.sub. Send an explicit client_id to select the client.