BUILDING Parts of this page describe what is not built yet, and each says so.
CI with GitHub Actions
A workflow can act as a RunningLake service principal without any RunningLake secret in your repository. The job asks GitHub for its identity token, sends it to RunningLake, and is given a token for that one principal that lasts minutes. Whether it is given one is decided by a trust policy you wrote, over facts about the job that GitHub signs and the job cannot change.
What the job can do today is prove who it is: after the exchange it can ask RunningLake which principal is calling. Starting a run from a CI job is not built yet: no plane can run a pipeline yet. Everything on this page that is about getting the identity works now.
What you need
- A RunningLake organisation, and the address of its control plane.
- A service principal, and a trust policy on it. Making a service principal and adding its trust policy are console-only: they need an owner or admin who confirmed it is them in the last five minutes, which no API token has.
-
Your organisation’s id. The workflow below sets it as
RL_ORG, and it is part of the audience GitHub is asked to sign the identity token for. The Use it in CI panel on the principal’s CI trust tab shows it, with the control plane’s address and the principal’s id, in a workflow ready to copy. -
A repository on github.com. Its numeric id and its owner’s numeric id are what the policy trusts:
gh api repos/OWNER/REPO --jq '.id, .owner.id'prints both.
Add a service principal and a policy
- In the console, open API access, then Service principals, and choose New service principal. Name it, choose what it may do and which environments and pipelines it may reach. It has no secret: you will not mint one.
-
Open its CI trust tab and choose Add a CI trust policy. Choose GitHub Actions as the provider
and fill in:
- Repository owner id and Repository id: whole numbers, exactly as GitHub reports them.
-
Which jobs may exchange: an exact ref (such as
refs/heads/main), any protected ref, or an environment together with a ref. An environment alone is not enough, because the token does not say whether the environment has protection rules. -
Allowed events: one or more of
push,workflow_dispatchandschedule. -
Workflow (optional, and recommended for push): the workflow file, exactly, including its
@ref. - Token lives (minutes): at most 60, and 15 if you do not choose.
- The environments and pipelines the policy may reach, which can only narrow what the principal may reach.
- Use Test and recent exchanges to check the policy against the claims of a job before you rely on it.
The workflow
Replace the three values under env. The first step exchanges the job’s identity for a token and the second uses it
to ask who is calling. The token is masked in the log as soon as it exists, and nothing here is stored in GitHub as a secret.
name: reach RunningLake
on:
workflow_dispatch:
permissions:
id-token: write # lets the job ask GitHub for its identity token
contents: read
env:
RL_SERVER: https://YOUR-CONTROL-PLANE
RL_ORG: 00000000-0000-0000-0000-000000000000
RL_PRINCIPAL: 00000000-0000-0000-0000-000000000000
jobs:
who-am-i:
runs-on: ubuntu-latest
steps:
- name: Exchange this job's identity for a RunningLake token
run: |
set -euo pipefail
ID_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=runninglake:org:$RL_ORG" | jq -r .value)
echo "::add-mask::$ID_TOKEN"
RL_TOKEN=$(curl -sS -X POST "$RL_SERVER/runninglake.v1.TokenExchangeService/ExchangeToken" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg org "$RL_ORG" --arg principal "$RL_PRINCIPAL" --arg token "$ID_TOKEN" \
'{org: $org, principal: $principal, subject_token: $token}')" | jq -r .access_token)
echo "::add-mask::$RL_TOKEN"
echo "RL_TOKEN=$RL_TOKEN" >> "$GITHUB_ENV"
- name: Ask RunningLake who is calling
run: |
curl -sS -X POST "$RL_SERVER/runninglake.v1.CallerService/GetCaller" \
-H "Authorization: Bearer $RL_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
The job must have id-token: write, or GitHub will not give it an identity token at all, and it must run on an event the policy allows.
The answer to the second step names the principal and the organisation, the credential’s expiry and the policy that admitted the job.
rl does the same exchange for you: set RL_SERVER, RL_ORG and RL_PRINCIPAL and run any verb, such as
rl auth whoami. It keeps the token in memory and never prints it. There is no public download of rl yet, which is why this page uses curl.
What the job gets
- A token for that one principal, limited to what the principal and the policy both allow, that lives 15 minutes unless the policy says less or more (at most 60) and is not refreshed. A job that needs longer exchanges again.
- A GitHub identity token can be exchanged once, so a leaked one is refused outright. Ask GitHub for a new one each time, as the first step does.
-
Every exchange is written to the audit log as
principal.federatewith the claims that were verified, so you can see which run of which workflow acted. The audit vocabulary lists the words.
Why a policy pins these things
-
Ids, not names. A repository can be renamed, transferred, deleted and recreated under the old name. Its numeric id and its owner’s
cannot be taken over. The label you give a policy is shown in words and is never matched. The
substring GitHub sends is never read for a decision, because its shape changes with the repository’s settings. -
Only push, workflow_dispatch and schedule. A job on a fork’s pull request is run by someone who does not
hold your repository, and
pull_request_targetandworkflow_runrun your own code with input the author of a pull request influenced, under a name that looks like your main branch. A policy can never allowpull_request,pull_request_targetorworkflow_run. - An exact ref or a protected one. Matching is exact: no prefix, no wildcard. Anyone who can push to an unprotected branch can make a job that runs on it, so a branch that is not pinned exactly must be protected.
-
A workflow file. Without one, any workflow in the repository that runs on the allowed ref can exchange. A job that calls a reusable workflow
carries the calling workflow in
workflow_refand the reusable one injob_workflow_ref; the console’s form pins the first. -
Plain ASCII. A pinned ref, environment or workflow is printable ASCII, so that a policy cannot read as pinning
mainwhile pinning a look-alike.
When an exchange is refused
Every refusal is the same answer, byte for byte, whether the principal does not exist, the organisation is wrong, the signature is bad, the token has expired or the claims do not match. A stranger sending guesses learns nothing from it, and neither can you from the job’s log. Work from the console instead:
- Test and recent exchanges lists the exchanges whose token was genuine and came from an owner a policy of this principal trusts. Failed verifications are not recorded.
-
Paste the job’s decoded claims into the test box, or run
rl trust evaluatewith a file of them, and the answer names the first claim that does not match. Never paste the token itself: a token that has not been exchanged is a credential. The claims you need arerepository_id,repository_owner_id,ref,ref_protected,environment,event_nameandworkflow_ref. -
Check the usual causes: the job lacks
id-token: write; it runs on an event the policy does not allow; the audience GitHub was asked for is notrunninglake:org:followed by your organisation’s id; the token was already exchanged; the policy was deleted, which revokes every token issued under it; or the principal is disabled.
What is not supported
Self-managed GitLab, GitHub Enterprise Server and ghe.com are not supported. Only the issuer of github.com Actions is accepted: a control plane asks a fixed list of issuers for their keys and never fetches from an address a token or a customer supplies. CI with GitLab covers gitlab.com.