1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::control::{ControlId, builtin};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum SlsaTrack {
13 Source,
14 Build,
15 Dependencies,
17}
18
19impl fmt::Display for SlsaTrack {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Self::Source => f.write_str("source"),
23 Self::Build => f.write_str("build"),
24 Self::Dependencies => f.write_str("dependencies"),
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum SlsaLevel {
33 L0,
34 L1,
35 L2,
36 L3,
37 L4,
38}
39
40impl SlsaLevel {
41 pub fn is_valid_for_track(self, track: SlsaTrack) -> bool {
42 match track {
43 SlsaTrack::Source => true,
44 SlsaTrack::Build => self <= SlsaLevel::L3,
45 SlsaTrack::Dependencies => true,
46 }
47 }
48}
49
50impl fmt::Display for SlsaLevel {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::L0 => f.write_str("L0"),
54 Self::L1 => f.write_str("L1"),
55 Self::L2 => f.write_str("L2"),
56 Self::L3 => f.write_str("L3"),
57 Self::L4 => f.write_str("L4"),
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct SlsaMapping {
65 pub track: SlsaTrack,
66 pub level: SlsaLevel,
67}
68
69pub fn control_slsa_mapping(id: &ControlId) -> Option<SlsaMapping> {
73 match id.as_str() {
74 builtin::SOURCE_AUTHENTICITY => Some(SlsaMapping {
76 track: SlsaTrack::Source,
77 level: SlsaLevel::L1,
78 }),
79 builtin::REVIEW_INDEPENDENCE => Some(SlsaMapping {
80 track: SlsaTrack::Source,
81 level: SlsaLevel::L1,
82 }),
83 builtin::BRANCH_HISTORY_INTEGRITY => Some(SlsaMapping {
84 track: SlsaTrack::Source,
85 level: SlsaLevel::L2,
86 }),
87 builtin::BRANCH_PROTECTION_ENFORCEMENT => Some(SlsaMapping {
88 track: SlsaTrack::Source,
89 level: SlsaLevel::L3,
90 }),
91 builtin::TWO_PARTY_REVIEW => Some(SlsaMapping {
92 track: SlsaTrack::Source,
93 level: SlsaLevel::L4,
94 }),
95
96 builtin::BUILD_PROVENANCE => Some(SlsaMapping {
98 track: SlsaTrack::Build,
99 level: SlsaLevel::L1,
100 }),
101 builtin::REQUIRED_STATUS_CHECKS => Some(SlsaMapping {
102 track: SlsaTrack::Build,
103 level: SlsaLevel::L1,
104 }),
105 builtin::HOSTED_BUILD_PLATFORM => Some(SlsaMapping {
106 track: SlsaTrack::Build,
107 level: SlsaLevel::L2,
108 }),
109 builtin::PROVENANCE_AUTHENTICITY => Some(SlsaMapping {
110 track: SlsaTrack::Build,
111 level: SlsaLevel::L2,
112 }),
113 builtin::BUILD_ISOLATION => Some(SlsaMapping {
114 track: SlsaTrack::Build,
115 level: SlsaLevel::L3,
116 }),
117
118 builtin::DEPENDENCY_SIGNATURE => Some(SlsaMapping {
120 track: SlsaTrack::Dependencies,
121 level: SlsaLevel::L1,
122 }),
123 builtin::DEPENDENCY_PROVENANCE_CHECK => Some(SlsaMapping {
124 track: SlsaTrack::Dependencies,
125 level: SlsaLevel::L2,
126 }),
127 builtin::DEPENDENCY_SIGNER_VERIFIED => Some(SlsaMapping {
128 track: SlsaTrack::Dependencies,
129 level: SlsaLevel::L3,
130 }),
131 builtin::DEPENDENCY_COMPLETENESS => Some(SlsaMapping {
132 track: SlsaTrack::Dependencies,
133 level: SlsaLevel::L4,
134 }),
135
136 _ => None,
138 }
139}
140
141pub fn controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<ControlId> {
143 ALL_SLSA_CONTROLS
144 .iter()
145 .map(|&s| ControlId::new(s))
146 .filter(|id| control_slsa_mapping(id).is_some_and(|m| m.track == track && m.level <= level))
147 .collect()
148}
149
150const ALL_SLSA_CONTROLS: &[&str] = &[
151 builtin::SOURCE_AUTHENTICITY,
152 builtin::REVIEW_INDEPENDENCE,
153 builtin::BRANCH_HISTORY_INTEGRITY,
154 builtin::BRANCH_PROTECTION_ENFORCEMENT,
155 builtin::TWO_PARTY_REVIEW,
156 builtin::BUILD_PROVENANCE,
157 builtin::REQUIRED_STATUS_CHECKS,
158 builtin::HOSTED_BUILD_PLATFORM,
159 builtin::PROVENANCE_AUTHENTICITY,
160 builtin::BUILD_ISOLATION,
161 builtin::DEPENDENCY_SIGNATURE,
162 builtin::DEPENDENCY_PROVENANCE_CHECK,
163 builtin::DEPENDENCY_SIGNER_VERIFIED,
164 builtin::DEPENDENCY_COMPLETENESS,
165];
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn source_l1_controls() {
173 let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L1);
174 let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
175 assert!(ids.contains(&builtin::SOURCE_AUTHENTICITY));
176 assert!(ids.contains(&builtin::REVIEW_INDEPENDENCE));
177 assert!(!ids.contains(&builtin::BRANCH_HISTORY_INTEGRITY));
178 }
179
180 #[test]
181 fn source_l4_includes_all_source_controls() {
182 let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L4);
183 assert_eq!(controls.len(), 5);
184 }
185
186 #[test]
187 fn build_l3_includes_all_build_controls() {
188 let controls = controls_for_level(SlsaTrack::Build, SlsaLevel::L3);
189 assert_eq!(controls.len(), 5);
190 }
191
192 #[test]
193 fn compliance_controls_have_no_slsa_mapping() {
194 assert!(control_slsa_mapping(&ControlId::new(builtin::CHANGE_REQUEST_SIZE)).is_none());
195 assert!(control_slsa_mapping(&ControlId::new(builtin::TEST_COVERAGE)).is_none());
196 }
197
198 #[test]
199 fn l4_not_valid_for_build_track() {
200 assert!(!SlsaLevel::L4.is_valid_for_track(SlsaTrack::Build));
201 assert!(SlsaLevel::L4.is_valid_for_track(SlsaTrack::Source));
202 }
203
204 #[test]
205 fn dependencies_l1_includes_signature_only() {
206 let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L1);
207 let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
208 assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
209 assert_eq!(controls.len(), 1);
210 }
211
212 #[test]
213 fn dependencies_l2_includes_provenance() {
214 let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L2);
215 let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
216 assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
217 assert!(ids.contains(&builtin::DEPENDENCY_PROVENANCE_CHECK));
218 assert_eq!(controls.len(), 2);
219 }
220
221 #[test]
222 fn dependencies_l3_includes_signer_verified() {
223 let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L3);
224 assert_eq!(controls.len(), 3);
225 }
226
227 #[test]
228 fn dependencies_l4_includes_all_4_controls() {
229 let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L4);
230 let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
231 assert!(ids.contains(&builtin::DEPENDENCY_COMPLETENESS));
232 assert_eq!(controls.len(), 4);
233 }
234
235 #[test]
236 fn dependency_controls_have_slsa_mapping() {
237 for &id in &[
238 builtin::DEPENDENCY_SIGNATURE,
239 builtin::DEPENDENCY_PROVENANCE_CHECK,
240 builtin::DEPENDENCY_SIGNER_VERIFIED,
241 builtin::DEPENDENCY_COMPLETENESS,
242 ] {
243 assert!(
244 control_slsa_mapping(&ControlId::new(id)).is_some(),
245 "{id} should have SLSA mapping"
246 );
247 }
248 }
249}