diff --git a/README.md b/README.md index 78c0551..f7a88d8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ecr fedora # any Docker Hub image ## Library -`ecr` is a cargo workspace: `crates/ecr` is the library crate (package `ecr`), `crates/ecr-cli` the thin command-line front-end. The library never writes to the terminal: diagnostics go through the `log` crate, and downloads, extraction and initramfs creation report progress through a caller-supplied `Progress` sink (pass `&NoProgress` for silence, or implement the trait to render your own UI). Rust consumers can use the library directly — resolve an image, download it through the content cache, and run commands with a fully caller-controlled environment, bind targets and architecture: +`ecr` is a cargo workspace: `crates/ecr` is the library crate (package `ecr`), `crates/ecr-cli` the thin command-line front-end. The library never writes to the terminal: diagnostics go through the `log` crate, and downloads, extraction and initramfs creation report progress through a caller-supplied `Progress` sink (pass `&NoProgress` for silence, or implement the trait to render your own UI). Public functions return `Result`: match on the `Error` variants for host-readiness failures (user namespaces disabled, binfmt_misc unregistered, missing subuid/subgid entry, architecture the registry does not serve) — everything else arrives as `Error::Other` with the full context chain. Rust consumers can use the library directly — resolve an image, download it through the content cache, and run commands with a fully caller-controlled environment, bind targets and architecture: ```toml [dependencies] @@ -159,7 +159,7 @@ Requirements: ## Cache -Downloaded images are cached in `~/.cache/ecr/`. For `latest` OCI tags the registry manifest digest is checked on each run — the image is only re-downloaded when it has actually changed. Use `--no-cache` to force a fresh pull regardless. The library's `PreparedRootfs::persist` can replace a cache entry with a provisioned rootfs (see *Provisioned-rootfs caching* above). +Downloaded images are cached in `~/.cache/ecr/`. Downloads are verified before they enter the cache: OCI layers and manifests against the registry's content digests, direct distro tarballs against the checksums Alpine and Ubuntu publish next to them (a missing checksum artifact degrades to an unverified download with a warning). For `latest` OCI tags the registry manifest digest is checked on each run — the image is only re-downloaded when it has actually changed. Use `--no-cache` to force a fresh pull regardless. The library's `PreparedRootfs::persist` can replace a cache entry with a provisioned rootfs (see *Provisioned-rootfs caching* above). ## Requirements diff --git a/SPEC.md b/SPEC.md index 38ec99d..c4ffe56 100644 --- a/SPEC.md +++ b/SPEC.md @@ -20,6 +20,28 @@ spinners (indicatif) — is owned by `crates/ecr-cli`. ## Library API +### Errors (`ecr::error`) + +Public functions return `Result` so consumers can match on +the failure modes that call for different handling. anyhow remains the +internal error type; semantic variants are raised at the sites that detect +the conditions, everything else converts to: + +- `Error::Other` — any other failure, transparently wrapping the anyhow + context chain (`source()` walks it; `Display` shows the top message). +- `Error::UserNamespacesUnavailable` — unprivileged user namespaces are + disabled; `remediation` names the sysctl to set. +- `Error::BinfmtNotRegistered` — binfmt_misc missing for the target + architecture (foreign-arch namespace mode). +- `Error::ArchNotAvailable` — the registry serves no linux manifest for + the requested architecture; `available` lists what it does serve. +- `Error::MissingSubidRange` — `/etc/subuid` or `/etc/subgid` has no + usable entry for the user. + +Errors raised inside the namespace child (mount setup, resolv.conf, +chroot exec) cross the process boundary as formatted chains and surface +as `Error::Other`. + ### Rootfs lifecycle (`ecr::rootfs`) - `RootfsCache::new(dir)` / `RootfsCache::default_dir()` — the image tarball @@ -195,6 +217,23 @@ If architecture is not available in manifest list, error with available architec Error: No manifest found for architecture 'riscv64'. Available: amd64, arm64, ppc64le, s390x ``` +### Content Verification + +Downloaded content is hashed (sha256 and sha512 in parallel) while +streaming, and verified before it enters the cache — a mismatch fails the +download and nothing partial is kept: + +- OCI architecture manifests requested by digest are checked against that + digest (the layer digests read from them are trusted next). +- OCI layer blobs are checked against the digest the manifest names. A + digest algorithm other than sha256/sha512 cannot be checked: warn and + accept. +- Direct distro tarballs are checked against the checksum each distro + publishes next to them (Alpine: per-file `.sha256`; Ubuntu: `SHA256SUMS` + in the release directory). A missing or unfetchable checksum artifact + degrades to an unverified download with a warning, mirroring the + freshness-check policy; a checksum mismatch always fails. + ### Layer Extraction `extract::extract_tarball` handles plain rootfs tarballs and OCI layer