1pub mod actions_pinned_dependencies;
2pub mod branch_history_integrity;
3pub mod branch_protection_admin_enforcement;
4pub mod branch_protection_enforcement;
5pub mod build_isolation;
6pub mod build_provenance;
7pub mod change_request_size;
8pub mod code_scanning_alerts_resolved;
9pub mod codeowners_coverage;
10pub mod conventional_title;
11pub mod dependency_completeness;
12pub mod dependency_license_compliance;
13pub mod dependency_provenance;
14pub mod dependency_signature;
15pub mod dependency_signer_verified;
16pub mod description_quality;
17pub mod dismiss_stale_reviews_on_push;
18pub mod environment_protection_rules;
19pub mod hosted_build_platform;
20pub mod issue_linkage;
21pub mod merge_commit_policy;
22pub mod privileged_workflow_detection;
23pub mod provenance_authenticity;
24pub mod release_asset_attestation;
25pub mod release_traceability;
26pub mod required_status_checks;
27pub mod review_independence;
28pub mod sbom_attestation;
29pub mod scoped_change;
30pub mod secret_scanning;
31pub mod secret_scanning_push_protection;
32pub mod security_file_change;
33pub mod security_policy;
34pub mod source_authenticity;
35pub mod stale_review;
36pub mod test_coverage;
37pub mod two_party_review;
38pub mod vulnerability_scanning;
39
40use crate::control::{Control, builtin};
41use crate::slsa::{SlsaLevel, SlsaTrack};
42
43use self::actions_pinned_dependencies::ActionsPinnedDependenciesControl;
44use self::branch_history_integrity::BranchHistoryIntegrityControl;
45use self::branch_protection_admin_enforcement::BranchProtectionAdminEnforcementControl;
46use self::branch_protection_enforcement::BranchProtectionEnforcementControl;
47use self::build_isolation::BuildIsolationControl;
48use self::build_provenance::BuildProvenanceControl;
49use self::change_request_size::ChangeRequestSizeControl;
50use self::code_scanning_alerts_resolved::CodeScanningAlertsResolvedControl;
51use self::codeowners_coverage::CodeownersCoverageControl;
52use self::conventional_title::ConventionalTitleControl;
53use self::dependency_completeness::DependencyCompletenessControl;
54use self::dependency_license_compliance::DependencyLicenseComplianceControl;
55use self::dependency_provenance::DependencyProvenanceControl;
56use self::dependency_signature::DependencySignatureControl;
57use self::dependency_signer_verified::DependencySignerVerifiedControl;
58use self::description_quality::DescriptionQualityControl;
59use self::dismiss_stale_reviews_on_push::DismissStaleReviewsOnPushControl;
60use self::environment_protection_rules::EnvironmentProtectionRulesControl;
61use self::hosted_build_platform::HostedBuildPlatformControl;
62use self::issue_linkage::IssueLinkageControl;
63use self::merge_commit_policy::MergeCommitPolicyControl;
64use self::privileged_workflow_detection::PrivilegedWorkflowDetectionControl;
65use self::provenance_authenticity::ProvenanceAuthenticityControl;
66use self::release_asset_attestation::ReleaseAssetAttestationControl;
67use self::release_traceability::ReleaseTraceabilityControl;
68use self::required_status_checks::RequiredStatusChecksControl;
69use self::review_independence::ReviewIndependenceControl;
70use self::sbom_attestation::SbomAttestationControl;
71use self::scoped_change::ScopedChangeControl;
72use self::secret_scanning::SecretScanningControl;
73use self::secret_scanning_push_protection::SecretScanningPushProtectionControl;
74use self::security_file_change::SecurityFileChangeControl;
75use self::security_policy::SecurityPolicyControl;
76use self::source_authenticity::SourceAuthenticityControl;
77use self::stale_review::StaleReviewControl;
78use self::test_coverage::TestCoverageControl;
79use self::two_party_review::TwoPartyReviewControl;
80use self::vulnerability_scanning::VulnerabilityScanningControl;
81
82fn instantiate(id: &str) -> Option<Box<dyn Control>> {
84 match id {
85 builtin::SOURCE_AUTHENTICITY => Some(Box::new(SourceAuthenticityControl)),
86 builtin::REVIEW_INDEPENDENCE => Some(Box::new(ReviewIndependenceControl)),
87 builtin::BRANCH_HISTORY_INTEGRITY => Some(Box::new(BranchHistoryIntegrityControl)),
88 builtin::BRANCH_PROTECTION_ENFORCEMENT => {
89 Some(Box::new(BranchProtectionEnforcementControl))
90 }
91 builtin::TWO_PARTY_REVIEW => Some(Box::new(TwoPartyReviewControl)),
92 builtin::BUILD_PROVENANCE => Some(Box::new(BuildProvenanceControl)),
93 builtin::REQUIRED_STATUS_CHECKS => Some(Box::new(RequiredStatusChecksControl)),
94 builtin::HOSTED_BUILD_PLATFORM => Some(Box::new(HostedBuildPlatformControl)),
95 builtin::PROVENANCE_AUTHENTICITY => Some(Box::new(ProvenanceAuthenticityControl)),
96 builtin::BUILD_ISOLATION => Some(Box::new(BuildIsolationControl)),
97 builtin::DEPENDENCY_SIGNATURE => Some(Box::new(DependencySignatureControl)),
98 builtin::DEPENDENCY_PROVENANCE_CHECK => Some(Box::new(DependencyProvenanceControl)),
99 builtin::DEPENDENCY_SIGNER_VERIFIED => Some(Box::new(DependencySignerVerifiedControl)),
100 builtin::DEPENDENCY_COMPLETENESS => Some(Box::new(DependencyCompletenessControl)),
101 builtin::CHANGE_REQUEST_SIZE => Some(Box::new(ChangeRequestSizeControl)),
102 builtin::TEST_COVERAGE => Some(Box::new(TestCoverageControl)),
103 builtin::SCOPED_CHANGE => Some(Box::new(ScopedChangeControl)),
104 builtin::ISSUE_LINKAGE => Some(Box::new(IssueLinkageControl)),
105 builtin::STALE_REVIEW => Some(Box::new(StaleReviewControl)),
106 builtin::DESCRIPTION_QUALITY => Some(Box::new(DescriptionQualityControl)),
107 builtin::MERGE_COMMIT_POLICY => Some(Box::new(MergeCommitPolicyControl)),
108 builtin::CONVENTIONAL_TITLE => Some(Box::new(ConventionalTitleControl)),
109 builtin::SECURITY_FILE_CHANGE => Some(Box::new(SecurityFileChangeControl)),
110 builtin::RELEASE_TRACEABILITY => Some(Box::new(ReleaseTraceabilityControl)),
111 builtin::CODEOWNERS_COVERAGE => Some(Box::new(CodeownersCoverageControl)),
112 builtin::SECRET_SCANNING => Some(Box::new(SecretScanningControl)),
113 builtin::VULNERABILITY_SCANNING => Some(Box::new(VulnerabilityScanningControl)),
114 builtin::SECURITY_POLICY => Some(Box::new(SecurityPolicyControl)),
115 builtin::SECRET_SCANNING_PUSH_PROTECTION => {
116 Some(Box::new(SecretScanningPushProtectionControl))
117 }
118 builtin::BRANCH_PROTECTION_ADMIN_ENFORCEMENT => {
119 Some(Box::new(BranchProtectionAdminEnforcementControl))
120 }
121 builtin::DISMISS_STALE_REVIEWS_ON_PUSH => Some(Box::new(DismissStaleReviewsOnPushControl)),
122 builtin::ACTIONS_PINNED_DEPENDENCIES => Some(Box::new(ActionsPinnedDependenciesControl)),
123 builtin::ENVIRONMENT_PROTECTION_RULES => Some(Box::new(EnvironmentProtectionRulesControl)),
124 builtin::CODE_SCANNING_ALERTS_RESOLVED => Some(Box::new(CodeScanningAlertsResolvedControl)),
125 builtin::DEPENDENCY_LICENSE_COMPLIANCE => {
126 Some(Box::new(DependencyLicenseComplianceControl))
127 }
128 builtin::SBOM_ATTESTATION => Some(Box::new(SbomAttestationControl)),
129 builtin::RELEASE_ASSET_ATTESTATION => Some(Box::new(ReleaseAssetAttestationControl)),
130 builtin::PRIVILEGED_WORKFLOW_DETECTION => {
131 Some(Box::new(PrivilegedWorkflowDetectionControl))
132 }
133 _ => None,
134 }
135}
136
137pub fn control_description(id: &str) -> &'static str {
140 match instantiate(id) {
141 Some(c) => c.description(),
142 None => "Custom control",
143 }
144}
145
146pub fn slsa_controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<Box<dyn Control>> {
148 crate::slsa::controls_for_level(track, level)
149 .into_iter()
150 .filter_map(|id| instantiate(id.as_str()))
151 .collect()
152}
153
154pub fn slsa_controls(source_level: SlsaLevel, build_level: SlsaLevel) -> Vec<Box<dyn Control>> {
156 let mut controls = slsa_controls_for_level(SlsaTrack::Source, source_level);
157 controls.extend(slsa_controls_for_level(SlsaTrack::Build, build_level));
158 controls
159}
160
161pub fn all_slsa_controls() -> Vec<Box<dyn Control>> {
163 let mut controls = slsa_controls(SlsaLevel::L4, SlsaLevel::L3);
164 controls.extend(slsa_controls_for_level(
165 SlsaTrack::Dependencies,
166 SlsaLevel::L4,
167 ));
168 controls
169}
170
171pub fn compliance_controls() -> Vec<Box<dyn Control>> {
173 vec![
174 Box::new(ChangeRequestSizeControl),
175 Box::new(TestCoverageControl),
176 Box::new(ScopedChangeControl),
177 Box::new(IssueLinkageControl),
178 Box::new(StaleReviewControl),
179 Box::new(DescriptionQualityControl),
180 Box::new(MergeCommitPolicyControl),
181 Box::new(ConventionalTitleControl),
182 Box::new(SecurityFileChangeControl),
183 Box::new(ReleaseTraceabilityControl),
184 Box::new(CodeownersCoverageControl),
185 Box::new(SecretScanningControl),
186 Box::new(VulnerabilityScanningControl),
187 Box::new(SecurityPolicyControl),
188 Box::new(SecretScanningPushProtectionControl),
189 Box::new(BranchProtectionAdminEnforcementControl),
190 Box::new(DismissStaleReviewsOnPushControl),
191 Box::new(ActionsPinnedDependenciesControl),
192 Box::new(EnvironmentProtectionRulesControl),
193 Box::new(CodeScanningAlertsResolvedControl),
194 Box::new(DependencyLicenseComplianceControl),
195 Box::new(SbomAttestationControl),
196 Box::new(ReleaseAssetAttestationControl),
197 Box::new(PrivilegedWorkflowDetectionControl),
198 ]
199}
200
201pub fn posture_controls() -> Vec<Box<dyn Control>> {
206 vec![
207 Box::new(CodeownersCoverageControl),
208 Box::new(SecretScanningControl),
209 Box::new(VulnerabilityScanningControl),
210 Box::new(SecurityPolicyControl),
211 Box::new(SecretScanningPushProtectionControl),
212 Box::new(BranchProtectionAdminEnforcementControl),
213 Box::new(DismissStaleReviewsOnPushControl),
214 Box::new(ActionsPinnedDependenciesControl),
215 Box::new(EnvironmentProtectionRulesControl),
216 Box::new(CodeScanningAlertsResolvedControl),
217 Box::new(DependencyLicenseComplianceControl),
218 Box::new(SbomAttestationControl),
219 Box::new(ReleaseAssetAttestationControl),
220 Box::new(PrivilegedWorkflowDetectionControl),
221 ]
222}
223
224pub fn all_controls() -> Vec<Box<dyn Control>> {
226 let mut controls = all_slsa_controls();
227 controls.extend(compliance_controls());
228 controls
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::control::builtin;
235 use crate::slsa::control_slsa_mapping;
236
237 #[test]
238 fn slsa_l1_returns_l1_controls_only() {
239 let controls = slsa_controls(SlsaLevel::L1, SlsaLevel::L1);
240 for c in &controls {
241 let mapping = control_slsa_mapping(&c.id()).expect("should be SLSA-mapped");
242 assert!(
243 mapping.level <= SlsaLevel::L1,
244 "{:?} is L{:?} but should be L1 or below",
245 c.id(),
246 mapping.level
247 );
248 }
249 }
250
251 #[test]
252 fn all_slsa_includes_l3_build_and_l4_source() {
253 let controls = all_slsa_controls();
254 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
255 assert!(
256 ids.iter()
257 .any(|id| id.as_str() == builtin::TWO_PARTY_REVIEW)
258 );
259 assert!(ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
260 }
261
262 #[test]
263 fn all_controls_includes_compliance() {
264 let controls = all_controls();
265 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
266 assert!(
267 ids.iter()
268 .any(|id| id.as_str() == builtin::CHANGE_REQUEST_SIZE)
269 );
270 assert!(ids.iter().any(|id| id.as_str() == builtin::ISSUE_LINKAGE));
271 }
272
273 #[test]
274 fn compliance_plus_slsa_equals_all() {
275 use crate::control::builtin;
276 let compliance = compliance_controls();
277 let slsa = all_slsa_controls();
278 assert_eq!(
279 compliance.len() + slsa.len(),
280 builtin::ALL.len(),
281 "compliance + SLSA controls must cover all built-in controls"
282 );
283 }
284
285 #[test]
286 fn compliance_controls_are_not_slsa_mapped() {
287 use crate::slsa::control_slsa_mapping;
288 let controls = compliance_controls();
289 for c in &controls {
290 assert!(
291 control_slsa_mapping(&c.id()).is_none(),
292 "{:?} should not be SLSA-mapped",
293 c.id()
294 );
295 }
296 }
297
298 #[test]
299 fn compliance_controls_have_unique_ids() {
300 let controls = compliance_controls();
301 let mut ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
302 let original_len = ids.len();
303 ids.sort_by_key(|id| id.as_str().to_string());
304 ids.dedup();
305 assert_eq!(
306 ids.len(),
307 original_len,
308 "all compliance control IDs must be unique"
309 );
310 }
311
312 #[test]
313 fn all_controls_count() {
314 let slsa = all_slsa_controls();
315 let compliance = compliance_controls();
316 let all = all_controls();
317 assert_eq!(
318 all.len(),
319 slsa.len() + compliance.len(),
320 "all_controls = SLSA + compliance"
321 );
322 }
323
324 #[test]
325 fn slsa_controls_for_level_source_l2() {
326 let controls = slsa_controls_for_level(SlsaTrack::Source, SlsaLevel::L2);
327 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
328 assert!(
329 ids.iter()
330 .any(|id| id.as_str() == builtin::BRANCH_HISTORY_INTEGRITY)
331 );
332 assert!(
333 !ids.iter()
334 .any(|id| id.as_str() == builtin::BRANCH_PROTECTION_ENFORCEMENT)
335 );
336 }
337
338 #[test]
339 fn slsa_controls_for_level_build_l2() {
340 let controls = slsa_controls_for_level(SlsaTrack::Build, SlsaLevel::L2);
341 let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
342 assert!(
343 ids.iter()
344 .any(|id| id.as_str() == builtin::HOSTED_BUILD_PLATFORM)
345 );
346 assert!(
347 ids.iter()
348 .any(|id| id.as_str() == builtin::PROVENANCE_AUTHENTICITY)
349 );
350 assert!(!ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
351 }
352}