Skip to content
4 changes: 4 additions & 0 deletions common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3408,6 +3408,10 @@ pub struct TufArtifactMeta {

/// The size of the artifact in bytes.
pub size: u64,

/// Sign (root key hash table) hash of a signed image. Only applicable to
/// RoT or RoT bootloader artifacts.
pub rot_sign: Option<Vec<u8>>,
}

/// Data about a successful TUF repo import into Nexus.
Expand Down
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
///
/// This must be updated when you change the database schema. Refer to
/// schema/crdb/README.adoc in the root of this repository for details.
pub const SCHEMA_VERSION: Version = Version::new(173, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(174, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
// | leaving the first copy as an example for the next person.
// v
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
KnownVersion::new(174, "add-tuf-rot-by-sign"),
KnownVersion::new(173, "inv-internal-dns"),
KnownVersion::new(172, "add-zones-with-mupdate-override"),
KnownVersion::new(171, "inv-clear-mupdate-override"),
Expand Down
5 changes: 5 additions & 0 deletions nexus/db-model/src/tuf_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ pub struct TufArtifact {
pub sha256: ArtifactHash,
artifact_size: i64,
pub generation_added: Generation,
pub rot_sign: Option<Vec<u8>>,
}

impl TufArtifact {
Expand All @@ -174,6 +175,7 @@ impl TufArtifact {
sha256: ArtifactHash,
artifact_size: u64,
generation_added: external::Generation,
rot_sign: Option<Vec<u8>>,
) -> Self {
Self {
id: TypedUuid::new_v4().into(),
Expand All @@ -184,6 +186,7 @@ impl TufArtifact {
sha256,
artifact_size: artifact_size as i64,
generation_added: generation_added.into(),
rot_sign,
}
}

Expand All @@ -202,6 +205,7 @@ impl TufArtifact {
artifact.hash.into(),
artifact.size,
generation_added,
artifact.rot_sign,
)
}

Expand All @@ -215,6 +219,7 @@ impl TufArtifact {
},
hash: self.sha256.into(),
size: self.artifact_size as u64,
rot_sign: self.rot_sign,
}
}

Expand Down
3 changes: 3 additions & 0 deletions nexus/db-queries/src/db/datastore/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,7 @@ mod tests {
},
hash: ZONE_ARTIFACT_HASH_1,
size: 0,
rot_sign: None,
},
TufArtifactMeta {
id: ArtifactId {
Expand All @@ -3245,6 +3246,7 @@ mod tests {
},
hash: HOST_ARTIFACT_HASH_1,
size: 0,
rot_sign: None,
},
TufArtifactMeta {
id: ArtifactId {
Expand All @@ -3254,6 +3256,7 @@ mod tests {
},
hash: HOST_ARTIFACT_HASH_2,
size: 0,
rot_sign: None,
},
],
},
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/target_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ mod test {
},
hash,
size: 0,
rot_sign: None,
}],
},
)
Expand Down
1 change: 1 addition & 0 deletions nexus/db-schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ table! {
sha256 -> Text,
artifact_size -> Int8,
generation_added -> Int8,
rot_sign -> Nullable<Binary>,
}
}

Expand Down
3 changes: 2 additions & 1 deletion nexus/reconfigurator/planning/src/mgs_updates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,8 @@ mod test {
kind: kind.into(),
},
hash,
size: 0, // unused here
size: 0, // unused here
rot_sign: None, // unused here
}
}

Expand Down
1 change: 1 addition & 0 deletions nexus/reconfigurator/planning/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5512,6 +5512,7 @@ pub(crate) mod test {
},
hash: ArtifactHash([0; 32]),
size: 0,
rot_sign: None,
}
};
}
Expand Down
10 changes: 10 additions & 0 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -25384,6 +25384,16 @@
}
]
},
"rot_sign": {
"nullable": true,
"description": "Sign (root key hash table) hash of a signed image. Only applicable to RoT or RoT bootloader artifacts.",
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
},
"size": {
"description": "The size of the artifact in bytes.",
"type": "integer",
Expand Down
1 change: 1 addition & 0 deletions schema/crdb/add-tuf-rot-by-sign/up1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE omicron.public.tuf_artifact ADD COLUMN IF NOT EXISTS rot_sign BYTES;
5 changes: 4 additions & 1 deletion schema/crdb/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2513,6 +2513,9 @@ CREATE TABLE IF NOT EXISTS omicron.public.tuf_artifact (
-- The generation number this artifact was added for.
generation_added INT8 NOT NULL,

-- Sign (root key hash table) hash of a signed RoT or RoT bootloader image.
rot_sign BYTES, -- nullable

CONSTRAINT unique_name_version_kind UNIQUE (name, version, kind)
);

Expand Down Expand Up @@ -6347,7 +6350,7 @@ INSERT INTO omicron.public.db_metadata (
version,
target_version
) VALUES
(TRUE, NOW(), NOW(), '173.0.0', NULL)
(TRUE, NOW(), NOW(), '174.0.0', NULL)
ON CONFLICT DO NOTHING;

COMMIT;
38 changes: 26 additions & 12 deletions update-common/src/artifacts/update_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
artifact_kind.into(),
None,
self.log,
)?;

