Cloud access (OIDC)
A workflow run can prove its identity to your own cloud and receive short-lived credentials in return, with no AWS, GCP, or Azure keys stored anywhere. The same pattern GitHub Actions uses to deploy without secrets: your cloud trusts Boardwalk's OIDC issuer, and each run mints a signed id-token that says exactly which org, workflow, and run is asking.
How it works
- The workflow declares
permissions.id_token: "write"in its manifest. - The program calls
runtime.idToken(audience), which mints a signed JWT (RS256, 15-minute expiry) asserting the run's identity. - The program exchanges that JWT at your cloud's federation endpoint (AWS
AssumeRoleWithWebIdentity, GCP workload identity, Azure federated credentials) for short-lived credentials scoped to a role you control.
The token is minted fresh on every call and is redacted from everything the model sees, like a secret. Your cloud verifies it against the issuer's public keys; Boardwalk never holds your cloud credentials, and you never store them.
Grant the permission
export const meta = {
slug: "s3-report",
title: "S3 Report",
triggers: [{ kind: "cron", expr: "0 7 * * 1" }],
permissions: { id_token: "write" },
};
const jwt = await runtime.idToken("sts.amazonaws.com");Without the grant, runtime.idToken() fails with an error naming the missing permission. The audience is the party you intend to present the token to; for AWS it is sts.amazonaws.com.
Set up AWS
One-time, in your AWS account: create an IAM OIDC identity provider for the Boardwalk issuer, then a role whose trust policy accepts tokens from it.
aws iam create-open-id-connect-provider \
--url https://oidc.boardwalk.sh \
--client-id-list sts.amazonaws.comThe role's trust policy pins which runs may assume it. IAM can match on aud and sub; the sub claim is org:<org id>:workflow:<workflow id>:run:<run id>, so pin your org (or a single workflow) with a pattern. Pin the stable ids, never display names: boardwalk whoami prints your org id, and boardwalk workflows list --json prints workflow ids.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::<account>:oidc-provider/oidc.boardwalk.sh" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": { "oidc.boardwalk.sh:aud": "sts.amazonaws.com" },
"StringLike": { "oidc.boardwalk.sh:sub": "org:<your org id>:workflow:<workflow id>:*" }
}
}]
}Grant the role only what the workflow needs; the credentials it issues carry the role's permissions and expire on their own.
Exchange the token
AssumeRoleWithWebIdentity is an unsigned call (the token is the authentication), so plain fetchworks; the AWS SDK's @aws-sdk/client-sts works the same way if you prefer it. Ordinary code is enough — a suspended run resumes with its exact program state, so the credentials you hold survive the wait (mint fresh ones if they expired while you waited):
const creds = await (async () => {
const jwt = await runtime.idToken("sts.amazonaws.com");
const params = new URLSearchParams({
Action: "AssumeRoleWithWebIdentity",
Version: "2011-06-15",
RoleArn: "arn:aws:iam::<account>:role/<role>",
RoleSessionName: `bw-${runtime.runId}`,
WebIdentityToken: jwt,
});
const res = await fetch(`https://sts.amazonaws.com/?${params}`, {
method: "POST",
headers: { Accept: "application/json" },
});
if (!res.ok) throw new Error(`STS: ${res.status}`);
const body = await res.json();
return body.AssumeRoleWithWebIdentityResponse.AssumeRoleWithWebIdentityResult.Credentials;
});The returned AccessKeyId / SecretAccessKey / SessionToken plug into any AWS SDK client. Mint a new id-token whenever you need one; a run that slept for an hour just calls runtime.idToken() again.
The claims
{
"iss": "https://oidc.boardwalk.sh",
"sub": "org:<org id>:workflow:<workflow id>:run:<run id>",
"aud": "sts.amazonaws.com",
"org_id": "...", "workflow_id": "...", "workflow_version_id": "...", "run_id": "...",
"trigger_kind": "cron", "actor_type": "user", "runs_on": "boardwalk/linux",
"stage": "production", "exp": 900
}Everything a policy needs to reason about which run is asking: the org, the workflow, the exact run, how it was triggered, and who triggered it. AWS trust policies match aud and sub; clouds that support custom claim mapping (GCP, Azure) can use the rest directly. Verification keys are public at https://oidc.boardwalk.sh/.well-known/jwks.json.
When to use stored keys instead
Federation needs a one-time trust setup in the target cloud. For a service that doesn't support OIDC federation, or a quick integration where that setup isn't worth it, store a credential as a secret and read it with secrets.get(): the same redaction guarantee applies. Prefer federation for your own cloud accounts; it removes the long-lived key entirely.