Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
ea2dc59
wip
iliana Dec 18, 2025
ece6da9
define and wire up metadata
iliana Dec 18, 2025
bc122f2
editor refactor; allow the CLI to guess kinds
iliana Dec 19, 2025
09fd212
todo -> todone
iliana Dec 19, 2025
2e4f484
add edit/ls/cat commands
iliana Dec 19, 2025
e5f270e
match tokio-util version with omicron
iliana Dec 19, 2025
b6a79ba
impl Eq for Artifacts
iliana Dec 20, 2025
2d998ad
wip changes while working on omicron integration
iliana Jan 9, 2026
243782f
load repo from opened zip; flatten io error variants
iliana Jan 12, 2026
10f3578
fix incorrect conversion into TransportErrorKind::FileNotFound
iliana Jan 13, 2026
fdd859e
ability to retrieve archive path/sha256 from loaded repo
iliana Jan 13, 2026
cae592a
impl From<T> for Sign
iliana Jan 14, 2026
734c376
refactor RepositoryEditor::fake
iliana Jan 15, 2026
9153796
add Repository::fake shorthand method
iliana Jan 15, 2026
a05e82a
add `Artifacts::filter_tags`
iliana Jan 26, 2026
49fd3ff
clean up unwraps from fake artifact generation
iliana Jan 26, 2026
3fed6a3
big-ass editor refactor
iliana Jan 28, 2026
2089329
clippy pedantic cleanup and explicit unwraps
iliana Jan 28, 2026
1f329c4
small nit
iliana Jan 28, 2026
9a48652
ugh clippy
iliana Jan 28, 2026
fb729bc
add a `Repository::verify_targets` method
iliana Jan 28, 2026
846b468
ensure target streams impl `Sync`
iliana Jan 29, 2026
8afcc64
add ErrorKind::is_repository_error
iliana Feb 3, 2026
b487607
add ArtifactId and ArtifactHandle
iliana Feb 3, 2026
e69319a
i fought the borrow checker and won
iliana Feb 3, 2026
3d48760
create `Root` struct
iliana Feb 3, 2026
6225be4
various tweaks
iliana Feb 4, 2026
1aeda4a
add v1_compatibility tests
iliana Feb 4, 2026
24868ed
a few tweaks and tests
iliana Feb 5, 2026
8d88948
metadata will always be string:string map
iliana Feb 5, 2026
f5ced8f
don't bother with symlinks in zip files
iliana Feb 5, 2026
ebd9aa3
oops
iliana Feb 5, 2026
4515d8e
write a big comment about zip archive safety
iliana Feb 5, 2026
32c790d
regenerate installinator documents for v1 repos
iliana Feb 5, 2026
a23d0fe
disallow creating repos with unexpected tag collisions
iliana Feb 10, 2026
284002c
read the artifact version from measurement corpus
iliana Feb 11, 2026
dae6f91
better corim errors / partial guess test
iliana Feb 11, 2026
1b702b2
installinator document must contain the artifact version
iliana Feb 11, 2026
38e45c7
impl Display for Root
iliana Feb 12, 2026
cf894c5
impl Clone for RepositoryEditor
iliana Feb 12, 2026
3e892c6
nit
iliana Feb 12, 2026
3f01e5c
rewrite ZipTransport::stream
iliana Feb 13, 2026
865bc27
add RepositoryEditor::inconsistent_fake
iliana Feb 23, 2026
7cb0bef
deal with weird zone file names
iliana Feb 24, 2026
e478d77
update fake sp/rot tags for what omicron tests expect
iliana Feb 24, 2026
be9c1e7
implement strongly-typed repo metadata
iliana Mar 6, 2026
c6b269e
rework common to_map function
iliana Mar 6, 2026
2b44f17
bump tar
iliana Mar 24, 2026
b3415b7
bump rsa
iliana Apr 7, 2026
dbd0cf7
move DisplayTags into tags.rs
iliana Apr 7, 2026
b099e58
remove ArtifactId
iliana Apr 7, 2026
370f380
consistently use re-export of ExpirationEnforcement
iliana Apr 7, 2026
def2f94
clean up composite archive types from brand-metadata
iliana Apr 7, 2026
bc4fd3d
refactor / docs pass on tufaceous-artifact
iliana Apr 9, 2026
159e047
docs pass, remove InstallinatorArtifactId etc
iliana Apr 15, 2026
76b6c4b
oops
iliana Apr 15, 2026
cce2ef2
upstream tough PR got merged yay
iliana Apr 18, 2026
865bf09
README; problem checker; verify subcommand
iliana Apr 20, 2026
3fdbb39
further checks and tests for some checks
iliana Apr 21, 2026
9f19c9c
further comment nits
iliana Apr 21, 2026
152463b
rename "sign" -> "RKTH"
iliana Apr 28, 2026
72f9778
tough 0.22.0
iliana Apr 28, 2026
2610c9e
os_board should not be an enum
iliana Apr 28, 2026
b0fce4f
add RotKeyTableHash::from_bytes
iliana Apr 28, 2026
f3ae21d
helix pls
iliana Apr 28, 2026
268680f
zip transport error handling nits
iliana Apr 28, 2026
a5c8765
rust 1.94; improve readability of ZipTransport::from_impl_blocking
iliana Apr 28, 2026
33885f7
update rawzip
iliana Apr 30, 2026
ef094c3
Merge remote-tracking branch 'origin/main' into iliana/rfd621
iliana May 5, 2026
cc29a9b
make `to_map` fallible
iliana May 6, 2026
f401a27
keep installinator documents if --no-installinator-document
iliana May 21, 2026
e53bd8f
rename ArtifactSet::get -> get_only
iliana May 21, 2026
ced6306
archive_* args to Repository::from_loaded are never Some
iliana May 21, 2026
115132a
KnownArtifactTags tweaks and Artifact docs
iliana May 21, 2026
3984261
also use the "causality hazard" warning here
iliana May 21, 2026
227fb0c
clarify the v1 compat stuff in the installinator module
iliana May 21, 2026
a4c160a
deserialize_from_empty -> deserialize_previously_valid
iliana May 21, 2026
4a41037
add TODO for LoadOptions
iliana May 21, 2026
4fb79bc
fixup! rename ArtifactSet::get -> get_only
iliana May 21, 2026
4ea661b
lock hubtools/rats-corim to same commits as omicron
iliana Jun 11, 2026
d64ef97
update packages implicated by `cargo audit`
iliana Jun 11, 2026
e021b77
serde_human_bytes -> byte-wrapper; sha256 0.11
iliana Jun 11, 2026
b8f65d3
remove target_name from Artifact
iliana Jun 11, 2026
38ebade
fix doc anchors on ZipTransport
iliana Jun 11, 2026
14f1fcb
macro (and documentation) for "usize fits in u64"
iliana Jun 11, 2026
7f4168f
bail early if more zip entries than EOCD claims
iliana Jun 11, 2026
be1d943
lift spawn_blocking out of ZipTransport::from_file
iliana Jun 11, 2026
0268a3c
doc comments for tufaceous_artifact::map
iliana Jun 12, 2026
41a264f
fix comment note on Metadata
iliana Jun 12, 2026
7a7e03d
move Option out of RotKeyTableHash
iliana Jun 12, 2026
4695ec1
make RepositoryEditor::new fallible
iliana Jun 12, 2026
e3594ce
also set artifact_version in RepositoryEditor::system_version
iliana Jun 12, 2026
d0dc957
verbify RepositoryEditor's methods
iliana Jun 12, 2026
cfd60a7
sprinkle `impl` into RepositoryEditor method args
iliana Jun 12, 2026
ef620c8
remove RepositoryEditor::add_fake_artifact
iliana Jun 12, 2026
94c93c4
properly handle invalid hash lengths from imported repos
iliana Jun 12, 2026
41b93cc
immediately check for conflicting artifact definitions
iliana Jun 12, 2026
7a653ce
some more s/sign/rkth/
iliana Jun 12, 2026
1af3f9b
remove errant eprintln!
iliana Jun 12, 2026
a02d5c0
fix logic bug in read_os_tarball_metadata_blocking
iliana Jun 12, 2026
34aebb8
stronger root types in edit::sign
iliana Jun 12, 2026
176d282
return Err in zip_writer::write_task if other end is dropped
iliana Jun 12, 2026
1fa4878
don't panic in deflate_heuristic
iliana Jun 12, 2026
ea8e14b
fmt
iliana Jun 12, 2026
44eff96
Update lib/src/edit/input.rs
iliana Jun 12, 2026
eb21197
() = result??;
iliana Jun 12, 2026
68bf84e
split ErrorKind::ReadZip
iliana Jun 15, 2026
abccf73
un-backwards the streams (remove stream::try_unfold)
iliana Jun 15, 2026
479e433
redo trust store options, implement in bin
iliana Jun 15, 2026
1a4bb85
bin docs pass
iliana Jun 15, 2026
77d4820
require compression param in ZipWriter::new_file
iliana Jun 15, 2026
80e93ab
fix doc test after 479e433
iliana Jun 15, 2026
ec5bb32
simplify the zip writer, moving it to edit::sign
iliana Jun 16, 2026
350530a
bleh
iliana Jun 16, 2026
0e0a7e4
generate a more-realistic fake OS phase 2 image
iliana Jun 16, 2026
47feead
attempt to explain Source/Input/Output
iliana Jun 16, 2026
4078d3f
explicitly tag source errors as #[source]
iliana Jun 16, 2026
a889f72
fixup! simplify the zip writer, moving it to edit::sign
iliana Jun 16, 2026
9f301a0
reject repos with bad target metadata
iliana Jun 16, 2026
cff1fc4
verify subcommand should print repo hash
iliana Jun 16, 2026
a0f34b8
fix added/changed flags in readme
iliana Jun 16, 2026
3f740da
make Repository::check_problems fallible
iliana Jun 16, 2026
509557c
list problem if metadata cannot convert to mapping
iliana Jun 16, 2026
1dedde5
make unix.z/cpio.z optional as intended
iliana Jun 16, 2026
89c4f39
comments for v1
iliana Jun 16, 2026
e81ad2a
document GuessInput/GuessResult
iliana Jun 16, 2026
0975ac6
consistently use `tuf_repo: tough::Repository`
iliana Jun 16, 2026
f503e6a
avoid overrelying on RotSlot::fmt
iliana Jun 16, 2026
5bd5bd0
ensure TargetSource::stream is Send
iliana Jun 25, 2026
52d37f2
correctly use the friendly CA name in the target name
iliana Jun 25, 2026
53b20ab
properly handle JoinError in the blocking stream pattern
iliana Jul 1, 2026
e37c399
max parallelism in RepositoryEditor::finish
iliana Jul 1, 2026
67e9ea2
wow
iliana Jul 1, 2026
470478c
return io errors while guessing artifacts
iliana Jul 1, 2026
85ba134
check max_root_size when blindly trusting 1.root.json
iliana Jul 1, 2026
50e55db
fixup! return io errors while guessing artifacts
iliana Jul 2, 2026
d692cc7
further guessing about guess errors
iliana Jul 2, 2026
2c4c642
fix os image guess error
iliana Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,997 changes: 491 additions & 1,506 deletions Cargo.lock

