Skip to content

Commit ef54b56

Browse files
authored
Add measurement corpus as part of the control plane artifacts (#21)
1 parent c269f57 commit ef54b56

File tree

9 files changed

+154
-8
lines changed

9 files changed

+154
-8
lines changed

artifact/src/kind.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ pub enum KnownArtifactKind {
196196
SwitchSp,
197197
SwitchRot,
198198
SwitchRotBootloader,
199+
200+
// Reference Measurements
201+
MeasurementCorpus,
199202
}
200203

201204
impl KnownArtifactKind {
@@ -222,6 +225,7 @@ impl KnownArtifactKind {
222225
| KnownArtifactKind::Trampoline
223226
| KnownArtifactKind::ControlPlane
224227
| KnownArtifactKind::Zone
228+
| KnownArtifactKind::MeasurementCorpus
225229
| KnownArtifactKind::PscSp
226230
| KnownArtifactKind::PscRotBootloader
227231
| KnownArtifactKind::SwitchSp
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This manifest has two measurement corpora that hash to the same contents. This
2+
# is not allowed.
3+
4+
system_version = "1.0.0"
5+
6+
[[artifact.control_plane]]
7+
name = "fake-control-plane"
8+
version = "1.0.0"
9+
[artifact.control_plane.source]
10+
kind = "composite-control-plane"
11+
zones = [
12+
{ kind = "fake", artifact_name = "zone-1", file_name = "zone1.tar.gz", size = "1MiB" },
13+
{ kind = "fake", artifact_name = "zone-2", file_name = "zone2.tar.gz", size = "1MiB" },
14+
]
15+
measurement_corpus = [
16+
{ kind = "fake", artifact_name = "measurement1", file_name = "measurement1.cbor", size = "1MiB" },
17+
{ kind = "fake", artifact_name = "measurement1", file_name = "measurement1-dup.cbor", size = "1MiB" },
18+
]

bin/invalid-manifests/duplicate-zone-file-name.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ zones = [
1212
{ kind = "fake", artifact_name = "zone-1", file_name = "zone1.tar.gz", size = "1MiB" },
1313
{ kind = "fake", artifact_name = "zone-1", file_name = "zone1-dup.tar.gz", size = "1MiB" },
1414
]
15+
measurement_corpus = [
16+
{ kind = "fake", artifact_name = "measurement1", file_name = "measurement1.cbor", size = "1MiB" },
17+
{ kind = "fake", artifact_name = "measurement2", file_name = "measurement2.cbor", size = "1MiB" },
18+
]

bin/manifests/fake-non-semver.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ zones = [
4444
{ kind = "fake", artifact_name = "zone-1", file_name = "zone1.tar.gz", size = "1MiB" },
4545
{ kind = "fake", artifact_name = "zone-2", file_name = "zone2.tar.gz", size = "1MiB" },
4646
]
47+
measurement_corpus = [
48+
{ kind = "fake", artifact_name = "measurement1", file_name = "a.cbor", size = "1MiB" },
49+
{ kind = "fake", artifact_name = "measurement2", file_name = "b.cbor", size = "1MiB" },
50+
]
4751

4852
[[artifact.psc_sp]]
4953
name = "fake-psc-sp"

bin/manifests/fake.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ zones = [
4242
{ kind = "fake", artifact_name = "zone-1", file_name = "zone1.tar.gz", size = "1MiB" },
4343
{ kind = "fake", artifact_name = "zone-2", file_name = "zone2.tar.gz", size = "1MiB" },
4444
]
45+
measurement_corpus = [
46+
{ kind = "fake", artifact_name = "measurement1", file_name = "a.cbor", size = "1MiB" },
47+
{ kind = "fake", artifact_name = "measurement2", file_name = "b.cbor", size = "1MiB" },
48+
]
4549

4650
[[artifact.psc_sp]]
4751
name = "fake-psc-sp"

bin/tests/integration-tests/command_tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,41 @@ fn test_assemble_duplicate_zone() -> Result<()> {
258258
Ok(())
259259
}
260260

261+
#[test]
262+
fn test_assemble_duplicate_measurement() -> Result<()> {
263+
let log_config = ConfigLogging::File {
264+
level: ConfigLoggingLevel::Trace,
265+
path: "UNUSED".into(),
266+
if_exists: ConfigLoggingIfExists::Fail,
267+
};
268+
let logctx =
269+
LogContext::new("test_assemble_duplicate_measurement", &log_config);
270+
let tempdir = tempfile::tempdir().unwrap();
271+
let key = Key::generate_ed25519()?;
272+
273+
let archive_path = tempdir.path().join("archive.zip");
274+
275+
let mut cmd = make_cmd(&key);
276+
cmd.args([
277+
"assemble",
278+
"--skip-all-present",
279+
"invalid-manifests/duplicate-measurement.toml",
280+
]);
281+
cmd.arg(&archive_path);
282+
cmd.assert()
283+
.failure()
284+
.stderr(predicate::str::contains(
285+
r#"a deployment unit with the same kind and hash already exists in this control_plane artifact:"#,
286+
))
287+
.stderr(predicate::str::contains("zone/"))
288+
.stderr(predicate::str::contains(
289+
r#"(existing name: measurement1.cbor, version: 1.0.0; new name: measurement1-dup.cbor, version: 1.0.0)"#,
290+
));
291+
292+
logctx.cleanup_successful();
293+
Ok(())
294+
}
295+
261296
#[test]
262297
fn test_assemble_duplicate_artifact() -> Result<()> {
263298
let log_config = ConfigLogging::File {

lib/src/artifact.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,31 +369,44 @@ impl RotArchives {
369369
}
370370
}
371371

372+
/// Represents an entry type in a control plane, used with `ControlPlaneImages::extract_into`
373+
pub enum ControlPlaneEntry {
374+
Zone,
375+
MeasurementCorpus,
376+
}
377+
372378
/// Represents control plane zone images.
373379
///
374380
/// The control plane artifact is actually a tarball that contains a set of zone
375381
/// images. This code extracts those images out of the tarball.
376382
#[derive(Clone, Debug)]
377383
pub struct ControlPlaneZoneImages {
378384
pub zones: Vec<(String, Bytes)>,
385+
pub measurement_corpus: Vec<(String, Bytes)>,
379386
}
380387

381388
impl ControlPlaneZoneImages {
382389
pub fn extract<R: io::Read>(reader: R) -> Result<Self> {
383390
let mut zones = Vec::new();
384-
Self::extract_into(reader, |name, reader| {
391+
let mut measurement_corpus = Vec::new();
392+
Self::extract_into(reader, |name, kind, reader| {
385393
let mut buf = Vec::new();
386394
io::copy(reader, &mut buf)?;
387-
zones.push((name, buf.into()));
395+
match kind {
396+
ControlPlaneEntry::Zone => zones.push((name, buf.into())),
397+
ControlPlaneEntry::MeasurementCorpus => {
398+
measurement_corpus.push((name, buf.into()))
399+
}
400+
}
388401
Ok(())
389402
})?;
390-
Ok(Self { zones })
403+
Ok(Self { zones, measurement_corpus })
391404
}
392405

393406
pub fn extract_into<R, F>(reader: R, mut handler: F) -> Result<()>
394407
where
395408
R: io::Read,
396-
F: FnMut(String, &mut dyn io::Read) -> Result<()>,
409+
F: FnMut(String, ControlPlaneEntry, &mut dyn io::Read) -> Result<()>,
397410
{
398411
let uncompressed =
399412
flate2::bufread::GzDecoder::new(BufReader::new(reader));
@@ -432,9 +445,24 @@ impl ControlPlaneZoneImages {
432445
.and_then(|s| s.to_str())
433446
.map(|s| s.to_string())
434447
{
435-
handler(name, &mut entry)?;
448+
handler(name, ControlPlaneEntry::Zone, &mut entry)?;
436449
}
437450
zone_found = true;
451+
} else if path
452+
.starts_with(CONTROL_PLANE_ARCHIVE_MEASUREMENT_DIRECTORY)
453+
{
454+
if let Some(name) = path
455+
.file_name()
456+
.and_then(|s| s.to_str())
457+
.map(|s| s.to_string())
458+
{
459+
handler(
460+
name,
461+
ControlPlaneEntry::MeasurementCorpus,
462+
&mut entry,
463+
)?;
464+
}
465+
// We will eventually want to make this required
438466
}
439467
}
440468

@@ -463,3 +491,4 @@ pub(crate) static HOST_PHASE_2_FILE_NAME: &str = "image/zfs.img";
463491
pub(crate) static ROT_ARCHIVE_A_FILE_NAME: &str = "archive-a.zip";
464492
pub(crate) static ROT_ARCHIVE_B_FILE_NAME: &str = "archive-b.zip";
465493
static CONTROL_PLANE_ARCHIVE_ZONE_DIRECTORY: &str = "zones";
494+
static CONTROL_PLANE_ARCHIVE_MEASUREMENT_DIRECTORY: &str = "measurements";

lib/src/artifact/composite.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use tufaceous_artifact::ArtifactHash;
1313
use tufaceous_brand_metadata::{ArchiveType, Metadata};
1414

1515
use super::{
16+
CONTROL_PLANE_ARCHIVE_MEASUREMENT_DIRECTORY,
1617
CONTROL_PLANE_ARCHIVE_ZONE_DIRECTORY, HOST_PHASE_1_FILE_NAME,
1718
HOST_PHASE_2_FILE_NAME, ROT_ARCHIVE_A_FILE_NAME, ROT_ARCHIVE_B_FILE_NAME,
1819
};
@@ -56,6 +57,21 @@ impl<W: Write> CompositeControlPlaneArchiveBuilder<W> {
5657
Ok((hash, name.to_owned()))
5758
}
5859

60+
pub fn append_measurement(
61+
&mut self,
62+
name: &str,
63+
entry: CompositeEntry<'_>,
64+
) -> Result<(ArtifactHash, String)> {
65+
let name_path = Utf8Path::new(name);
66+
if name_path.file_name() != Some(name) {
67+
bail!("measurement filenames should not contain paths");
68+
}
69+
let path = Utf8Path::new(CONTROL_PLANE_ARCHIVE_MEASUREMENT_DIRECTORY)
70+
.join(name_path);
71+
let hash = self.inner.append_file(path.as_str(), entry)?;
72+
Ok((hash, name.to_owned()))
73+
}
74+
5975
pub fn finish(self) -> Result<W> {
6076
self.inner.finish()
6177
}

lib/src/assemble/manifest.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ impl ArtifactManifest {
247247
}
248248
DeserializedArtifactSource::CompositeControlPlane {
249249
zones,
250+
measurement_corpus,
250251
} => {
251252
ensure!(
252253
kind == KnownArtifactKind::ControlPlane,
@@ -289,6 +290,21 @@ impl ArtifactManifest {
289290
hash,
290291
})?;
291292
}
293+
for manifest in measurement_corpus {
294+
let (hash, name) = manifest.with_name_and_entry(
295+
&artifact_data.version,
296+
|name, entry| {
297+
builder.append_measurement(name, entry)
298+
},
299+
)?;
300+
data_builder.insert(DeploymentUnitData {
301+
name: name.to_owned(),
302+
version: artifact_data.version.clone(),
303+
kind: zone_kind.clone(),
304+
hash,
305+
})?;
306+
}
307+
292308
(
293309
ArtifactSource::Memory(builder.finish()?.into()),
294310
data_builder.finish_units(),
@@ -338,7 +354,13 @@ impl ArtifactManifest {
338354
/// details if any artifacts are missing.
339355
pub fn verify_all_present(&self) -> Result<()> {
340356
let all_artifacts: BTreeSet<_> = KnownArtifactKind::iter()
341-
.filter(|k| !matches!(k, KnownArtifactKind::Zone))
357+
.filter(|k| {
358+
!matches!(
359+
k,
360+
KnownArtifactKind::Zone
361+
| KnownArtifactKind::MeasurementCorpus
362+
)
363+
})
342364
.collect();
343365
let present_artifacts: BTreeSet<_> =
344366
self.artifacts.keys().copied().collect();
@@ -377,7 +399,8 @@ impl<'a> FakeDataAttributes<'a> {
377399
KnownArtifactKind::Host
378400
| KnownArtifactKind::Trampoline
379401
| KnownArtifactKind::ControlPlane
380-
| KnownArtifactKind::Zone => {
402+
| KnownArtifactKind::Zone
403+
| KnownArtifactKind::MeasurementCorpus => {
381404
return make_filler_text(
382405
&self.kind.to_string(),
383406
self.version,
@@ -582,6 +605,7 @@ pub enum DeserializedArtifactSource {
582605
},
583606
CompositeControlPlane {
584607
zones: Vec<DeserializedControlPlaneZoneSource>,
608+
measurement_corpus: Vec<DeserializedControlPlaneZoneSource>,
585609
},
586610
}
587611

@@ -608,10 +632,16 @@ impl DeserializedArtifactSource {
608632
archive_b.apply_size_delta(size_delta)?;
609633
Ok(())
610634
}
611-
DeserializedArtifactSource::CompositeControlPlane { zones } => {
635+
DeserializedArtifactSource::CompositeControlPlane {
636+
zones,
637+
measurement_corpus,
638+
} => {
612639
for zone in zones {
613640
zone.apply_size_delta(size_delta)?;
614641
}
642+
for manifest in measurement_corpus {
643+
manifest.apply_size_delta(size_delta)?;
644+
}
615645
Ok(())
616646
}
617647
}
@@ -696,6 +726,8 @@ impl DeserializedFileArtifactSource {
696726
}
697727
}
698728

729+
// FIXME probably give this a better name if we're going to use it for
730+
// measurements
699731
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
700732
#[serde(tag = "kind", rename_all = "snake_case")]
701733
pub enum DeserializedControlPlaneZoneSource {

0 commit comments

Comments
 (0)