1 puntos por GN⁺ 2023-10-30 | 1 comentarios | Compartir por WhatsApp
  • Se demostró que la ISO mínima de instalación de NixOS puede reconstruirse de forma independiente y quedar idéntica bit por bit a la distribuida por Hydra, lo que permite verificar si los binarios distribuidos coinciden con el código fuente
  • Esta verificación reproduce no solo los paquetes incluidos en la ISO, sino también el propio proceso de generación de la ISO, por lo que cubre un alcance mayor que la simple reproducibilidad de paquetes
  • La reconstrucción comenzó desde un appliance de NixOS 20.03 para VirtualBox, usando la revisión 63678e9f3d3a de nixpkgs, y con --option substitute false para desactivar la dependencia de la caché binaria
  • Si la OVA de 2020 o el git descargado hubieran tenido una puerta trasera sofisticada, seguirían siendo un vector de ataque, así que aún falta una verificación basada en un sistema totalmente bootstrappeado
  • La reconstrucción de la ISO mínima es un hito importante, pero los siguientes retos son eliminar soluciones temporales, reproducir más medios de instalación, contar con infraestructura de reconstrucción independiente periódica y herramientas de prueba de build

Reproducibilidad verificada en la ISO mínima

  • Se volvió a construir de forma independiente el build de la ISO nixos-minimal publicado por Hydra y se obtuvo un resultado idéntico bit por bit
  • El alcance de la reproducibilidad se divide en dos ejes
    • Todos los paquetes que entran en la ISO
    • El propio proceso de build que crea la ISO
  • También se construyeron los paquetes necesarios para generar la ISO aunque no queden incluidos dentro de ella, y sin depender de binarios en caché
  • Los builds reproducibles ofrecen una ruta de confianza para comprobar si los binarios distribuidos son fieles al código fuente y si no fueron alterados en pipelines de build como Hydra

Procedimiento de reconstrucción y limitaciones

  • La reconstrucción se realizó partiendo de un appliance nuevo de NixOS 20.03 para VirtualBox
    • Se asignó suficiente CPU y memoria, y se expandió el disco a unos 65GB
    • Después de instalar git, se clonó nixpkgs y se hizo checkout de la revisión 63678e9f3d3a
    • Con --option substitute false, los elementos necesarios se construyeron en la máquina local en lugar de obtenerse desde la caché binaria
  • El procedimiento incluye medidas temporales para rodear problemas conocidos
  • Siguen existiendo limitaciones desde la perspectiva de confianza en la cadena de suministro

Diferencia entre el anuncio de 2021 y este resultado

  • En 2021 hubo un anuncio de que la ISO mínima era 100% reproducible, pero en ese momento solo se habían reproducido individualmente los paquetes necesarios para el build de la ISO, y aún quedaban diferencias en la reconstrucción real de la ISO
    • La causa eran problemas pendientes con la caché de Hydra y la forma de generar la ISO
    • Después, mientras se corregían esos problemas, aparecieron regresiones como un problema upstream de Python 3.10, y recién esta semana volvió a haber un estado en el que se pudo verificar toda la cadena
  • Los siguientes pasos incluyen eliminar las soluciones temporales, reproducir más paquetes y reproducir otros medios de instalación como la ISO de Gnome
  • También se necesita infraestructura periódica de reconstrucción independiente y herramientas para compartir y consumir pruebas de build, como trustix

