GitHub Actions
Give GitHub Actions workflows firewall access without stored secrets. Register a Trusted Builder and exchange the workflow's OpenID Connect identity for a short-lived token.
A workflow that installs packages through the firewall needs a token. Storing a long-lived Service Access Token as a repository secret works, but it is a stored credential: it can leak, and someone has to rotate it. GitHub Actions can do better, because every workflow run can prove its identity with an OpenID Connect (OIDC) ID token signed by GitHub.
This tutorial registers your repository as a Trusted Builder and sets up a workflow that exchanges its OIDC identity for a firewall token at run time. The exchanged token is a Service Access Token valid for 15 minutes; there is nothing to store and nothing to rotate.
The examples use <namespace-id> and <firewall-id> placeholders. Use IDs, not namespace or firewall names.
Before you start
- You can create Service Access Tokens (the
ns:create:service-tokenpermission, held by admins by default). A Trusted Builder creates a Service Access Token on every exchange, so registering one requires this permission. - Your repository already has the firewall configured as its registry (see your ecosystem page), with the token read from
BYTESAFE_TOKEN.
Step 1: register the Trusted Builder
In the dashboard, open the user menu in the lower-left corner and select Access Tokens (not in the main sidebar), switch to the Trusted Builders tab and select Add Builder:
- Name: something that identifies the workflow, for example
acme/webshop CI. - Repository owner and Repository: your GitHub organization and repository, for example
acmeandacme/webshop. The exchange verifies these against the token's claims; leave a field empty to not constrain it. - Repository ID: GitHub's numeric repository ID. Optional but recommended: names can be reassigned after a repository is deleted or renamed, the ID cannot.
- Permissions: what the exchanged token may do. For installs,
fw:accessis enough. - Restrict to firewalls: limit the token to the firewalls this workflow needs. Empty allows all firewalls.
Save it and copy the audience value. It has the form:
bytesafe:trusted-builder:6f1e9c2a-...A Trusted Builder cannot grant more than you hold yourself; permissions are capped at creation just like manual Service Access Tokens.
Step 2: add the exchange to the workflow
The job needs id-token: write permission, which is what lets it request an OIDC token from GitHub. Then bsfw oidc exchange fetches the ID token for your audience and swaps it for a firewall token:
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
BYTESAFE_ENDPOINT: https://eu-sov-1.bytesafecloud.eu
BYTESAFE_NAMESPACE: <namespace-id>
BYTESAFE_TRUSTED_BUILDER_AUDIENCE: bytesafe:trusted-builder:6f1e9c2a-...
steps:
- uses: actions/checkout@v4
- name: Get firewall token
run: echo "BYTESAFE_TOKEN=$(bsfw oidc exchange)" >> "$GITHUB_ENV"
- name: Install dependencies
run: npm ciThe exchange endpoint is unauthenticated, so no BYTESAFE_TOKEN is needed before this step. The exchanged token lands in BYTESAFE_TOKEN, where your committed registry config already expects it.
The step above assumes the bsfw binary is available on the runner. If it is not, use the curl version below, which needs nothing but curl and jq.
The exchanged token is valid for 15 minutes from the exchange, not from the start of the job. In a long job, run the exchange again in the step that needs it rather than once at the top.
To correlate all requests from one run in the logs, append the run ID: echo "BYTESAFE_TOKEN=$(bsfw oidc exchange)::${GITHUB_RUN_ID}" >> "$GITHUB_ENV".
Without the CLI
The exchange is two HTTP calls, so a workflow can do it with curl:
- name: Get firewall token
run: |
ID_TOKEN=$(curl -sSf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$BYTESAFE_TRUSTED_BUILDER_AUDIENCE" | jq -r .value)
ACCESS_TOKEN=$(curl -sSf -X POST \
"$BYTESAFE_ENDPOINT/v1/$BYTESAFE_NAMESPACE/oidc/exchange" \
-H "Content-Type: application/json" \
-d "{\"idToken\": \"$ID_TOKEN\"}" | jq -r .accessToken)
echo "BYTESAFE_TOKEN=$ACCESS_TOKEN" >> "$GITHUB_ENV"ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are injected by GitHub into any job with id-token: write.
Step 3: verify
Run the workflow, then check the firewall logs:
bsfw logs --firewall <firewall-id> --all-usersRequests from the run are attributed to a token named github:acme/webshop:<run-id>. If you appended the run ID as an interaction ID, it appears on the entries too.
When the exchange fails
The exchange returns 401 without detail, so start with the two values the
workflow controls: the job needs id-token: write, and
BYTESAFE_TRUSTED_BUILDER_AUDIENCE has to match the audience shown on the
Trusted Builder registration exactly.
If the token identifies a configured Trusted Builder, the
audit log records the oidc.exchange
result and reasons such as repository mismatch or verification failure.
Malformed tokens and unknown audiences cannot be assigned to a tenant, so they
are not shown in its audit log. An exchange that fails without leaving an audit
entry points at the audience or the ID token itself.
Next steps
- Restrict what the workflow can do: the Trusted Builder's permissions and firewall list are the place, see Manage tokens.
- Correlate runs in logs: interaction IDs.
- Reference: Tokens, CLI.
Access Tokens
Personal Access Tokens (PAT), Service Access Tokens (SAT), and Trusted Builders with OpenID Connect (OIDC). Which one to use where, and how to create them.
Config as code
Keep firewall configuration in git. Export it, change policy in a pull request, import it back, and roll back when needed.