use std::fmt;
use serde::{Deserialize, Serialize};
use crate::control::{ControlId, builtin};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SlsaTrack {
Source,
Build,
Dependencies,
}
impl fmt::Display for SlsaTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Source => f.write_str("source"),
Self::Build => f.write_str("build"),
Self::Dependencies => f.write_str("dependencies"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SlsaLevel {
L0,
L1,
L2,
L3,
L4,
}
impl SlsaLevel {
pub fn is_valid_for_track(self, track: SlsaTrack) -> bool {
match track {
SlsaTrack::Source => true,
SlsaTrack::Build => self <= SlsaLevel::L3,
SlsaTrack::Dependencies => true,
}
}
}
impl fmt::Display for SlsaLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::L0 => f.write_str("L0"),
Self::L1 => f.write_str("L1"),
Self::L2 => f.write_str("L2"),
Self::L3 => f.write_str("L3"),
Self::L4 => f.write_str("L4"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlsaMapping {
pub track: SlsaTrack,
pub level: SlsaLevel,
}
pub fn control_slsa_mapping(id: &ControlId) -> Option<SlsaMapping> {
match id.as_str() {
builtin::SOURCE_AUTHENTICITY => Some(SlsaMapping {
track: SlsaTrack::Source,
level: SlsaLevel::L1,
}),
builtin::REVIEW_INDEPENDENCE => Some(SlsaMapping {
track: SlsaTrack::Source,
level: SlsaLevel::L1,
}),
builtin::BRANCH_HISTORY_INTEGRITY => Some(SlsaMapping {
track: SlsaTrack::Source,
level: SlsaLevel::L2,
}),
builtin::BRANCH_PROTECTION_ENFORCEMENT => Some(SlsaMapping {
track: SlsaTrack::Source,
level: SlsaLevel::L3,
}),
builtin::TWO_PARTY_REVIEW => Some(SlsaMapping {
track: SlsaTrack::Source,
level: SlsaLevel::L4,
}),
builtin::BUILD_PROVENANCE => Some(SlsaMapping {
track: SlsaTrack::Build,
level: SlsaLevel::L1,
}),
builtin::REQUIRED_STATUS_CHECKS => Some(SlsaMapping {
track: SlsaTrack::Build,
level: SlsaLevel::L1,
}),
builtin::HOSTED_BUILD_PLATFORM => Some(SlsaMapping {
track: SlsaTrack::Build,
level: SlsaLevel::L2,
}),
builtin::PROVENANCE_AUTHENTICITY => Some(SlsaMapping {
track: SlsaTrack::Build,
level: SlsaLevel::L2,
}),
builtin::BUILD_ISOLATION => Some(SlsaMapping {
track: SlsaTrack::Build,
level: SlsaLevel::L3,
}),
builtin::DEPENDENCY_SIGNATURE => Some(SlsaMapping {
track: SlsaTrack::Dependencies,
level: SlsaLevel::L1,
}),
builtin::DEPENDENCY_PROVENANCE_CHECK => Some(SlsaMapping {
track: SlsaTrack::Dependencies,
level: SlsaLevel::L2,
}),
builtin::DEPENDENCY_SIGNER_VERIFIED => Some(SlsaMapping {
track: SlsaTrack::Dependencies,
level: SlsaLevel::L3,
}),
builtin::DEPENDENCY_COMPLETENESS => Some(SlsaMapping {
track: SlsaTrack::Dependencies,
level: SlsaLevel::L4,
}),
_ => None,
}
}
pub fn controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<ControlId> {
ALL_SLSA_CONTROLS
.iter()
.map(|&s| ControlId::new(s))
.filter(|id| control_slsa_mapping(id).is_some_and(|m| m.track == track && m.level <= level))
.collect()
}
const ALL_SLSA_CONTROLS: &[&str] = &[
builtin::SOURCE_AUTHENTICITY,
builtin::REVIEW_INDEPENDENCE,
builtin::BRANCH_HISTORY_INTEGRITY,
builtin::BRANCH_PROTECTION_ENFORCEMENT,
builtin::TWO_PARTY_REVIEW,
builtin::BUILD_PROVENANCE,
builtin::REQUIRED_STATUS_CHECKS,
builtin::HOSTED_BUILD_PLATFORM,
builtin::PROVENANCE_AUTHENTICITY,
builtin::BUILD_ISOLATION,
builtin::DEPENDENCY_SIGNATURE,
builtin::DEPENDENCY_PROVENANCE_CHECK,
builtin::DEPENDENCY_SIGNER_VERIFIED,
builtin::DEPENDENCY_COMPLETENESS,
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_l1_controls() {
let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L1);
let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
assert!(ids.contains(&builtin::SOURCE_AUTHENTICITY));
assert!(ids.contains(&builtin::REVIEW_INDEPENDENCE));
assert!(!ids.contains(&builtin::BRANCH_HISTORY_INTEGRITY));
}
#[test]
fn source_l4_includes_all_source_controls() {
let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L4);
assert_eq!(controls.len(), 5);
}
#[test]
fn build_l3_includes_all_build_controls() {
let controls = controls_for_level(SlsaTrack::Build, SlsaLevel::L3);
assert_eq!(controls.len(), 5);
}
#[test]
fn compliance_controls_have_no_slsa_mapping() {
assert!(control_slsa_mapping(&ControlId::new(builtin::CHANGE_REQUEST_SIZE)).is_none());
assert!(control_slsa_mapping(&ControlId::new(builtin::TEST_COVERAGE)).is_none());
}
#[test]
fn l4_not_valid_for_build_track() {
assert!(!SlsaLevel::L4.is_valid_for_track(SlsaTrack::Build));
assert!(SlsaLevel::L4.is_valid_for_track(SlsaTrack::Source));
}
#[test]
fn dependencies_l1_includes_signature_only() {
let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L1);
let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
assert_eq!(controls.len(), 1);
}
#[test]
fn dependencies_l2_includes_provenance() {
let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L2);
let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
assert!(ids.contains(&builtin::DEPENDENCY_PROVENANCE_CHECK));
assert_eq!(controls.len(), 2);
}
#[test]
fn dependencies_l3_includes_signer_verified() {
let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L3);
assert_eq!(controls.len(), 3);
}
#[test]
fn dependencies_l4_includes_all_4_controls() {
let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L4);
let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
assert!(ids.contains(&builtin::DEPENDENCY_COMPLETENESS));
assert_eq!(controls.len(), 4);
}
#[test]
fn dependency_controls_have_slsa_mapping() {
for &id in &[
builtin::DEPENDENCY_SIGNATURE,
builtin::DEPENDENCY_PROVENANCE_CHECK,
builtin::DEPENDENCY_SIGNER_VERIFIED,
builtin::DEPENDENCY_COMPLETENESS,
] {
assert!(
control_slsa_mapping(&ControlId::new(id)).is_some(),
"{id} should have SLSA mapping"
);
}
}
}