fix(runner): add token-safe 110 drain registration guard
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m23s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-02 11:01:49 +08:00
parent dfe0a018c8
commit f426610d83
8 changed files with 519 additions and 10 deletions

View File

@@ -85,6 +85,33 @@ sanitized JSON不得收集 token、不得 workflow_dispatch、不得註冊或
不得操作 host / Docker / K8s。若 unauthenticated actions list API 回
`401 token is required`,這是 readback evidence不是要求 Codex 取得 token。
### 110 controlled CD drain lane registration
2026-07-02 起110 專用 `awoooi-cd-lane-drain.service` 的 runner registration
必須走 `register-awoooi-110-controlled-cd-lane-drain.sh`。此 helper 與 188
non-110 helper 採同一個安全契約:只允許 interactive TTY hidden prompt
不接受 `RUNNER_TOKEN` environment不使用 `--token` argv不列印 token不讀
`.runner` 內容。
先在 110 上做非 secret dry-run
```bash
ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh --check
```
正式註冊時必須先把 helper 放到 110再用 interactive TTY 執行;不要把 script
透過 stdin pipe 進去跑正式註冊,因為 token entry 需要 TTY
```bash
ssh -tt wooo@192.168.0.110 'cd /home/wooo/awoooi-cd-lane-drain && /path/to/register-awoooi-110-controlled-cd-lane-drain.sh'
```
若要在註冊成功後同一輪受控啟動 service必須明確加 `--enable-service`。helper
會先用 `REQUIRE_ACTIVE_SERVICE=0 check-awoooi-110-controlled-cd-lane-readiness.sh`
確認 config、ELF binary、registration metadata、systemd guardrails、
legacy fail-closed、root restore-source 與 host pressure 都通過後,才會
`systemctl enable --now awoooi-cd-lane-drain.service`
### 監控
```bash

View File

@@ -8,6 +8,7 @@ set -euo pipefail
TARGET_HOST_IP="${TARGET_HOST_IP:-192.168.0.110}"
CD_LANE_DRAIN_DIR="${CD_LANE_DRAIN_DIR:-/home/wooo/awoooi-cd-lane-drain}"
CD_LANE_DRAIN_SERVICE="${CD_LANE_DRAIN_SERVICE:-awoooi-cd-lane-drain.service}"
CD_LANE_DRAIN_USER="${CD_LANE_DRAIN_USER:-wooo}"
CD_LANE_DRAIN_BINARY="${CD_LANE_DRAIN_BINARY:-${CD_LANE_DRAIN_DIR}/awoooi_cd_lane_controlled}"
CD_LANE_DRAIN_CONFIG="${CD_LANE_DRAIN_CONFIG:-${CD_LANE_DRAIN_DIR}/config.yaml}"
CD_LANE_DRAIN_REGISTRATION_PATHS="${CD_LANE_DRAIN_REGISTRATION_PATHS:-${CD_LANE_DRAIN_DIR}/data/.runner ${CD_LANE_DRAIN_DIR}/.runner}"
@@ -29,6 +30,7 @@ CONFIG_READY=0
BINARY_READY=0
REGISTRATION_READY=0
SERVICE_READY=0
SERVICE_GUARDRAILS_READY=0
LEGACY_FAILCLOSED=0
PRIMARY_LANE_FAILCLOSED=0
ROOT_RESTORE_LEFT="unknown"
@@ -51,6 +53,26 @@ command_exists() {
command -v "$1" >/dev/null 2>&1
}
service_user_test() {
local flag="$1"
local path="$2"
if [ -z "$CD_LANE_DRAIN_USER" ] || [ "$CD_LANE_DRAIN_USER" = "$(id -un 2>/dev/null || true)" ]; then
test "$flag" "$path"
return $?
fi
if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then
if command_exists sudo; then
sudo -n -u "$CD_LANE_DRAIN_USER" test "$flag" "$path" 2>/dev/null
return $?
fi
if command_exists runuser; then
runuser -u "$CD_LANE_DRAIN_USER" -- test "$flag" "$path" 2>/dev/null
return $?
fi
fi
test "$flag" "$path"
}
host_ips() {
if command_exists ip; then
ip -o -4 addr show 2>/dev/null | awk '{print $4}' | sed 's#/.*##' | sort -u
@@ -192,14 +214,20 @@ check_host_selector() {
check_config() {
section "controlled lane config"
local capacity labels label name has_host=0 has_ubuntu=0 forbidden=0
local capacity labels label name has_host=0 has_ubuntu=0 forbidden=0 service_user_readable=0
if [ ! -r "$CD_LANE_DRAIN_CONFIG" ]; then
printf 'CD_LANE_CONFIG path=%s readable=0\n' "$CD_LANE_DRAIN_CONFIG"
blocker "controlled_cd_lane_config_missing"
return 0
fi
if service_user_test -r "$CD_LANE_DRAIN_CONFIG"; then
service_user_readable=1
else
blocker "controlled_cd_lane_config_not_readable_by_service_user:${CD_LANE_DRAIN_USER}"
fi
capacity="$(extract_runner_capacity "$CD_LANE_DRAIN_CONFIG" | head -1)"
printf 'CD_LANE_CONFIG path=%s readable=1 capacity=%s max_capacity=%s\n' "$CD_LANE_DRAIN_CONFIG" "${capacity:-missing}" "$MAX_CAPACITY"
printf 'CD_LANE_CONFIG path=%s readable=1 service_user=%s service_user_readable=%s capacity=%s max_capacity=%s\n' \
"$CD_LANE_DRAIN_CONFIG" "$CD_LANE_DRAIN_USER" "$service_user_readable" "${capacity:-missing}" "$MAX_CAPACITY"
if ! printf '%s' "${capacity:-}" | grep -Eq '^[0-9]+$'; then
blocker "controlled_cd_lane_capacity_missing"
elif [ "$capacity" -gt "$MAX_CAPACITY" ]; then
@@ -235,6 +263,7 @@ check_config() {
[ "$has_host" -eq 1 ] || blocker "controlled_cd_lane_awoooi_host_label_missing"
[ "$has_ubuntu" -eq 1 ] || blocker "controlled_cd_lane_awoooi_ubuntu_label_missing"
if [ "$has_host" -eq 1 ] && [ "$has_ubuntu" -eq 1 ] && [ "$forbidden" -eq 0 ] \
&& [ "$service_user_readable" -eq 1 ] \
&& printf '%s' "${capacity:-}" | grep -Eq '^[0-9]+$' && [ "$capacity" -le "$MAX_CAPACITY" ]; then
CONFIG_READY=1
fi
@@ -242,11 +271,17 @@ check_config() {
check_binary() {
section "controlled lane binary"
local kind
local kind service_user_executable=0
kind="$(file -b "$CD_LANE_DRAIN_BINARY" 2>/dev/null || echo missing)"
printf 'CD_LANE_BINARY path=%s executable=%s kind=%s\n' \
"$CD_LANE_DRAIN_BINARY" "$([ -x "$CD_LANE_DRAIN_BINARY" ] && [ -f "$CD_LANE_DRAIN_BINARY" ] && echo 1 || echo 0)" "$kind"
if [ -x "$CD_LANE_DRAIN_BINARY" ] && [ -f "$CD_LANE_DRAIN_BINARY" ] && grep -qi "ELF" <<<"$kind"; then
if service_user_test -x "$CD_LANE_DRAIN_BINARY"; then
service_user_executable=1
else
blocker "controlled_cd_lane_binary_not_executable_by_service_user:${CD_LANE_DRAIN_USER}"
fi
printf 'CD_LANE_BINARY path=%s executable=%s service_user=%s service_user_executable=%s kind=%s\n' \
"$CD_LANE_DRAIN_BINARY" "$([ -x "$CD_LANE_DRAIN_BINARY" ] && [ -f "$CD_LANE_DRAIN_BINARY" ] && echo 1 || echo 0)" \
"$CD_LANE_DRAIN_USER" "$service_user_executable" "$kind"
if [ -x "$CD_LANE_DRAIN_BINARY" ] && [ -f "$CD_LANE_DRAIN_BINARY" ] && [ "$service_user_executable" -eq 1 ] && grep -qi "ELF" <<<"$kind"; then
BINARY_READY=1
else
blocker "controlled_cd_lane_binary_not_ready"
@@ -255,13 +290,22 @@ check_binary() {
check_registration() {
section "controlled lane registration metadata"
local registration found=0 mode size
local registration found=0 mode size service_user_readable=0
for registration in $CD_LANE_DRAIN_REGISTRATION_PATHS; do
if [ -f "$registration" ] && [ -s "$registration" ]; then
service_user_readable=0
if service_user_test -r "$registration"; then
service_user_readable=1
else
blocker "controlled_cd_lane_registration_not_readable_by_service_user:${CD_LANE_DRAIN_USER}"
fi
mode="$(stat -c '%a' "$registration" 2>/dev/null || stat -f '%Lp' "$registration" 2>/dev/null || echo unknown)"
size="$(stat -c '%s' "$registration" 2>/dev/null || stat -f '%z' "$registration" 2>/dev/null || echo unknown)"
printf 'CD_LANE_REGISTRATION path=%s present=1 size_bytes=%s mode=%s content_read=false\n' "$registration" "$size" "$mode"
found=1
printf 'CD_LANE_REGISTRATION path=%s present=1 size_bytes=%s mode=%s service_user=%s service_user_readable=%s content_read=false\n' \
"$registration" "$size" "$mode" "$CD_LANE_DRAIN_USER" "$service_user_readable"
if [ "$service_user_readable" -eq 1 ]; then
found=1
fi
else
printf 'CD_LANE_REGISTRATION path=%s present=0 content_read=false\n' "$registration"
fi
@@ -333,6 +377,9 @@ check_service() {
fi
printf 'CD_LANE_SERVICE_GUARDRAILS unit=%s active=%s main_pid=%s limits=%s target_match=%s registration_condition=%s\n' \
"$CD_LANE_DRAIN_SERVICE" "${active:-unknown}" "${mainpid:-0}" "$limits_ok" "$target_ok" "$condition_ok"
if [ "$limits_ok" -eq 1 ] && [ "$target_ok" -eq 1 ] && [ "$condition_ok" -eq 1 ]; then
SERVICE_GUARDRAILS_READY=1
fi
if [ "$REQUIRE_ACTIVE_SERVICE" = "1" ]; then
if [ "${active:-}" != "active" ] || ! printf '%s' "${mainpid:-0}" | grep -Eq '^[1-9][0-9]*$'; then
blocker "controlled_cd_lane_service_not_active"
@@ -407,6 +454,7 @@ print_verdict() {
printf 'BINARY_READY=%s\n' "$BINARY_READY"
printf 'REGISTRATION_READY=%s\n' "$REGISTRATION_READY"
printf 'SERVICE_READY=%s\n' "$SERVICE_READY"
printf 'SERVICE_GUARDRAILS_READY=%s\n' "$SERVICE_GUARDRAILS_READY"
printf 'LEGACY_FAILCLOSED=%s\n' "$LEGACY_FAILCLOSED"
printf 'PRIMARY_LANE_FAILCLOSED=%s\n' "$PRIMARY_LANE_FAILCLOSED"
printf 'WARNING_COUNT=%s\n' "${#WARNINGS[@]}"
@@ -417,7 +465,9 @@ print_verdict() {
return 0
fi
printf 'AWOOOI_110_CONTROLLED_CD_LANE_READY=0\n'
if [ "$REGISTRATION_READY" -eq 0 ]; then
if [ "$CONFIG_READY" -eq 0 ] || [ "$BINARY_READY" -eq 0 ] || [ "$SERVICE_GUARDRAILS_READY" -eq 0 ]; then
printf 'safe_next_step=restore_awoooi_cd_lane_drain_staging_artifacts_then_rerun_this_verifier\n'
elif [ "$REGISTRATION_READY" -eq 0 ]; then
printf 'safe_next_step=restore_or_register_awoooi_cd_lane_drain_registration_without_printing_token_then_rerun_this_verifier\n'
elif [ "$ROOT_RESTORE_LEFT" != "0" ]; then
printf 'safe_next_step=quarantine_cd_lane_root_restore_sources_then_rerun_this_verifier\n'

View File

@@ -0,0 +1,215 @@
#!/usr/bin/env bash
set -euo pipefail
# Register the AWOOOI 110 controlled CD drain lane without putting the runner
# token in shell history, argv, environment, logs, or command output. Codex must
# not run the token entry path; it is for an interactive TTY on 110.
GITEA_INSTANCE="${GITEA_INSTANCE:-http://192.168.0.110:3001}"
RUNNER_NAME="${RUNNER_NAME:-awoooi-110-cd-lane-drain}"
RUNNER_LABELS="${RUNNER_LABELS:-awoooi-host:host,awoooi-ubuntu:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04}"
RUNNER_DIR="${RUNNER_DIR:-/home/wooo/awoooi-cd-lane-drain}"
RUNNER_WORKDIR="${RUNNER_WORKDIR:-${RUNNER_DIR}/data}"
RUNNER_BINARY="${RUNNER_BINARY:-${RUNNER_DIR}/awoooi_cd_lane_controlled}"
RUNNER_CONFIG="${RUNNER_CONFIG:-${RUNNER_DIR}/config.yaml}"
RUNNER_REGISTRATION="${RUNNER_REGISTRATION:-${RUNNER_WORKDIR}/.runner}"
SERVICE_NAME="${SERVICE_NAME:-awoooi-cd-lane-drain.service}"
READINESS_VERIFIER="${READINESS_VERIFIER:-/usr/local/bin/check-awoooi-110-controlled-cd-lane-readiness.sh}"
DRY_RUN=0
ENABLE_SERVICE_AFTER_REGISTRATION=0
usage() {
cat <<'EOF'
Usage:
ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh --check
ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh
ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh --enable-service
Remote registration:
Copy this script to 110 first, then run it from ssh -tt on the target host.
Do not pipe the script over stdin for the real registration path, because
token entry needs the interactive TTY.
Environment overrides:
GITEA_INSTANCE, RUNNER_NAME, RUNNER_LABELS, RUNNER_DIR, RUNNER_WORKDIR,
RUNNER_BINARY, RUNNER_CONFIG, RUNNER_REGISTRATION, SERVICE_NAME,
READINESS_VERIFIER
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
ALLOW_REREGISTER=1 is set
- only starts the service when --enable-service is explicit and the
non-secret readiness verifier passes with REQUIRE_ACTIVE_SERVICE=0
EOF
}
log() {
printf '%s\n' "$*"
}
die() {
printf 'ERROR %s\n' "$*" >&2
exit 1
}
metadata() {
log "secret_values_collected_by_codex=false"
log "runner_token_echoed=false"
log "runner_token_in_argv=false"
log "raw_runner_registration_read=false"
log "gitea_instance=${GITEA_INSTANCE}"
log "runner_name=${RUNNER_NAME}"
log "runner_labels=${RUNNER_LABELS}"
log "runner_dir=${RUNNER_DIR}"
log "runner_workdir=${RUNNER_WORKDIR}"
log "runner_binary=${RUNNER_BINARY}"
log "runner_config=${RUNNER_CONFIG}"
log "runner_registration=${RUNNER_REGISTRATION}"
log "service_name=${SERVICE_NAME}"
log "readiness_verifier=${READINESS_VERIFIER}"
log "enable_service_after_registration=${ENABLE_SERVICE_AFTER_REGISTRATION}"
}
preflight() {
[ -d "$RUNNER_DIR" ] || die "runner_dir_missing:${RUNNER_DIR}"
[ -d "$RUNNER_WORKDIR" ] || die "runner_workdir_missing:${RUNNER_WORKDIR}"
[ -x "$RUNNER_BINARY" ] || die "runner_binary_missing_or_not_executable:${RUNNER_BINARY}"
[ -r "$RUNNER_CONFIG" ] || die "runner_config_missing_or_not_readable:${RUNNER_CONFIG}"
if [ -s "$RUNNER_REGISTRATION" ] && [ "${ALLOW_REREGISTER:-0}" != "1" ]; then
log "runner_registration_present=1"
log "runner_registration_content_read=false"
if [ "$ENABLE_SERVICE_AFTER_REGISTRATION" = "1" ]; then
if [ "$DRY_RUN" = "1" ]; then
log "dry_run=true"
log "safe_next_step=enable_service_after_registration_with_verifier"
exit 0
fi
enable_service
exit $?
fi
log "safe_next_step=enable_awoooi_cd_lane_drain_service_and_rerun_110_readiness_verifier"
exit 0
fi
}
require_tty() {
if [ -t 0 ]; then
return 0
fi
{ : </dev/tty; } 2>/dev/null || die "interactive_tty_required"
}
read_token_from_tty() {
local prompt="Gitea runner token: "
set +x
HISTFILE=/dev/null
export HISTFILE
if { : </dev/tty; } 2>/dev/null; then
IFS= read -r -s -p "$prompt" RUNNER_TOKEN </dev/tty
printf '\n' >/dev/tty
else
IFS= read -r -s -p "$prompt" RUNNER_TOKEN
printf '\n'
fi
[ -n "${RUNNER_TOKEN:-}" ] || die "runner_token_empty"
}
register_runner() {
cd "$RUNNER_WORKDIR"
set +x
printf '%s\n' "$RUNNER_TOKEN" | "$RUNNER_BINARY" register \
--instance "$GITEA_INSTANCE" \
--name "$RUNNER_NAME" \
--labels "$RUNNER_LABELS" \
--config "$RUNNER_CONFIG"
unset RUNNER_TOKEN
}
postcheck_registration() {
[ -s "$RUNNER_REGISTRATION" ] || die "runner_registration_not_created:${RUNNER_REGISTRATION}"
chmod go-rwx "$RUNNER_REGISTRATION" 2>/dev/null || true
log "runner_registration_present=1"
log "runner_registration_content_read=false"
}
verifier_available() {
[ -x "$READINESS_VERIFIER" ]
}
pre_enable_guard() {
if ! verifier_available; then
die "readiness_verifier_missing_or_not_executable:${READINESS_VERIFIER}"
fi
log "pre_enable_verifier_start=1"
REQUIRE_ACTIVE_SERVICE=0 "$READINESS_VERIFIER"
}
enable_service() {
pre_enable_guard
log "service_enable_requested=1"
sudo -n systemctl daemon-reload
sudo -n systemctl enable --now "$SERVICE_NAME"
log "service_enable_attempted=1"
if verifier_available; then
"$READINESS_VERIFIER"
else
log "safe_next_step=rerun_110_controlled_cd_lane_readiness_verifier"
fi
}
main() {
while [ "$#" -gt 0 ]; do
case "$1" in
--help|-h)
usage
exit 0
;;
--check|--dry-run)
DRY_RUN=1
;;
--enable-service)
ENABLE_SERVICE_AFTER_REGISTRATION=1
;;
*)
usage >&2
exit 2
;;
esac
shift
done
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
if [ "$DRY_RUN" = "1" ]; then
log "dry_run=true"
log "safe_next_step=run_this_script_from_interactive_tty_without_capturing_token"
exit 0
fi
require_tty
read_token_from_tty
register_runner
postcheck_registration
if [ "$ENABLE_SERVICE_AFTER_REGISTRATION" = "1" ]; then
enable_service
else
log "safe_next_step=enable_awoooi_cd_lane_drain_service_and_rerun_110_readiness_verifier"
fi
}
main "$@"

View File

@@ -371,12 +371,16 @@ def test_non110_runner_keepalive_sources_stay_on_controlled_runtime_profile() ->
expected_sources = [
"ops/runner/check-awoooi-non110-runner-readiness.sh)",
"ops/runner/install-awoooi-non110-runner-user-service.sh)",
"ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh)",
"ops/runner/test_check_awoooi_non110_runner_readiness.py)",
"ops/runner/test_install_awoooi_non110_runner_user_service.py)",
"ops/runner/test_register_awoooi_110_controlled_cd_lane_drain.py)",
"../../ops/runner/check-awoooi-non110-runner-readiness.sh",
"../../ops/runner/install-awoooi-non110-runner-user-service.sh",
"../../ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh",
"../../ops/runner/test_check_awoooi_non110_runner_readiness.py",
"../../ops/runner/test_install_awoooi_non110_runner_user_service.py",
"../../ops/runner/test_register_awoooi_110_controlled_cd_lane_drain.py",
]
for source in expected_sources:
assert source in text
@@ -755,7 +759,9 @@ def test_post_start_recovery_verifiers_stay_on_controlled_runtime_profile() -> N
"ops/reboot-recovery/full-stack-cold-start-baseline.yml)",
"ops/runner/awoooi-cd-lane-drain.service)",
"ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh)",
"ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh)",
"ops/runner/test_check_awoooi_110_controlled_cd_lane_readiness.py)",
"ops/runner/test_register_awoooi_110_controlled_cd_lane_drain.py)",
"scripts/backup/gitea-repo-bundle-backup.sh)",
"scripts/ops/backup-health-textfile-exporter.py)",
"scripts/ops/docker-disk-pressure-retention-cleanup.py)",
@@ -795,6 +801,7 @@ def test_post_start_recovery_verifiers_stay_on_controlled_runtime_profile() -> N
"../../ops/monitoring/alerts.yml",
"../../ops/reboot-recovery/full-stack-cold-start-baseline.yml",
"../../ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh",
"../../ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh",
"../../scripts/reboot-recovery/deploy-to-110.sh",
"../../scripts/reboot-recovery/enforce-110-runner-failclosed.sh",
"../../scripts/reboot-recovery/recover-110-control-path-and-harbor-local.sh",
@@ -823,6 +830,7 @@ def test_post_start_recovery_verifiers_stay_on_controlled_runtime_profile() -> N
"../../scripts/reboot-recovery/tests/test_recover_110_control_path_and_harbor_local.py",
"../../scripts/reboot-recovery/tests/test_momo_source_arrival_gate.py",
"../../ops/runner/test_check_awoooi_110_controlled_cd_lane_readiness.py",
"../../ops/runner/test_register_awoooi_110_controlled_cd_lane_drain.py",
]
for source in expected_sources:
assert source in text

View File

@@ -218,9 +218,13 @@ def test_110_controlled_cd_lane_ready_without_printing_registration_content(
assert result.returncode == 0, result.stdout + result.stderr
assert "AWOOOI_110_CONTROLLED_CD_LANE_READY=1" in result.stdout
assert "SERVICE_GUARDRAILS_READY=1" in result.stdout
assert "runner_token_read=false" in result.stdout
assert "raw_runner_registration_read=false" in result.stdout
assert "content_read=false" in result.stdout
assert "service_user=wooo" in result.stdout
assert "service_user_readable=1" in result.stdout
assert "service_user_executable=1" in result.stdout
assert "secret-token-like-content" not in result.stdout
assert "CD_LANE_SERVICE_GUARDRAILS" in result.stdout
assert "registration_condition=1" in result.stdout
@@ -256,12 +260,46 @@ def test_110_controlled_cd_lane_requires_registration_condition(tmp_path: Path)
assert result.returncode == 1
assert "BLOCKER controlled_cd_lane_service_registration_condition_missing" in result.stdout
assert "SERVICE_GUARDRAILS_READY=0" in result.stdout
assert "AWOOOI_110_CONTROLLED_CD_LANE_READY=0" in result.stdout
def test_110_controlled_cd_lane_prioritizes_staging_before_registration(
tmp_path: Path,
) -> None:
result = _run_verifier(
tmp_path,
registration_present=False,
include_registration_condition=False,
)
assert result.returncode == 1
assert "BLOCKER controlled_cd_lane_registration_missing" in result.stdout
assert "BLOCKER controlled_cd_lane_service_registration_condition_missing" in result.stdout
assert "SERVICE_GUARDRAILS_READY=0" in result.stdout
assert (
"safe_next_step=restore_awoooi_cd_lane_drain_staging_artifacts_then_rerun_this_verifier"
in result.stdout
)
def test_110_controlled_cd_lane_blocks_active_legacy_runner(tmp_path: Path) -> None:
result = _run_verifier(tmp_path, legacy_active=True)
assert result.returncode == 1
assert "BLOCKER legacy_runner_unit_not_failclosed:gitea-act-runner-host.service" in result.stdout
assert "AWOOOI_110_CONTROLLED_CD_LANE_READY=0" in result.stdout
def test_110_controlled_cd_lane_verifier_checks_service_user_access() -> None:
text = VERIFIER.read_text(encoding="utf-8")
assert "CD_LANE_DRAIN_USER" in text
assert "service_user_test -r \"$CD_LANE_DRAIN_CONFIG\"" in text
assert "service_user_test -x \"$CD_LANE_DRAIN_BINARY\"" in text
assert "service_user_test -r \"$registration\"" in text
assert "SERVICE_GUARDRAILS_READY" in text
assert "restore_awoooi_cd_lane_drain_staging_artifacts_then_rerun_this_verifier" in text
assert "controlled_cd_lane_config_not_readable_by_service_user" in text
assert "controlled_cd_lane_binary_not_executable_by_service_user" in text
assert "controlled_cd_lane_registration_not_readable_by_service_user" in text

View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
REGISTER = ROOT / "ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh"
def _make_lane(tmp_path: Path) -> dict[str, str]:
lane_dir = tmp_path / "awoooi-cd-lane-drain"
workdir = lane_dir / "data"
workdir.mkdir(parents=True)
binary = lane_dir / "awoooi_cd_lane_controlled"
binary.write_text("#!/usr/bin/env bash\nexit 99\n", encoding="utf-8")
binary.chmod(0o755)
config = lane_dir / "config.yaml"
config.write_text(
"\n".join(
[
"runner:",
" capacity: 1",
" labels:",
' - "awoooi-host:host"',
' - "awoooi-ubuntu:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"',
"",
]
),
encoding="utf-8",
)
return {
"RUNNER_DIR": str(lane_dir),
"RUNNER_WORKDIR": str(workdir),
"RUNNER_BINARY": str(binary),
"RUNNER_CONFIG": str(config),
"RUNNER_REGISTRATION": str(workdir / ".runner"),
"READINESS_VERIFIER": str(tmp_path / "missing-verifier"),
}
def test_110_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
assert "Do not pipe the script over stdin" in text
assert "REQUIRE_ACTIVE_SERVICE=0" in text
def test_110_register_helper_rejects_runner_token_env(tmp_path: Path) -> None:
env = {**os.environ, **_make_lane(tmp_path), "RUNNER_TOKEN": "fake-token-must-not-leak"}
result = subprocess.run(
["bash", str(REGISTER), "--check"],
check=False,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert result.returncode != 0
assert "runner_token_env_not_allowed" in result.stderr
assert "fake-token-must-not-leak" not in result.stdout
assert "fake-token-must-not-leak" not in result.stderr
def test_110_register_helper_dry_run_requires_no_token(tmp_path: Path) -> None:
result = subprocess.run(
["bash", str(REGISTER), "--check"],
check=False,
env={**os.environ, **_make_lane(tmp_path)},
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert result.returncode == 0
assert "dry_run=true" in result.stdout
assert "safe_next_step=run_this_script_from_interactive_tty_without_capturing_token" in result.stdout
assert "runner_token_in_argv=false" in result.stdout
assert "--token" not in result.stdout
def test_110_register_helper_existing_registration_does_not_read_content(tmp_path: Path) -> None:
env = {**os.environ, **_make_lane(tmp_path)}
Path(env["RUNNER_REGISTRATION"]).write_text("secret-like-registration-content\n", encoding="utf-8")
result = subprocess.run(
["bash", str(REGISTER), "--check"],
check=False,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert result.returncode == 0
assert "runner_registration_present=1" in result.stdout
assert "runner_registration_content_read=false" in result.stdout
assert "secret-like-registration-content" not in result.stdout
assert "secret-like-registration-content" not in result.stderr
assert "safe_next_step=enable_awoooi_cd_lane_drain_service_and_rerun_110_readiness_verifier" in result.stdout
def test_110_register_helper_enable_service_dry_run_does_not_call_systemctl(tmp_path: Path) -> None:
fake_bin = tmp_path / "bin"
fake_bin.mkdir()
systemctl = fake_bin / "systemctl"
systemctl.write_text(
"#!/usr/bin/env bash\nprintf 'systemctl_must_not_run\\n' >&2\nexit 44\n",
encoding="utf-8",
)
systemctl.chmod(0o755)
env = {
**os.environ,
**_make_lane(tmp_path),
"PATH": f"{fake_bin}:{os.environ['PATH']}",
}
Path(env["RUNNER_REGISTRATION"]).write_text("secret-like-registration-content\n", encoding="utf-8")
result = subprocess.run(
["bash", str(REGISTER), "--check", "--enable-service"],
check=False,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert result.returncode == 0
assert "dry_run=true" in result.stdout
assert "safe_next_step=enable_service_after_registration_with_verifier" in result.stdout
assert "systemctl_must_not_run" not in result.stderr