Skip to content

Commit 980fa54

Browse files
Add support for SLSA Build Provenance (#55)
* Add SLSA Build Provenance generators Signed-off-by: Marcela Melara <[email protected]> * Debug SLSA provenance generation Signed-off-by: Marcela Melara <[email protected]> * Fix compiler errors Signed-off-by: Marcela Melara <[email protected]> * Remove SLSA test script Signed-off-by: Marcela Melara <[email protected]> * Add key path checking to SLSA cli generator Signed-off-by: Marcela Melara <[email protected]> * Use encoding flag in every command Signed-off-by: Marcela Melara <[email protected]> * Fix error Signed-off-by: Marcela Melara <[email protected]> --------- Signed-off-by: Marcela Melara <[email protected]>
1 parent 4487c25 commit 980fa54

File tree

9 files changed

+408
-26
lines changed

9 files changed

+408
-26
lines changed

src/cli/commands.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ pub enum DatasetCommands {
6161
#[arg(long = "print")]
6262
print: bool,
6363

64-
/// Output format (json or cbor)
65-
#[arg(long = "format", default_value = "json")]
66-
format: String,
64+
/// Output encoding (json or cbor)
65+
#[arg(long = "encoding", default_value = "json")]
66+
encoding: String,
6767

6868
/// Storage backend (local or rekor)
6969
#[arg(long = "storage-type", default_value = "database")]
@@ -289,9 +289,9 @@ pub enum ManifestCommands {
289289
#[arg(long = "storage-url", default_value = "http://localhost:8080")]
290290
storage_url: Box<String>,
291291

292-
/// Output format (json or yaml)
293-
#[arg(long = "format", default_value = "json")]
294-
format: String,
292+
/// Output encoding (json or yaml)
293+
#[arg(long = "encoding", default_value = "json")]
294+
encoding: String,
295295

296296
/// Output file path (defaults to stdout if not provided)
297297
#[arg(short, long)]
@@ -351,9 +351,9 @@ pub enum EvaluationCommands {
351351
#[arg(long = "print")]
352352
print: bool,
353353

354-
/// Output format (json or cbor)
355-
#[arg(long = "format", default_value = "json")]
356-
format: String,
354+
/// Output encoding (json or cbor)
355+
#[arg(long = "encoding", default_value = "json")]
356+
encoding: String,
357357

358358
/// Storage backend (local or rekor)
359359
#[arg(long = "storage-type", default_value = "database")]
@@ -458,9 +458,9 @@ pub enum SoftwareCommands {
458458
#[arg(long = "print")]
459459
print: bool,
460460

461-
/// Output format (json or cbor)
462-
#[arg(long = "format", default_value = "json")]
463-
format: String,
461+
/// Output encoding (json or cbor)
462+
#[arg(long = "encoding", default_value = "json")]
463+
encoding: String,
464464

465465
/// Storage backend (local or rekor)
466466
#[arg(long = "storage-type", default_value = "database")]
@@ -534,3 +534,49 @@ pub enum SoftwareCommands {
534534
storage_url: Box<String>,
535535
},
536536
}
537+
538+
#[derive(Debug, Subcommand)]
539+
pub enum PipelineCommands {
540+
/// Generate SLSA Build Provenance v1 for the given pipeline
541+
GenerateProvenance {
542+
/// Paths to any pipeline inputs and other external parameters
543+
#[arg(long = "inputs", num_args = 1.., value_delimiter = ',')]
544+
inputs: Vec<PathBuf>,
545+
546+
/// Path to pipeline script or configuration
547+
#[arg(long = "pipeline")]
548+
pipeline: PathBuf,
549+
550+
/// Paths to any pipeline products
551+
#[arg(long = "products", num_args = 1.., value_delimiter = ',')]
552+
products: Vec<PathBuf>,
553+
554+
/// Path to private key file for signing (PEM format)
555+
#[arg(long = "key")]
556+
key: Option<PathBuf>,
557+
558+
/// Hash algorithm to use for signing (default: sha384)
559+
#[arg(long = "hash-alg", value_enum, default_value = "sha384")]
560+
hash_alg: HashAlgorithmChoice,
561+
562+
/// Only print SLSA Provenance without storing
563+
#[arg(long = "print")]
564+
print: bool,
565+
566+
/// Output encoding (json or cbor)
567+
#[arg(long = "encoding", default_value = "json")]
568+
encoding: String,
569+
570+
/// Storage backend (only local supported)
571+
#[arg(long = "storage-type", default_value = "local-fs")]
572+
storage_type: Box<String>,
573+
574+
/// Storage URL
575+
#[arg(long = "storage-url", default_value = "http://localhost:8080")]
576+
storage_url: Box<String>,
577+
578+
/// Collect the underlying TDX attestation, if available
579+
#[arg(long = "with-tdx", default_value = "false")]
580+
with_tdx: bool,
581+
},
582+
}

src/cli/handlers.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ use crate::error::{Error, Result};
22

33
use super::commands::{
44
CCAttestationCommands, DatasetCommands, EvaluationCommands, ManifestCommands, ModelCommands,
5-
SoftwareCommands,
5+
PipelineCommands, SoftwareCommands,
66
};
77
use crate::cc_attestation;
88
use crate::manifest;
99
use crate::manifest::config::ManifestCreationConfig;
1010
use crate::manifest::dataset::list_dataset_manifests;
11+
use crate::slsa;
1112
use crate::storage::database::DatabaseStorage;
1213
use crate::storage::filesystem::FilesystemStorage;
1314
use crate::storage::rekor::RekorStorage;
@@ -28,7 +29,7 @@ pub fn handle_dataset_command(cmd: DatasetCommands) -> Result<()> {
2829
storage_type,
2930
storage_url,
3031
print,
31-
format,
32+
encoding,
3233
key,
3334
hash_alg,
3435
with_tdx,
@@ -59,7 +60,7 @@ pub fn handle_dataset_command(cmd: DatasetCommands) -> Result<()> {
5960
linked_manifests,
6061
storage,
6162
print,
62-
output_encoding: format,
63+
output_encoding: encoding,
6364
key_path: key,
6465
hash_alg: hash_alg.to_cose_algorithm(),
6566
with_cc: with_tdx,
@@ -286,7 +287,7 @@ pub fn handle_manifest_command(cmd: ManifestCommands) -> Result<()> {
286287
id,
287288
storage_type,
288289
storage_url,
289-
format,
290+
encoding,
290291
output,
291292
max_depth,
292293
} => {
@@ -300,7 +301,7 @@ pub fn handle_manifest_command(cmd: ManifestCommands) -> Result<()> {
300301
manifest::export_provenance(
301302
&id,
302303
&*storage,
303-
format.as_str(),
304+
encoding.as_str(),
304305
output.as_deref(),
305306
max_depth,
306307
)
@@ -322,7 +323,7 @@ pub fn handle_evaluation_command(cmd: EvaluationCommands) -> Result<()> {
322323
storage_type,
323324
storage_url,
324325
print,
325-
format,
326+
encoding,
326327
key,
327328
hash_alg,
328329
} => {
@@ -352,7 +353,7 @@ pub fn handle_evaluation_command(cmd: EvaluationCommands) -> Result<()> {
352353
linked_manifests: None, // Will be populated by create_manifest
353354
storage,
354355
print,
355-
output_encoding: format,
356+
output_encoding: encoding,
356357
key_path: key,
357358
hash_alg: hash_alg.to_cose_algorithm(),
358359
with_cc: false,
@@ -437,7 +438,7 @@ pub fn handle_software_command(cmd: SoftwareCommands) -> Result<()> {
437438
storage_type,
438439
storage_url,
439440
print,
440-
format,
441+
encoding,
441442
key,
442443
hash_alg,
443444
with_tdx,
@@ -468,7 +469,7 @@ pub fn handle_software_command(cmd: SoftwareCommands) -> Result<()> {
468469
linked_manifests,
469470
storage,
470471
print,
471-
output_encoding: format,
472+
output_encoding: encoding,
472473
key_path: key,
473474
hash_alg: hash_alg.to_cose_algorithm(),
474475
with_cc: with_tdx,
@@ -540,3 +541,40 @@ pub fn handle_software_command(cmd: SoftwareCommands) -> Result<()> {
540541
}
541542
}
542543
}
544+
545+
pub fn handle_pipeline_command(cmd: PipelineCommands) -> Result<()> {
546+
match cmd {
547+
PipelineCommands::GenerateProvenance {
548+
inputs,
549+
pipeline,
550+
products,
551+
key,
552+
hash_alg,
553+
encoding,
554+
print,
555+
storage_type,
556+
storage_url,
557+
with_tdx,
558+
} => {
559+
let storage: Option<&'static dyn StorageBackend> = match storage_type.as_str() {
560+
"local-fs" => {
561+
let fs_storage = Box::new(FilesystemStorage::new(storage_url.as_str())?);
562+
Some(Box::leak(fs_storage))
563+
}
564+
_ => None,
565+
};
566+
567+
slsa::cli::generate_build_provenance(
568+
inputs,
569+
pipeline,
570+
products,
571+
key,
572+
hash_alg.to_cose_algorithm(),
573+
encoding,
574+
print,
575+
storage,
576+
with_tdx,
577+
)
578+
}
579+
}
580+
}

src/cli/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ use crate::error::Error;
44

55
// Re-export commonly used items
66
pub use commands::{
7-
CCAttestationCommands, DatasetCommands, ManifestCommands, ModelCommands, SoftwareCommands,
7+
CCAttestationCommands, DatasetCommands, ManifestCommands, ModelCommands, PipelineCommands,
8+
SoftwareCommands,
89
};
910
pub use handlers::{
1011
handle_cc_attestation_command, handle_dataset_command, handle_manifest_command,
11-
handle_model_command, handle_software_command,
12+
handle_model_command, handle_pipeline_command, handle_software_command,
1213
};
1314

1415
// Optional: Add any CLI-specific constants or shared utilities
1516
pub const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
16-
pub const CLI_NAME: &str = "c2pa-cli";
17+
pub const CLI_NAME: &str = "atlas-cli";
1718

1819
pub fn format_error(error: &Error) -> String {
1920
match error {

src/in_toto/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::{Error, Result};
2+
use crate::hash;
23
use crate::signing::signable::Signable;
34

45
use atlas_c2pa_lib::cose::HashAlgorithm;
@@ -7,7 +8,7 @@ use in_toto_attestation::v1::resource_descriptor::ResourceDescriptor;
78
use protobuf::well_known_types::struct_::Struct;
89
use protobuf_json_mapping::{parse_from_str, print_to_string};
910
use std::collections::HashMap;
10-
use std::path::PathBuf;
11+
use std::path::{Path, PathBuf};
1112

1213
pub mod dsse;
1314

@@ -33,6 +34,21 @@ pub fn make_minimal_resource_descriptor(name: &str, alg: &str, digest: &str) ->
3334
rd
3435
}
3536

37+
pub fn generate_file_resource_descriptor_from_path(
38+
path: &Path,
39+
algorithm: &HashAlgorithm,
40+
) -> Result<ResourceDescriptor> {
41+
let file_hash = hash::calculate_file_hash_with_algorithm(path, algorithm)?;
42+
43+
let digest_set = HashMap::from([(algorithm.as_str().to_string(), file_hash.to_string())]);
44+
45+
let mut rd = ResourceDescriptor::new();
46+
rd.name = String::from(path.to_string_lossy());
47+
rd.digest = digest_set;
48+
49+
Ok(rd)
50+
}
51+
3652
pub fn generate_signed_statement_v1(
3753
subject: &[ResourceDescriptor],
3854
predicate_type: &str,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub mod hash;
3838
pub mod in_toto;
3939
pub mod manifest;
4040
pub mod signing;
41+
pub mod slsa;
4142
pub mod storage;
4243
#[cfg(test)]
4344
mod tests;

src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use atlas_cli::{
33
self,
44
commands::{
55
CCAttestationCommands, DatasetCommands, EvaluationCommands, ManifestCommands,
6-
ModelCommands, SoftwareCommands,
6+
ModelCommands, PipelineCommands, SoftwareCommands,
77
},
88
},
99
error::Result,
@@ -44,6 +44,11 @@ enum Commands {
4444
#[command(subcommand)]
4545
command: EvaluationCommands,
4646
},
47+
/// Pipeline-related commands
48+
Pipeline {
49+
#[command(subcommand)]
50+
command: PipelineCommands,
51+
},
4752
/// CC Attestation-related commands
4853
CCAttestation {
4954
#[command(subcommand)]
@@ -66,6 +71,7 @@ fn main() -> Result<()> {
6671

6772
Commands::Manifest { command } => cli::handlers::handle_manifest_command(command),
6873
Commands::Evaluation { command } => cli::handlers::handle_evaluation_command(command),
74+
Commands::Pipeline { command } => cli::handlers::handle_pipeline_command(command),
6975
Commands::CCAttestation { command } => {
7076
cli::handlers::handle_cc_attestation_command(command)
7177
}

0 commit comments

Comments
 (0)