Large diffs are not rendered by default.

68 changes: 34 additions & 34 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,65 @@ members = [
]

[workspace.package]
version = "0.1.0"
edition = "2021"
version = "0.2.0"
edition = "2024"
license = "MPL-2.0"
publish = false

[workspace.dependencies]
anyhow = "1.0.96"
assert_cmd = "2.0.16"
async-trait = "0.1.86"
atomicwrites = "0.4.4"
aws-lc-rs = "1.16.2"
base64 = "0.22.1"
buf-list = "1.0.3"
byte-wrapper = { version = "0.1.0", default-features = false, features = ["alloc", "hex", "serde"] }
bytes = "1.11.1"
camino = { version = "1.1.9", features = ["serde1"] }
camino = "1.1.9"
camino-tempfile = "1.1.1"
chrono = { version = "0.4.40", default-features = false, features = ["std"] }
clap = { version = "4.5.31", features = ["cargo", "derive", "env", "wrap_help"] }
console = { version = "0.15.10", default-features = false }
ciborium = "0.2.2"
clap = { version = "4.5.31", features = ["cargo", "derive", "wrap_help"] }
daft = { version = "0.1.1", features = ["derive"] }
datatest-stable = "0.2.9"
debug-ignore = "1.0.5"
dropshot = "0.17.0"
digest-io = "0.1.0"
expectorate = "1.1.0"
flate2 = "1.0.35"
fs-err = "2.11.0"
futures = "0.3.31"
futures-util = "0.3.31"
hex = "0.4.3"
hubtools = { git = "https://github.com/oxidecomputer/hubtools.git", branch = "main" }
humantime = "2.1.0"
indent_write = "2.2.0"
itertools = "0.13.0"
parse-size = "1.1.0"
predicates = "3.1.3"
hubtools = { git = "https://github.com/oxidecomputer/hubtools.git", rev = "2b1ef9b38d75563ea800baa3b17327eec17b1b7a" }
memchr = "2.7.4"
proptest = "1.5.0"
rand = "0.8.6"
rats-corim = { git = "https://github.com/oxidecomputer/rats-corim.git", rev = "f0d5d5168d3d31487a56df32c676b0c6240bcc6b" }
rawzip = "0.4.4"
regex = "1.11.1"
schemars = { version = "0.8.21", features = ["semver"] }
schemars = "0.8.21"
semver = { version = "1.0.25", features = ["serde"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_human_bytes = { git = "https://github.com/oxidecomputer/serde_human_bytes", branch = "main" }
serde_json = "1.0.139"
serde_path_to_error = "0.1.16"
sha2 = "0.10.8"
sha2 = "0.11.0"
slog = "2.7.0"
slog-async = "2.8.0"
slog-envlogger = "2.2.0"
slog-term = "2.9.1"
strum = { version = "0.26.3", features = ["derive"] }
slog-term = "2.9.2"
tar = "0.4.45"
tempfile = "3.13.0"
test-strategy = "0.4.0"
thiserror = "2.0.12"
tokio = "1.43.0"
toml = "0.8.20"
tough = { version = "0.22.0", features = [ "http" ] }
tufaceous-artifact = { path = "artifact", default-features = false }
tufaceous-brand-metadata = { path = "brand-metadata" }
tufaceous-lib = { path = "lib" }
tokio = "1.47.0"
tokio-test = "0.4.5"
tough = "0.22.0"
tufaceous.path = "lib"
tufaceous-artifact.path = "artifact"
tufaceous-brand-metadata.path = "brand-metadata"
url = "2.5.4"
zip = { version = "4.2.0", default-features = false }

[workspace.lints.clippy]
pedantic = { level = "warn", priority = -1 }
missing-errors-doc = "allow"
must-use-candidate = "allow"

[profile.dev.package.flate2]
opt-level = 3
[profile.dev.package.miniz_oxide]
opt-level = 3
[profile.dev.package.rawzip]
opt-level = 3
[profile.dev.package.sha2]
opt-level = 3
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Tufaceous

Tufaceous is the library and tooling for Oxide update repositories. It is a layer of Oxide-specific tooling on top of [tough](https://github.com/awslabs/tough), a Rust library that implements [TUF](https://theupdateframework.io/).

This README is intended to provide a high-level overview of the library and instructions for adding to it. More context can be found in [RFD 621](https://rfd.shared.oxide.computer/rfd/0621).

## Repository format

We selected ZIP archives as the primary format for our repositories, despite ZIP's many shortcomings. Because Oxide customers are receiving update repositories and uploading them to their systems, we wanted them to be readable for most users so that they could see what is inside.

The ZIP archive has a top-level `repo` directory. Inside this directory is a `metadata` directory, containing the signed TUF metadata, and a `targets` directory, containing all the targets described by that metadata.

## Artifacts

"Artifacts" are our jargon for a file that the Oxide control plane needs in order to perform updates. (All artifacts are "targets", TUF jargon for a file described by the repository metadata, but not all targets are artifacts.)

Artifacts in the repository are described by the `artifacts-v2.json`. Each artifact has a set of tags that is used by the control plane to decide what to do with the artifact. When creating and using artifacts, these tags are strongly-typed (see `KnownArtifactTags` in `artifact/src/tags.rs`), but they are encoded as an arbitrary key-value mapping. This allows us to potentially release a new type of artifact and use it in the same release, once Nexus has been updated.

As a general rule it is not possible to use this tooling to create a repository with arbitrary blobs and artifacts in it. The library is very opinionated about how artifacts are named to keep things usable for the CLI and for users of these repositories.

## Tooling

Most of the time you're working with repositories generated by the [Omicron release tooling](https://github.com/oxidecomputer/omicron/blob/main/docs/releng.adoc) (`cargo xtask releng` in Omicron). There is also a binary target that is a thin CLI on top of the library, which you can build with `cargo build --release --bin tufaceous`.

In all commands that read from a repository, use either `-r`/`--trust-roots` to provide a list of trust roots to verify signatures against, or `--blindly-trust` to use the `metadata/1.root.json` root in the repository as the trust root. If you want to load a repository with an expired signature, use `--allow-expired`. For convenience you can combine `--allow-expired` and `--blindly-trust` with `-f`/`--force-load`.

### Verify a repository

`tufaceous verify` will read a repository, print its SHA256 checksum, and verify the metadata and checksums of all the metadata in the repository. It will also run a series of compatibility checks on the repository and warn you of any detected issues.
Comment thread
iliana marked this conversation as resolved.

### Unpack a repository

Scripts can use `tufaceous list-targets` to get a list of targets from the repository, and `tufaceous show-target` to write a target to stdout.

To unpack an entire repository, use `unzip` or ZIP tooling provided by your OS.

### Build a repository

`tufaceous build` has two required flags:

- `--output file.zip`, which provides the path to write to
- `--system-version 1.0.0`, which provides the system version

The arguments are the set of artifacts to add:

- A measurement corpus is a CoRIM file
- An OS image is a directory containing `cosmo.rom`, `gimlet.rom`, and `zfs.img`, as produced by `helios-build image`
Comment thread
iliana marked this conversation as resolved.
- An RoT, RoT bootloader, or SP image is a Hubris archive (ZIP format)
- A zone image is a tarball that starts with the `oxide.json` Omicron brand metadata

When building a repository, an Installinator document is automatically created based on the artifacts in the repository. You can avoid this with `--no-installinator-document`.

### Add/remove artifacts from a repository

Using `tufaceous edit` you can remove targets from a repository using `--remove-targets` and add new artifacts using `--add-artifacts`.

The Installinator document is automatically regenerated based on the artifacts in the final repository. You can avoid this with `--no-installinator-document`, which will keep an existing Installinator document if there is one.

## Adding new artifacts

In order to add a new type of artifact, you will need to make a series of changes to the library:

- Modify `KnownArtifactTags` to describe your new artifact
- Add methods for adding an actual artifact and a fake artifact to `RepositoryEditor`
- Add a fake artifact in `Input::fake` (`lib/src/edit/fake.rs`)
- Add code for guessing this type of artifact to `Input::guess` (`lib/src/edit/guess.rs`)

As a general rule, Tufaceous artifacts are self-describing. The "guess" code is used by the CLI and generates tags from the artifact to avoid needing an unwieldy set of command line options to describe tags. If your artifact is not self-describing, you can staple a header of some kind to the beginning of the artifact that control plane code knows to strip before using the artifact.
11 changes: 6 additions & 5 deletions artifact/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
rust-version = "1.94"

[features]
hubtools = ["dep:hubtools"]
proptest = ["dep:proptest", "dep:test-strategy"]
schemars = ["dep:schemars"]
schemars = ["dep:schemars", "byte-wrapper/schemars08"]

[dependencies]
byte-wrapper.workspace = true
daft.workspace = true
hex.workspace = true
hubtools = { workspace = true, optional = true }
proptest = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
semver.workspace = true
serde.workspace = true
serde_human_bytes.workspace = true
strum.workspace = true
serde_json.workspace = true
test-strategy = { workspace = true, optional = true }
thiserror.workspace = true

Expand Down
Loading
Loading