From f06e8731188a4be9b5ef6a82895c11af65dfa37c Mon Sep 17 00:00:00 2001 From: "l.kirchner" Date: Fri, 12 Jun 2026 15:04:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(runner):=20Review-Findings=2090/88/82/80=20?= =?UTF-8?q?(+70)=20=E2=80=94=20Re-Run=20ohne=20deploy.env,=20Sicherheitsmo?= =?UTF-8?q?dell=20dokumentiert,=20Requires=3Ddocker,=20strikte=20Input-Val?= =?UTF-8?q?idierung,=20argv=20statt=20Shell-Interpolation,=20Unit-H=C3=A4r?= =?UTF-8?q?tung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/runner-install.sh | 100 +++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 22 deletions(-) diff --git a/install/runner-install.sh b/install/runner-install.sh index 12d9364..de33790 100644 --- a/install/runner-install.sh +++ b/install/runner-install.sh @@ -4,11 +4,22 @@ # Allgemeiner, INSTANZWEITER Gitea-Actions-Runner ohne Deploy-Rechte: # - Node.js (für actions/checkout im Host-Mode) + git + rsync # - Docker (für Wegwerf-Test-Container in CI, z. B. pgvector) -# - act_runner als unprivilegierter User "runner" (Mitglied der docker-Gruppe, -# KEIN sudo, keine sudoers-Datei — bewusst, vgl. Kopfkommentar in ct/runner.sh) -# - systemd-Unit act-runner.service +# - act_runner als unprivilegierter User "runner" in der docker-Gruppe +# - systemd-Unit act-runner.service (Requires=docker.service) # -# Idempotent: erneuter Lauf überspringt vorhandene Registrierung/Pakete. +# SICHERHEITSMODELL (Review-Finding 88, bewusst akzeptiert & dokumentiert): +# docker-Gruppe == de-facto root IN DIESEM LXC. Da der Runner instanzweit +# ist, kann jedes Repo der Gitea-Instanz via Workflow den Runner-Container +# vollständig kontrollieren. Das ist hier akzeptiert, weil (a) alle Repos +# der Instanz vom selben Admin (Lutz) stammen — kein Multi-Tenant — und +# (b) dieser LXC GENAU DESHALB nichts besitzt: keine sudoers, keine +# Produktions-Mounts, keine Secrets außer dem (geshredderten) Reg-Token. +# Bei Öffnung der Instanz für Dritte: Docker rootless oder eigener Runner +# pro Vertrauenszone. NICHT auf LXCs mit Deploy-Rechten übertragen. +# +# Idempotent: erneuter Lauf (auch OHNE /root/runner.deploy.env) überspringt +# Vorhandenes und provisioniert nur nach — Registrierung braucht die env-Datei +# nur beim Erstlauf (Finding 90). set -euo pipefail @@ -17,19 +28,35 @@ source <(curl -fsSL "$LIB_URL/install.func") [[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; } +APP_USER="runner" +APP_HOME="/opt/runner" +RUNNER_DIR="$APP_HOME/data" + +# ── Konfiguration laden (optional bei Re-Run, Finding 90) ───────────────────── CONF="/root/runner.deploy.env" -[[ -f "$CONF" ]] || { msg_err "$CONF not found (host bootstrap incomplete)"; exit 1; } -set -a; . "$CONF"; set +a -: "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL}" -: "${RUNNER_TOKEN:?missing RUNNER_TOKEN}" +if [[ -f "$CONF" ]]; then + set -a; . "$CONF"; set +a +fi RUNNER_NAME="${RUNNER_NAME:-$(hostname)}" RUNNER_LABELS="${RUNNER_LABELS:-homelab:host}" RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" NODE_MAJOR="${NODE_MAJOR:-22}" -APP_USER="runner" -APP_HOME="/opt/runner" -RUNNER_DIR="$APP_HOME/data" +# ── Validierung (Finding 80): Werte landen in Shell-Fragmenten/systemd — +# strikte Zeichenklassen statt Vertrauen. Re-Runs ohne CONF validieren +# nur, was gesetzt ist; Registrierungs-Pflichtwerte prüft der Reg-Block. ── +valid_url() { [[ "$1" =~ ^https?://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ ]]; } +valid_token() { [[ "$1" =~ ^[A-Za-z0-9_-]{16,128}$ ]]; } +valid_token_word() { [[ "$1" =~ ^[A-Za-z0-9._:,-]+$ ]]; } + +[[ -z "${GITEA_INSTANCE_URL:-}" ]] || valid_url "$GITEA_INSTANCE_URL" \ + || { msg_err "GITEA_INSTANCE_URL ungültig: nur http(s)://host[:port]"; exit 1; } +[[ -z "${RUNNER_TOKEN:-}" ]] || valid_token "$RUNNER_TOKEN" \ + || { msg_err "RUNNER_TOKEN ungültig (erwartet 16–128 Zeichen [A-Za-z0-9_-])"; exit 1; } +valid_token_word "$RUNNER_NAME" || { msg_err "RUNNER_NAME enthält unzulässige Zeichen"; exit 1; } +valid_token_word "$RUNNER_LABELS" || { msg_err "RUNNER_LABELS enthält unzulässige Zeichen"; exit 1; } +[[ "$RUNNER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { msg_err "RUNNER_VERSION ungültig"; exit 1; } +[[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] || { msg_err "NODE_MAJOR ungültig"; exit 1; } run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } @@ -74,27 +101,47 @@ usermod -aG docker "$APP_USER" mkdir -p "$RUNNER_DIR" chown -R "$APP_USER:$APP_USER" "$APP_HOME" -# ── Registrierung (idempotent) ──────────────────────────────────────────────── +# ── Registrierung (idempotent; braucht CONF-Werte nur beim Erstlauf) ────────── if [[ ! -f "$RUNNER_DIR/.runner" ]]; then + : "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL (Erstlauf braucht /root/runner.deploy.env)}" + : "${RUNNER_TOKEN:?missing RUNNER_TOKEN (Erstlauf braucht /root/runner.deploy.env)}" msg_info "Registering runner '$RUNNER_NAME' [$RUNNER_LABELS] with $GITEA_INSTANCE_URL (instance scope)..." - run_user bash -c "cd '$RUNNER_DIR' && /usr/local/bin/act_runner register \ + # Werte sind oben strikt validiert (keine Quotes/Whitespace möglich) — + # Übergabe als argv an runuser, keine erneute Shell-Interpolation (vgl. Finding 76). + run_user /usr/local/bin/act_runner register \ --no-interactive \ - --instance '$GITEA_INSTANCE_URL' \ - --token '$RUNNER_TOKEN' \ - --name '$RUNNER_NAME' \ - --labels '$RUNNER_LABELS'" + --config /dev/null \ + --instance "$GITEA_INSTANCE_URL" \ + --token "$RUNNER_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + 2>&1 | sed "s#$RUNNER_TOKEN##g" || { msg_err "Registrierung fehlgeschlagen"; exit 1; } + # act_runner schreibt .runner ins CWD des Users — sicherstellen, dass es im RUNNER_DIR liegt + if [[ -f "$APP_HOME/.runner" && ! -f "$RUNNER_DIR/.runner" ]]; then + mv "$APP_HOME/.runner" "$RUNNER_DIR/.runner" + fi + [[ -f "$RUNNER_DIR/.runner" ]] || { msg_err ".runner nach Registrierung nicht gefunden"; exit 1; } chown -R "$APP_USER:$APP_USER" "$RUNNER_DIR" msg_ok "Runner registered" else msg_warn "Runner already registered (.runner exists), skipping" fi -# ── systemd-Unit (bewusst KEINE sudoers-Datei) ──────────────────────────────── +# ── SSH-Root-Login-Policy anwenden, falls vom Host-Script übergeben +# (Funktion existiert in install.func ab K-114/PR #5 — guarded Aufruf, +# damit dieser Branch vor und nach dem Merge funktioniert) ──────────────── +if declare -F configure_ssh_root_login >/dev/null 2>&1; then + configure_ssh_root_login +fi + +# ── systemd-Unit (bewusst KEINE sudoers-Datei; Findings 82 + 70) ────────────── cat >/etc/systemd/system/act-runner.service <"$CRED_FILE" <} Name/Label: $RUNNER_NAME [$RUNNER_LABELS] User: $APP_USER (docker-Gruppe, KEIN sudo) Workdir: $RUNNER_DIR Logs: journalctl -u act-runner -f Repos nutzen ihn mit: runs-on: ${RUNNER_LABELS%%:*} -Deploy-Jobs gehören NICHT hierher — die bleiben auf dem repo-scoped -nexus-Runner (Produktions-LXC, sudoers-Deploy-Rechte). Trennung beibehalten. + +SICHERHEITSMODELL: docker-Gruppe == de-facto root in DIESEM LXC; instanzweit +heißt: jedes Repo der Instanz kann den Runner-LXC kontrollieren. Akzeptiert, +weil Single-Admin-Instanz und dieser LXC nichts besitzt (keine sudoers, keine +Produktions-Mounts). Deploy-Jobs gehören NICHT hierher — die bleiben auf dem +repo-scoped nexus-Runner. Trennung beibehalten. EOF chmod 600 "$CRED_FILE" -shred -u "$CONF" 2>/dev/null || rm -f "$CONF" +if [[ -f "$CONF" ]]; then + shred -u "$CONF" 2>/dev/null || rm -f "$CONF" +fi apt_cleanup msg_ok "runner installation finished"