diff --git a/sysdig/internal/client/v2/vulnerability_rule_bundle_model.go b/sysdig/internal/client/v2/vulnerability_rule_bundle_model.go index 85d44283..a79cf7b5 100644 --- a/sysdig/internal/client/v2/vulnerability_rule_bundle_model.go +++ b/sysdig/internal/client/v2/vulnerability_rule_bundle_model.go @@ -21,15 +21,16 @@ type VulnerabilityRulePredicate struct { type VulnerabilityRulePredicateExtra struct { // Common fields for different predicate types - Level *Level `json:"level,omitempty"` - Age *int `json:"age,omitempty"` - Days *int `json:"days,omitempty"` - PkgType *string `json:"pkgType,omitempty"` - VulnIDS []string `json:"vulnIds,omitempty"` - Packages []Package `json:"packages,omitempty"` - Key *string `json:"key,omitempty"` - User *string `json:"user,omitempty"` - Value any `json:"value,omitempty"` // For image labels or CVSS score + Level *Level `json:"level,omitempty"` + Age *int `json:"age,omitempty"` + Days *int `json:"days,omitempty"` + PkgType *string `json:"pkgType,omitempty"` + VulnIDS []string `json:"vulnIds,omitempty"` + Packages []Package `json:"packages,omitempty"` + Key *string `json:"key,omitempty"` + User *string `json:"user,omitempty"` + Value any `json:"value,omitempty"` // For image labels or CVSS score + RequiredLabels []string `json:"requiredLabels,omitempty"` // Disclosure Date Range StartDate *string `json:"startDate,omitempty"` diff --git a/sysdig/resource_sysdig_secure_vulnerability_rule_bundle.go b/sysdig/resource_sysdig_secure_vulnerability_rule_bundle.go index fc3f7af6..e16ef5e7 100644 --- a/sysdig/resource_sysdig_secure_vulnerability_rule_bundle.go +++ b/sysdig/resource_sysdig_secure_vulnerability_rule_bundle.go @@ -56,6 +56,32 @@ func vulnerabilityRuleSchemaImageConfigLabel() *schema.Schema { }, }, }, + "label_with_value_and_required_labels": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "A label key-value pair that must exist along with additional required labels.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_label": { + Type: schema.TypeString, + Required: true, + Description: "Label key that must exist with the specified value.", + }, + "target_value": { + Type: schema.TypeString, + Required: true, + Description: "Expected value for the target label. Can be empty.", + }, + "required_labels": { + Type: schema.TypeList, + Required: true, + Description: "Additional label keys that must also exist.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, }, }, } @@ -574,6 +600,17 @@ func vulnerabilityRuleImageConfigLabelToData(ruleBundle v2.VulnerabilityRule) (m }}, }}, }, nil + case "imageConfigLabelWithValueAndLabelsExist": + return map[string]any{ + "image_label": []map[string]any{{ + "id": ruleBundle.ID, + "label_with_value_and_required_labels": []map[string]any{{ + "target_label": ruleBundle.Predicates[0].Extra.Key, + "target_value": ruleBundle.Predicates[0].Extra.Value, + "required_labels": ruleBundle.Predicates[0].Extra.RequiredLabels, + }}, + }}, + }, nil } 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 { return nil } +func validateImageLabelRule(ruleBody map[string]any) error { + count := 0 + if val, ok := ruleBody["label_must_exist"]; ok && val.(string) != "" { + count++ + } + if val, ok := ruleBody["label_must_not_exist"]; ok && val.(string) != "" { + count++ + } + if val, ok := ruleBody["label_must_exist_and_contain_value"]; ok && len(val.([]any)) > 0 { + count++ + } + if val, ok := ruleBody["label_with_value_and_required_labels"]; ok && len(val.([]any)) > 0 { + count++ + } + if count > 1 { + return errors.New("only one image label predicate can be set per image_label block") + } + return nil +} + func vulnerabilityRuleImageConfigLabelFromMap(ruleBody map[string]any) (v2.VulnerabilityRule, error) { + if err := validateImageLabelRule(ruleBody); err != nil { + return v2.VulnerabilityRule{}, err + } + rule := v2.VulnerabilityRule{ ID: new(ruleBody["id"].(string)), Type: v2.VulnerabilityRuleTypeImageConfigLabel, @@ -765,6 +826,23 @@ func vulnerabilityRuleImageConfigLabelFromMap(ruleBody map[string]any) (v2.Vulne }) } + if label, ok := ruleBody["label_with_value_and_required_labels"]; ok && len(label.([]any)) > 0 { + contents := label.([]any)[0].(map[string]any) + rawLabels := contents["required_labels"].([]any) + requiredLabels := make([]string, len(rawLabels)) + for i, v := range rawLabels { + requiredLabels[i] = v.(string) + } + rule.Predicates = append(rule.Predicates, v2.VulnerabilityRulePredicate{ + Type: "imageConfigLabelWithValueAndLabelsExist", + Extra: &v2.VulnerabilityRulePredicateExtra{ + Key: new(contents["target_label"].(string)), + Value: new(contents["target_value"].(string)), + RequiredLabels: requiredLabels, + }, + }) + } + if len(rule.Predicates) == 0 { return v2.VulnerabilityRule{}, errors.New("no predicate has been specified for image label rule") } diff --git a/sysdig/resource_sysdig_secure_vulnerability_rule_bundle_test.go b/sysdig/resource_sysdig_secure_vulnerability_rule_bundle_test.go index b6c1f602..29178dba 100644 --- a/sysdig/resource_sysdig_secure_vulnerability_rule_bundle_test.go +++ b/sysdig/resource_sysdig_secure_vulnerability_rule_bundle_test.go @@ -63,6 +63,10 @@ func TestAccVulnerabilityRuleBundle(t *testing.T) { Config: errorVulnerabilityRuleBundleConfig_ExploitConflict(random()), ExpectError: regexp.MustCompile("`public_exploit_available` and `public_exploit_available_since_days` are mutually exclusive"), }, + { + Config: errorVulnerabilityRuleBundleConfig_ImageLabelConflict(random()), + ExpectError: regexp.MustCompile("only one image label predicate can be set"), + }, { Config: minimalVulnerabilityRuleBundleConfig_ImageLabel(random()), Check: resource.ComposeTestCheckFunc( @@ -94,6 +98,16 @@ func TestAccVulnerabilityRuleBundle(t *testing.T) { resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_must_exist_and_contain_value.0.required_value", "required-value"), ), }, + { + Config: singleRuleConfig_label_with_value_and_required_labels(random()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.target_label", "Vendor"), + resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.target_value", "Acme"), + resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.#", "2"), + resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.0", "Team"), + resource.TestCheckResourceAttr("sysdig_secure_vulnerability_rule_bundle.sample", "rule.0.image_label.0.label_with_value_and_required_labels.0.required_labels.1", "Org"), + ), + }, { Config: fullVulnerabilityRuleBundleConfig_Severities(random()), Check: resource.ComposeTestCheckFunc( @@ -259,6 +273,24 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" { `, suffix) } +func singleRuleConfig_label_with_value_and_required_labels(suffix string) string { + return fmt.Sprintf(` +resource "sysdig_secure_vulnerability_rule_bundle" "sample" { + name = "TERRAFORM TEST %s" + description = "rule with label_with_value_and_required_labels" + rule { + image_label { + label_with_value_and_required_labels { + target_label = "Vendor" + target_value = "Acme" + required_labels = ["Team", "Org"] + } + } + } +} +`, suffix) +} + func fullVulnerabilityRuleBundleConfig_Severities(suffix string) string { return fmt.Sprintf(` resource "sysdig_secure_vulnerability_rule_bundle" "sample" { @@ -400,6 +432,24 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" { `, suffix) } +func errorVulnerabilityRuleBundleConfig_ImageLabelConflict(suffix string) string { + return fmt.Sprintf(` +resource "sysdig_secure_vulnerability_rule_bundle" "sample" { + name = "TERRAFORM TEST %s" + rule { + image_label { + label_must_exist = "required-label" + label_with_value_and_required_labels { + target_label = "Vendor" + target_value = "Acme" + required_labels = ["Team", "Org"] + } + } + } +} +`, suffix) +} + func variantVulnerabilityRuleBundleConfig_DisclosureDate(suffix string) string { return fmt.Sprintf(` resource "sysdig_secure_vulnerability_rule_bundle" "sample" { @@ -469,6 +519,16 @@ resource "sysdig_secure_vulnerability_rule_bundle" "sample" { } } + rule { + image_label { + label_with_value_and_required_labels { + target_label = "Vendor" + target_value = "Acme" + required_labels = ["Team", "Org"] + } + } + } + rule { severities_and_threats { severity_at_least = "high" diff --git a/website/docs/r/secure_vulnerability_rule_bundle.md b/website/docs/r/secure_vulnerability_rule_bundle.md index 7e595340..7fb613bc 100644 --- a/website/docs/r/secure_vulnerability_rule_bundle.md +++ b/website/docs/r/secure_vulnerability_rule_bundle.md @@ -47,6 +47,17 @@ resource "sysdig_secure_vulnerability_rule_bundle" "example_image_label" { } } } + + # Rule to ensure a label exists with a value AND other labels also exist + rule { + image_label { + label_with_value_and_required_labels { + target_label = "Vendor" + target_value = "Acme" + required_labels = ["Team", "Org"] + } + } + } } ``` @@ -114,6 +125,10 @@ Defines rules based on image labels to evaluate image configuration. Only one of * `label_must_exist_and_contain_value` - (Optional) A block specifying a label key and value that must exist in the image configuration. * `required_label` - (Required) The label key that must exist. * `required_value` - (Required) The expected value for the given label key. +* `label_with_value_and_required_labels` - (Optional) A block specifying a label key-value pair that must exist along with additional required labels. + * `target_label` - (Required) The label key that must exist with the specified value. + * `target_value` - (Required) The expected value for the target label. Can be empty. + * `required_labels` - (Required) A list of additional label keys that must also exist. #### `severities_and_threats`