Skip to content

hetzner: validate GitHub runner token before provisioning any VM#19

Merged
igorpecovnik merged 1 commit intomainfrom
hetzner-validate-github-token
Apr 24, 2026
Merged

hetzner: validate GitHub runner token before provisioning any VM#19
igorpecovnik merged 1 commit intomainfrom
hetzner-validate-github-token

Conversation

@igorpecovnik
Copy link
Copy Markdown
Member

@igorpecovnik igorpecovnik commented Apr 24, 2026

Summary

Stale / under-scoped GITHUB_TOKEN currently produces this silent failure on the Hetzner runner workflow:

  1. Action reports success — VM reaches STATUS_RUNNING within ~30s.
  2. cloud-init runs for 5–10 min (apt update, Docker install, armbian-config install, runner tarball download).
  3. module_armbian_runners install POSTs /orgs/<org>/actions/runners/registration-token — gets 401/403.
  4. Response body is piped into jq -r .token which silently yields the literal string "null".
  5. ./config.sh --token null --unattended fails with a cryptic message buried in /var/log/cloud-init-output.log.
  6. Zero runners come online; the VM gets billed for PERIOD minutes doing nothing.

(We hit exactly this in https://github.com/armbian/armbian.github.io/actions/runs/24497359721 — the cloud-init log showed jq: error (at <stdin>:4): Cannot iterate over null (null) repeated 9× from the runner-list paging loop in module_armbian_runners, which is how the expired token surfaced.)

Change

Add a pre-flight GET /orgs/armbian/actions/runners?per_page=1 with the supplied token, invoked from main() right after the existing "token present" check and before the hcloud.Client is even constructed. Exit non-zero on 401/403/404/network, naming the exact scope the token needs.

Reason it's a safe proxy for the scope we actually need: the same permission that authorises GET /orgs/<org>/actions/runners also authorises POST /orgs/<org>/actions/runners/registration-token, so a 200 here reliably implies runner registration will work. Listing is side-effect-free and cheap.

Organisation is hardcoded to armbian to match the value baked into get_cloud_init_config() on the module_armbian_runners install organisation=armbian line. If the action is ever reused for a different org, both should be parameterised together in a follow-up.

Test plan

  • Dispatch armbian.github.io Hetzner runner workflow with a valid token → validation passes silently, VM provisioning proceeds as before.
  • Dispatch with an expired token → action fails inside validate_github_token with HTTP 401 — Token is invalid or expired; no Hetzner VM is created.
  • Dispatch with a token missing admin:org / Self-hosted runners scope → action fails with HTTP 403 — Token authenticates but lacks permission to manage runners on 'armbian'.
  • --action delete path skips the check (no registration happens during teardown).

Previously, a stale / under-scoped GITHUB_TOKEN produced this
silent failure:

  1. action reports success — Hetzner VM reaches STATUS_RUNNING
     within ~30s
  2. cloud-init runs for 5-10 min (apt update, Docker install,
     armbian-config install, runner download)
  3. armbian-config --api module_armbian_runners install POSTs
     /orgs/<org>/actions/runners/registration-token — gets
     401/403
  4. the response body is piped into `jq -r .token` which
     silently yields the string "null"
  5. ./config.sh --token null --unattended fails with a cryptic
     message buried in /var/log/cloud-init-output.log
  6. zero runners ever come online, the VM gets billed for
     PERIOD minutes doing nothing

Add a pre-flight check: GET /orgs/armbian/actions/runners
?per_page=1 with the supplied token. The same scope that can
call registration-token can also list runners, so this is a
cheap side-effect-free proxy. Fail loudly on 401/403/404/
network, naming the exact scope the token needs, before touching
Hetzner.

Organisation is hardcoded (armbian) to match the value baked
into get_cloud_init_config(); parameterise together in a future
pass if the action is ever reused for a different org.
@igorpecovnik igorpecovnik merged commit 1a940ed into main Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant