fix(runner): harden non110 registration ceremony [skip ci]

This commit is contained in:
Your Name
2026-06-28 21:09:45 +08:00
parent 8ddd52afc1
commit 666454212a
4 changed files with 76 additions and 3 deletions

View File

@@ -1,3 +1,19 @@
## 2026-06-28 — 21:09 non-110 runner registration ceremony hardening
**完成內容**
- `ops/runner/register-awoooi-non110-runner.sh` 補上正式 `--check` / `--dry-run` check-mode。
- registration helper 現在明確拒絕 `RUNNER_TOKEN` environment避免 runner token 透過 env / process context 進入 Codex 或 shell 流程;仍只允許互動 TTY silent prompt 與 stdin registration。
- `ops/runner/README.md` 同步 188 check-mode 與正式註冊邊界Codex 不接觸 token、不使用 `--token` argv、不列印 token、不讀 `.runner` 內容。
**驗證結果**
- `bash -n ops/runner/register-awoooi-non110-runner.sh ops/runner/install-awoooi-non110-runner-user-service.sh ops/runner/check-awoooi-non110-runner-readiness.sh`:通過。
- `python3.11 -m pytest ops/runner/test_register_awoooi_non110_runner.py ops/runner/test_check_awoooi_non110_runner_readiness.py ops/runner/test_guard_gitea_runner_pressure.py -q``12 passed`
- `python3 ops/runner/guard-gitea-runner-pressure.py --root .``GITEA_RUNNER_PRESSURE_GUARD_OK workflow_files=10 scheduled_workflows=3 auto_branch_events_on_110=0 generic_runner_labels=0`
- 188 registration helper check-mode`dry_run=true``runner_token_in_argv=false``raw_runner_registration_read=false``safe_next_step=run_this_script_from_interactive_tty_without_capturing_token`
- 188 readiness verifier`READY_CONFIG_COUNT=1``READY_BINARY_COUNT=1``READY_SERVICE_COUNT=1``READY_REGISTRATION_COUNT=0``READY_ACTIVE_SERVICE_COUNT=0``UNMANAGED_RUNNER_CONTAINERS=none``HEAVY_PROCESS_COUNT=0``BLOCKER_COUNT=3`
**邊界**:沒有讀 `.runner` 內容、runner token、secret、raw session、SQLite、auth 或 `.env`;沒有啟動 runner service沒有重開 110 runner沒有重啟 Docker / Nginx / firewall / K3s / DB沒有使用 GitHub。
## 2026-06-28 — 20:45 Delivery Workbench non-110 runner readiness 投影
**完成內容**

View File

@@ -484,6 +484,18 @@ ssh ollama@192.168.0.188 'bash -s' \
< ops/runner/check-awoooi-non110-runner-readiness.sh
```
runner registration 只能由 operator 在互動 TTY 執行Codex 不得接觸 token。
註冊 helper 的 check-mode 不需要 token也不得接受 `RUNNER_TOKEN` environment
```bash
ssh ollama@192.168.0.188 'bash -s -- --check' \
< ops/runner/register-awoooi-non110-runner.sh
```
正式註冊時helper 會用 silent TTY 讀取 runner token透過 stdin 餵給
`act_runner register`,不使用 `--token` argv、不列印 token、不讀 `.runner`
內容;若 `.runner` 已存在,預設只回報下一步 enable / verifier不覆寫。
`--enable` 只允許在 `AWOOOI_NON110_ENABLE=1``act_runner` executable、
`config.yaml` present、`.runner` present 且 service 已由 verifier 證明 target / limits
正確後使用。installer 不會讀 `.runner` 內容,也不會寫 runner tokenrollback 只會

View File

@@ -17,7 +17,7 @@ RUNNER_REGISTRATION="${RUNNER_REGISTRATION:-${RUNNER_DIR}/.runner}"
usage() {
cat <<'EOF'
Usage:
DRY_RUN=1 ops/runner/register-awoooi-non110-runner.sh
ops/runner/register-awoooi-non110-runner.sh --check
ops/runner/register-awoooi-non110-runner.sh
Environment overrides:
@@ -27,6 +27,7 @@ Environment overrides:
Safety contract:
- requires an interactive TTY for token entry
- reads the runner token with terminal echo disabled
- refuses RUNNER_TOKEN from the environment
- feeds the token to act_runner over stdin, not argv
- never prints the token or .runner content
- refuses to overwrite an existing non-empty registration unless
@@ -114,10 +115,23 @@ main() {
usage
exit 0
fi
if [ "${1:-}" = "--check" ] || [ "${1:-}" = "--dry-run" ]; then
DRY_RUN=1
export DRY_RUN
shift
fi
if [ "$#" -gt 0 ]; then
usage >&2
exit 2
fi
umask 077
trap 'unset RUNNER_TOKEN 2>/dev/null || true' EXIT
if [ "${RUNNER_TOKEN+x}" = "x" ]; then
die "runner_token_env_not_allowed"
fi
metadata
preflight

View File

@@ -15,11 +15,43 @@ USER_SERVICE_INSTALLER = ROOT / "ops/runner/install-awoooi-non110-runner-user-se
def test_register_helper_has_no_token_argv_path() -> None:
text = REGISTER.read_text(encoding="utf-8")
assert "--token" not in text
assert "runner_token_env_not_allowed" in text
assert "runner_token_in_argv=false" in text
assert "runner_token_echoed=false" in text
assert "raw_runner_registration_read=false" in text
def test_register_helper_rejects_runner_token_env(tmp_path: Path) -> None:
runner_dir = tmp_path / "runner"
runner_dir.mkdir(parents=True)
binary = runner_dir / "act_runner"
binary.write_text("#!/usr/bin/env bash\nexit 99\n", encoding="utf-8")
binary.chmod(0o755)
config = runner_dir / "config.yaml"
config.write_text("runner:\n capacity: 1\n", encoding="utf-8")
result = subprocess.run(
["bash", str(REGISTER), "--check"],
check=False,
env={
**os.environ,
"RUNNER_TOKEN": "fake-token-that-must-not-be-accepted",
"RUNNER_DIR": str(runner_dir),
"RUNNER_BINARY": str(binary),
"RUNNER_CONFIG": str(config),
"RUNNER_REGISTRATION": str(runner_dir / ".runner"),
},
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert result.returncode != 0
assert "runner_token_env_not_allowed" in result.stderr
assert "fake-token-that-must-not-be-accepted" not in result.stdout
assert "fake-token-that-must-not-be-accepted" not in result.stderr
def test_user_installer_has_no_token_argv_path() -> None:
wrapper = COMPAT_WRAPPER.read_text(encoding="utf-8")
installer = USER_SERVICE_INSTALLER.read_text(encoding="utf-8")
@@ -39,11 +71,10 @@ def test_register_helper_dry_run_requires_no_token(tmp_path: Path) -> None:
config.write_text("runner:\n capacity: 1\n", encoding="utf-8")
result = subprocess.run(
["bash", str(REGISTER)],
["bash", str(REGISTER), "--check"],
check=False,
env={
**os.environ,
"DRY_RUN": "1",
"RUNNER_DIR": str(runner_dir),
"RUNNER_BINARY": str(binary),
"RUNNER_CONFIG": str(config),