clawft_kernel/workload_pkg/
store.rs1use std::path::Path;
7
8use crate::artifact_store::{ArtifactStore, ArtifactType};
9use crate::workload_kind::KindRegistry;
10
11use super::manifest::{FileRef, MANIFEST_FILE, MAX_MANIFEST_BYTES};
12use super::trust::TrustAnchors;
13use super::verify::{
14 DirSource, FileSource, VerifiedPackage, VerifyError, VerifyPolicy, read_bounded,
15 verify_with_source, verify_with_source_in,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct StoredPackage {
21 pub package_id: String,
23 pub manifest_hash: String,
26 pub files: Vec<(String, String)>,
28}
29
30pub struct StoreSource<'a> {
32 store: &'a ArtifactStore,
33}
34
35impl<'a> StoreSource<'a> {
36 pub fn new(store: &'a ArtifactStore) -> Self {
38 Self { store }
39 }
40}
41
42impl FileSource for StoreSource<'_> {
43 fn read(&self, file: &FileRef) -> Result<Vec<u8>, VerifyError> {
44 if !self.store.contains(&file.blake3) {
45 return Err(VerifyError::FileMissing {
46 path: file.path.clone(),
47 });
48 }
49 self.store.load(&file.blake3).map_err(|e| {
50 let msg = e.to_string();
51 if msg.contains("integrity error") {
52 VerifyError::HashMismatch {
55 path: file.path.clone(),
56 expected: file.blake3.clone(),
57 actual: "stored content no longer matches its hash".into(),
58 }
59 } else {
60 VerifyError::Io {
61 path: file.path.clone(),
62 msg,
63 }
64 }
65 })
66 }
67}
68
69fn store_err(path: &str, e: impl std::fmt::Display) -> VerifyError {
70 VerifyError::Io {
71 path: path.to_string(),
72 msg: e.to_string(),
73 }
74}
75
76pub fn store_package(
78 store: &ArtifactStore,
79 dir: &Path,
80 anchors: &TrustAnchors,
81 policy: &VerifyPolicy,
82) -> Result<StoredPackage, VerifyError> {
83 let manifest = read_bounded(
84 &dir.join(MANIFEST_FILE),
85 MANIFEST_FILE,
86 MAX_MANIFEST_BYTES as u64,
87 )?;
88 let source = DirSource::new(dir);
89 let verified = verify_with_source(&manifest, &source, anchors, policy)?;
90
91 let mut files = Vec::new();
92 for file in verified.body.files() {
93 let content = source.read(file)?;
95 super::verify::check_file(file, &content)?;
96 let kind = if file.path == "cog.toml" {
97 ArtifactType::ConfigBundle
98 } else {
99 ArtifactType::Generic
100 };
101 let hash = store
102 .store(&content, kind)
103 .map_err(|e| store_err(&file.path, e))?;
104 files.push((file.path.clone(), hash));
105 }
106 let manifest_hash = store
107 .store(&manifest, ArtifactType::AppManifest)
108 .map_err(|e| store_err(MANIFEST_FILE, e))?;
109 Ok(StoredPackage {
110 package_id: verified.package_id,
111 manifest_hash,
112 files,
113 })
114}
115
116pub fn verify_stored(
119 store: &ArtifactStore,
120 manifest_hash: &str,
121 anchors: &TrustAnchors,
122 policy: &VerifyPolicy,
123) -> Result<VerifiedPackage, VerifyError> {
124 verify_stored_in(
125 store,
126 manifest_hash,
127 anchors,
128 policy,
129 &KindRegistry::builtin(),
130 )
131}
132
133pub fn verify_stored_in(
135 store: &ArtifactStore,
136 manifest_hash: &str,
137 anchors: &TrustAnchors,
138 policy: &VerifyPolicy,
139 kinds: &KindRegistry,
140) -> Result<VerifiedPackage, VerifyError> {
141 if !store.contains(manifest_hash) {
142 return Err(VerifyError::FileMissing {
143 path: format!("{MANIFEST_FILE} ({manifest_hash})"),
144 });
145 }
146 let manifest = store
147 .load(manifest_hash)
148 .map_err(|e| store_err(MANIFEST_FILE, e))?;
149 verify_with_source_in(&manifest, &StoreSource::new(store), anchors, policy, kinds)
150}