Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter available artifacts by signing hash #5851

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 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
Loading