Skip to content

Commit

Permalink
Filter available artifacts by signing hash
Browse files Browse the repository at this point in the history
The artifact repository now allows multiple versions of the same
artifact type signed with different keys but this isn't quite
sufficent. Wicket still needs to know which version to display
for updates. Make the signing information available to wicket
for display artifact versions. This also makes it clear if the
repository does not contain an image signed with the expected
keys.
  • Loading branch information
labbott committed Jun 6, 2024
1 parent 4fd9dd2 commit 8610b4c
Show file tree
Hide file tree
Showing 19 changed files with 199 additions and 62 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion gateway/src/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ pub struct SpComponentCaboose {
pub board: String,
pub name: String,
pub version: String,
pub sign: Option<String>,
}

/// Identity of a host phase2 recovery image.
Expand Down Expand Up @@ -837,6 +838,7 @@ async fn sp_component_caboose_get(
const CABOOSE_KEY_BOARD: [u8; 4] = *b"BORD";
const CABOOSE_KEY_NAME: [u8; 4] = *b"NAME";
const CABOOSE_KEY_VERSION: [u8; 4] = *b"VERS";
const CABOOSE_KEY_SIGN: [u8; 4] = *b"SIGN";

let apictx = rqctx.context();
let PathSpComponent { sp, component } = path.into_inner();
Expand Down Expand Up @@ -890,13 +892,23 @@ async fn sp_component_caboose_get(
sp: sp_id,
err,
})?;
// Not all images include the SIGN in the caboose, if it's not present
// don't treat it as an error
let sign = match sp
.read_component_caboose(component, firmware_slot, CABOOSE_KEY_SIGN)
.await
.ok()
{
None => None,
Some(v) => Some(from_utf8(&CABOOSE_KEY_SIGN, v)?),
};

let git_commit = from_utf8(&CABOOSE_KEY_GIT_COMMIT, git_commit)?;
let board = from_utf8(&CABOOSE_KEY_BOARD, board)?;
let name = from_utf8(&CABOOSE_KEY_NAME, name)?;
let version = from_utf8(&CABOOSE_KEY_VERSION, version)?;

let caboose = SpComponentCaboose { git_commit, board, name, version };
let caboose = SpComponentCaboose { git_commit, board, name, version, sign };

Ok(HttpResponseOk(caboose))
}
Expand Down
2 changes: 2 additions & 0 deletions nexus/inventory/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,7 @@ mod test {
git_commit: String::from("git_commit1"),
name: String::from("name1"),
version: String::from("version1"),
sign: None,
};
assert!(!builder
.found_caboose_already(&bogus_baseboard, CabooseWhich::SpSlot0));
Expand Down Expand Up @@ -1155,6 +1156,7 @@ mod test {
git_commit: String::from("git_commit2"),
name: String::from("name2"),
version: String::from("version2"),
sign: None,
},
)
.unwrap_err();
Expand Down
1 change: 1 addition & 0 deletions nexus/inventory/src/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ pub fn caboose(unique: &str) -> SpComponentCaboose {
git_commit: format!("git_commit_{}", unique),
name: format!("name_{}", unique),
version: format!("version_{}", unique),
sign: None,
}
}

