Skip to content

Commit a37211b

Browse files
authored
feat(secure): add imageConfigLabelWithValueAndLabelsExist predicate (#721)
The backend shipped a new composite image label predicate (`imageConfigLabelWithValueAndLabelsExist`) that allows rules like "IF label Vendor exists AND its value contains BNPP, THEN labels Team and Org must also exist." This adds the new `label_with_value_and_required_labels` block to the `image_label` rule type in `sysdig_secure_vulnerability_rule_bundle`, following the exact same patterns as the existing three imageConfigLabel predicates. - Model: added `RequiredLabels` field to `VulnerabilityRulePredicateExtra` - Schema: added `label_with_value_and_required_labels` block with `target_label`, `target_value`, and `required_labels` - Read/write paths for the new predicate type - Acceptance tests and documentation
1 parent 56617d8 commit a37211b

4 files changed

Lines changed: 163 additions & 9 deletions

File tree

sysdig/internal/client/v2/vulnerability_rule_bundle_model.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ type VulnerabilityRulePredicate struct {
2121

2222
type VulnerabilityRulePredicateExtra struct {
2323
// Common fields for different predicate types
24-
Level *Level `json:"level,omitempty"`
25-
Age *int `json:"age,omitempty"`
26-
Days *int `json:"days,omitempty"`
27-
PkgType *string `json:"pkgType,omitempty"`
28-
VulnIDS []string `json:"vulnIds,omitempty"`
29-
Packages []Package `json:"packages,omitempty"`
30-
Key *string `json:"key,omitempty"`
31-
User *string `json:"user,omitempty"`
32-
Value any `json:"value,omitempty"` // For image labels or CVSS score
24+
Level *Level `json:"level,omitempty"`
25+
Age *int `json:"age,omitempty"`
26+
Days *int `json:"days,omitempty"`
27+
PkgType *string `json:"pkgType,omitempty"`
28+
VulnIDS []string `json:"vulnIds,omitempty"`
29+
Packages []Package `json:"packages,omitempty"`
30+
Key *string `json:"key,omitempty"`
31+
User *string `json:"user,omitempty"`
32+
Value any `json:"value,omitempty"` // For image labels or CVSS score
33+
RequiredLabels []string `json:"requiredLabels,omitempty"`
3334

3435
// Disclosure Date Range
3536
StartDate *string `json:"startDate,omitempty"`

sysdig/resource_sysdig_secure_vulnerability_rule_bundle.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,32 @@ func vulnerabilityRuleSchemaImageConfigLabel() *schema.Schema {
5656
},
5757
},
5858
},
59+
"label_with_value_and_required_labels": {
60+
Type: schema.TypeList,
61+
Optional: true,
62+
MaxItems: 1,
63+
Description: "A label key-value pair that must exist along with additional required labels.",
64+
Elem: &schema.Resource{
65+
Schema: map[string]*schema.Schema{
66+
"target_label": {
67+
Type: schema.TypeString,
68+
Required: true,
69+
Description: "Label key that must exist with the specified value.",
70+
},
71+
"target_value": {
72+
Type: schema.TypeString,
73+
Required: true,
74+
Description: "Expected value for the target label. Can be empty.",
75+
},
76+
"required_labels": {
77+
Type: schema.TypeList,
78+
Required: true,
79+
Description: "Additional label keys that must also exist.",
80+
Elem: &schema.Schema{Type: schema.TypeString},
81+
},
82+
},
83+
},
84+
},
5985
},
6086
},
6187
}
@@ -574,6 +600,17 @@ func vulnerabilityRuleImageConfigLabelToData(ruleBundle v2.VulnerabilityRule) (m
574600
}},
575601
}},
576602
}, nil
603+
case "imageConfigLabelWithValueAndLabelsExist":
604+
return map[string]any{
605+
"image_label": []map[string]any{{
606+
"id": ruleBundle.ID,
607+
"label_with_value_and_required_labels": []map[string]any{{
608+
"target_label": ruleBundle.Predicates[0].Extra.Key,
609+
"target_value": ruleBundle.Predicates[0].Extra.Value,
610+
"required_labels": ruleBundle.Predicates[0].Extra.RequiredLabels,
611+
}},
612+
}},
613+
}, nil
577614
}
578615

