Saltar al contenido principal
Version: 1.0.0

Las protecciones, y por qué la v1 no aplica cambios

Una política de endurecimiento puede dejarle fuera de la máquina que está endureciendo. bitenforcer mide ese riesgo contra la sesión en la que realmente se está ejecutando, y lo reporta antes de que se aplique nada. Esas mismas protecciones son la razón por la que la v1 no aplica absolutamente nada: siete revisores adversarios independientes llegaron cada uno con una forma de bloqueo que ellas no reconocían.

Esta página cubre las protecciones, cómo poner una política bajo su control en CI, y el relato completo de por qué la aplicación efectiva queda fuera del alcance de esta versión. Para lo que la herramienta hace hoy, consulte la visión general de bitenforcer.

Las protecciones contra bloqueo de acceso​

Dieciocho protecciones, cada una cubriendo una forma en que una política de endurecimiento puede dejarle fuera de su propia máquina. Puede listarlas con bitenforcer guards:

ssh-operator-lockout the SSH change would lock out the account you are running as
(FR) la modification SSH verrouillerait le compte avec lequel
vous êtes connecté
--acknowledge ssh-operator-lockout

firewall-ipv6-unhandled IPv4 would be locked down while IPv6 stays open (silent bypass)
(FR) IPv4 serait verrouillé alors qu'IPv6 resterait ouvert
--acknowledge firewall-ipv6-unhandled

Conjunto completo: session-undetermined, ssh-no-auth-method, ssh-operator-lockout, ssh-listen-address-unusable, ssh-listen-address-excludes-session, ssh-crypto-narrowing, firewall-session-drop, firewall-deny-before-allow, firewall-ipv6-unhandled, selinux-relabel-required, selinux-port-context-loss, selinux-access-loss, services-remote-access-loss, users-operator-lockout, kernel-remote-access-loss, kernel-boot-risk, security-regression, unreviewed-change.

Importan dos reglas de diseño:

No existe ningún --force global

Una protección se desactiva únicamente nombrándola — --acknowledge <guard-id> — y reconocer una protección deja todas las demás armadas. unreviewed-change no puede reconocerse como clase en absoluto: el token nombra un módulo, un tipo y una clave, y el propio rechazo imprime el token exacto que hay que usar.

Una sesión que no pudo medirse no es reconocible para ninguna ejecución que toque el acceso remoto. A un operador al que no se puede localizar no se le puede proteger, y fingir lo contrario es como acaba la gente bloqueada fuera.

Poner una política bajo control en CI​

--guards=blocking convierte cualquier hallazgo no reconocido en una salida distinta de cero:

bitenforcer apply --config policy.yaml --dry-run --guards=blocking
# exit 1 when any guard fires — fails the pipeline

Así es como se impide que una línea base de endurecimiento que cortaría el acceso remoto llegue siquiera a una ventana de cambio.

Por qué la aplicación efectiva no está en v1​

La ruta de aplicación efectiva se construyó. Después se revisó siete veces, de forma adversaria, por siete revisores independientes en hosts reales. Todos y cada uno de ellos, o bien se bloquearon a sí mismos el acceso al host de pruebas, o bien falsificaron la confirmación que se suponía que debía detectar eso — diez formas distintas de bloqueo, varias de ellas reportando éxito y código de salida 0 en ese mismo momento.

Las formas nunca fueron las mismas dos veces: los bytes de sshd_config; un plan de firewall; un drop-in de endurecimiento de systemd que no es un "stop" y por eso nunca llegó al evaluador; un cambio de propietario/grupo en authorized_keys; un sysctl fuera de los dos patrones que un evaluador reconocía; un método de autenticación "superviviente" que la cuenta solo-con-clave del operador no podía usar realmente; una confirmación aceptada por loopback; una prueba que no podía realizarse interpretada como un fallo.

Cada oleada cerró la forma que se le mostró, y el siguiente revisor llegó con una nueva. Esa es la firma de un problema de mundo abierto: "enumere todas las formas en que una política podría dejar este host inalcanzable" no tiene último elemento, y cualquier versión de las protecciones es una lista de los ataques que alguien ya se había imaginado.

Se construyó una reversión dead-man precisamente porque no necesita enumeración alguna — sea lo que sea lo que rompió el host, la confirmación no llega y el cambio se deshace. Es el modelo correcto y el código se conserva. Tampoco sobrevivió a la revisión, y su último hallazgo puso fin a la discusión:

