Skip to content

Commit cc52c67

Browse files
Merge pull request #795 from DataDog/isabella.garza/SDS-1790-SDS-validator-configs
[SDS-1790] support validators with configs and pattern capture groups Co-authored-by: isabella-garza-datadog <[email protected]>
2 parents 95c6fdd + 1c03ed6 commit cc52c67

File tree

5 files changed

+336
-7
lines changed

5 files changed

+336
-7
lines changed

crates/cli/src/model/cli_configuration.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ mod tests {
209209
look_ahead_character_count: Some(30),
210210
priority: RulePriority::Medium,
211211
validators: Some(vec![]),
212+
validators_v2: None,
212213
match_validation: None,
214+
pattern_capture_groups: vec![],
213215
};
214216

215217
let secret_rule2 = SecretRule {
@@ -223,7 +225,9 @@ mod tests {
223225
look_ahead_character_count: Some(30),
224226
priority: RulePriority::Medium,
225227
validators: Some(vec![]),
228+
validators_v2: None,
226229
match_validation: None,
230+
pattern_capture_groups: vec![],
227231
};
228232

229233
let cli_configuration_base = CliConfiguration {

crates/cli/src/model/datadog_api.rs

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use kernel::model::rule_test::RuleTest;
44
use kernel::model::ruleset::RuleSet;
55
use secrets::model::secret_rule::{
66
SecretRule, SecretRuleMatchValidation, SecretRuleMatchValidationHttp,
7-
SecretRuleMatchValidationHttpCode, SecretRuleMatchValidationHttpMethod,
7+
SecretRuleMatchValidationHttpCode, SecretRuleMatchValidationHttpMethod, SecretRuleValidator,
88
};
99
use serde::{Deserialize, Serialize};
1010
use std::collections::BTreeMap;
@@ -346,6 +346,22 @@ impl TryFrom<SecretRuleApiMatchValidation> for SecretRuleMatchValidation {
346346
}
347347
}
348348

349+
#[derive(Serialize, Deserialize, Debug, Clone)]
350+
pub struct SecretRuleApiValidator {
351+
#[serde(rename = "type")]
352+
pub r#type: String,
353+
pub config: Option<serde_json::Value>,
354+
}
355+
356+
impl From<SecretRuleApiValidator> for SecretRuleValidator {
357+
fn from(val: SecretRuleApiValidator) -> Self {
358+
SecretRuleValidator {
359+
type_: val.r#type,
360+
config: val.config,
361+
}
362+
}
363+
}
364+
349365
#[derive(Serialize, Deserialize, Debug, Clone)]
350366
pub struct SecretRuleApiAttributes {
351367
pub name: String,
@@ -357,7 +373,9 @@ pub struct SecretRuleApiAttributes {
357373
pub default_excluded_keywords: Option<Vec<String>>,
358374
pub look_ahead_character_count: Option<usize>,
359375
pub validators: Option<Vec<String>>,
376+
pub validators_v2: Option<Vec<SecretRuleApiValidator>>,
360377
pub match_validation: Option<SecretRuleApiMatchValidation>,
378+
pub pattern_capture_groups: Option<Vec<String>>,
361379
}
362380

363381
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -392,8 +410,16 @@ impl TryFrom<SecretRuleApiType> for SecretRule {
392410
look_ahead_character_count: val.attributes.look_ahead_character_count,
393411
priority: val.attributes.priority.as_str().try_into()?,
394412
validators: val.attributes.validators,
413+
validators_v2: val
414+
.attributes
415+
.validators_v2
416+
.map(|v| v.into_iter().map(|validator| validator.into()).collect()),
395417
match_validation: Some(validation),
396418
sds_id: val.attributes.sds_id,
419+
pattern_capture_groups: val
420+
.attributes
421+
.pattern_capture_groups
422+
.unwrap_or_default(),
397423
}),
398424
Err(s) => Err(s),
399425
}
@@ -415,7 +441,12 @@ impl TryFrom<SecretRuleApiType> for SecretRule {
415441
.unwrap_or_default(),
416442
look_ahead_character_count: val.attributes.look_ahead_character_count,
417443
validators: val.attributes.validators,
444+
validators_v2: val
445+
.attributes
446+
.validators_v2
447+
.map(|v| v.into_iter().map(|validator| validator.into()).collect()),
418448
match_validation: None,
449+
pattern_capture_groups: val.attributes.pattern_capture_groups.unwrap_or_default(),
419450
})
420451
}
421452
}
@@ -558,6 +589,7 @@ mod tests {
558589
default_excluded_keywords: None,
559590
look_ahead_character_count: None,
560591
validators: None,
592+
validators_v2: None,
561593
match_validation: Some(SecretRuleApiMatchValidation {
562594
r#type: "foo".to_string(),
563595
endpoint: None,
@@ -568,6 +600,7 @@ mod tests {
568600
valid_http_status_code: None,
569601
invalid_http_status_code: None,
570602
}),
603+
pattern_capture_groups: None,
571604
},
572605
};
573606
let converted = <SecretRuleApiType as TryInto<SecretRule>>::try_into(
@@ -591,6 +624,7 @@ mod tests {
591624
look_ahead_character_count: None,
592625
sds_id: "71A7A0ED-DD03-45C5-9C2E-56B30CB566E0".to_string(),
593626
validators: None,
627+
validators_v2: None,
594628
match_validation: Some(SecretRuleApiMatchValidation {
595629
r#type: SecretRuleApiMatchValidation::CUSTOM_HTTP_STRING.to_string(),
596630
endpoint: Some("endpoint".to_string()),
@@ -601,6 +635,7 @@ mod tests {
601635
valid_http_status_code: None,
602636
invalid_http_status_code: None,
603637
}),
638+
pattern_capture_groups: None,
604639
},
605640
};
606641
let converted = <SecretRuleApiType as TryInto<SecretRule>>::try_into(
@@ -623,6 +658,7 @@ mod tests {
623658
look_ahead_character_count: None,
624659
sds_id: "71A7A0ED-DD03-45C5-9C2E-56B30CB566E0".to_string(),
625660
validators: None,
661+
validators_v2: None,
626662
match_validation: Some(SecretRuleApiMatchValidation {
627663
r#type: SecretRuleApiMatchValidation::AWS_SECRET_STRING.to_string(),
628664
endpoint: None,
@@ -633,6 +669,7 @@ mod tests {
633669
valid_http_status_code: None,
634670
invalid_http_status_code: None,
635671
}),
672+
pattern_capture_groups: None,
636673
},
637674
};
638675
let converted = <SecretRuleApiType as TryInto<SecretRule>>::try_into(
@@ -661,6 +698,7 @@ mod tests {
661698
default_excluded_keywords: None,
662699
look_ahead_character_count: None,
663700
validators: None,
701+
validators_v2: None,
664702
match_validation: Some(SecretRuleApiMatchValidation {
665703
r#type: SecretRuleApiMatchValidation::AWS_ID_STRING.to_string(),
666704
endpoint: None,
@@ -671,6 +709,7 @@ mod tests {
671709
valid_http_status_code: None,
672710
invalid_http_status_code: None,
673711
}),
712+
pattern_capture_groups: None,
674713
},
675714
};
676715
let converted = <SecretRuleApiType as TryInto<SecretRule>>::try_into(
@@ -699,6 +738,7 @@ mod tests {
699738
default_excluded_keywords: None,
700739
look_ahead_character_count: None,
701740
validators: None,
741+
validators_v2: None,
702742
match_validation: Some(SecretRuleApiMatchValidation {
703743
r#type: SecretRuleApiMatchValidation::AWS_SESSION_STRING.to_string(),
704744
endpoint: None,
@@ -709,6 +749,7 @@ mod tests {
709749
valid_http_status_code: None,
710750
invalid_http_status_code: None,
711751
}),
752+
pattern_capture_groups: None,
712753
},
713754
};
714755
let converted = <SecretRuleApiType as TryInto<SecretRule>>::try_into(
@@ -722,4 +763,104 @@ mod tests {
722763
SecretRuleMatchValidation::AwsSession
723764
);
724765
}
766+
767+
#[test]
768+
fn convert_secrets_rules_with_validators_v2() {
769+
let json_data = json!({
770+
"data": [
771+
{
772+
"id": "secrets/adobe-access-token",
773+
"type": "secret_rule",
774+
"attributes": {
775+
"default_included_keywords": [],
776+
"description": "test description",
777+
"license": "legal notice",
778+
"name": "Adobe Access Token Scanner",
779+
"pattern": "\\b(?<sds_match>abc)",
780+
"pattern_capture_groups": ["sds_match"],
781+
"priority": "medium",
782+
"sds_id": "qORGfxt5PrpmZ3uvQA937v",
783+
"validators": null,
784+
"validators_v2": [
785+
{
786+
"type": "JwtClaimsValidator",
787+
"config": {
788+
"required_claims": {
789+
"as": {"type": "Present"},
790+
"client_id": {"type": "Present"}
791+
},
792+
"required_headers": {
793+
"itt": {"type": "ExactValue", "config": "at"},
794+
"x5u": {"type": "Present"}
795+
}
796+
}
797+
}
798+
]
799+
}
800+
},
801+
{
802+
"id": "secrets/github-access-token",
803+
"type": "secret_rule",
804+
"attributes": {
805+
"default_included_keywords": ["access", "github", "token"],
806+
"description": "test description",
807+
"license": "legal notice",
808+
"name": "Github Access Token Scanner",
809+
"pattern": "\\bgh[opsu]_[0-9a-zA-Z]{36}\\b",
810+
"priority": "high",
811+
"sds_id": "5rjXkBMvQ3GbbYrpVP_HdQ",
812+
"validators": ["GithubTokenChecksum"],
813+
"validators_v2": [
814+
{
815+
"type": "GithubTokenChecksum"
816+
}
817+
]
818+
}
819+
}
820+
]
821+
});
822+
823+
let api_response: StaticAnalysisSecretsAPIResponse =
824+
serde_json::from_value(json_data).expect("Failed to deserialize JSON");
825+
826+
// Test Adobe Access Token with JwtClaimsValidator config
827+
let adobe_rule: SecretRule = api_response.data[0]
828+
.clone()
829+
.try_into()
830+
.expect("Failed to convert Adobe rule");
831+
832+
assert_eq!(adobe_rule.id, "secrets/adobe-access-token");
833+
assert!(adobe_rule.validators_v2.is_some());
834+
let adobe_validators = adobe_rule.validators_v2.as_ref().unwrap();
835+
assert_eq!(adobe_validators.len(), 1);
836+
assert_eq!(adobe_validators[0].type_, "JwtClaimsValidator");
837+
assert!(adobe_validators[0].config.is_some());
838+
839+
// Verify the validator can be successfully converted to dd_sds::SecondaryValidator
840+
let secondary_validator = adobe_validators[0].try_to_secondary_validator(false);
841+
assert!(
842+
secondary_validator.is_some(),
843+
"Should successfully convert to SecondaryValidator::JwtClaimsValidator"
844+
);
845+
846+
// Check pattern capture groups
847+
assert_eq!(adobe_rule.pattern_capture_groups.len(), 1);
848+
assert_eq!(adobe_rule.pattern_capture_groups[0], "sds_match");
849+
850+
// Test Github Access Token with simple validator (no config)
851+
let github_rule: SecretRule = api_response.data[1]
852+
.clone()
853+
.try_into()
854+
.expect("Failed to convert Github rule");
855+
856+
assert_eq!(github_rule.id, "secrets/github-access-token");
857+
assert!(github_rule.validators_v2.is_some());
858+
let github_validators = github_rule.validators_v2.as_ref().unwrap();
859+
assert_eq!(github_validators.len(), 1);
860+
assert_eq!(github_validators[0].type_, "GithubTokenChecksum");
861+
assert!(github_validators[0].config.is_none());
862+
863+
// Check pattern capture group is empty
864+
assert!(github_rule.pattern_capture_groups.is_empty());
865+
}
725866
}

crates/cli/src/sarif/sarif_utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,9 @@ mod tests {
14431443
default_excluded_keywords: vec![],
14441444
look_ahead_character_count: Some(30),
14451445
validators: Some(vec![]),
1446+
validators_v2: None,
14461447
match_validation: None,
1448+
pattern_capture_groups: vec![],
14471449
};
14481450

14491451
#[rustfmt::skip]
@@ -1589,7 +1591,9 @@ mod tests {
15891591
default_excluded_keywords: vec![],
15901592
look_ahead_character_count: Some(30),
15911593
validators: Some(vec![]),
1594+
validators_v2: None,
15921595
match_validation: None,
1596+
pattern_capture_groups: vec![],
15931597
};
15941598
let expected_level = get_level_from_severity(map_priority_to_severity(rule.priority));
15951599

0 commit comments

Comments
 (0)