From bbfd85e47235b23e789dcf34fb70154ee4c4203a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 29 Aug 2026 09:51:04 +0000 Subject: [PATCH] rq-historical: add READ-ONLY code/hashes/code-list (allowlist) for RQ Phase 13.5 --- .../control-plane/cp2a2_2b/rq-historical | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 red-queen-architecture/control-plane/cp2a2_2b/rq-historical diff --git a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical new file mode 100644 index 0000000..0266e33 --- /dev/null +++ b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical @@ -0,0 +1,132 @@ +#!/bin/bash +# ============================================================================ +# rq-historical — eng begrenzter root-Wrapper fuer Red Queen (rq-host) +# RED-QUEEN-VPS-HOST-ACCESS + PHASE-13.5-READONLY | Rain Ocampo (Hermes) | 2026-08-29 +# +# EINZIGER ZWECK: kontrollierter READ-ONLY-Zugriff + freigegebener Backfill- +# Start ausschliesslich auf den Historical-Service. Least-Privilege. +# +# SICHERHEIT: +# - Container-/Skript-Namen sind FEST im Script (kein Benutzer-Argument +# fuer Container/Skript/Pfad). +# - Alle Subkommandos akzeptieren NUR strikt validierte, vollstaendig +# konsumierte Argumente; unbekannte/ueberzaehlige Argumente -> ERR. +# - code/hashes/code-list nutzen eine FESTE Allowlist: User uebergibt nur +# einen Key, der intern auf einen festen Containerpfad gemappt wird. +# KEIN frei durchgereichter Pfad, KEIN Shell-String aus User-Input. +# - Argumente werden als argv-Array an `docker exec` uebergeben +# (KEINE Shell-Interpolation von Benutzereingaben). +# - KEIN sudo ALL / NOPASSWD ALL / keine Root-Shell / kein docker run. +# +# AUFRUF (ueber /etc/sudoers.d/rq-historical, NUR als root): +# sudo rq-historical ps +# sudo rq-historical inspect +# sudo rq-historical logs [--tail N] +# sudo rq-historical run [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--limit-days N] +# sudo rq-historical code +# sudo rq-historical hashes +# sudo rq-historical code-list +# ============================================================================ +set -euo pipefail + +CONTAINER=historical-service + +# ------------------------------------------------------------------ Allowlist +# FESTE Mapping-Tabelle (Key -> Containerpfad). Read-only, hart im Script. +# Keys NICHT benutzerdefiniert erweiterbar. +# NOTE: Bei assign-assoc-array die Assignments OHNE Leerzeichen um '=' schreiben, +# sonst Syntaxfehler. (Korrektur unten: einzeilig mit declare -A init.) +declare -A CODE_FILES=( + [runner]="/app/app/run_backfill_year.py" + [backfill]="/app/app/backfill.py" + [pipeline]="/app/app/pipeline_v2.py" + [repository]="/app/app/persistence/repository.py" + [provenance]="/app/app/trust/provenance.py" + [trust]="/app/app/trust/trust.py" + [sanity]="/app/app/trust/sanity.py" + [trust_gate]="/app/app/trust/trust_gate.py" +) + +# Relevanter Pfad fuer den 'run'-Subbefehl (Backfill-Entrypoint) +SCRIPT=/app/app/run_backfill_year.py + +# ---------------------------------------------------------------- Validierung +die() { echo "ERR: $*" >&2; exit 2; } +req_ok() { + docker inspect -f '{{.State.Running}}' "$CONTAINER" >/dev/null 2>&1 \ + || die "Container '$CONTAINER' nicht vorhanden oder nicht am Laufen" +} + +# -------------------------------------------------------------------- dispatch +SUBCMD="${1:-}" +shift || true + +case "$SUBCMD" in + ps) + [ $# -eq 0 ] || die "unerlaubtes Argument 'ps $*'" + exec docker ps + ;; + inspect) + req_ok + [ $# -eq 0 ] || die "unerlaubtes Argument 'inspect $*'" + exec docker inspect "$CONTAINER" + ;; + logs) + req_ok + tail_n=200 + while [ $# -gt 0 ]; do + case "$1" in + --tail) + [ $# -ge 2 ] || die "--tail braucht Wert" + [[ "$2" =~ ^[0-9]+$ ]] || die "ungueltiges --tail" + tail_n="$2"; shift 2 ;; + *) die "unerlaubtes Argument 'logs $1'" ;; + esac + done + exec docker logs --tail "$tail_n" "$CONTAINER" + ;; + run) + req_ok + declare -a RUN=() + while [ $# -gt 0 ]; do + case "$1" in + --start) [[ "${2:-}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || die "ungueltiges --start"; RUN+=("--start" "$2"); shift 2 ;; + --end) [[ "${2:-}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || die "ungueltiges --end"; RUN+=("--end" "$2"); shift 2 ;; + --limit-days) [[ "${2:-}" =~ ^[0-9]+$ ]] || die "ungueltiges --limit-days"; RUN+=("--limit-days" "$2"); shift 2 ;; + *) die "unerlaubtes Argument 'run $1'" ;; + esac + done + echo "[rq-historical] Start: docker exec $CONTAINER python $SCRIPT ${RUN[*]}" >&2 + exec docker exec "$CONTAINER" python "$SCRIPT" "${RUN[@]}" + ;; + code) + # Fester Allowlist-Key -> fester Containerpfad. KEIN frei gereichter Pfad. + req_ok + [ $# -eq 1 ] || die "code braucht genau EINEN Key (siehe code-list)" + key="$1" + path="${CODE_FILES[$key]:-}" + [ -n "$path" ] || die "unbekannter Key 'code $key' (siehe code-list)" + exec docker exec "$CONTAINER" cat "$path" + ;; + hashes) + # SHA256 der festen Allowlist-Dateien aus dem laufenden Container. + req_ok + [ $# -eq 0 ] || die "unerlaubtes Argument 'hashes $*'" + # Feste, vollstaendig konsumierte Dateiliste -> argv-Array (keine Interpolation). + declare -a PATHS=() + for k in "${!CODE_FILES[@]}"; do PATHS+=("${CODE_FILES[$k]}"); done + exec docker exec "$CONTAINER" sha256sum "${PATHS[@]}" + ;; + code-list) + # Nur die verfuegbaren fixen Keys + Containerpfade anzeigen. Kein FS-Zugriff. + req_ok + [ $# -eq 0 ] || die "unerlaubtes Argument 'code-list $*'" + echo "Verfuegbare READ-ONLY-Code-Keys (historical-service):" + for k in "${!CODE_FILES[@]}"; do + printf ' %-11s -> %s\n' "$k" "${CODE_FILES[$k]}" + done + ;; + *) + die "Aufruf: rq-historical {ps|inspect|logs [--tail N]|run [--start --end --limit-days]|code |hashes|code-list}" + ;; +esac