Compare commits

..

No commits in common. "0370cc9818e9c488edc066eb9889a5973056cb0a" and "25d10b084d255263cec1382c8a64535ad368f7a1" have entirely different histories.

4 changed files with 8 additions and 73 deletions

View file

@ -176,19 +176,6 @@ case "${INSTANCE#*://}" in
*/*[!/]*) die "--instance takes the instance ROOT, not a repository URL: got ${INSTANCE}. Scope comes from the token, not the URL." 2 ;; */*[!/]*) die "--instance takes the instance ROOT, not a repository URL: got ${INSTANCE}. Scope comes from the token, not the URL." 2 ;;
esac esac
VERSION="${VERSION#v}" VERSION="${VERSION#v}"
# The same sane-version charset the resolve-latest path enforces further down.
# An explicit pin skipped it entirely and went straight into the download URL,
# so a value carrying `/` or `..` was interpolated into a URL PATH rather than
# refused. Not a trust boundary — this command is already root, and the operator
# typed the flag — but a pin that cannot name a release should fail BY NAME at
# parse time, not as an opaque 404 forty lines later. install.sh's
# valid_version is the same instinct, and the asymmetry was the whole defect:
# the value rig resolves for itself was checked, the one it is handed was not.
case "$VERSION" in
"") ;; # unset — the latest release is resolved and validated below
*[!0-9.]*|.*|*.)
die "--version must be a release number like 12.13.2 (got: ${VERSION})" 2 ;;
esac
[ -n "$LABELS" ] || die "--labels must not be empty" 2 [ -n "$LABELS" ] || die "--labels must not be empty" 2
# The tenant user is the default when it is there: inside a ci-box the runner # The tenant user is the default when it is there: inside a ci-box the runner

View file

@ -1,13 +1,9 @@
- **Creds-free by default.** The box starts with no Forgejo credentials and no - **Creds-free by default.** The box starts with no Forgejo credentials and no
git credentials. The runner binary is installed but **not registered**: git credentials. The runner binary is installed but **not registered**:
registration needs a token the operator mints in Forgejo (Site registration needs a short-lived token the operator mints in Forgejo
Administration, org, or repo → Actions → Runners) and hands to (Site Administration, org, or repo → Actions → Runners) and hands to
`sudo rig forgejo-runner install --instance <url>`. rig never writes that `sudo rig forgejo-runner install --instance <url>`. The token is consumed at
token to disk — but it does **not expire, and registering does not spend registration and never written to disk by rig. After that, the runner's own
it**. A Forgejo registration token stays valid until somebody mints a credential lives in `~/forgejo-runner/.runner`, mode 0600 — never copy it,
replacement at that same scope, and it will register as many runners as it is print it, or commit it. Secrets that CI jobs need belong in Forgejo's repo or
shown to. Treat a leaked one as live until it has been replaced. (GitHub's org secrets, injected per job, not on this box.
equivalent expires in about an hour; do not carry that habit across.) After
registration the runner's own credential lives in `~/forgejo-runner/.runner`,
mode 0600 — never copy it, print it, or commit it. Secrets that CI jobs need
belong in Forgejo's repo or org secrets, injected per job, not on this box.

View file

@ -3,20 +3,10 @@
# TENANT_USER/TENANT_HOME/TENANT_GROUP/ROLE exported. # TENANT_USER/TENANT_HOME/TENANT_GROUP/ROLE exported.
# #
# This lands the BINARY ONLY. Registration is deliberately not here: it needs a # This lands the BINARY ONLY. Registration is deliberately not here: it needs a
# registration token from the Forgejo instance, and a tenant install is # short-lived token from the Forgejo instance, and a tenant install is
# creds-free by contract — box auto-runs it at mint, holding nothing. The # creds-free by contract — box auto-runs it at mint, holding nothing. The
# operator registers afterwards, out loud: # operator registers afterwards, out loud:
# #
# Deliberately NOT described the way the GitHub sibling describes its own
# registration token, which really does expire in about an hour. That adjective
# must not cross this forge boundary — a test pins its absence from this file
# and from creds.md, so do not reintroduce it by copying from `rig runner`.
# Forgejo's ActionRunnerToken carries no expiry field at all;
# NewRunnerToken invalidates prior tokens only when a replacement is minted at
# the same scope, and Register leaves the one it was handed active. It is
# reusable until replaced, so a leak stays live. See creds.md, which is the
# copy an agent inside the box actually reads.
#
# box shell ci-box # box shell ci-box
# sudo rig forgejo-runner install --instance https://forgejo.example.com # sudo rig forgejo-runner install --instance https://forgejo.example.com
# #

