Files
takana/scripts/qorpa/evidence-probe.sh
T
Sergio 1c3e185167 takana: las variables de entorno leen las dos formas, gana la nueva
TAKANA_X con caída a HAMMER_X (ADR 0016). No es un sed: estas variables son
contrato de usuario —knobs del instalador, el entorno del worker, mirror-env.sh—
y viven en perfiles de shell y units FUERA del repo, así que un renombre duro no
falla ruidosamente: la variable no aparece, se toma el default y el build se
comporta distinto sin que nada lo diga.

Rust: takana_core::env::{var,var_os} toma el nombre canónico y deriva el viejo
cambiando el prefijo — se le pasa TAKANA_* para que un grep del nombre nuevo
encuentre todas las lecturas. 5 tests, con DOS controles negativos: sin ninguna
de las dos no hay valor, y un nombre sin prefijo no inventa una caída.
Migrados los 17 sitios directos y los indirectos que el grep no mostraba
(bases_de_mirror, las constantes de kernel_cmd, ROOT_ENV de qorpa, env_path de
recover). takana-recover lleva la caída inline: es un mini-binario que se copia
a /usr/sbin y no vale arrastrarle una dep entera por dos líneas.

Scripts: 30 lecturas pasan a default, manteniendo el
nombre INTERNO de la variable para no tocar sus 190 usos.

Y donde el script EXPORTA en vez de leer, se ponen LAS DOS (mirror-env.sh y el
fragmento in-VM de bootstrap): ahí el lector puede ser un binario viejo —worker
sin recompilar, el /usr/bin/hammer pinado del baseline— que sólo conoce HAMMER_*.
La caída sirve para lectores nuevos; los viejos necesitan que la vieja siga puesta.

Verificado de punta a punta con el binario, no sólo con unit tests: HAMMER_LAB
sigue surtiendo efecto, TAKANA_LAB hace lo mismo, y con las dos gana TAKANA_LAB.
605 tests en verde y el hash de zlib sigue en b3:dc363f26… , intacto.

Cambio de comportamiento que va aparte y hay que decir: el hostname por defecto
de una instalación nueva pasa de 'hammer' a 'takana' (sólo si no se fija ninguna
de las dos variables).

NO se tocan: las rutas /var/lib/hammer de sistemas instalados, el volid
HAMMER_LIVE del ISO, ni el namespace HARKAQ_*, que es de otro subsistema.
2026-09-09 19:14:49 +00:00

75 lines
4.4 KiB
Bash
Executable File

#!/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 </dev/null || true; }
sed -i 's/^seal_image = false/seal_image = true/' "$INST/instance.toml"
echo "== 1. sin tocar nada prohibido"
corre 'true' "$LOG/limpio"
grep -q "HERMÉTICO" "$LOG/limpio" || { tail -5 "$LOG/limpio"; echo "FALLA: no dio hermético" >&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"