Skip to Content
DocsIn Your PipelinesIdentity of Runs

Identity of Runs

Rather than storing long-lived keys as secrets, a run can prove which organization it belongs to. Cadence.CI is an OpenID Connect issuer: each run gets short-lived identity tokens, which AWS, Google Cloud, Azure, Vault and OpenBao accept once you set up a trust, as they accept the tokens of GitHub Actions and GitLab CI jobs.

Getting a Token

On a Cadence.CI worker, the api-id:<audience> locator gives the identity token of the run for an audience, as a Secret<string>: api-id:sts.amazonaws.com for AWS, api-id:vault for a Vault role bound to the vault audience. Pass it to the element exchanging it for access, as you would any secret.

A token is valid for 10 minutes; the engine requests a new one once it has expired. Like api:, api-id: only works on Cadence.CI workers, and the token is never shown.

Issuer

Issuerhttps://api.melodium.tech
Discoveryhttps://api.melodium.tech/.well-known/openid-configuration
Keyshttps://api.melodium.tech/.well-known/jwks.json
SignatureRS256
Lifetime10 minutes

Claims

ClaimValue
isshttps://api.melodium.tech
audthe audience requested
suborganization:<organization-id>, followed by :public for public runs and the runs they start
organization_idthe organization of the run
run_idthe run
group_idits execution group
parent_idthe run that started it, if any
tagsthe tags of the run
publicwhether the run, or a run that started it, is public
melodium_versionthe Mélodium version of the engine
iat, nbf, expwhen it was issued, from when it is valid, when it expires
jtiits unique identifier

The subject is the same for every private run of an organization, so that providers comparing it exactly, as AWS and Azure do, trust the organization as a whole. Conditions on tags or runs go through the other claims, where the provider can test them, as Google Cloud, Vault and OpenBao do. Public runs, and the runs they start, have a subject of their own: a trust set up for organization:<organization-id> never accepts them. Keep it that way for anything sensitive, as public runs execute code anyone can propose.

The identifier of your organization is on the Organization page of the dashboard.

AWS

  1. In IAM, under Identity providers, add an OpenID Connect provider with the URL https://api.melodium.tech and the audience sts.amazonaws.com.
  2. Create a role for this provider, with a trust policy limited to your organization:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<account-id>:oidc-provider/api.melodium.tech" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "api.melodium.tech:aud": "sts.amazonaws.com", "api.melodium.tech:sub": "organization:<organization-id>" } } } ] }
  1. In the program, give api-id:sts.amazonaws.com to what assumes the role with AssumeRoleWithWebIdentity, with the ARN of the role.

Google Cloud

  1. Create a workload identity pool, and a provider trusting the runs of your organization:
gcloud iam workload-identity-pools create cadence-ci --location=global gcloud iam workload-identity-pools providers create-oidc cadence-ci \ --location=global \ --workload-identity-pool=cadence-ci \ --issuer-uri="https://api.melodium.tech" \ --allowed-audiences="gcp" \ --attribute-mapping="google.subject=assertion.sub,attribute.organization_id=assertion.organization_id" \ --attribute-condition="assertion.organization_id == '<organization-id>' && !assertion.public"
  1. Grant the roles the runs need to the principal set of the organization, principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/cadence-ci/attribute.organization_id/<organization-id>, or let it impersonate a service account with roles/iam.workloadIdentityUser.
  2. In the program, request the token for the allowed audience: api-id:gcp.

Azure

  1. On a user-assigned managed identity, or on an app registration, add a federated credential for the organization:
az identity federated-credential create \ --name cadence-ci \ --identity-name <identity> \ --resource-group <resource-group> \ --issuer "https://api.melodium.tech" \ --subject "organization:<organization-id>" \ --audiences "api://AzureADTokenExchange"
  1. Give the identity the roles the runs need.
  2. In the program, request the token for the audience Azure expects: api-id:api://AzureADTokenExchange.

Vault and OpenBao

  1. Enable the jwt authentication method, trusting Cadence.CI, and a role for the runs of your organization (with OpenBao, use bao instead of vault):
vault auth enable jwt vault write auth/jwt/config \ oidc_discovery_url="https://api.melodium.tech" \ bound_issuer="https://api.melodium.tech" vault write auth/jwt/role/cadence-ci - <<EOF { "role_type": "jwt", "user_claim": "sub", "bound_audiences": ["vault"], "bound_subject": "organization:<organization-id>", "token_policies": ["ci"], "token_ttl": "10m" } EOF
  1. In the program, the vault::Vault model logs in with the identity token, and secrets located vault:<path>#<field> are read with the policies of the role:
use sql::SqlPool use std/ops/option::|wrap use vault::Vault treatment database(const password: Secret<string> = "vault:kv/data/app/db#password") model vault: Vault(address="https://vault.example.com:8200", auth="jwt", role="cadence-ci", jwt="api-id:vault") model pool: SqlPool(url="postgresql://app@db.example.com/app", password=|wrap<Secret<string>>(password)) { }

bound_claims narrows the role further, on the other claims, such as run_id or tags.

Going Further