View file

@ -3190,18 +3190,6 @@ check "forgejo-runner: --instance needs a value" 2 "needs a value" "$FR" -
check "forgejo-runner: unknown flag exits 2" 2 "unknown flag" "$FR" --instance https://f.example.com --nope check "forgejo-runner: unknown flag exits 2" 2 "unknown flag" "$FR" --instance https://f.example.com --nope
check "forgejo-runner: empty --labels refused" 2 "must not be empty" "$FR" --instance https://f.example.com --labels '' check "forgejo-runner: empty --labels refused" 2 "must not be empty" "$FR" --instance https://f.example.com --labels ''
check "forgejo-runner: the runner user is never root" 2 "must not be root" "$FR" --instance https://f.example.com --user root check "forgejo-runner: the runner user is never root" 2 "must not be root" "$FR" --instance https://f.example.com --user root
# The pin rig RESOLVES was charset-checked; the pin it is HANDED was not, and
# that one is the one that reaches a URL path. Refused by name at parse time.
check "forgejo-runner: --version refuses a path, not a release number" 2 "release number like" \
"$FR" --instance https://f.example.com --version ../../etc/passwd
check "forgejo-runner: --version refuses a non-numeric pin" 2 "release number like" \
"$FR" --instance https://f.example.com --version latest
# Reaching the root check is the proof a good pin got THROUGH validation: this
# runs as a normal user in CI, so "must run as root" is the next gate down.
check "forgejo-runner: a plain release number passes validation" 1 "must run as root" \
"$FR" --instance https://f.example.com --version 12.13.2
check "forgejo-runner: a leading v is stripped before that check" 1 "must run as root" \
"$FR" --instance https://f.example.com --version v12.13.2
# A schemeless host and a repo URL are the two ways an operator mis-states the # A schemeless host and a repo URL are the two ways an operator mis-states the
# instance, and only one of them would fail loudly on its own — a repo URL # instance, and only one of them would fail loudly on its own — a repo URL
# registers somewhere subtly wrong instead. Both refuse by name. # registers somewhere subtly wrong instead. Both refuse by name.
@ -3396,32 +3384,6 @@ check "ci-box: no warn-and-continue checksum branch either" 1 "" \
check "forgejo-runner: install routes through the shared checksum policy" 0 "fetch_and_verify_sha256" \ check "forgejo-runner: install routes through the shared checksum policy" 0 "fetch_and_verify_sha256" \
grep -o "fetch_and_verify_sha256 \"\$URL\"" "$FR" grep -o "fetch_and_verify_sha256 \"\$URL\"" "$FR"
# --- GitHub's token adjective must not cross the forge boundary -------------
# Measured in Forgejo's own source, not inferred: ActionRunnerToken carries NO
# expiry field; NewRunnerToken invalidates prior tokens only when a replacement
# is minted at the same scope; Register leaves the token it was handed active.
# Reusable until replaced — where GitHub's expires in about an hour.
#
# This is pinned rather than merely fixed because creds.md is spliced into the
# ci-box's own CONTEXT.md: it is the text an AGENT INSIDE THE BOX reads about
# its own credentials. "short-lived" there makes a leaked token look
# self-expiring while it is still registering runners. The wording arrived by
# being copied from the GitHub sibling, so the same copy can bring it back.
CICREDS="$ROOT/docs/templates/ci-box/creds.md"
check "ci-box: creds.md never calls the registration token short-lived" 1 "" \
grep -qi "short-lived" "$CICREDS"
# Plain absence, not a phrase match: the wording this replaced wrapped across
# two comment lines, so a phrase pin would have passed against the very text it
# was written to catch. The file explains the ban without spelling the word.
check "ci-box: the install header does not call it short-lived either" 1 "" \
grep -qi "short-lived" "$CIBOX"
# ...and says the true thing, so the pin cannot be satisfied by deleting the
# claim rather than correcting it.
check "ci-box: creds.md states the token does not expire" 0 "not expire" \
grep -o "not expire" "$CICREDS"
check "ci-box: creds.md states a leaked token stays live" 0 "leaked" \
grep -o "leaked" "$CICREDS"
# --- --version must converge, not be swallowed (review !110) ---------------- # --- --version must converge, not be swallowed (review !110) ----------------
# forgejo-runner does NOT self-update, and a ci-box's template preinstalls the # forgejo-runner does NOT self-update, and a ci-box's template preinstalls the
# binary at mint — so a bare presence check would make --version dead on the # binary at mint — so a bare presence check would make --version dead on the