BUILDING Parts of this page describe what is not built yet, and each says so.
CI with GitLab
A gitlab.com job can act as a RunningLake service principal without any RunningLake secret in your project. GitLab gives the job an ID token for an audience you name, the job 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 GitLab 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 job below uses it twice, as
RL_ORGand inside the audience of the ID token:runninglake:org:followed by the id. The Use it in CI panel on the principal’s CI trust tab shows both, in a job ready to copy. - A project on gitlab.com. Its numeric project id and its namespace’s numeric id are what the policy trusts; both are on the project’s settings page.
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 GitLab as the provider and fill in:
- Namespace id and Project id: whole numbers, exactly as GitLab reports them.
- Which jobs may exchange: an exact ref, with its type (branch or tag), or any protected ref. A branch or a tag must be protected, because anyone who can push can create either; an environment is optional and must be protected if you pin one.
-
Allowed events, the pipeline sources: one or more of
push,webandschedule. Never a merge request pipeline, a trigger, an API call or a child pipeline. - CI config (optional): the pipeline file, exactly.
- 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 job
Replace the three values under variables and the organisation id in the audience. The first line of the script exchanges the ID token for a RunningLake token
and the second uses it to ask who is calling. Nothing here is stored in GitLab as a variable you have to protect.
who-am-i:
image: alpine:3.20
id_tokens:
RL_OIDC_TOKEN:
aud: runninglake:org:00000000-0000-0000-0000-000000000000
variables:
RL_SERVER: https://YOUR-CONTROL-PLANE
RL_ORG: 00000000-0000-0000-0000-000000000000
RL_PRINCIPAL: 00000000-0000-0000-0000-000000000000
script:
- apk add --no-cache curl jq
- |
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 "$RL_OIDC_TOKEN" \
'{org: $org, principal: $principal, subject_token: $token}')" | jq -r .access_token)
- |
curl -sS -X POST "$RL_SERVER/runninglake.v1.CallerService/GetCaller" \
-H "Authorization: Bearer $RL_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
The ID token is named RL_OIDC_TOKEN because that is the variable rl reads: set RL_SERVER, RL_ORG and RL_PRINCIPAL and run any verb, such as
rl auth whoami, and rl does the same exchange for you and keeps the token in memory. There is no public download of rl yet, which is why this page uses curl.
The image installs curl and jq; use one that has them.
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 gets one ID token, so it can be exchanged until it expires, and every exchange is audited. That is a difference from GitHub, whose identity token is exchangeable once, and it means a leaked GitLab token is bounded by its short life and not by a single use.
-
Every exchange is written to the audit log as
principal.federatewith the claims that were verified. The audit vocabulary lists the words.
Why a policy pins these things
- Ids, not names. A project can be renamed, moved and recreated under the old path. Its numeric id and its namespace’s cannot be taken over. The project path you see in the audit log is what the job claimed, shown in words, and is never matched.
- Only push, web and schedule. A merge request pipeline can run code from a fork or from a branch anyone can push, and a trigger or API call can be made by whoever holds the trigger token. A policy can never allow them.
- A protected ref. Matching is exact: no prefix, no wildcard. A branch or tag that anyone with push access can create is not a fact you can trust, so a policy that pins one requires it to be protected.
-
Plain ASCII. A pinned ref, environment or pipeline file 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 a namespace 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: while it is valid it is a credential. The claims you need arenamespace_id,project_id,ref,ref_type,ref_protected,environmentandpipeline_source. -
Check the usual causes: the job has no
id_tokensblock, or its audience is notrunninglake:org:followed by your organisation’s id; the ref is not protected; the pipeline source is not one the policy allows; 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 gitlab.com 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 GitHub Actions covers github.com.