Cloud identity & keys#
MAQPNA components get cloud credentials the way the cloud intends: from the pod's workload identity, never from a
long-lived key in a Secret. One credential layer (pkg/cloudcreds) serves every component that calls a cloud API: KMS
signing, keyless WORM shipping and the identity broker's token exchange. On top of it, signing keys can stay
non-exportable in AWS KMS, Google Cloud KMS, Azure Key Vault or HashiCorp Vault / OpenBao Transit.
flowchart LR SA["ServiceAccount<br/>annotations / pod labels"] --> WI["Workload identity<br/>Pod Identity · IRSA · GKE WIF · Entra WI · OKE WI"] WI --> CC["pkg/cloudcreds<br/>cached, refreshed, single-flight"] CC --> KMS["kms:// signers<br/>checkpoints, evidence, tokens"] CC --> WORM["WORM sink<br/>S3 Object Lock, keyless"] CC --> EX["identity broker<br/>aws-sts · gcp-sts · azure-entra"]
How a component finds its credentials#
Each component reads the standard environment that the cloud's admission webhook or agent injects. Nothing is configured in MAQPNA beyond the ServiceAccount annotation or pod label the cloud asks for.
| Cloud | Mechanism | What the pod gets | Order |
|---|---|---|---|
| AWS | Static keys (development only) | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN) |
1 |
| AWS | IRSA | AWS_ROLE_ARN + AWS_WEB_IDENTITY_TOKEN_FILE (from the eks.amazonaws.com/role-arn annotation) → STS AssumeRoleWithWebIdentity |
2 |
| AWS | EKS Pod Identity (preferred) | AWS_CONTAINER_CREDENTIALS_FULL_URI + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (Pod Identity agent) |
3 |
| AWS | IMDSv2 (node instance role) | Off by default; MAQPNA_AWS_IMDS=true or cloudCredentials.aws.imds |
4 |
| Google Cloud | Workload Identity Federation for GKE | Metadata server token (GCE_METADATA_HOST overrides the host) |
— |
| Google Cloud | Non-GKE clusters | GOOGLE_APPLICATION_CREDENTIALS → an external_account JSON (file-sourced subject token, optional service account impersonation) |
— |
| Azure | Entra Workload ID | AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_AUTHORITY_HOST → client-credentials token with a client assertion |
1 |
| Azure | AKS identity bindings | Opt in with MAQPNA_AZURE_IDENTITY_BINDING=true; uses AZURE_KUBERNETES_TOKEN_PROXY, AZURE_KUBERNETES_SNI_NAME, AZURE_KUBERNETES_CA_FILE |
1 |
| Azure | Managed identity (IMDS) | Off by default (it is the node's identity); MAQPNA_AZURE_MANAGED_IDENTITY=true |
2 |
| OCI | OKE workload identity | MAQPNA_OCI_AUTH=oke-workload-identity (the pod's ServiceAccount token, exchanged in-cluster) |
— |
| OCI | Resource principal v2.2 | MAQPNA_OCI_AUTH=resource-principal with OCI_RESOURCE_PRINCIPAL_VERSION=2.2, OCI_RESOURCE_PRINCIPAL_RPST, OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM, OCI_RESOURCE_PRINCIPAL_REGION |
— |
| Vault / OpenBao | Kubernetes auth or a token | VAULT_ADDR, VAULT_NAMESPACE, VAULT_CACERT; MAQPNA_VAULT_K8S_ROLE (+ MAQPNA_VAULT_K8S_MOUNT, default kubernetes) or VAULT_TOKEN_FILE / VAULT_TOKEN |
— |
The AWS order matches the AWS SDKs: when a pod has both IRSA and Pod Identity, IRSA wins, so configure one per component. Credentials are cached and refreshed before they expire, concurrent callers share one refresh, every call has a strict timeout, credential endpoints are fixed or validated (no request-controlled URLs), and no secret value is logged.
The gateway's gateway.json key cloudCredentials overrides the environment; the chart renders it from
gateway.cloudCredentials, and only the values you set:
gateway:
cloudCredentials:
aws:
region: eu-central-1
imds: false # IMDSv2 fallback, off by default
partitionDNSSuffix: "" # e.g. amazonaws.eu
stsEndpoint: "" # like AWS_ENDPOINT_URL_STS
azure:
managedIdentity: false
identityBinding: false
gcp:
metadataHost: ""
Chart settings#
Every component (gateway, identity, attestation, operator) has serviceAccount.annotations,
serviceAccount.labels and podLabels. The identity broker gets its own ServiceAccount when you set either map (it
already has one with identity.bootstrap.enabled); the attestation service gets one only when you set either map.
| Value | Purpose |
|---|---|
<component>.serviceAccount.annotations |
e.g. eks.amazonaws.com/role-arn, iam.gke.io/gcp-service-account, azure.workload.identity/client-id |
<component>.serviceAccount.labels |
Extra ServiceAccount labels |
<component>.podLabels |
e.g. azure.workload.identity/use: "true" |
gateway.secretsStoreCSI, identity.secretsStoreCSI |
Mount a SecretProviderClass volume (see Secrets) |
gateway.cloudCredentials |
See above |
identity.federationKey, identity.federationKeySecret, identity.federationIssuer |
Broker federation signing key and https issuer URL (see Token exchange) |
audit.worm.credentials |
static (Secret credentialsSecret) or aws (keyless) |
AWS (EKS)#
Pod Identity is AWS's recommendation and needs no annotation:
aws eks create-pod-identity-association --cluster-name prod \
--namespace maqpna-system --service-account maqpna-gateway \
--role-arn arn:aws:iam::111122223333:role/maqpna-gateway
IRSA is needed where Pod Identity is not supported (Fargate, hybrid nodes) and for AWS Marketplace metering
(RegisterUsage must be signed with IRSA):
gateway:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/maqpna-gateway
Partitions. ARNs, STS and KMS endpoints follow the partition of the region or role ARN: aws
(amazonaws.com), aws-cn (amazonaws.com.cn), aws-us-gov (amazonaws.com, us-gov-* regions) and the AWS
European Sovereign Cloud aws-eusc (amazonaws.eu, region eusc-de-east-1). The European Sovereign Cloud identifiers
are not fully verified against AWS's published endpoint list, so they are configurable: set
MAQPNA_AWS_PARTITION_DNS_SUFFIX (or cloudCredentials.aws.partitionDNSSuffix) and, if needed,
AWS_ENDPOINT_URL_STS / AWS_ENDPOINT_URL_KMS (HTTPS only). Credentials never cross partitions.
Keyless WORM. With audit.worm.credentials: aws the gateway signs S3 Object Lock writes with the temporary
credentials of its own identity (session token included) and no credentialsSecret is needed:
audit:
sink: worm
worm:
endpoint: https://s3.eu-central-1.amazonaws.com
bucket: maqpna-audit
region: eu-central-1
credentials: aws
The role needs s3:PutObject, s3:PutObjectRetention and s3:GetObject on the bucket, and s3:GetBucketObjectLockConfiguration: the sink checks at startup that Object Lock is enabled and refuses to start otherwise.
Google Cloud (GKE)#
With Workload Identity Federation for GKE, grant the Kubernetes ServiceAccount directly (no annotation):
gcloud kms keys add-iam-policy-binding maqpna-audit --keyring maqpna --location europe-west3 \
--role roles/cloudkms.signerVerifier \
--member "principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/maqpna-system/sa/maqpna-gateway"
or impersonate a Google service account with the iam.gke.io/gcp-service-account annotation (and
roles/iam.workloadIdentityUser on that service account):
gateway:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: maqpna-gateway@PROJECT_ID.iam.gserviceaccount.com
Outside GKE (EKS, AKS, on premises), create a workload identity pool provider that trusts your cluster's OIDC
issuer, generate the credential configuration with gcloud iam workload-identity-pools create-cred-config
... --credential-source-file=/var/run/secrets/tokens/gcp-token, mount it and a projected token with that audience,
and set GOOGLE_APPLICATION_CREDENTIALS through extraEnv. Only file-sourced external_account configurations are
accepted; URL- and executable-sourced ones and service account key files are refused.
Azure (AKS)#
Enable the OIDC issuer and workload identity on the cluster, create a user-assigned managed identity with a federated credential for the ServiceAccount, then:
gateway:
podLabels:
azure.workload.identity/use: "true"
serviceAccount:
annotations:
azure.workload.identity/client-id: 00000000-0000-0000-0000-000000000000
az identity federated-credential create --name maqpna-gateway \
--identity-name maqpna-gateway --resource-group rg-maqpna \
--issuer "$(az aks show -g rg-maqpna -n prod --query oidcIssuerProfile.issuerUrl -o tsv)" \
--subject system:serviceaccount:maqpna-system:maqpna-gateway \
--audiences api://AzureADTokenExchange
Identity bindings (one federated credential per identity across many clusters) route the token request through an
AKS-operated proxy; opt in with cloudCredentials.azure.identityBinding: true (or MAQPNA_AZURE_IDENTITY_BINDING=true).
Managed identity through IMDS is the node's identity and stays off unless you set managedIdentity: true.
AZURE_AUTHORITY_HOST selects sovereign clouds (Azure Government, Azure operated by 21Vianet); AZURE_CLIENT_SECRET
(or AZURE_CLIENT_SECRET_FILE) is still accepted for clusters without workload identity.
Oracle Cloud (OKE)#
OKE workload identity scopes OCI IAM policies to a pod's ServiceAccount. Set MAQPNA_OCI_AUTH=oke-workload-identity
(and OCI_RESOURCE_PRINCIPAL_REGION) through extraEnv, and write a policy such as:
Allow any-user to use keys in compartment maqpna where all {
request.principal.type = 'workload',
request.principal.namespace = 'maqpna-system',
request.principal.service_account = 'maqpna-gateway',
request.principal.cluster_id = 'ocid1.cluster.oc1..…' }
MAQPNA_OCI_AUTH=resource-principal reads a resource principal v2.2 session (OCI_RESOURCE_PRINCIPAL_RPST,
OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM, each a value or a file path). OCI requests are signed with the OCI HTTP
signature scheme. OCI Vault is not yet a kms:// signing backend (Planned).
The gateway signs model requests with this identity only for hosts under oraclecloud.com; for another realm
(for example a government or dedicated region), list its domains in MAQPNA_OCI_REALM_DOMAINS
(comma-separated, e.g. oraclegovcloud.com). Likewise the gateway's AWS identity signs model requests only for
the partition DNS suffixes and api.aws (see the model providers guide).
Signing keys in a KMS#
Release images resolve these key URIs; the private key never leaves the KMS or HSM, and every signature is checked against the key's public key before it is used.
| URI | Key types → JWS alg | Permissions |
|---|---|---|
kms://aws/<key-id \| key ARN \| alias/name> |
ECC_NIST_P256 → ES256; ECC_NIST_EDWARDS25519 → EdDSA; RSA_2048+ → RS256 |
kms:Sign, kms:GetPublicKey |
kms://gcp/projects/P/locations/L/keyRings/R/cryptoKeys/K/cryptoKeyVersions/V |
EC_SIGN_P256_SHA256 → ES256; EC_SIGN_ED25519 → EdDSA; RSA_SIGN_PKCS1_*_SHA256 → RS256 |
roles/cloudkms.signerVerifier and roles/cloudkms.publicKeyViewer |
kms://azure/<vault>/<key>[/<version>], azurekv://<vault>/<key>[/<version>] |
EC P-256 (EC-HSM) → ES256; RSA → RS256 (Key Vault has no Ed25519) | Key Vault Crypto User |
kms://vault/<mount>/keys/<name>, kms://openbao/<mount>/keys/<name> |
Transit ed25519 → EdDSA; ecdsa-p256 → ES256; rsa-2048 → RS256 |
policy below |
file:///abs/path |
Ed25519, ECDSA P-256 or RSA (2048+) PKCS#8 PEM | — |
pkcs11:… (RFC 7512) |
EC P-256 → ES256 (CKM_ECDSA), Ed25519 → EdDSA (CKM_EDDSA), RSA → RS256 (CKM_SHA256_RSA_PKCS) |
Only in binaries built with CGO_ENABLED=1 go build -tags pkcs11 (tested against SoftHSM v2); release images are static and refuse it |
An alias (kms://aws/alias/…) is resolved once at start-up and the key ARN is pinned; a Cloud KMS or Transit key is
pinned to its version. Rotating the key therefore takes a restart, and old signatures keep verifying against the
retired public key you keep in the JWKS.
Vault / OpenBao Transit policy:
path "transit/sign/maqpna-audit" { capabilities = ["update"] }
path "transit/sign/maqpna-audit/*" { capabilities = ["update"] }
path "transit/keys/maqpna-audit" { capabilities = ["read"] }
Use a key URI anywhere MAQPNA signs:
audit:
checkpointSigning:
enabled: true
keyURI: kms://aws/alias/maqpna-audit
Audit checkpoints, evidence packs, usage reports, memory erasure certificates and licences take the algorithm from the
key (EdDSA, ES256 or RS256 in the JWS header), and the published JWKS carries the matching key (kty: OKP/crv:
Ed25519, kty: EC/crv: P-256, or kty: RSA), so maqpna audit verify, maqpna evidence verify, maqpna usage
verify and third parties verify any of them. Verifiers bind the header alg to the key type, so a signature cannot be
relabelled. Identity tokens, Txn-Tokens, A2A cards and backups stay EdDSA: for those, a KMS key must be Ed25519 (AWS
ECC_NIST_EDWARDS25519, Cloud KMS EC_SIGN_ED25519, Transit ed25519; Azure Key Vault has none). The chart accepts
built-in KMS URIs without customKeyBackends; pkcs11: signing URIs, other kms:// providers (ovh, stackit) and
any kms:///pkcs11: sealing URI (gateway.captureArgs.keyURI) still need it.
Identity broker: token exchange to cloud credentials#
The broker's RFC 8693 exchange (POST /v1/exchange) can turn an agent's MAQPNA token into cloud credentials scoped to
that agent. Add the provider to an allowlisted audience in identity.exchange.audiences:
| Provider | Mode | What happens |
|---|---|---|
aws-sts |
webIdentity (default) |
The broker signs a federation JWT (sub = agent SPIFFE ID) and calls AssumeRoleWithWebIdentity |
aws-sts |
assumeRole |
The broker's own AWS identity calls AssumeRole with SourceIdentity and session tags maqpna-namespace, maqpna-agent, maqpna-session (ABAC per agent) |
gcp-sts |
federated (default) |
Federation JWT → STS token exchange against a workload identity pool provider, optionally impersonating serviceAccount |
gcp-sts |
impersonate |
The broker's own Google identity → generateAccessToken for serviceAccount |
azure-entra |
— | Federation JWT as the client assertion of an Entra app or managed identity |
identity:
federationKey: kms://aws/alias/maqpna-federation # ES256 or RS256
exchange:
audiences:
aws:bedrock:
provider: aws-sts
maxTTLSeconds: 900
params:
roleArn: arn:aws:iam::111122223333:role/maqpna-agents
mode: webIdentity # or assumeRole
durationSeconds: "900" # STS minimum
tokenAudience: sts.amazonaws.com
subject: session # session | agent | namespace
gcp:vertex:
provider: gcp-sts
params:
audience: //iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/maqpna/providers/broker
serviceAccount: maqpna-agents@PROJECT_ID.iam.gserviceaccount.com
scope: https://www.googleapis.com/auth/cloud-platform
azure:openai:
provider: azure-entra
params:
tenantId: 00000000-0000-0000-0000-000000000000
clientId: 11111111-1111-1111-1111-111111111111
scope: https://cognitiveservices.azure.com/.default
# subject defaults to "agent" here: Entra matches sub exactly
Unknown params, malformed role ARNs or WIF audiences, and federated modes without a federation key fail the broker at start-up, not at the first exchange.
The federation key. Clouds do not accept EdDSA tokens, so the federated modes need identity.federationKey
(MAQPNA_IDENTITY_FEDERATION_KEY, broker flag -federation-key): an ES256 or RS256 key URI, or
identity.federationKeySecret (Secret with federation.pem). AWS and Google accept RS256 and ES256; Entra officially
supports RS256 only, so use an RSA key for azure-entra. The public key is published in the broker's
/.well-known/jwks.json next to its Ed25519 keys (MAQPNA's own verifiers ignore it).
The issuer. Federation tokens carry iss = identity.federationIssuer (MAQPNA_IDENTITY_FEDERATION_ISSUER, flag
-federation-issuer), an https URL. With a federation key the broker serves GET /.well-known/openid-configuration
(whose jwks_uri is <issuer>/.well-known/federation/jwks.json) and that federation-only JWKS. Expose exactly those two
paths at the issuer URL (an Ingress or HTTPRoute to the broker), or copy both documents to a static HTTPS location. The
token's sub is the agent's SPIFFE ID at the subject granularity: session (default,
spiffe://<td>/ns/<ns>/agent/<agent>/session/<id>), agent or namespace; claims maqpna_ns, maqpna_agent,
maqpna_session, scope and the on-behalf-of act are included for conditions and attribute mappings.
Trust. Each cloud must trust the broker's issuer:
- AWS:
aws iam create-open-id-connect-provider --url <issuer> --client-id-list sts.amazonaws.com; the role trust policy conditions on<issuer>:sub(the agent SPIFFE ID, wildcards allowed withStringLike) and<issuer>:aud. - Google:
gcloud iam workload-identity-pools providers create-oidc broker --issuer-uri=<issuer> --jwk-json-path=jwks.json --attribute-mapping=google.subject=assertion.sub,attribute.namespace=assertion.maqpna_ns(an uploaded JWKS means the issuer need not be reachable from Google). - Entra: a federated identity credential on the app or managed identity with
--issuer <issuer>,--subject spiffe://<td>/ns/<ns>/agent/<agent>(one credential per agent) and audienceapi://AzureADTokenExchange.
For AWS and Entra the issuer's /.well-known/openid-configuration and JWKS must be reachable over HTTPS from the cloud.
Responses. gcp-sts and azure-entra return a bearer access token. aws-sts returns issued_token_type
urn:maqpna:params:oauth:token-type:aws-credentials and an access_token holding JSON
{"AccessKeyId","SecretAccessKey","SessionToken","Expiration"} for SigV4 clients. AWS session credentials last at
least 900 seconds, longer than a short MAQPNA token. The gateway refuses to forward such a response as a bearer token
(an MCP upstream with auth.tokenExchange needs a bearer provider).
Secrets from a cloud secret manager#
Every secret MAQPNA reads can come from a file, so the Secrets Store CSI driver and External Secrets Operator both work:
- Gateway config already takes files:
apiKeyFile(model routes),clientSecretFile,headersFile(SIEM, OTel),dsnFile(state backend),bearerTokenFile,hmacSecretFile,signingSecretFile,accessTokenFile. - Every secret environment variable has a
_FILEtwin (when both are set, the file wins, so a CSI mount can override the chart's Secret):MAQPNA_ADMIN_TOKEN_FILE,MAQPNA_AUDIT_HMAC_KEY_FILE,MAQPNA_BROKER_TOKEN_FILE,MAQPNA_AUDIT_WORM_ACCESS_KEY_FILE,MAQPNA_AUDIT_WORM_SECRET_KEY_FILE,AZURE_CLIENT_SECRET_FILE,VAULT_TOKEN_FILE.
External Secrets Operator#
ESO writes ordinary Kubernetes Secrets, so the chart's Secret references work unchanged. One ExternalSecret per
Secret MAQPNA reads, e.g. the WORM keys (audit.worm.credentialsSecret) and the tokens Secret (secrets.existingSecret):
# AWS Secrets Manager (SecretStore with auth.jwt.serviceAccountRef or Pod Identity)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: {name: maqpna-audit-s3, namespace: maqpna-system}
spec:
refreshInterval: 1h
secretStoreRef: {kind: ClusterSecretStore, name: aws-secretsmanager}
target: {name: maqpna-audit-s3}
data:
- secretKey: access-key
remoteRef: {key: maqpna/audit-s3, property: access-key}
- secretKey: secret-key
remoteRef: {key: maqpna/audit-s3, property: secret-key}
---
# Google Secret Manager
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: {name: maqpna-tokens, namespace: maqpna-system}
spec:
secretStoreRef: {kind: ClusterSecretStore, name: gcp-secretmanager}
target: {name: maqpna-tokens}
data:
- {secretKey: admin-token, remoteRef: {key: maqpna-admin-token}}
- {secretKey: broker-token, remoteRef: {key: maqpna-broker-token}}
- {secretKey: audit-hmac-key, remoteRef: {key: maqpna-audit-hmac-key}}
---
# Azure Key Vault (SecretStore with authType: WorkloadIdentity)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: {name: maqpna-siem-headers, namespace: maqpna-system}
spec:
secretStoreRef: {kind: SecretStore, name: azure-kv}
target: {name: maqpna-siem-headers}
data:
- {secretKey: headers, remoteRef: {key: maqpna-siem-headers}}
---
# HashiCorp Vault KV v2 (SecretStore with auth.kubernetes)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: {name: maqpna-state, namespace: maqpna-system}
spec:
secretStoreRef: {kind: SecretStore, name: vault}
target: {name: maqpna-state}
data:
- {secretKey: dsn, remoteRef: {key: maqpna/state, property: dsn}}
Use external-secrets.io/v1beta1 on ESO releases older than 0.17.
Secrets Store CSI driver#
The CSI driver mounts secrets as files without creating Kubernetes Secrets. Create a SecretProviderClass and enable
the volume; files appear under mountPath (default /mnt/secrets-store):
gateway:
secretsStoreCSI:
enabled: true
secretProviderClass: maqpna-gateway
extraEnv:
- name: MAQPNA_ADMIN_TOKEN_FILE
value: /mnt/secrets-store/admin-token
Point file settings at the same directory, e.g. a model route's apiKeyFile: /mnt/secrets-store/openai-key. The
gateway re-reads apiKeyFile per call, so a rotation the CSI driver syncs takes effect without a restart.
# AWS Secrets Manager (ASCP, with EKS Pod Identity)
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata: {name: maqpna-gateway, namespace: maqpna-system}
spec:
provider: aws
parameters:
usePodIdentity: "true"
objects: |
- objectName: maqpna/admin-token
objectType: secretsmanager
objectAlias: admin-token
---
# Azure Key Vault (workload identity)
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata: {name: maqpna-gateway, namespace: maqpna-system}
spec:
provider: azure
parameters:
clientID: 00000000-0000-0000-0000-000000000000
keyvaultName: kv-maqpna
tenantId: 11111111-1111-1111-1111-111111111111
objects: |
array:
- |
objectName: admin-token
objectType: secret
---
# Google Secret Manager
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata: {name: maqpna-gateway, namespace: maqpna-system}
spec:
provider: gcp
parameters:
secrets: |
- resourceName: projects/PROJECT_ID/secrets/maqpna-admin-token/versions/latest
path: admin-token
---
# HashiCorp Vault
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata: {name: maqpna-gateway, namespace: maqpna-system}
spec:
provider: vault
parameters:
vaultAddress: https://vault.vault.svc:8200
roleName: maqpna-gateway
objects: |
- objectName: admin-token
secretPath: secret/data/maqpna
secretKey: admin-token
The serviceAccount annotations and podLabels above are what the AWS and Azure providers use to authenticate the
mount. The chart does not render a SecretProviderClass; it is yours to create in the release namespace.
Real-cloud smoke test#
An opt-in test, built with the cloud tag, signs and verifies with real keys. Each case is skipped unless its
variable names a key URI. Run it from a pod (or a shell) that has the workload identity under test:
# EKS (Pod Identity or IRSA), GKE (WIF), AKS (Entra Workload ID), Vault
export MAQPNA_CLOUD_TEST_AWS_KMS_KEY=kms://aws/alias/maqpna-smoke
export MAQPNA_CLOUD_TEST_GCP_KMS_KEY=kms://gcp/projects/P/locations/L/keyRings/R/cryptoKeys/K/cryptoKeyVersions/1
export MAQPNA_CLOUD_TEST_AZURE_KV_KEY=kms://azure/kv-maqpna/maqpna-smoke
export MAQPNA_CLOUD_TEST_VAULT_KEY=kms://vault/transit/keys/maqpna-smoke
# Credential sources only (STS GetCallerIdentity, Google tokeninfo, an ARM token, a signed OCI call):
export MAQPNA_CLOUD_TEST_AWS=1 MAQPNA_CLOUD_TEST_GCP=1 MAQPNA_CLOUD_TEST_AZURE=1 MAQPNA_CLOUD_TEST_OCI=1
go test -tags cloud ./pkg/cloudcreds/ ./pkg/keys/ -run Cloud -v
PKCS#11 has its own opt-in test against SoftHSM v2 and OpenSC's pkcs11-tool:
CGO_ENABLED=1 go test -tags pkcs11 ./pkg/keys/ -run PKCS11 -v.
A quick way on a cluster: run a golang pod with the gateway's ServiceAccount (and pod labels), copy the repository in,
and run the command above. Record the result as a dated test/cloud/<provider>/RESULTS.md.
What is not validated yet#
- No EKS, GKE, AKS or OKE run is recorded: Pod Identity, IRSA, GKE WIF, Entra Workload ID, identity bindings, OKE workload identity and resource principal flows are tested only against fakes built from the providers' API docs.
- The AWS European Sovereign Cloud partition (
aws-eusc,amazonaws.eu) and the AKS identity-binding proxy variables are taken from public documentation that was not fully verified; both are configurable. The OKE proxymux exchange follows the OCI Go SDK's wire format and has not been run against OKE. - The broker's cloud federation (AWS IAM OIDC provider, Google WIF provider, Entra federated credential trusting the broker issuer) has not been set up against a real account.
- Entra acceptance of ES256 federation tokens is undocumented (RS256 is the supported algorithm).
- PKCS#11 is tested against SoftHSM only, and only in
-tags pkcs11builds. - OCI Vault signing, Alibaba RRSA and IBM Trusted Profiles are Planned.