La maquinaria de recuperación se convirtió en una escalada local de privilegios a root. La reversión dead-man restauró un permiso a través de un enlace simbólico que una usuaria sin privilegios había plantado en su propio ~/.ssh, llevando /etc/shadow de 0640 a 0777. El manifiesto de la ejecución registró "4 restored, 0 failed".

Distribuir eso habría puesto una escalada de privilegios a root en todas las máquinas de nuestros clientes, entregada por la propia red de seguridad. Así que la aplicación efectiva queda fuera del alcance de v1.

Qué se demostró, y cómo​

Una huella de todo el sistema de ficheros — ruta, modo, propietario, tamaño y SHA-256 de cada fichero bajo /etc, /var/lib y los árboles de systemd — tomada antes y después de una ejecución de todos los módulos como root, en un contenedor con sshd, nft, iptables y getenforce reales, es idéntica. Tanto para apply (que se niega) como para apply --dry-run (que reporta). Eso son 39 aserciones en la prueba de superficie de extremo a extremo del repositorio.

Después se hicieron deliberadamente mal dos compilaciones y se pasaron por el mismo script, porque una prueba que nadie ha intentado romper es decoración:

  • declaración de alcance eliminada → 6 fallos, incluido ROOT apply CHANGED the filesystem;
  • el comando deadman retenido vuelto a registrar → 7 fallos, cada uno nombrando el comando que regresó y el estado que creó.

Qué deja fuera v1 deliberadamente​

ComandoEn v1Por qué
validate✅Rechaza una política inválida — y rechaza claves que nada implementa.
apply --dry-run✅La comprobación previa.
apply --dry-run --guards=blocking✅Control de políticas para CI.
guards, session, version✅De solo lectura.
generate✅De solo lectura excepto con --output-file, que escribe — vea la advertencia más abajo.
apply (con aplicación efectiva)❌Declara el alcance de la versión en EN + FR, nombra lo que sí puede ejecutar y sale con código distinto de cero.
rollback, confirm❌Nada en v1 puede crear una instantánea ni armar una reversión, así que ambos solo podrían responder "aquí no hay nada" — lo que aun así anuncia una capacidad.
deadman❌Retenido. Restaurar es modificar el host, sobre un directorio de estado proporcionado por el operador — aquí es donde vivía la escalada de privilegios.
--output-file escribe en el host y sobrescribirá un fichero existente

--output-file es una opción global, así que también se aplica a generate. Cuando está presente, generate crea el directorio padre (0750) y escribe el fichero (0640) — sin comprobación de existencia, sin O_EXCL y sin confirmación. Apuntarla a una ruta que ya existe reemplaza ese fichero.

Esta página listaba antes generate entre los comandos que son "todos de solo lectura", y ahí estaba el peligro: fiándose de esa frase, un operador apuntó --output-file a una ruta bajo /etc y sobrescribió la configuración que ya había allí. Todo lo demás de la tabla es genuinamente de solo lectura; esta opción es la excepción, y se señala aquí en lugar de corregirse en silencio.

Ejecutar apply sin --dry-run no es una operación nula silenciosa. Le dice la verdad y sale con código distinto de cero:

$ bitenforcer apply --config policy.yaml
Error: apply: this release does not change hosts, and nothing on this one was changed.

bitenforcer v1 ships policy validation and pre-flight. Enforcement — writing
sshd_config, firewall rules, sysctls, accounts and unit state — is deliberately not
part of it, and no flag turns it on. It is held back because seven independent
adversarial reviews of that path each locked themselves out of a test host or forged
the confirmation meant to catch it.

What this build does, and does well:
bitenforcer apply --config policy.yaml --dry-run
bitenforcer apply --config policy.yaml --dry-run --guards=blocking
bitenforcer validate --config policy.yaml

(FR) Cette version ne modifie aucun hôte et rien n'a été modifié ici. […]

No se ha borrado nada del código base. El motor, las protecciones, el código de instantánea/reversión y la maquinaria dead-man permanecen todos, con sus pruebas. Son la base de la próxima versión. Simplemente no están conectados a nada que pueda modificar su host.

Cuándo vuelve la aplicación efectiva​

El listón está escrito para que no se pueda negociar a la baja más adelante:

Un revisor independiente debe fracasar al falsificar una confirmación Y fracasar al sobrevivir a una reversión, en un host real, dos veces seguidas.

Dos pasadas adversarias limpias y consecutivas por parte de un revisor que no escribió la corrección — no "las formas que ahora reconocemos están cubiertas", una afirmación que ya se ha hecho y se ha falsado siete veces.

Próximos pasos​

¿Te resultó útil esta página?