hammer-bootstrap: producto atestado cableado en product() (I4 / Etapa D, estructurar bien #1)
`product_attested()` + CLI `hammer bootstrap product --attest [--policy] [--rootkey]`:
estructura el spike attest-boot-test.sh dentro de hammer-bootstrap. Hidrata el init
CON gate (arje-zero-attest) sobre /usr/bin/arje-zero, fija attest_policy, deriva los
--bin label=path de la PROPIA seed (PID1 + execs Native, DFS) y FIRMA con arje-packager
(host, static musl) → seed firmada en /ente/seed.card.json; regenera /ente/attest.json
coherente con el init nuevo y sella product-attested-rootfs APARTE (núcleo+product base
intactos, Separación Mecanismo/Política).
- AttestConfig{policy, rootkey:[u8;32]}; DEV_ATTEST_ROOTKEY determinista ⇒ firmas
Ed25519 reproducibles ⇒ árbol sellado reproducible. product_attested_hash v1.
- GOTCHA hardlinks read-only del store: romper /ente/seed.card.json antes de que el
packager escriba (EACCES) y /ente/attest.json antes de reescribir; tmp de firma en
staging/.attest-build se limpia antes de sellar.
- critical_bins_from_seed: getty+sshd comparten /bin/busybox ⇒ 3 concesiones por hash.
- Validado en host contra los sellos reales (3 concesiones, init 13MB, hammer attest ✓).
- Test hermético assemble_attested_swaps_gate_signs_seed_and_regenerates_attest (packager
sintético). 40 tests verde.
- scripts/attest-boot-test.sh: MODO A REAL (PRODUCT_ATTESTED=<hash> bootea el artefacto
real) + MODO B SPIKE fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,17 @@ enum BootstrapCmd {
|
||||
/// Directorio de recetas (`openssh.toml`, `netup.toml`, …).
|
||||
#[arg(long, default_value = "recipes")]
|
||||
recipes: String,
|
||||
/// Produce además el producto ATESTADO (`product-attested-rootfs`): hidrata el init CON gate
|
||||
/// (`arje-zero-attest`) y FIRMA la seed con `arje-packager`. Imprime el hash del atestado.
|
||||
#[arg(long)]
|
||||
attest: bool,
|
||||
/// Política del gate de atestación: `halt` (default) | `degraded` | `warn`. Sólo con `--attest`.
|
||||
#[arg(long, default_value = "halt")]
|
||||
policy: String,
|
||||
/// Fichero con la rootkey de 32 bytes raw para firmar. Sin él se usa la rootkey de desarrollo
|
||||
/// determinista (firmas reproducibles). Sólo con `--attest`.
|
||||
#[arg(long)]
|
||||
rootkey: Option<String>,
|
||||
},
|
||||
/// [Stage 2] Ancla el content-hash del rootfs de Stage 1 como referencia de reproducibilidad
|
||||
/// (anota la línea del manifiesto). Con `--verify <hash>` compara contra un rebuild `stage1'`
|
||||
@@ -616,18 +627,25 @@ fn main() -> anyhow::Result<()> {
|
||||
let hash = hammer_bootstrap::stage1(&spec, &base_cfg, &store)?;
|
||||
println!("{hash}");
|
||||
}
|
||||
BootstrapCmd::Product { rootfs, recipes } => {
|
||||
BootstrapCmd::Product { rootfs, recipes, attest, policy, rootkey } => {
|
||||
let store = hammer_core::Store::open(&cli.store)?;
|
||||
// El zig del lab (no la semilla): los servicios usan su `zig_version` propia
|
||||
// (openssh ⇒ 0.13.0, hermano del default), y si ya están sellados, salen cacheados.
|
||||
let base_cfg = hammer_build::BuildConfig::from_env_or_defaults(store.root());
|
||||
let base = hammer_core::ArtifactHash::from_hex(rootfs.trim_start_matches("b3:"));
|
||||
let hash = hammer_bootstrap::product(
|
||||
&base,
|
||||
&base_cfg,
|
||||
std::path::Path::new(&recipes),
|
||||
&store,
|
||||
)?;
|
||||
let recipes_dir = std::path::Path::new(&recipes);
|
||||
let hash = if attest {
|
||||
let mut cfg = hammer_bootstrap::AttestConfig { policy, ..Default::default() };
|
||||
if let Some(path) = rootkey {
|
||||
let bytes = std::fs::read(&path)?;
|
||||
cfg.rootkey = bytes.as_slice().try_into().map_err(|_| {
|
||||
anyhow::anyhow!("la rootkey {path} debe ser exactamente 32 bytes (son {})", bytes.len())
|
||||
})?;
|
||||
}
|
||||
hammer_bootstrap::product_attested(&base, &base_cfg, recipes_dir, &store, &cfg)?
|
||||
} else {
|
||||
hammer_bootstrap::product(&base, &base_cfg, recipes_dir, &store)?
|
||||
};
|
||||
println!("{hash}");
|
||||
}
|
||||
BootstrapCmd::Stage2 { rootfs, verify } => {
|
||||
|
||||
Reference in New Issue
Block a user