Expand Down
3 changes: 3 additions & 0 deletions openapi/gateway.json
Original file line number Diff line number Diff line change
Expand Up @@ -2611,6 +2611,9 @@
},
"version": {
"type": "string"
},
"sign": {
"type": "string"
}
},
"required": [
Expand Down
16 changes: 14 additions & 2 deletions openapi/wicketd.json
Original file line number Diff line number Diff line change
Expand Up @@ -1658,7 +1658,16 @@
"items": {
"$ref": "#/components/schemas/ArtifactHashId"
}
}
},
"sign": {
"nullable": true,
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
}
},
"required": [
"artifact_id",
Expand Down Expand Up @@ -2908,7 +2917,10 @@
},
"version": {
"type": "string"
}
},
"sign": {
"type": "string"
}
},
"required": [
"board",
Expand Down
32 changes: 4 additions & 28 deletions sp-sim/src/gimlet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,40 +1441,16 @@ impl SpHandler for Handler {
(SpComponent::ROT, b"NAME", _, _) => ROT_NAME,
(SpComponent::ROT, b"VERS", 0, _) => ROT_VERS0,
(SpComponent::ROT, b"VERS", 1, _) => ROT_VERS1,
// gimlet staging/devel hash
(SpComponent::ROT, b"SIGN", _, _) => &"11594bb5548a757e918e6fe056e2ad9e084297c9555417a025d8788eacf55daf".as_bytes(),
(SpComponent::STAGE0, b"GITC", 0, false) => STAGE0_GITC0,
(SpComponent::STAGE0, b"GITC", 1, false) => STAGE0_GITC1,
(SpComponent::STAGE0, b"BORD", _, false) => STAGE0_BORD,
(SpComponent::STAGE0, b"NAME", _, false) => STAGE0_NAME,
(SpComponent::STAGE0, b"VERS", 0, false) => STAGE0_VERS0,
(SpComponent::STAGE0, b"VERS", 1, false) => STAGE0_VERS1,
_ => return Err(SpError::NoSuchCabooseKey(key)),
};

buf[..val.len()].copy_from_slice(val);
Ok(val.len())
}