Expand Down Expand Up @@ -419,11 +420,13 @@ impl<'a> UpdatePlanBuilder<'a> {
let (artifact_id, bootloader_caboose) =
read_hubris_caboose_from_archive(artifact_id, data.clone())?;

let sign = bootloader_caboose.sign.expect("required SIGN in caboose");

// We restrict the bootloader to exactly one entry per (kind, signature)
match self.rot_by_sign.entry(RotSignData {
kind: artifact_kind,
sign: bootloader_caboose.sign.expect("required SIGN in caboose"),
}) {
match self
.rot_by_sign
.entry(RotSignData { kind: artifact_kind, sign: sign.clone() })
{
hash_map::Entry::Occupied(_) => {
return Err(RepositoryError::DuplicateBoardEntry {
board: bootloader_caboose.board,
Expand Down Expand Up @@ -454,6 +457,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
bootloader_kind,
Some(sign),
self.log,
)?;

Expand Down Expand Up @@ -555,10 +559,9 @@ impl<'a> UpdatePlanBuilder<'a> {
});
}

let entry_a = RotSignData {
kind: artifact_kind,
sign: image_a_caboose.sign.expect("required SIGN in caboose"),
};
let sign_a = image_a_caboose.sign.expect("required SIGN in caboose");

let entry_a = RotSignData { kind: artifact_kind, sign: sign_a.clone() };

let target_a = RotSignTarget {
id: artifact_id.clone(),
Expand All @@ -583,10 +586,9 @@ impl<'a> UpdatePlanBuilder<'a> {
}
};

let entry_b = RotSignData {
kind: artifact_kind,
sign: image_b_caboose.sign.expect("required SIGN in caboose"),
};
let sign_b = image_b_caboose.sign.expect("required SIGN in caboose");

let entry_b = RotSignData { kind: artifact_kind, sign: sign_b.clone() };

let target_b = RotSignTarget {
id: artifact_id.clone(),
Expand Down Expand Up @@ -620,12 +622,14 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id.clone(),
rot_a_data,
rot_a_kind,
Some(sign_a),
self.log,
)?;
self.record_extracted_artifact(
artifact_id,
rot_b_data,
rot_b_kind,
Some(sign_b),
self.log,
)?;

Expand Down Expand Up @@ -668,12 +672,14 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id.clone(),
phase_1_data,
ArtifactKind::HOST_PHASE_1,
None,
self.log,
)?;
self.record_extracted_artifact(
artifact_id,
phase_2_data,
ArtifactKind::HOST_PHASE_2,
None,
self.log,
)?;

Expand Down Expand Up @@ -725,12 +731,14 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id.clone(),
phase_1_data,
ArtifactKind::TRAMPOLINE_PHASE_1,
None,
self.log,
)?;
self.record_extracted_artifact(
artifact_id,
phase_2_data,
ArtifactKind::TRAMPOLINE_PHASE_2,
None,
self.log,
)?;

Expand Down Expand Up @@ -761,6 +769,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
artifact_kind,
None,
self.log,
)?;

Expand Down Expand Up @@ -796,6 +805,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
KnownArtifactKind::ControlPlane.into(),
None,
self.log,
)?;
}
Expand Down Expand Up @@ -830,6 +840,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
artifact_kind,
None,
self.log,
)?;

Expand Down Expand Up @@ -994,6 +1005,7 @@ impl<'a> UpdatePlanBuilder<'a> {
artifact_id,
data,
KnownArtifactKind::Zone.into(),
None,
self.log,
)?;
Ok(())
Expand All @@ -1017,6 +1029,7 @@ impl<'a> UpdatePlanBuilder<'a> {
tuf_repo_artifact_id: ArtifactId,
data: ExtractedArtifactDataHandle,
data_kind: ArtifactKind,
rot_sign: Option<Vec<u8>>,
log: &Logger,
) -> Result<(), RepositoryError> {
use std::collections::hash_map::Entry;
Expand Down Expand Up @@ -1059,6 +1072,7 @@ impl<'a> UpdatePlanBuilder<'a> {
id: artifacts_meta_id,
hash: data.hash(),
size: data.file_size() as u64,
rot_sign,
});
by_hash_slot.insert(data);

Expand Down
Loading