Skip to content

Commit

Permalink
Add SCAI support
Browse files Browse the repository at this point in the history
  • Loading branch information
mlieberman85 committed Dec 14, 2023
1 parent afd9bc0 commit d587226
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 51 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ schemars = { version = "0.8.12", features = ["chrono", "url"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
syn = "2.0.15"
typify = "0.0.12"
typify = "0.0.14"
url = "2.2"

[dev-dependencies]
Expand Down
51 changes: 46 additions & 5 deletions src/bin/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use spector::{
models::{
intoto::{
predicate::Predicate, provenance::SLSAProvenanceV1Predicate,
statement::InTotoStatementV1,
statement::{InTotoStatementV1}, scai::SCAIV02Predicate,
},
sbom::{spdx22::Spdx22Document, spdx23::Spdx23},
},
Expand Down Expand Up @@ -104,6 +104,8 @@ enum ValidateDocumentSubCommand {
#[derive(Parser)]
enum GenerateDocumentSubCommand {
InTotoV1(GenerateInTotoV1),
SLSAProvenanceV01,
SCAIV02,
}

// The In-Toto v1 validate document subcommand
Expand Down Expand Up @@ -149,9 +151,10 @@ struct GenerateInTotoV1 {
predicate: Option<PredicateOption>,
}

#[derive(Copy, Clone, ValueEnum)]
#[derive(Debug, Copy, Clone, ValueEnum)]
enum PredicateOption {
SLSAProvenanceV1,
SCAIV02Predicate,
}

#[derive(Parser)]
Expand All @@ -171,6 +174,8 @@ fn validate_cmd(validate: Validate) -> Result<()> {
fn generate_cmd(generate: SchemaGenerate) -> Result<()> {
match generate.document {
GenerateDocumentSubCommand::InTotoV1(in_toto) => generate_intoto_v1(in_toto),
GenerateDocumentSubCommand::SLSAProvenanceV01 => generate_slsa_provenancev01(),
GenerateDocumentSubCommand::SCAIV02 => generate_scaiv02()
}
}

Expand All @@ -190,19 +195,38 @@ fn validate_intoto_v1(in_toto: ValidateInTotoV1) -> Result<()> {
Ok(())
}
// TODO(mlieberman85): Uncomment below once additional predicate types are supported.
/*Some(_) => {
eprintln!("Invalid InTotoV1 SLSAProvenanceV1 document");
Some(_) => {
eprintln!("Invalid InTotoV1 SLSAProvenanceV1 document. Unexpected predicateType: {:?}", in_toto.predicate);
eprintln!("Document: {}", &pretty_json);
Err(anyhow::anyhow!(
"Invalid InTotoV1 SLSAProvenanceV1 document"
))
}*/
}
None => {
println!("Valid InTotoV1 SLSAProvenanceV1 document");
println!("Document: {}", &pretty_json);
Ok(())
}
},
Predicate::SCAIV02(_) => match in_toto.predicate {
Some(PredicateOption::SCAIV02Predicate) => {
println!("Valid InTotoV1 SCAIV02Predicate document");
println!("Document: {}", &pretty_json);
Ok(())
}
Some(_) => {
eprintln!("Invalid InTotoV1 SCAIV02Predicate document. Unexpected predicateType: {:?}", in_toto.predicate);
eprintln!("Document: {}", &pretty_json);
Err(anyhow::anyhow!(
"Invalid InTotoV1 SCAIV02Predicate document"
))
}
None => {
println!("Valid InTotoV1 SCAIV02Predicate document");
println!("Document: {}", &pretty_json);
Ok(())
}
}
_ => {
if let Some(PredicateOption::SLSAProvenanceV1) = in_toto.predicate {
eprintln!("Invalid InTotoV1 SLSAProvenanceV1 document");
Expand All @@ -211,6 +235,14 @@ fn validate_intoto_v1(in_toto: ValidateInTotoV1) -> Result<()> {
"Unexpected predicateType: {:?}",
statement.predicate_type.as_str()
))
}
else if let Some(PredicateOption::SCAIV02Predicate) = in_toto.predicate {
eprintln!("Invalid InTotoV1 SCAIV02Predicate document");
eprintln!("Document: {}", &pretty_json);
Err(anyhow::anyhow!(
"Unexpected predicateType: {:?}",
statement.predicate_type.as_str()
))
} else {
println!(
"Unknown predicateType: {:?}",
Expand Down Expand Up @@ -256,10 +288,19 @@ fn validate_document<T: DeserializeOwned>(file_path: PathBuf) -> Result<()> {
fn generate_intoto_v1(in_toto: GenerateInTotoV1) -> Result<()> {
match in_toto.predicate {
Some(PredicateOption::SLSAProvenanceV1) => print_schema::<SLSAProvenanceV1Predicate>(),
Some(PredicateOption::SCAIV02Predicate) => print_schema::<SCAIV02Predicate>(),
None => print_schema::<InTotoStatementV1>(),
}
}

fn generate_scaiv02() -> Result<()> {
print_schema::<InTotoStatementV1<SCAIV02Predicate>>()
}

fn generate_slsa_provenancev01() -> Result<()> {
print_schema::<InTotoStatementV1<SLSAProvenanceV1Predicate>>()
}

/// Generates Rust code from a JSON schema file.
fn code_generate_cmd(cg: CodeGenerate) -> Result<()> {
match cg.codegen {
Expand Down
4 changes: 4 additions & 0 deletions src/models/intoto/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub fn deserialize_predicate(
let slsa_provenance = deserialize_helper::<SLSAProvenanceV1Predicate>(predicate_json)?;
Ok(Predicate::SLSAProvenanceV1(slsa_provenance))
}
"https://in-toto.io/attestation/scai/attribute-report" => {
let scai_v02 = deserialize_helper::<SCAIV02Predicate>(predicate_json)?;
Ok(Predicate::SCAIV02(scai_v02))
}
_ => {
let other_predicate = deserialize_helper::<Value>(predicate_json)?;
Ok(Predicate::Other(other_predicate))
Expand Down
3 changes: 2 additions & 1 deletion src/models/intoto/scai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ use super::provenance::ResourceDescriptor;
/// }
/// }

/// A structure representing the SLSA Provenance v1 Predicate.
/// A struct representing the SCAI V0.2 Predicate.
#[derive(Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct SCAIV02Predicate {
pub attributes: Vec<Attribute>,
#[serde(skip_serializing_if = "Option::is_none")]
pub producer: Option<ResourceDescriptor>,
}

/// A struct
#[derive(Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct Attribute {
pub attribute: String,
Expand Down
7 changes: 4 additions & 3 deletions src/models/intoto/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@ use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use url::Url;
use std::fmt::Debug;

use crate::models::{
helpers::url_serde,
intoto::predicate::{deserialize_predicate, Predicate},
};

/// Represents an In-Toto v1 statement.
#[derive(Debug, Serialize, JsonSchema)]
pub struct InTotoStatementV1 {
#[derive(Debug, Serialize, PartialEq, JsonSchema)]
pub struct InTotoStatementV1<T: Debug + Serialize + PartialEq + JsonSchema = Predicate> {
#[serde(rename = "_type", with = "url_serde")]
#[schemars(with = "Url")]
pub _type: Url,
pub subject: Vec<Subject>,
#[serde(rename = "predicateType", with = "url_serde")]
#[schemars(with = "Url")]
pub predicate_type: Url,
pub predicate: Predicate,
pub predicate: T,
}

/// Enum for the supported hashing algorithms.
Expand Down
2 changes: 1 addition & 1 deletion tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ fn test_generate_rust_code() {
.assert()
.success()
.stdout(predicate::str::contains(fixture));
}
}
64 changes: 27 additions & 37 deletions tests/fixtures/in_toto_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![allow(clippy::all)]
#![allow(warnings)]
use serde::{Deserialize, Serialize};
///A struct
#[derive(Clone, Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct Attribute {
pub attribute: String,
Expand All @@ -28,17 +29,16 @@ impl Attribute {
pub struct BuildDefinition {
#[serde(rename = "buildType")]
pub build_type: String,
///The parameters that are under external control, such as those set by a user or tenant of the build platform. They MUST be complete at SLSA Build L3, meaning that there is no additional mechanism for an external party to influence the build. (At lower SLSA Build levels, the completeness MAY be best effort.)\nThe build platform SHOULD be designed to minimize the size and complexity of externalParameters, in order to reduce fragility and ease verification. Consumers SHOULD have an expectation of what “good” looks like; the more information that they need to check, the harder that task becomes.\nVerifiers SHOULD reject unrecognized or unexpected fields within externalParameters.
#[serde(rename = "externalParameters")]
pub external_parameters: std::collections::HashMap<String, serde_json::Value>,
pub external_parameters: serde_json::Map<String, serde_json::Value>,
///Unordered collection of artifacts needed at build time. Completeness is best effort, at least through SLSA Build L3. For example, if the build script fetches and executes “example.com/foo.sh”, which in turn fetches “example.com/bar.tar.gz”, then both “foo.sh” and “bar.tar.gz” SHOULD be listed here.
#[serde(
rename = "internalParameters",
default,
skip_serializing_if = "Option::is_none"
)]
pub internal_parameters: Option<
std::collections::HashMap<String, serde_json::Value>,
>,
pub internal_parameters: Option<serde_json::Map<String, serde_json::Value>>,
///Unordered collection of artifacts needed at build time. Completeness is best effort, at least through SLSA Build L3. For example, if the build script fetches and executes “example.com/foo.sh”, which in turn fetches “example.com/bar.tar.gz”, then both “foo.sh” and “bar.tar.gz” SHOULD be listed here.
#[serde(
rename = "resolvedDependencies",
Expand Down Expand Up @@ -103,6 +103,7 @@ impl Builder {
builder::Builder::default()
}
}
///Represents a set of digests, mapping algorithms to their respective digest strings.
#[derive(Clone, Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct DigestSet(pub std::collections::HashMap<String, String>);
impl std::ops::Deref for DigestSet {
Expand All @@ -128,22 +129,22 @@ impl From<std::collections::HashMap<String, String>> for DigestSet {
}
///Represents an In-Toto v1 statement.
#[derive(Clone, Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct InTotoStatementV1 {
pub struct InTotoStatementV1ForPredicate {
pub predicate: Predicate,
#[serde(rename = "predicateType")]
pub predicate_type: String,
pub subject: Vec<Subject>,
#[serde(rename = "_type")]
pub type_: String,
}
impl From<&InTotoStatementV1> for InTotoStatementV1 {
fn from(value: &InTotoStatementV1) -> Self {
impl From<&InTotoStatementV1ForPredicate> for InTotoStatementV1ForPredicate {
fn from(value: &InTotoStatementV1ForPredicate) -> Self {
value.clone()
}
}
impl InTotoStatementV1 {
pub fn builder() -> builder::InTotoStatementV1 {
builder::InTotoStatementV1::default()
impl InTotoStatementV1ForPredicate {
pub fn builder() -> builder::InTotoStatementV1ForPredicate {
builder::InTotoStatementV1ForPredicate::default()
}
}
/**An enum representing different predicate types.
Expand Down Expand Up @@ -175,7 +176,7 @@ impl Predicate {
pub struct ResourceDescriptor {
///This field MAY be used to provide additional information or metadata about the resource or artifact that may be useful to the consumer when evaluating the attestation against a policy.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<std::collections::HashMap<String, serde_json::Value>>,
pub annotations: Option<serde_json::Map<String, serde_json::Value>>,
///The contents of the resource or artifact. This field is REQUIRED unless either uri or digest is set.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
Expand Down Expand Up @@ -230,7 +231,7 @@ impl RunDetails {
builder::RunDetails::default()
}
}
///This is based on the model in: { "predicateType": "https://in-toto.io/attestation/scai/attribute-report/v0.2", "predicate": { "attributes": [{ "attribute": "<ATTRIBUTE>", "target": { [ResourceDescriptor] }, // optional "conditions": { /* object */ }, // optional "evidence": { [ResourceDescriptor] } // optional }], "producer": { [ResourceDescriptor] } // optional } } A structure representing the SLSA Provenance v1 Predicate.
///This is based on the model in: { "predicateType": "https://in-toto.io/attestation/scai/attribute-report/v0.2", "predicate": { "attributes": [{ "attribute": "<ATTRIBUTE>", "target": { [ResourceDescriptor] }, // optional "conditions": { /* object */ }, // optional "evidence": { [ResourceDescriptor] } // optional }], "producer": { [ResourceDescriptor] } // optional } } A struct representing the SCAI V0.2 Predicate.
#[derive(Clone, Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct Scaiv02Predicate {
pub attributes: Vec<Attribute>,
Expand Down Expand Up @@ -377,12 +378,9 @@ pub mod builder {
#[derive(Clone, Debug)]
pub struct BuildDefinition {
build_type: Result<String, String>,
external_parameters: Result<
std::collections::HashMap<String, serde_json::Value>,
String,
>,
external_parameters: Result<serde_json::Map<String, serde_json::Value>, String>,
internal_parameters: Result<
Option<std::collections::HashMap<String, serde_json::Value>>,
Option<serde_json::Map<String, serde_json::Value>>,
String,
>,
resolved_dependencies: Result<Option<Vec<super::ResourceDescriptor>>, String>,
Expand Down Expand Up @@ -415,9 +413,7 @@ pub mod builder {
}
pub fn external_parameters<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<
std::collections::HashMap<String, serde_json::Value>,
>,
T: std::convert::TryInto<serde_json::Map<String, serde_json::Value>>,
T::Error: std::fmt::Display,
{
self
Expand All @@ -432,9 +428,7 @@ pub mod builder {
}
pub fn internal_parameters<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<
Option<std::collections::HashMap<String, serde_json::Value>>,
>,
T: std::convert::TryInto<Option<serde_json::Map<String, serde_json::Value>>>,
T::Error: std::fmt::Display,
{
self
Expand Down Expand Up @@ -636,13 +630,13 @@ pub mod builder {
}
}
#[derive(Clone, Debug)]
pub struct InTotoStatementV1 {
pub struct InTotoStatementV1ForPredicate {
predicate: Result<super::Predicate, String>,
predicate_type: Result<String, String>,
subject: Result<Vec<super::Subject>, String>,
type_: Result<String, String>,
}
impl Default for InTotoStatementV1 {
impl Default for InTotoStatementV1ForPredicate {
fn default() -> Self {
Self {
predicate: Err("no value supplied for predicate".to_string()),
Expand All @@ -652,7 +646,7 @@ pub mod builder {
}
}
}
impl InTotoStatementV1 {
impl InTotoStatementV1ForPredicate {
pub fn predicate<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<super::Predicate>,
Expand Down Expand Up @@ -706,9 +700,10 @@ pub mod builder {
self
}
}
impl std::convert::TryFrom<InTotoStatementV1> for super::InTotoStatementV1 {
impl std::convert::TryFrom<InTotoStatementV1ForPredicate>
for super::InTotoStatementV1ForPredicate {
type Error = String;
fn try_from(value: InTotoStatementV1) -> Result<Self, String> {
fn try_from(value: InTotoStatementV1ForPredicate) -> Result<Self, String> {
Ok(Self {
predicate: value.predicate?,
predicate_type: value.predicate_type?,
Expand All @@ -717,8 +712,8 @@ pub mod builder {
})
}
}
impl From<super::InTotoStatementV1> for InTotoStatementV1 {
fn from(value: super::InTotoStatementV1) -> Self {
impl From<super::InTotoStatementV1ForPredicate> for InTotoStatementV1ForPredicate {
fn from(value: super::InTotoStatementV1ForPredicate) -> Self {
Self {
predicate: Ok(value.predicate),
predicate_type: Ok(value.predicate_type),
Expand Down Expand Up @@ -804,10 +799,7 @@ pub mod builder {
}
#[derive(Clone, Debug)]
pub struct ResourceDescriptor {
annotations: Result<
Option<std::collections::HashMap<String, serde_json::Value>>,
String,
>,
annotations: Result<Option<serde_json::Map<String, serde_json::Value>>, String>,
content: Result<Option<String>, String>,
digest: Result<Option<std::collections::HashMap<String, String>>, String>,
download_location: Result<Option<String>, String>,
Expand All @@ -831,9 +823,7 @@ pub mod builder {
impl ResourceDescriptor {
pub fn annotations<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<
Option<std::collections::HashMap<String, serde_json::Value>>,
>,
T: std::convert::TryInto<Option<serde_json::Map<String, serde_json::Value>>>,
T::Error: std::fmt::Display,
{
self
Expand Down
Loading

0 comments on commit d587226

Please sign in to comment.