579616
return nil, fmt.Errorf("unsupported image config label rule for predicate: %s", ruleBundle.Predicates[0].Type)
@@ -729,7 +766,31 @@ func validateSeveritiesAndThreatsRule(ruleBody map[string]any) error {
729766
return nil
730767
}
731768

769+
func validateImageLabelRule(ruleBody map[string]any) error {
770+
count := 0
771+
if val, ok := ruleBody["label_must_exist"]; ok && val.(string) != "" {
772+
count++
773+
}
774+
if val, ok := ruleBody["label_must_not_exist"]; ok && val.(string) != "" {
775+
count++
776+
}
777+
if val, ok := ruleBody["label_must_exist_and_contain_value"]; ok && len(val.([]any)) > 0 {
778+
count++
779+
}
780+
if val, ok := ruleBody["label_with_value_and_required_labels"]; ok && len(val.([]any)) > 0 {
781+
count++
782+
}
783+
if count > 1 {
784+
return errors.New("only one image label predicate can be set per image_label block")
785+
}
786+
return nil
787+
}
788+
732789
func vulnerabilityRuleImageConfigLabelFromMap(ruleBody map[string]any) (v2.VulnerabilityRule, error) {
790+
if err := validateImageLabelRule(ruleBody); err != nil {
791+
return v2.VulnerabilityRule{}, err
792+
}
793+
733794
rule := v2.VulnerabilityRule{
734795
ID: new(ruleBody["id"].(string)),
735796
Type: v2.VulnerabilityRuleTypeImageConfigLabel,
@@ -765,6 +826,23 @@ func vulnerabilityRuleImageConfigLabelFromMap(ruleBody map[string]any) (v2.Vulne
765826
})
766827
}
767828

829+
if label, ok := ruleBody["label_with_value_and_required_labels"]; ok && len(label.([]any)) > 0 {
830+
contents := label.([]any)[0].(map[string]any)
831+
rawLabels := contents["required_labels"].([]any)
832+
requiredLabels := make([]string, len(rawLabels))
833+
for i, v := range rawLabels {
834+
requiredLabels[i] = v.(string)
835+
}
836+
rule.Predicates = append(rule.Predicates, v2.VulnerabilityRulePredicate{
837+
Type: "imageConfigLabelWithValueAndLabelsExist",
838+
Extra: &v2.VulnerabilityRulePredicateExtra{
839+
Key: new(contents["target_label"].(string)),
840+
Value: new(contents["target_value"].(string)),
841+
RequiredLabels: requiredLabels,
842+
},
843+
})
844+
}
845+
768846
if len(rule.Predicates) == 0 {
769847
return v2.VulnerabilityRule{}, errors.New("no predicate has been specified for image label rule")
770848
}

