This repository provides a manually triggered GitHub Actions workflow for a
bounded, ephemeral RHEL 10 development runner. It provisions the exact
rhel10-x86_64-16c-64g runner requested by infra#257,
joins the bootc-dev tailnet with Tailscale workload identity federation, and
provides either Tailscale SSH or ordinary OpenSSH for Unix user runner.
An administrator must configure the non-secret repository variables
TS_OAUTH_CLIENT_ID and TS_AUDIENCE. The Tailscale federated identity must
allow this exact GitHub subject:
repo:bootc-dev@202312630/cgwalters-*-sandbox@*
The Tailscale identity needs writable auth_keys scope and the
tag:bootc-dev-sandbox tag. Tailnet SSH policy must allow the intended source
identity to reach that tag as OS user runner. No repository secret or OAuth
client secret is used.
The default mode enables Tailscale SSH. It authenticates the source tailnet identity/device through the Tailscale SSH policy:
gh workflow run "Tailscale development runner" --repo bootc-dev/cgwalters-devspace-sandbox \
--ref main -f duration=30
tailscale ssh runner@<hostname-or-IP>
gh run cancel <run-id> --repo bootc-dev/cgwalters-devspace-sandboxFor ordinary OpenSSH, supply exactly one Ed25519 public-key line. The workflow does not enable Tailscale SSH in this mode; sshd listens only on the ephemeral node's Tailscale IPv4 address and additionally requires the matching private key:
gh workflow run "Tailscale development runner" --repo bootc-dev/cgwalters-devspace-sandbox \
--ref main -f duration=30 -f ssh_public_key="$(<~/.ssh/id_ed25519.pub)"
ssh -i ~/.ssh/id_ed25519 runner@<tailscale-ip>The recommended flow generates a temporary key, dispatches safely, polls for
the matching run and Tailscale peer, connects with ordinary ssh, and cancels
that exact run on exit (the private key is never uploaded):
./tools/devspace-ssh --duration 30It uses the host Tailscale daemon from Toolbox when necessary. The existing
tools/tailscale-client wrapper remains useful for status and ping; do not
use it for tailscale ssh, because that subcommand can spawn a nested proxy
which loses a non-default daemon socket.
The helper intentionally does not use gh run watch: this workflow waits in a
keepalive step. It polls until the peer is available, connects, then cancels
the exact run when the session ends. Ordinary sshd is governed by Tailscale
network ACLs, not Tailscale SSH ACL rules. It prints each failed SSH attempt's
stderr, uses a bounded retry deadline, and emits one final verbose diagnostic
before failing.
In ordinary-SSH mode, the workflow installs and uses the stock packaged
sshd.service. It replaces the disposable runner's /etc/ssh/sshd_config
with a minimal hardened configuration bound only to the Tailscale IPv4,
generates host keys, validates the configuration, and restarts that service.
The keepalive checks the service's active state, MainPID and sshd_t SELinux
context, and the exact Tailscale-bound port 22 every five seconds. SELinux must
remain enforcing. If any check fails, the job prints bounded non-secret service,
socket, journal, and recent SELinux AVC diagnostics and fails immediately.
There is no restoration step because the runner is disposable; cancellation
cleans up through runner disposal and Tailscale action logout.
From Toolbox, use the repository helper so the host Tailscale daemon socket is selected automatically for status and ping:
./tools/tailscale-client status --selfThe helper uses the normal daemon socket when present, otherwise detects a
container and uses /run/host/run/tailscale/tailscaled.sock. An explicit
--socket argument is passed through unchanged; if neither socket is
available, it reports how to fix the environment.
The duration choice is 30, 60, or 120 minutes. The job has an independent 125-minute timeout. Cancellation and expiry run the Tailscale action cleanup; the workflow prints only safe hostname, IPv4, and status information. Treat the runner as disposable and do not store secrets on it.
If dispatch is unavailable, verify that this workflow is on main. If joining
fails, check both repository variables, the federated subject above, the
identity's tag and scope, and device approval. A Tailscale 403 Unauthorized
from tailscale up means the trust credential must be updated to allow the
subject above. The workflow installs iptables and links the action's
/usr/local/bin binaries into RHEL's sudo path before connecting; these are
required by this runner image. If SSH fails after a successful join, check the
tailnet ACL for the source identity, destination tag, and runner OS-user
mapping. For ordinary OpenSSH, also check the supplied key and network ACL;
Tailscale SSH ACL rules do not apply.