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