sysdig/resource_sysdig_secure_vulnerability_rule_bundle_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ func TestAccVulnerabilityRuleBundle(t *testing.T) {
6363
Config: errorVulnerabilityRuleBundleConfig_ExploitConflict(random()),
6464
ExpectError: regexp.MustCompile("`public_exploit_available` and `public_exploit_available_since_days` are mutually exclusive"),
6565
},
66+
{
67+
Config: errorVulnerabilityRuleBundleConfig_ImageLabelConflict(random()),
68+
ExpectError: regexp.MustCompile("only one image label predicate can be set"),
69+
},
6670
{
6771
Config: minimalVulnerabilityRuleBundleConfig_ImageLabel(random()),
6872
Check: resource.ComposeTestCheckFunc(
@@ -94,6 +98,16 @@ func TestAccVulnerabilityRuleBundle(t *testing.T) {
9498
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_must_exist_and_contain_value.0.required_value", "required-value"),
9599
),
96100
},
101+
{
102+
Config: singleRuleConfig_label_with_value_and_required_labels(random()),
103+
Check: resource.ComposeTestCheckFunc(
104+
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.target_label", "Vendor"),
105+
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.target_value", "Acme"),
106+
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.#", "2"),
107+
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.0", "Team"),
108+
resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.1", "Org"),
109+
),
110+
},
97111
{
98112
Config: fullVulnerabilityRuleBundleConfig_Severities(random()),
99113
Check: resource.ComposeTestCheckFunc(
@@ -259,6 +273,24 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
259273
`, suffix)
260274
}
261275

276+
func singleRuleConfig_label_with_value_and_required_labels(suffix string) string {
277+
return fmt.Sprintf(`
278+
resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
279+
name = "TERRAFORM TEST %s"
280+
description = "rule with label_with_value_and_required_labels"
281+
rule {
282+
image_label {
283+
label_with_value_and_required_labels {
284+
target_label = "Vendor"
285+
target_value = "Acme"
286+
required_labels = ["Team", "Org"]
287+
}
288+
}
289+
}
290+
}
291+
`, suffix)
292+
}
293+
262294
func fullVulnerabilityRuleBundleConfig_Severities(suffix string) string {
263295
return fmt.Sprintf(`
264296
resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
@@ -400,6 +432,24 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
400432
`, suffix)
401433
}
402434

435+
func errorVulnerabilityRuleBundleConfig_ImageLabelConflict(suffix string) string {
436+
return fmt.Sprintf(`
437+
resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
438+
name = "TERRAFORM TEST %s"
439+
rule {
440+
image_label {
441+
label_must_exist = "required-label"
442+
label_with_value_and_required_labels {
443+
target_label = "Vendor"
444+
target_value = "Acme"
445+
required_labels = ["Team", "Org"]
446+
}
447+
}
448+
}
449+
}
450+
`, suffix)
451+
}
452+
403453
func variantVulnerabilityRuleBundleConfig_DisclosureDate(suffix string) string {
404454
return fmt.Sprintf(`
405455
resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
@@ -469,6 +519,16 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" {
469519
}
470520
}
471521
522+
rule {
523+
image_label {
524+
label_with_value_and_required_labels {
525+
target_label = "Vendor"
526+
target_value = "Acme"
527+
required_labels = ["Team", "Org"]
528+
}
529+
}
530+
}
531+
472532
rule {
473533
severities_and_threats {
474534
severity_at_least = "high"

website/docs/r/secure_vulnerability_rule_bundle.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ resource "sysdig_secure_vulnerability_rule_bundle" "example_image_label" {
4747
}
4848
}
4949
}
50+
51+
# Rule to ensure a label exists with a value AND other labels also exist
52+
rule {
53+
image_label {
54+
label_with_value_and_required_labels {
55+
target_label = "Vendor"
56+
target_value = "Acme"
57+
required_labels = ["Team", "Org"]
58+
}
59+
}
60+
}
5061
}
5162
```
5263

@@ -114,6 +125,10 @@ Defines rules based on image labels to evaluate image configuration. Only one of
114125
* `label_must_exist_and_contain_value` - (Optional) A block specifying a label key and value that must exist in the image configuration.
115126
* `required_label` - (Required) The label key that must exist.
116127
* `required_value` - (Required) The expected value for the given label key.
128+
* `label_with_value_and_required_labels` - (Optional) A block specifying a label key-value pair that must exist along with additional required labels.
129+
* `target_label` - (Required) The label key that must exist with the specified value.
130+
* `target_value` - (Required) The expected value for the target label. Can be empty.
131+
* `required_labels` - (Required) A list of additional label keys that must also exist.
117132

118133
#### `severities_and_threats`
119134

0 commit comments

Comments
 (0)