#!/bin/sh # Prueba el canal de evidencia de una instancia qorpa: que distinga HERMÉTICO de IMPURO, y que se # NIEGUE a opinar cuando no hay nada que medir. ADR 0015 D9 punto 3. # # ── POR QUÉ ESTE GUARDIÁN ─────────────────────────────────────────────────────────────────────── # De un binario ajeno no hay fuente que leer: lo único observable es lo que el kernel le NIEGA y # anota. Si ese canal se rompe —caps que se pierden al recompilar, un kernel sin audit, una # política que ya no deniega nada—, el síntoma es **un veredicto limpio**. O sea que el modo de # fallo de este instrumento es *parecer que todo está bien*, y por eso hace falta medirlo con algo # que TIENE que salir sucio. # # ── LAS TRES ASERCIONES ───────────────────────────────────────────────────────────────────────── # 1. una instancia que no toca nada prohibido → HERMÉTICO # 2. la MISMA instancia tocando /usr y /opt → IMPURO, y con esas rutas # 3. una instancia sin `seal_image` → se NIEGA a correr (no hay nada denegable, así que # un `denials=[]` sería el instrumento mintiendo) # La 2 es la que sostiene a la 1: sin ella, «HERMÉTICO» lo cumple igual un canal muerto. set -eu QORPA_ROOT="${HAMMER_QORPA_ROOT:-/var/lib/hammer/qorpa}" HAMMER="${TAKANA_BIN:-${HAMMER_BIN:-./target/release/takana}}" ID="${1:-arch-probe}" export HAMMER_QORPA_ROOT="$QORPA_ROOT" INST="$QORPA_ROOT/instances/$ID" [ -f "$INST/instance.toml" ] || { echo "no existe la instancia $ID en $QORPA_ROOT" >&2; exit 2; } AUDIT="${HARKAQ_BIN:-$HOME/.cache/harkaq}/harkaq-audit" if [ ! -x "$AUDIT" ]; then echo "falta $AUDIT — gcc -O1 -Wall -o $AUDIT scripts/harkaq/harkaq-audit.c" >&2; exit 2 fi # El `setcap` se pierde en CADA recompilación del lector, y ésa es la forma más probable de que el # canal muera sin que nadie se entere: por eso se comprueba acá y no se descubre en el veredicto. case "$(getcap "$AUDIT" 2>/dev/null)" in *cap_audit_read*) : ;; *) echo "el lector no tiene capabilities (se pierden al recompilar):" >&2 echo " sudo setcap cap_audit_read,cap_audit_control+ep $AUDIT" >&2; exit 2 ;; esac LOG=$(mktemp -d); trap 'rm -rf "$LOG"' EXIT # A fichero y no a `$( )`: la sustitución espera al PIPE, no al proceso, y un descendiente vivo la # cuelga para siempre (misma cicatriz que `pressure-vessel-probe.sh`). corre() { timeout -k 5 180 "$HAMMER" qorpa run "$ID" --evidence -- /bin/sh -c "$1" > "$2" 2>&1 &2; exit 1; } echo " HERMÉTICO ✓" echo "== 2. tocando la imagen sellada" corre 'touch /usr/INTRUSO 2>/dev/null; mkdir /opt/INTRUSO 2>/dev/null; true' "$LOG/sucio" grep -q "IMPURO" "$LOG/sucio" || { tail -8 "$LOG/sucio"; echo "FALLA: el canal no vio nada — un canal muerto se ve así" >&2; exit 1; } for ruta in "/usr" "/opt"; do grep -q " $ruta\$" "$LOG/sucio" || { tail -8 "$LOG/sucio"; echo "FALLA: falta la denegación de $ruta" >&2; exit 1; } done grep -E "fs\.(make_reg|make_dir)" "$LOG/sucio" | sed 's/^/ /' echo " IMPURO con las dos rutas ✓" echo "== 3. sin seal_image: tiene que NEGARSE, no dar limpio" sed -i 's/^seal_image = true/seal_image = false/' "$INST/instance.toml" corre 'true' "$LOG/sinsello" sed -i 's/^seal_image = false/seal_image = true/' "$INST/instance.toml" # Se busca la LÍNEA de veredicto (`evidencia: …`), no la palabra «denials»: el propio mensaje de # rechazo la contiene al explicar por qué se niega, y la primera versión de este guardián falló # contra su propia explicación. if grep -q "^evidencia:" "$LOG/sinsello"; then tail -5 "$LOG/sinsello"; echo "FALLA: opinó sobre una política que no puede denegar nada" >&2; exit 1 fi grep -q "seal_image" "$LOG/sinsello" || { tail -5 "$LOG/sinsello"; echo "FALLA: no explicó por qué se niega" >&2; exit 1; } echo " se niega y dice por qué ✓" echo "OK: el canal de evidencia distingue hermético de impuro, y no opina cuando no puede medir"