libverify_core/controls/
mod.rs1pub mod branch_history_integrity;
2pub mod branch_protection_enforcement;
3pub mod build_isolation;
4pub mod build_provenance;
5pub mod change_request_size;
6pub mod codeowners_coverage;
7pub mod conventional_title;
8pub mod dependency_completeness;
9pub mod dependency_provenance;
10pub mod dependency_signature;
11pub mod dependency_signer_verified;
12pub mod description_quality;
13pub mod hosted_build_platform;
14pub mod issue_linkage;
15pub mod merge_commit_policy;
16pub mod provenance_authenticity;
17pub mod release_traceability;
18pub mod required_status_checks;
19pub mod review_independence;
20pub mod scoped_change;
21pub mod secret_scanning;
22pub mod security_file_change;
23pub mod security_policy;
24pub mod source_authenticity;
25pub mod stale_review;
26pub mod test_coverage;
27pub mod two_party_review;
28pub mod vulnerability_scanning;
29
30use crate::control::{Control, builtin};
31use crate::slsa::{SlsaLevel, SlsaTrack};
32
33use self::branch_history_integrity::BranchHistoryIntegrityControl;
34use self::branch_protection_enforcement::BranchProtectionEnforcementControl;
35use self::build_isolation::BuildIsolationControl;
36use self::build_provenance::BuildProvenanceControl;
37use self::change_request_size::ChangeRequestSizeControl;
38use self::codeowners_coverage::CodeownersCoverageControl;
39use self::conventional_title::ConventionalTitleControl;
40use self::dependency_completeness::DependencyCompletenessControl;
41use self::dependency_provenance::DependencyProvenanceControl;
42use self::dependency_signature::DependencySignatureControl;
43use self::dependency_signer_verified::DependencySignerVerifiedControl;
44use self::description_quality::DescriptionQualityControl;
45use self::hosted_build_platform::HostedBuildPlatformControl;
46use self::issue_linkage::IssueLinkageControl;
47use self::merge_commit_policy::MergeCommitPolicyControl;
48use self::provenance_authenticity::ProvenanceAuthenticityControl;
49use self::release_traceability::ReleaseTraceabilityControl;
50use self::required_status_checks::RequiredStatusChecksControl;
51use self::review_independence::ReviewIndependenceControl;
52use self::scoped_change::ScopedChangeControl;
53use self::secret_scanning::SecretScanningControl;
54use self::security_file_change::SecurityFileChangeControl;
55use self::security_policy::SecurityPolicyControl;
56use self::source_authenticity::SourceAuthenticityControl;
57use self::stale_review::StaleReviewControl;
58use self::test_coverage::TestCoverageControl;
59use self::two_party_review::TwoPartyReviewControl;
60use self::vulnerability_scanning::VulnerabilityScanningControl;
61
62fn instantiate(id: &str) -> Option<Box<dyn Control>> {
64 match id {
65 builtin::SOURCE_AUTHENTICITY => Some(Box::new(SourceAuthenticityControl)),
66 builtin::REVIEW_INDEPENDENCE => Some(Box::new(ReviewIndependenceControl)),
67 builtin::BRANCH_HISTORY_INTEGRITY => Some(Box::new(BranchHistoryIntegrityControl)),
68 builtin::BRANCH_PROTECTION_ENFORCEMENT => {
69 Some(Box::new(BranchProtectionEnforcementControl))
70 }
71 builtin::TWO_PARTY_REVIEW => Some(Box::new(TwoPartyReviewControl)),
72 builtin::BUILD_PROVENANCE => Some(Box::new(BuildProvenanceControl)),
73 builtin::REQUIRED_STATUS_CHECKS => Some(Box::new(RequiredStatusChecksControl)),
74 builtin::HOSTED_BUILD_PLATFORM => Some(Box::new(HostedBuildPlatformControl)),
75 builtin::PROVENANCE_AUTHENTICITY => Some(Box::new(ProvenanceAuthenticityControl)),
76 builtin::BUILD_ISOLATION => Some(Box::new(BuildIsolationControl)),
77 builtin::DEPENDENCY_SIGNATURE => Some(Box::new(DependencySignatureControl)),
78 builtin::DEPENDENCY_PROVENANCE_CHECK => Some(Box::new(DependencyProvenanceControl)),
79 builtin::DEPENDENCY_SIGNER_VERIFIED => Some(Box::new(DependencySignerVerifiedControl)),
80 builtin::DEPENDENCY_COMPLETENESS => Some(Box::new(DependencyCompletenessControl)),
81 builtin::CHANGE_REQUEST_SIZE => Some(Box::new(ChangeRequestSizeControl)),
82 builtin::TEST_COVERAGE => Some(Box::new(TestCoverageControl)),
83 builtin::SCOPED_CHANGE => Some(Box::new(ScopedChangeControl)),
84 builtin::ISSUE_LINKAGE => Some(Box::new(IssueLinkageControl)),
85 builtin::STALE_REVIEW => Some(Box::new(StaleReviewControl)),
86 builtin::DESCRIPTION_QUALITY => Some(Box::new(DescriptionQualityControl)),
87 builtin::MERGE_COMMIT_POLICY => Some(Box::new(MergeCommitPolicyControl)),
88 builtin::CONVENTIONAL_TITLE => Some(Box::new(ConventionalTitleControl)),
89 builtin::SECURITY_FILE_CHANGE => Some(Box::new(SecurityFileChangeControl)),
90 builtin::RELEASE_TRACEABILITY => Some(Box::new(ReleaseTraceabilityControl)),
91 builtin::CODEOWNERS_COVERAGE => Some(Box::new(CodeownersCoverageControl)),
92 builtin::SECRET_SCANNING => Some(Box::new(SecretScanningControl)),
93 builtin::VULNERABILITY_SCANNING => Some(Box::new(VulnerabilityScanningControl)),
94 builtin::SECURITY_POLICY => Some(Box::new(SecurityPolicyControl)),
95 _ => None,
96 }
97}
98
99pub fn control_description(id: &str) -> &'static str {
102 match instantiate(id) {
103 Some(c) => c.description(),
104 None => "Custom control",
105 }
106}
107
108pub fn slsa_controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<Box<dyn Control>> {
110 crate::slsa::controls_for_level(track, level)
111 .into_iter()
112 .filter_map(|id| instantiate(id.as_str()))
113 .collect()
114}
115
116pub fn slsa_controls(source_level: SlsaLevel, build_level: SlsaLevel) -> Vec<Box<dyn Control>> {
118 let mut controls = slsa_controls_for_level(SlsaTrack::Source, source_level);
119 controls.extend(slsa_controls_for_level(SlsaTrack::Build, build_level));
120 controls
121}
122
123pub fn all_slsa_controls() -> Vec<Box<dyn Control>> {
125 let mut controls = slsa_controls(SlsaLevel::L4, SlsaLevel::L3);
126 controls.extend(slsa_controls_for_level(
127 SlsaTrack::Dependencies,
128 SlsaLevel::L4,
129 ));
130 controls
131}
132
133pub fn compliance_controls() -> Vec<Box<dyn Control>> {
135 vec![
136 Box::new(ChangeRequestSizeControl),
137 Box::new(TestCoverageControl),
138 Box::new(ScopedChangeControl),
139 Box::new(IssueLinkageControl),
140 Box::new(StaleReviewControl),
141 Box::new(DescriptionQualityControl),
142 Box::new(MergeCommitPolicyControl),
143 Box::new(ConventionalTitleControl),
144 Box::new(SecurityFileChangeControl),
145 Box::new(ReleaseTraceabilityControl),
146 Box::new(CodeownersCoverageControl),
147 Box::new(SecretScanningControl),
148 Box::new(VulnerabilityScanningControl),
149 Box::new(SecurityPolicyControl),
150 ]
151}
152
153pub fn all_controls() -> Vec<Box<dyn Control>> {
155 let mut controls = all_slsa_controls();
156 controls.extend(compliance_controls());
157 controls
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::control::builtin;
164 use crate::slsa::control_slsa_mapping;
165
166 #[test]
167 fn slsa_l1_returns_l1_controls_only() {
168 let controls = slsa_controls(SlsaLevel::L1, SlsaLevel::L1);
169 for c in &controls {
170 let mapping = control_slsa_mapping(&c.id()).expect("should be SLSA-mapped");
171 assert!(
172 mapping.level <= SlsaLevel::L1,
173 "{:?} is L{:?} but should be L1 or below",
174 c.id(),
175 mapping.level
176 );
177 }
178 }
179
180 #[test]
181 fn all_slsa_includes_l3_build_and_l4_source() {
182 let controls = all_slsa_controls();
183 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
184 assert!(
185 ids.iter()
186 .any(|id| id.as_str() == builtin::TWO_PARTY_REVIEW)
187 );
188 assert!(ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
189 }
190
191 #[test]
192 fn all_controls_includes_compliance() {
193 let controls = all_controls();
194 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
195 assert!(
196 ids.iter()
197 .any(|id| id.as_str() == builtin::CHANGE_REQUEST_SIZE)
198 );
199 assert!(ids.iter().any(|id| id.as_str() == builtin::ISSUE_LINKAGE));
200 }
201
202 #[test]
203 fn compliance_plus_slsa_equals_all() {
204 use crate::control::builtin;
205 let compliance = compliance_controls();
206 let slsa = all_slsa_controls();
207 assert_eq!(
208 compliance.len() + slsa.len(),
209 builtin::ALL.len(),
210 "compliance + SLSA controls must cover all built-in controls"
211 );
212 }
213
214 #[test]
215 fn compliance_controls_are_not_slsa_mapped() {
216 use crate::slsa::control_slsa_mapping;
217 let controls = compliance_controls();
218 for c in &controls {
219 assert!(
220 control_slsa_mapping(&c.id()).is_none(),
221 "{:?} should not be SLSA-mapped",
222 c.id()
223 );
224 }
225 }
226
227 #[test]
228 fn compliance_controls_have_unique_ids() {
229 let controls = compliance_controls();
230 let mut ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
231 let original_len = ids.len();
232 ids.sort_by_key(|id| id.as_str().to_string());
233 ids.dedup();
234 assert_eq!(
235 ids.len(),
236 original_len,
237 "all compliance control IDs must be unique"
238 );
239 }
240
241 #[test]
242 fn all_controls_count() {
243 let slsa = all_slsa_controls();
244 let compliance = compliance_controls();
245 let all = all_controls();
246 assert_eq!(
247 all.len(),
248 slsa.len() + compliance.len(),
249 "all_controls = SLSA + compliance"
250 );
251 }
252
253 #[test]
254 fn slsa_controls_for_level_source_l2() {
255 let controls = slsa_controls_for_level(SlsaTrack::Source, SlsaLevel::L2);
256 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
257 assert!(
258 ids.iter()
259 .any(|id| id.as_str() == builtin::BRANCH_HISTORY_INTEGRITY)
260 );
261 assert!(
262 !ids.iter()
263 .any(|id| id.as_str() == builtin::BRANCH_PROTECTION_ENFORCEMENT)
264 );
265 }
266
267 #[test]
268 fn slsa_controls_for_level_build_l2() {
269 let controls = slsa_controls_for_level(SlsaTrack::Build, SlsaLevel::L2);
270 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
271 assert!(
272 ids.iter()
273 .any(|id| id.as_str() == builtin::HOSTED_BUILD_PLATFORM)
274 );
275 assert!(
276 ids.iter()
277 .any(|id| id.as_str() == builtin::PROVENANCE_AUTHENTICITY)
278 );
279 assert!(!ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
280 }
281}