1 comentarios

 
GN⁺ 2023-10-30
Opiniones de Hacker News
  • Haber reconstruido la ISO mínima desde el código fuente es un hito impresionante en el camino hacia un sistema que se pueda compilar de forma reproducible a partir del código fuente
    Guix también logró recientemente un avance ortogonal, pero igual de impresionante, en ese mismo camino: arrancó toda la cadena de herramientas de compilación a partir de un único binario reproducible de 357 bytes, sin ningún otro blob binario de compilador
    Quizá algún día pronto ambos esfuerzos se combinen y permitan compilar de forma reproducible toda una distribución desde el código fuente
    https://guix.gnu.org/en/blog/2023/the-full-source-bootstrap-...

    • Impresionante, y da gusto ver que hay gente librando la buena batalla, incluso entre reacciones del tipo “si hay una puerta trasera, ¿no recibiríamos todos la misma puerta trasera de todos modos?”
      Lo que mucha gente no entiende es que el punto no es demostrar que el resultado sea 100% confiable, sino demostrar que el resultado es 100% fiel al código fuente
      Es decir, si se descubre algo sospechoso, como una puerta trasera oculta, siempre podrá reproducirse de forma concluyente
      Para los malos, eso significa que no habrá dónde correr ni dónde esconderse
    • Todavía no llega tan lejos como stage0 de Guix, pero en NixCon hubo una charla interesante sobre arrancar Nix a partir de TinyCC: https://media.ccc.de/v/nixcon-2023-34402-bootstrapping-nix-a...
    • Que el binario del compilador de arranque sea de 357 bytes es realmente impresionante
    • Con 357 bytes, me pregunto si realmente hace falta que el binario sea reproducible
      Parece que se podrían documentar a mano los 357 bytes completos de código máquina para que una persona pudiera entenderlos
  • Puede que sea una pregunta tonta porque nunca he hecho algo así, pero me pregunto por qué la reproducibilidad no es el comportamiento predeterminado
    Si compilaste dos copias de software desde el mismo código fuente, no entiendo bien qué impide que salgan exactamente iguales cada vez
    Sé que hay muchas piezas móviles, pero sigo sin entender cómo aparecen las diferencias

    • Hay muchas causas concretas, y probablemente las marcas de tiempo sean el problema más común
      Aquí se puede ver una lista de problemas frecuentes: https://reproducible-builds.org/docs/
      La clave general es que los desarrolladores no prueban si las compilaciones son reproducibles
      Si se incluye en las pruebas de lanzamiento, por lo general se mantienen reproducibles
    • Hay muchísimas causas
      Entre los ejemplos típicos que explícitamente hacen que algo no sea reproducible están las marcas de tiempo y la información del autor
      También hay lugares donde se rompe implícitamente la reproducibilidad por defecto; por ejemplo, muchos runtimes no definen el orden de las entradas de un hashmap, y a veces el compilador recorre ese hashmap para construir el binario
    • Puede deberse al paralelismo
      Puede haber tareas cuyo orden sí importe, y según el estado de la CPU podrían salir binarios ligeramente distintos, aunque todos sean resultados correctos
    • El equipo de Go publicó recientemente un artículo sobre lo que hizo para que la cadena de herramientas de Go fuera completamente reproducible: https://go.dev/blog/rebuild
    • A veces es por algoritmos aleatorios, a veces por cuestiones de rendimiento; por ejemplo, porque es más rápido no ordenar algo
      A veces también se debe a metadatos que dependen del tiempo o del entorno, o al orden de ejecución de los hilos
  • Perdón por la ignorancia, pero pensaba que una de las principales razones de existir de NixOS era la reproducibilidad
    Creía que estos problemas ya estaban resueltos
    Solo usé NixOS unas 2 horas y quería probar Hyprland; como Hyprland requiere algo de configuración, pensé que sería más fácil tomar la configuración de otra persona en NixOS que en otras distribuciones
    Pero fue difícil encontrar configuraciones, encontré unas 3 en gists aleatorios de GitHub y ninguna funcionó, así que me rendí

    • NixOS tiene la ventaja de que todo se construye dentro de su propio sandbox, con dependencias declaradas explícitamente y con hash
      Como no depende de todo el entorno del sistema, a diferencia de una distribución común, en muchos casos ya produce el mismo binario cada vez
      Pero el proceso de compilación en sí puede ser no determinista en muchos paquetes, así que eso por sí solo no da inmediatamente una reproducibilidad completa
    • Reproducibilidad tiene dos significados
      El significado en el que estás pensando es que, al reconstruir fácilmente paquetes binarios, se usen las mismas versiones de dependencias, las mismas opciones de compilación, etc.
      Significa que no debería haber margen para que aparezcan errores de compilación del tipo “en mi laptop sí funcionaba”
      El significado del que se habla aquí es que todos los artefactos de compilación sean binarios idénticos byte por byte
      No deben depender del nombre de la máquina, de la hora de compilación, del orden en que terminan de compilarse los archivos en una compilación paralela, etc.; y esto último es mucho más difícil
    • Nix es una herramienta difícil de aprender
      Si no estás familiarizado con ella, casi nunca es la herramienta que elegirías en una situación de “quiero que esto funcione ahora mismo”
      En NixOS, decir que algo es “reproducible” se parece más a “con el mismo código Nix obtengo el mismo comportamiento del programa”
      Es parecido a lo que la gente espera de un Dockerfile, y apunta a resolver problemas como “en mi máquina funcionaba” o “la vez pasada funcionaba”
      En cambio, las “compilaciones reproducibles” buscan que los artefactos creados en máquinas distintas sean idénticos bit a bit
      Eso agrega una capa de seguridad, porque permite verificar si el código se compiló a partir de un conjunto específico de fuentes
      También me da curiosidad qué términos de búsqueda usaste para encontrar configuraciones
      Si buscas “nixos configuration”, aparecen resultados como https://github.com/search?q=nixos%20configuration&type=repos..., y solo para Hyprland también se ven bastantes, como https://github.com/search?q=wayland.windowManager.hyprland&t...
  • Conviene ver https://github.com/donovanglover/nix-config
    Es una configuración basada en Flake e incluye Hyprland y varias cosas buenas
    Actualmente NixOS no es una herramienta para gente débil o con poco tiempo
    Espero que algún día cambie, pero si aguantas y superas la curva, puedes obtener beneficios

  • Hay que recordar que la reproducibilidad de Nix / NixOS / Nixpkgs es reproducibilidad de las fuentes
    Si las fuentes cambian, recibes una advertencia, pero eso no es lo mismo que la reproducibilidad de binarios que pueden cambiar en cada compilación
    La reproducibilidad binaria de Nix / NixOS / Nixpkgs, al menos de forma sistemática, no suele estar muy bien probada
    Guix, Arch Linux y Debian manejan la reproducibilidad binaria mejor que Nix / NixOS / Nixpkgs
    Fuentes: https://r13y.com/ (Nix*) / https://tests.reproducible-builds.org/debian/reproducible.ht... (Debian) / https://tests.reproducible-builds.org/archlinux/archlinux.ht... (Arch Linux) / https://data.guix.gnu.org/repository/1/branch/master/latest-... (Guix, puede tardar en cargar; copia en caché en https://archive.is/lTuPk)

    • En este contexto, “reproducibilidad” tiene dos definiciones
      Reproducibilidad de entradas significa “invalidación perfecta de caché respecto de las entradas”
      Nix y Guix lo hacen perfectamente por diseño y, a veces, provocan demasiadas recompilaciones
      Debian y Arch Linux no lo tienen como preocupación principal, y manejan el problema de qué paquetes reconstruir cuando se actualiza un archivo fuente específico con métodos provisionales como disparadores manuales de recompilación
      Reproducibilidad de salidas significa que “el proceso de compilación es determinista y siempre produce el mismo binario”, y es el tema del artículo original
      Nix compila paquetes en un sandbox, lo cual ayuda, pero no es una solución mágica
      En ese aspecto, Nix está en el mismo barco que Debian y Arch Linux
      De hecho, las distribuciones suelen enviar upstream parches que mejoran la reproducibilidad, y otras distribuciones también se benefician de ello
      En este contexto, https://reproducible.nixos.org es la contraparte de los otros enlaces presentados, y coincido en que el informe de Nix es menos detallado, pero eso no significa que la reproducibilidad binaria de Nix sea peor
      Si se lee como “Nix solo es bueno en reproducibilidad de entradas y no en reproducibilidad binaria”, eso es incorrecto
      Justamente estamos celebrando ese hito aquí
    • Creo que sería bueno leer el artículo
      Trata de reproducir bit a bit no solo los binarios, sino también la forma en que se empaquetan como ISO
      r13y.com está desactualizado, y, si no recuerdo mal, ese menos de 1% faltante también se debía a una regresión upstream de Python
      La reproducibilidad de los binarios en sí, excluyendo el empaquetado de la ISO, ya se había logrado hace unos años
      Cuando se pasa a paquetes más allá de la ISO básica, la comparación se vuelve complicada
      La forma en que se manejan los paquetes es sutilmente distinta, pero importante en este contexto, y muchos paquetes que probablemente estarían en AUR de Arch están en Nix como paquetes normales; además, la mayoría de los paquetes upstream -bin simplemente no son necesarios en Nix
      En general, Nix facilita crear compilaciones reproducibles, pero eso no siempre es posible independientemente de Nix y a menudo requiere parches
      Si además consideramos que el repositorio base de paquetes de Nix tiene más de 80 mil paquetes, mientras que Arch tiene menos de 15 mil excluyendo AUR, las comparaciones porcentuales no son muy útiles
      Un malentendido muy común es pensar que el hash de las rutas del Nix store se basa en la salida de la compilación, pero en realidad se basa en el conjunto completo de todas las fuentes y entradas usadas para compilar el binario en un entorno aislado, sin importar si son binarios o no
      Por eso no ofrece tal cual los beneficios de seguridad que la gente espera, pero a cambio permite usar software que no se compila de forma reproducible en una forma de distribución razonablemente reproducible, con las mismas funciones, configuración del compilador, versiones de dependencias, usuario, configuración, etc.
    • Creo que eso es exactamente lo que tratan la primera fuente y el artículo original
      Se trata de verificar si el binario es el mismo al compilar desde las mismas fuentes en máquinas distintas
      El punto es que el binario no cambie cada vez que se compila
      El método de prueba también dice que cada compilación se ejecuta dos veces en distintos momentos, distinto hardware y distintos kernels
    • No sabía que Arch Linux probaba la reproducibilidad
      Veo que aparece como 85.6% reproducible: https://reproducible.archlinux.org
      Me pregunto cuánto trabajo hará falta en NixOS, que tiene más de 80 mil paquetes en sus repositorios oficiales
  • Considerando los objetivos o la realidad de nixpkgs, eso no es cierto en absoluto
    El artículo original trata de reproducir una ISO mínima binaria que incluye varios paquetes binarios

  • Me da risa la ironía de que el proyecto OpenBSD esté avanzando con tanto empeño en la dirección opuesta
    OpenBSD tiene offsets de direcciones únicos y aleatorizados en cada instalación
    Entiendo que los dos objetivos —compilaciones reproducibles e instalaciones únicas— son ortogonales y pueden lograrse al mismo tiempo, pero esa dualidad igual me parece graciosa

    • Si los offsets de direcciones pudieran aleatorizarse con una semilla proporcionada, todavía sería posible demostrar la reproducibilidad
      Otra opción sería aleatorizar los offsets al iniciar el programa, lo que aumentaría la seguridad sin perder reproducibilidad
      Así, el offset cambiaría en cada ejecución
    • OpenBSD hace enlazado aleatorizado durante el arranque
      Los paquetes en sí aún pueden ser reproducibles
      Toda la aleatorización se realiza localmente después de descargar los paquetes y verificar sus checksums
  • Ahora solo faltaría que los mantenedores firmen los paquetes, como han hecho casi todas las demás distribuciones de Linux desde los 90
    Así todos podríamos tener cierta certeza de que el código que compilamos es el mismo código enviado y revisado por personas conocidas
    Hasta que las firmas estén estandarizadas, me cuesta imaginar usar Nix en producción para proteger algo valioso

    • Creo que los mantenedores de paquetes de Nix más bien ofrecen una interfaz útil que facilita componer software
      La mayoría no da ninguna garantía sobre el contenido de los paquetes
      Esperar garantías significativas de sus firmas se siente parecido a esperar soporte del producto por parte del repartidor
      Ni siquiera hace falta confiar en que no lo empaquetaron de forma maliciosa
      Como Nix hace compilaciones reproducibles, si no quieres depender de la caché de binarios, puedes revisar la derivation y compilarla tú mismo
      Si el contenido de fondo es malicioso, eso al final es un asunto entre el desarrollador y el usuario
      Si otras distribuciones te hicieron creer lo contrario, diría que eso es casi una forma de engaño
      La excepción que se me ocurre es Tails, aunque Tails no es tan amplio como Nix
    • Me da curiosidad qué distribuciones hacen eso
      Por lo que veo, Debian ya no lo hace y es el sistema de compilación el que firma las builds; Fedora tampoco lo hace, y con Arch no estoy seguro, pero creo que tampoco
      El sistema de compilación de NixOS firma todos los artefactos de build con su propia clave y verifica las firmas al descargar
      Si quieres ser paranoico, Nix al menos facilita compilarlo todo directamente desde el código fuente
  • Es un hito muy impresionante, y felicidades a quienes lo hicieron posible
    El texto dice que incluso al recompilar realmente la ISO aparecieron diferencias, y que las causas fueron problemas pendientes en la caché de Hydra y la forma en que se generaba la ISO
    Me pregunto si alguien puede explicar cómo corrigieron “la forma en que se generó la ISO”
    Hace tiempo intenté crear una ISO reproducible, pero no pude hacer que el sistema de archivos generara extents de manera determinista

    • En el caso de NixOS, está en la sección “cómo lo reprodujimos” del artículo
      El último paso de ese proceso genera la ISO en el directorio ./result/iso
      Parece que lo que buscas es el comando que invocó esa build, aunque no estoy muy seguro de qué paso estás buscando
      Por ejemplo, la invocación a xorriso está aquí: https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-...
  • ¿No habría que falsear la hora del sistema para hacer esto?
    La hora suele meterse en los binarios de una u otra forma

    • De hecho, los timestamps probablemente sean la causa más común de no determinismo
      Son tan comunes que muchos compiladores implementan SOURCE_DATE_EPOCH, una variable de facto estándar para falsear timestamps: https://reproducible-builds.org/docs/source-date-epoch/
    • Me gustaría ver ejemplos de cómo y por qué ocurre eso
    • Basta con no incluir timestamps en ninguna build, o establecerlos en 0 para que la fecha de compilación sea 1970 en todas partes
  • ¿Esto no ayuda a resolver el problema que Ken Thompson describió en “Reflections on Trusting Trust”?
    Si se puede bootstrappear por completo todo el sistema desde el código fuente, parecería más difícil colar algo como un compilador con backdoor

    • Sí ayuda, pero no es una “solución” completa
      En teoría, también podría haber un backdoor sofisticado dentro del entorno en el que se compila la ISO
      Si de verdad quieres resolver ese problema, puedes ver Diverse Double Compiling (https://dwheeler.com/trusting-trust/) o el bootstrap de todo el entorno (https://bootstrappable.org/)
      La sección del artículo “¿el enfoque anterior no tiene un problema de bootstrap?” también es relevante
      Aun así, solo reproducir las builds ayuda mucho a que ese tipo de ataque sea cada vez menos probable
  • Por trabajo reciente estuve dentro del ecosistema de Red Hat
    Me pregunto cómo se compara esto con cosas como Fedora Silverblue, Ansible o Fedora Silverblue + Ansible

    • En el ecosistema Fedora, lo más cercano al builder de ISO de NixOS y su reproducibilidad es osbuild / imagebuilder: https://www.osbuild.org/guides/introduction.html
      Imagebuilder afirma ser reproducible, pero hasta donde sé instala la mayoría de los paquetes rpm como binarios, no desde el código fuente
      Así que, si todos los paquetes de entrada no son también reproducibles, no es reproducibilidad estricta
      Si la explicación sobre compilar paquetes desde el código fuente, crear una imagen de distribución y tratar la reproducibilidad no te resonó, probablemente no seas el lector principal al que va dirigida
    • Nix es un SO declarativo: describe cómo debería verse el sistema operativo
      En cambio, Ansible especifica los pasos que debe seguir el sistema operativo

Silverblue y Nix son ortogonales entre sí, más allá de que ambos son distribuciones de Linux
Silverblue es un intento de cambiar la forma de entregar software usando solo contenedores sobre un host inmutable
Si buscas una alternativa a Ansible que imite en cierta medida a Nix usando Jsonnet y seguimiento de estado, vale la pena ver Etcha: https://etcha.dev

  • Ansible aplica cambios mutables al SO por unidad de tarea
    Nix es inmutable
    Los cambios nuevos se crean desde cero y, solo después de que la compilación tiene éxito, todos los paquetes se “enlazan simbólicamente” al sistema actual
    Fedora Silverblue está basado en ostree https://github.com/ostreedev/ostree
    Funciona de manera similar a git para el árbol raíz, pero para aplicar cambios hay que reiniciar todo el sistema
    Como Nix funciona enlazando simbólicamente los paquetes, no es necesario reiniciar el sistema
    Hay una explicación más detallada aquí: https://dataswamp.org/~solene/2023-07-12-intro-to-immutable-...