#[cfg(any(feature = "no-caboose", feature = "old-state"))]
fn get_component_caboose_value(
&mut self,
component: SpComponent,
slot: u16,
key: [u8; 4],
buf: &mut [u8],
) -> std::result::Result<usize, SpError> {
let val = match (component, &key, slot) {
(SpComponent::SP_ITSELF, b"GITC", 0) => SP_GITC0,
(SpComponent::SP_ITSELF, b"GITC", 1) => SP_GITC1,
(SpComponent::SP_ITSELF, b"BORD", _) => SP_BORD,
(SpComponent::SP_ITSELF, b"NAME", _) => SP_NAME,
(SpComponent::SP_ITSELF, b"VERS", 0) => SP_VERS0,
(SpComponent::SP_ITSELF, b"VERS", 1) => SP_VERS1,
(SpComponent::ROT, b"GITC", 0) => ROT_GITC0,
(SpComponent::ROT, b"GITC", 1) => ROT_GITC1,
(SpComponent::ROT, b"BORD", _) => ROT_BORD,
(SpComponent::ROT, b"NAME", _) => ROT_NAME,
(SpComponent::ROT, b"VERS", 0) => ROT_VERS0,
(SpComponent::ROT, b"VERS", 1) => ROT_VERS1,
// gimlet staging/devel hash
(SpComponent::STAGE0, b"SIGN", _, false) => &"11594bb5548a757e918e6fe056e2ad9e084297c9555417a025d8788eacf55daf".as_bytes(),
_ => return Err(SpError::NoSuchCabooseKey(key)),
};

Expand Down
4 changes: 4 additions & 0 deletions sp-sim/src/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1171,12 +1171,16 @@ impl SpHandler for Handler {
(SpComponent::ROT, b"NAME", _, _) => ROT_NAME,
(SpComponent::ROT, b"VERS", 0, _) => ROT_VERS0,
(SpComponent::ROT, b"VERS", 1, _) => ROT_VERS1,
// sidecar staging/devel hash
(SpComponent::ROT, b"SIGN", _, _) => &"1432cc4cfe5688c51b55546fe37837c753cfbc89e8c3c6aabcf977fdf0c41e27".as_bytes(),
(SpComponent::STAGE0, b"GITC", 0, false) => STAGE0_GITC0,
(SpComponent::STAGE0, b"GITC", 1, false) => STAGE0_GITC1,
(SpComponent::STAGE0, b"BORD", _, false) => STAGE0_BORD,
(SpComponent::STAGE0, b"NAME", _, false) => STAGE0_NAME,
(SpComponent::STAGE0, b"VERS", 0, false) => STAGE0_VERS0,
(SpComponent::STAGE0, b"VERS", 1, false) => STAGE0_VERS1,
// sidecar staging/devel hash
(SpComponent::STAGE0, b"SIGN", _, false) => &"1432cc4cfe5688c51b55546fe37837c753cfbc89e8c3c6aabcf977fdf0c41e27".as_bytes(),
_ => return Err(SpError::NoSuchCabooseKey(key)),
};

Expand Down
25 changes: 22 additions & 3 deletions update-common/src/artifacts/artifacts_with_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub struct ArtifactsWithPlan {
// will contain two entries mapping each of the images to their data.
by_hash: DebugIgnore<HashMap<ArtifactHashId, ExtractedArtifactDataHandle>>,

// Map from Rot artifact IDs to hash of signing information. This is
// used to select between different artifact versions in the same
// repository
rot_by_sign: DebugIgnore<HashMap<ArtifactId, Vec<u8>>>,
// The plan to use to update a component within the rack.
plan: UpdatePlan,
}
Expand Down Expand Up @@ -240,8 +244,13 @@ impl ArtifactsWithPlan {

// Ensure we know how to apply updates from this set of artifacts; we'll
// remember the plan we create.
let UpdatePlanBuildOutput { plan, by_id, by_hash, artifacts_meta } =
builder.build()?;
let UpdatePlanBuildOutput {
plan,
by_id,
by_hash,
rot_by_sign,
artifacts_meta,
} = builder.build()?;

let tuf_repository = repository.repo();

Expand All @@ -266,7 +275,13 @@ impl ArtifactsWithPlan {
let description =
TufRepoDescription { repo: repo_meta, artifacts: artifacts_meta };

Ok(Self { description, by_id, by_hash: by_hash.into(), plan })
Ok(Self {
description,
by_id,
by_hash: by_hash.into(),
rot_by_sign: rot_by_sign.into(),
plan,
})
}

/// Returns the `ArtifactsDocument` corresponding to this TUF repo.
Expand All @@ -289,6 +304,10 @@ impl ArtifactsWithPlan {
&self.plan
}

pub fn rot_by_sign(&self) -> &HashMap<ArtifactId, Vec<u8>> {
&self.rot_by_sign
}

pub fn get_by_hash(
&self,
id: &ArtifactHashId,
Expand Down
25 changes: 21 additions & 4 deletions update-common/src/artifacts/update_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ pub struct UpdatePlan {

// Used to represent the information extracted from signed RoT images. This
// is used when going from `UpdatePlanBuilder` -> `UpdatePlan` to check
// the versions on the RoT images
// the versions on the RoT images and also to generate the map of
// ArtifactId -> Sign hashes for checking artifacts
#[derive(Debug, Eq, Hash, PartialEq)]
struct RotSignData {
kind: KnownArtifactKind,
Expand Down Expand Up @@ -353,7 +354,10 @@ impl<'a> UpdatePlanBuilder<'a> {
read_hubris_sign_from_archive(artifact_id, image_a)?;

self.rot_by_sign
.entry(RotSignData { kind: artifact_kind, sign: image_a_sign })
.entry(RotSignData {
kind: artifact_kind,
sign: hex::decode(image_a_sign).expect("should decode"),
})
.or_default()
.push(artifact_id.clone());

Expand All @@ -377,7 +381,10 @@ impl<'a> UpdatePlanBuilder<'a> {
read_hubris_sign_from_archive(artifact_id, image_b)?;

self.rot_by_sign
.entry(RotSignData { kind: artifact_kind, sign: image_b_sign })
.entry(RotSignData {
kind: artifact_kind,
sign: hex::decode(image_b_sign).expect("should decode"),
})
.or_default()
.push(artifact_id.clone());

Expand Down Expand Up @@ -768,7 +775,7 @@ impl<'a> UpdatePlanBuilder<'a> {
// signing key have the same version. (i.e. allow gimlet_rot signed
// with a staging key to be a different version from gimlet_rot signed
// with a production key)
for (entry, versions) in self.rot_by_sign {
for (entry, versions) in &self.rot_by_sign {
let kind = entry.kind;
// This unwrap is safe because we check above that each of the types
// has at least one entry
Expand All @@ -784,6 +791,14 @@ impl<'a> UpdatePlanBuilder<'a> {
}
}
}

let mut rot_by_sign = HashMap::new();
for (k, v) in self.rot_by_sign {
for id in v {
rot_by_sign.insert(id, k.sign.clone());
}
}

// Repeat the same version check for all SP images. (This is a separate
// loop because the types of the iterators don't match.)
for (kind, mut single_board_sp_artifacts) in [
Expand Down Expand Up @@ -842,6 +857,7 @@ impl<'a> UpdatePlanBuilder<'a> {
plan,
by_id: self.by_id,
by_hash: self.by_hash,
rot_by_sign,
artifacts_meta: self.artifacts_meta,
})
}
Expand All @@ -852,6 +868,7 @@ pub struct UpdatePlanBuildOutput {
pub plan: UpdatePlan,
pub by_id: BTreeMap<ArtifactId, Vec<ArtifactHashId>>,
pub by_hash: HashMap<ArtifactHashId, ExtractedArtifactDataHandle>,
pub rot_by_sign: HashMap<ArtifactId, Vec<u8>>,
pub artifacts_meta: Vec<TufArtifactMeta>,
}

Expand Down
1 change: 1 addition & 0 deletions wicket/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ciborium.workspace = true
clap.workspace = true
crossterm.workspace = true
futures.workspace = true
hex.workspace = true
humantime.workspace = true
indexmap.workspace = true
indicatif.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion wicket/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub enum Event {
/// TUF repo artifacts unpacked by wicketd, and event reports
ArtifactsAndEventReports {
system_version: Option<SemverVersion>,
artifacts: Vec<ArtifactId>,
artifacts: Vec<(ArtifactId, Option<Vec<u8>>)>,
event_reports: EventReportMap,
},

Expand Down
30 changes: 30 additions & 0 deletions wicket/src/state/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ fn version_or_unknown(caboose: Option<&SpComponentCaboose>) -> String {
caboose.map(|c| c.version.as_str()).unwrap_or("UNKNOWN").to_string()
}

fn caboose_sign(caboose: Option<&SpComponentCaboose>) -> Option<Vec<u8>> {
match caboose {
None => None,
Some(c) => c.sign.as_ref().map(|s| hex::decode(s).expect("decode")),
}
}

impl Component {
pub fn sp(&self) -> &Sp {
match self {
Expand Down Expand Up @@ -171,6 +178,29 @@ impl Component {
self.sp().rot.as_ref().and_then(|rot| rot.caboose_b.as_ref()),
)
}

// Technically the slots could have different SIGN values in the
// caboose. An active slot implies the RoT is up and valid so
// we should rely on that value for selection
pub fn rot_sign(&self) -> Option<Vec<u8>> {
match self.rot_active_slot() {
None => return None,
Some(s) => match s {
RotSlot::A => caboose_sign(
self.sp()
.rot
.as_ref()
.and_then(|rot| rot.caboose_a.as_ref()),
),
RotSlot::B => caboose_sign(
self.sp()
.rot
.as_ref()
.and_then(|rot| rot.caboose_b.as_ref()),
),
},
}
}
}

/// The component type and its slot.
Expand Down
14 changes: 9 additions & 5 deletions wicket/src/state/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ use wicketd_client::types::{
pub struct RackUpdateState {
pub items: BTreeMap<ComponentId, UpdateItem>,
pub system_version: Option<SemverVersion>,
pub artifacts: Vec<ArtifactId>,
pub artifact_versions: BTreeMap<KnownArtifactKind, SemverVersion>,
pub artifacts: Vec<(ArtifactId, Option<Vec<u8>>)>,
pub artifact_versions:
BTreeMap<KnownArtifactKind, Vec<(SemverVersion, Option<Vec<u8>>)>>,
// The update item currently selected is recorded in
// state.rack_state.selected.
pub status_view_displayed: bool,
Expand Down Expand Up @@ -94,15 +95,18 @@ impl RackUpdateState {
&mut self,
logger: &Logger,
system_version: Option<SemverVersion>,
artifacts: Vec<ArtifactId>,
artifacts: Vec<(ArtifactId, Option<Vec<u8>>)>,
reports: EventReportMap,
) {
self.system_version = system_version;
self.artifacts = artifacts;
self.artifact_versions.clear();
for id in &mut self.artifacts {
for (id, s) in &mut self.artifacts {
if let Ok(known) = id.kind.parse() {
self.artifact_versions.insert(known, id.version.clone());
self.artifact_versions
.entry(known)
.and_modify(|x| x.push((id.version.clone(), s.clone())))
.or_insert(vec![(id.version.clone(), s.clone())]);
}
}

Expand Down
Loading

0 comments on commit 8610b4c

Please sign in to comment.