Skip to main content

Module jit

Module jit 

Source
Expand description

Just-in-time runner registration: the one call in this product that returns a secret.

POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig
POST /orgs/{org}/actions/runners/generate-jitconfig
body { name, runner_group_id, labels, work_folder }
  -> 201 { runner { … }, encoded_jit_config }

Two scopes, one request shape, one response shape, and — after D4 — one credential. The Actions-service credential chain and message protocol this replaces were disproved by d17-user-to-server-scale-set-chain.md; what is here instead is documented, stable REST against a host and a token that already exist.

§What the live spikes settled, and what each one costs to get wrong

v1 (docs/spikes/d18-org-jit-verification.md) drove both scopes against live GitHub. Four of its findings are load-bearing here rather than interesting:

  1. runner_group_id is mandatory. Omitting it answers 422 Invalid input: object is missing required key: runner_group_id. There is no server-side default, so JitRunnerRequest takes it as a required u64 rather than an Option — a field that cannot be omitted cannot be forgotten.
  2. An unusable group answers 403 or 404, depending on why. Group 2 — the GitHub-hosted group — answered 403; group 99999 answered 404. Error handling keyed on 404 alone misreports the first case as “no such group” when the truth is “not yours to administer”, so JitError::Forbidden and JitError::NotFound are separate outcomes and both name the runner group.
  3. 1 is not special. A non-default group id (3) also returned 201. Nothing here may hard-code 1.
  4. No labels are added implicitly, and labels are stored lower-cased. The 201 carries exactly the labels requested — no self-hosted, no OS, no architecture — so runs-on: self-hosted does not match a runner registered without that label. b1’s runner_manager_domain::policy::RoutingLabels::as_registration_labels is the array this module sends, and runner_manager_domain::model::Label lower-cases on construction, which is what keeps the labels this product asks for and the labels GitHub stores the same strings.

§The encoded configuration is the one short-lived secret in the product

07-security.md’s credential inventory lists exactly two sensitive values after D4: the persisted user access token, and this. It is returned in EncodedJitConfig, whose Debug and Display redact, which does not implement serde::Serialize, and which zeroises its buffer on drop.

This crate never writes it to disk and never puts it in an error message. Every JitError is built from the request — target, runner group, name — and from GitHub’s own message, never from a response body. The restrictive handoff to the runner process is d1’s primitive and e3’s job; the rule here is only that nothing leaves this module carrying the blob except JitRegistration.

§What the wrapper does not cover, stated rather than implied

crate::ApiResponse buffers the whole response body, so the encoded configuration also exists as bytes in that buffer until the response is dropped at the end of RestJit::generate_jit_config. That buffer is c2’s and is not zeroised. The intermediate String serde produces is zeroised here explicitly, immediately after the value is copied into the wrapper, because that one is this module’s to scrub.

The residual exposure is therefore one heap buffer, for the duration of one call, in a process that already holds the user access token. It is recorded rather than papered over: claiming the blob exists in exactly one place would be false, and a false claim is worse than a bounded one.

§There is no job reservation, and this call is not one

A JIT configuration registers a runner; it does not claim a job. The scale-set model’s AcquireJobs has no REST equivalent, so another host may take the job this runner was started for (01-current-architecture.md, edge case 6). The runner then receives nothing and exits on its idle timeout — the surplus-runner path, which is an accepted, bounded cost with a test of its own (h1 scenario 8).

Do not add a claim, a lease, or a local reservation table here to compensate, and do not read this call as one. demand::tests::nothing_in_this_crate_reserves_or_claims_a_job makes that executable across the whole crate.

Structs§

EncodedJitConfig
The encoded just-in-time configuration: a short-lived credential.
JitRegistration
A registered runner and the configuration that starts it.
JitRunner
The runner GitHub registered, as it described it in the 201.
JitRunnerRequest
One generate-jitconfig request, before a scope is chosen.
RestJit
JitGateway over api.github.com.

Enums§

JitError
Everything a just-in-time registration can fail with.

Constants§

CREATED
What GitHub answers a successful registration with.
DEFAULT_WORK_FOLDER
The runner’s working directory, relative to its installation root.
JITCONFIG_PATH
The endpoint suffix both scopes share.

Traits§

JitGateway
Just-in-time runner registration.