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:
runner_group_idis mandatory. Omitting it answers422 Invalid input: object is missing required key: runner_group_id. There is no server-side default, soJitRunnerRequesttakes it as a requiredu64rather than anOption— a field that cannot be omitted cannot be forgotten.- An unusable group answers
403or404, depending on why. Group2— the GitHub-hosted group — answered403; group99999answered404. Error handling keyed on404alone misreports the first case as “no such group” when the truth is “not yours to administer”, soJitError::ForbiddenandJitError::NotFoundare separate outcomes and both name the runner group. 1is not special. A non-default group id (3) also returned201. Nothing here may hard-code1.- No labels are added implicitly, and labels are stored lower-cased. The
201carries exactly the labels requested — noself-hosted, no OS, no architecture — soruns-on: self-hosteddoes not match a runner registered without that label.b1’srunner_manager_domain::policy::RoutingLabels::as_registration_labelsis the array this module sends, andrunner_manager_domain::model::Labellower-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§
- Encoded
JitConfig - 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. - JitRunner
Request - One
generate-jitconfigrequest, before a scope is chosen. - RestJit
JitGatewayoverapi.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.