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 3, 2024
1 parent 450f906 commit 2269bd1
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 37 deletions.
14 changes: 13 additions & 1 deletion gateway/src/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,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 @@ -773,6 +774,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 @@ -826,13 +828,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 @@ -1049,6 +1049,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 @@ -1115,6 +1116,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
4 changes: 4 additions & 0 deletions openapi/gateway.json
Original file line number Diff line number Diff line change
Expand Up @@ -2428,6 +2428,10 @@
"name": {
"type": "string"
},
"sign": {
"nullable": true,
"type": "string"
},
"version": {
"type": "string"
}
Expand Down
15 changes: 14 additions & 1 deletion openapi/wicketd.json
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,15 @@
"items": {
"$ref": "#/components/schemas/ArtifactHashId"
}
},
"sign": {
"nullable": true,
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
}
},
"required": [
Expand Down Expand Up @@ -2773,7 +2782,7 @@
]
},
"SpComponentCaboose": {
"description": "SpComponentCaboose\n\n<details><summary>JSON schema</summary>\n\n```json { \"type\": \"object\", \"required\": [ \"board\", \"git_commit\", \"name\", \"version\" ], \"properties\": { \"board\": { \"type\": \"string\" }, \"git_commit\": { \"type\": \"string\" }, \"name\": { \"type\": \"string\" }, \"version\": { \"type\": \"string\" } } } ``` </details>",
"description": "SpComponentCaboose\n\n<details><summary>JSON schema</summary>\n\n```json { \"type\": \"object\", \"required\": [ \"board\", \"git_commit\", \"name\", \"version\" ], \"properties\": { \"board\": { \"type\": \"string\" }, \"git_commit\": { \"type\": \"string\" }, \"name\": { \"type\": \"string\" }, \"sign\": { \"type\": [ \"string\", \"null\" ] }, \"version\": { \"type\": \"string\" } } } ``` </details>",
"type": "object",
"properties": {
"board": {
Expand All @@ -2785,6 +2794,10 @@
"name": {
"type": "string"
},
"sign": {
"nullable": true,
"type": "string"
},
"version": {
"type": "string"
}
Expand Down
4 changes: 2 additions & 2 deletions oximeter/collector/tests/output/self-stat-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
}
],
"datum_type": "cumulative_u64",
"created": "2024-05-21T18:32:24.199619581Z"
"created": "2024-06-03T14:56:43.613214591Z"
},
"oximeter_collector:failed_collections": {
"timeseries_name": "oximeter_collector:failed_collections",
Expand Down Expand Up @@ -86,6 +86,6 @@
}
],
"datum_type": "cumulative_u64",
"created": "2024-05-21T18:32:24.200514936Z"
"created": "2024-06-03T14:56:43.613836009Z"
}
}
2 changes: 2 additions & 0 deletions sp-sim/src/gimlet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,8 @@ impl SpHandler for Handler {
(SpComponent::ROT, b"BORD") => ROT_BORD,
(SpComponent::ROT, b"NAME") => ROT_NAME,
(SpComponent::ROT, b"VERS") => ROT_VERS,
// gimlet staging/devel hash
(SpComponent::ROT, b"SIGN") => &"11594bb5548a757e918e6fe056e2ad9e084297c9555417a025d8788eacf55daf".as_bytes(),
_ => return Err(SpError::NoSuchCabooseKey(key)),
};

Expand Down
2 changes: 2 additions & 0 deletions sp-sim/src/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,8 @@ impl SpHandler for Handler {
(SpComponent::ROT, b"BORD") => ROT_BORD,
(SpComponent::ROT, b"NAME") => ROT_NAME,
(SpComponent::ROT, b"VERS") => ROT_VERS,
// sidecar staging/devel hash
(SpComponent::ROT, b"SIGN") => &"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
15 changes: 13 additions & 2 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 @@ -768,7 +769,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 +785,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 +851,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 +862,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
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(|x| Vec::from(x.as_bytes())),
}
}

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
2 changes: 1 addition & 1 deletion wicket/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use inventory::{
pub use rack::{KnightRiderMode, RackState};
pub use status::ServiceStatus;
pub use update::{
parse_event_report_map, update_component_title,
parse_event_report_map, update_component_title, ArtifactVersion,
CreateClearUpdateStateOptions, CreateStartUpdateOptions, RackUpdateState,
UpdateItemState,
};
Expand Down
27 changes: 22 additions & 5 deletions wicket/src/state/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ use wicketd_client::types::{
ArtifactId, ClearUpdateStateOptions, SemverVersion, StartUpdateOptions,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactVersion {
pub version: SemverVersion,
pub sign: Option<Vec<u8>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
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<ArtifactVersion>>,
// The update item currently selected is recorded in
// state.rack_state.selected.
pub status_view_displayed: bool,
Expand Down Expand Up @@ -94,15 +100,26 @@ 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(ArtifactVersion {
version: id.version.clone(),
sign: s.clone(),
})
})
.or_insert(vec![ArtifactVersion {
version: id.version.clone(),
sign: s.clone(),
}]);
}
}

Expand Down
11 changes: 11 additions & 0 deletions wicket/src/ui/panes/overview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ fn append_caboose(
git_commit,
// Currently `name` is always the same as `board`, so we'll skip it.
name: _,
sign,
version,
} = caboose;
let label_style = style::text_label();
Expand All @@ -903,6 +904,16 @@ fn append_caboose(
]
.into(),
);
if let Some(s) = sign {
spans.push(
vec![
prefix.clone(),
Span::styled("Sign Hash: ", label_style),
Span::styled(s.clone(), ok_style),
]
.into(),
);
}
let mut version_spans =
vec![prefix.clone(), Span::styled("Version: ", label_style)];
version_spans.push(Span::styled(version, ok_style));
Expand Down
Loading

0 comments on commit 2269bd1

Please sign in to comment.