diff --git a/docs/resources/custom_issue.md b/docs/resources/custom_issue.md index b2b42bab..99bad675 100644 --- a/docs/resources/custom_issue.md +++ b/docs/resources/custom_issue.md @@ -53,7 +53,7 @@ resource "xray_custom_issue" "my-issue-1" { - `cve` (Block Set, Min: 1) CVE of the custom issue (see [below for nested schema](#nestedblock--cve)) - `description` (String) Description of custom issue - `name` (String) Name of the custom issue. It must not begin with 'xray' (case insensitive) -- `package_type` (String) Package Type of custom issue. Valid values are: alpine, bower, cargo, chef, cocoapods, composer, conan, conda, cran, debian, docker, gems, generic, gitlfs, go, gradle, helm, ivy, maven, npm, nuget, opkg, p2, puppet, pypi, rpm, sbt, swift, terraform, terraformbackend, vagrant, vcs +- `package_type` (String) Package Type of custom issue. Valid values are: alpine, bower, cargo, composer, conan, conda, cran, debian, docker, generic, go, gradle, huggingface, ivy, maven, npm, nuget, oci, pypi, rpm, rubygems, sbt, terraformbe - `provider_name` (String) Provider of custom issue. It must not be 'jfrog' (case insensitive) - `severity` (String) Severity of custom issue. Valid values: Critical, High, Medium, Low, Information - `source` (Block Set, Min: 1) List of sources (see [below for nested schema](#nestedblock--source)) diff --git a/docs/resources/security_policy.md b/docs/resources/security_policy.md index cde950ef..779ab89e 100644 --- a/docs/resources/security_policy.md +++ b/docs/resources/security_policy.md @@ -188,6 +188,9 @@ Optional: - `fix_version_dependant` (Boolean) Default value is `false`. Issues that do not have a fixed version are not generated until a fixed version is available. Must be `false` with `malicious_package` enabled. - `malicious_package` (Boolean) Default value is `false`. Generating a violation on a malicious package. - `min_severity` (String) The minimum security vulnerability severity that will be impacted by the policy. Valid values: `All Severities`, `Critical`, `High`, `Medium`, `Low` +- `package_name` (String) The package name to create a rule for +- `package_type` (String) The package type to create a rule for +- `package_versions` (Set of String) package versions to apply the rule on can be (,) for any version or a open range (1,4) or closed [1,4] or one version [1] - `vulnerability_ids` (Set of String) Creates policy rules for specific vulnerability IDs that you input. You can add multiple vulnerabilities IDs up to 100. CVEs and Xray IDs are supported. Example - CVE-2015-20107, XRAY-2344 diff --git a/pkg/xray/resource/policies.go b/pkg/xray/resource/policies.go index 58008b51..a3e55d04 100644 --- a/pkg/xray/resource/policies.go +++ b/pkg/xray/resource/policies.go @@ -11,6 +11,27 @@ import ( "github.com/jfrog/terraform-provider-shared/validator" ) +var validPackageTypesSupportedXraySecPolicies = []string{ + "alpine", + "cargo", + "composer", + "conan", + "conda", + "cran", + "debian", + "docker", + "generic", + "go", + "huggingface", + "maven", + "npm", + "nuget", + "oci", + "pypi", + "rpm", + "rubygems", +} + var commonActionsSchema = map[string]*schema.Schema{ "webhooks": { Type: schema.TypeSet, @@ -203,6 +224,9 @@ type PolicyRuleCriteria struct { MaliciousPackage bool `json:"malicious_package,omitempty"` VulnerabilityIds []string `json:"vulnerability_ids,omitempty"` Exposures *PolicyExposures `json:"exposures,omitempty"` + PackageName string `json:"package_name,omitempty"` + PackageType string `json:"package_type,omitempty"` + PackageVersions []string `json:"package_versions,omitempty"` // We use pointer for CVSSRange to address nil-verification for non-primitive types. // Unlike primitive types, when the non-primitive type in the struct is set // to nil, the empty key will be created in the JSON body anyway. @@ -316,6 +340,15 @@ func unpackSecurityCriteria(tfCriteria map[string]interface{}) *PolicyRuleCriter if _, ok := tfCriteria["exposures"]; ok { criteria.Exposures = unpackExposures(tfCriteria["exposures"].([]interface{})) } + if v, ok := tfCriteria["package_name"]; ok { + criteria.PackageName = v.(string) + } + if v, ok := tfCriteria["package_type"]; ok { + criteria.PackageType = v.(string) + } + if v, ok := tfCriteria["package_versions"]; ok { + criteria.PackageVersions = sdk.CastToStringArr(v.(*schema.Set).List()) + } // This is also picky about not allowing empty values to be set cvss := unpackCVSSRange(tfCriteria["cvss_range"].([]interface{})) if cvss == nil { @@ -620,6 +653,9 @@ func packSecurityCriteria(criteria *PolicyRuleCriteria) []interface{} { m["fix_version_dependant"] = criteria.FixVersionDependant m["malicious_package"] = criteria.MaliciousPackage m["exposures"] = packExposures(criteria.Exposures) + m["package_name"] = criteria.PackageName + m["package_type"] = criteria.PackageType + m["package_versions"] = criteria.PackageVersions return []interface{}{m} } diff --git a/pkg/xray/resource/resource_xray_custom_issue.go b/pkg/xray/resource/resource_xray_custom_issue.go index 9c435483..e1f37845 100644 --- a/pkg/xray/resource/resource_xray_custom_issue.go +++ b/pkg/xray/resource/resource_xray_custom_issue.go @@ -19,35 +19,26 @@ var validPackageTypes = []string{ "alpine", "bower", "cargo", - "chef", - "cocoapods", "composer", "conan", "conda", "cran", "debian", "docker", - "gems", "generic", - "gitlfs", "go", "gradle", - "helm", + "huggingface", "ivy", "maven", "npm", "nuget", - "opkg", - "p2", - "puppet", + "oci", "pypi", "rpm", + "rubygems", "sbt", - "swift", - "terraform", - "terraformbackend", - "vagrant", - "vcs", + "terraformbe", } type VulnerableRange struct { diff --git a/pkg/xray/resource/resource_xray_security_policy.go b/pkg/xray/resource/resource_xray_security_policy.go index 2d79b4d9..3a20dbf8 100644 --- a/pkg/xray/resource/resource_xray_security_policy.go +++ b/pkg/xray/resource/resource_xray_security_policy.go @@ -106,6 +106,28 @@ func ResourceXraySecurityPolicyV2() *schema.Resource { }, }, }, + "package_name": { + Type: schema.TypeString, + Optional: true, + Description: "The package name to create a rule for", + }, + "package_type": { + Type: schema.TypeString, + Optional: true, + Description: "The package type to create a rule for", + ValidateDiagFunc: validator.StringInSlice(true, validPackageTypesSupportedXraySecPolicies...), + }, + "package_versions": { + Type: schema.TypeSet, + Optional: true, + Description: "package versions to apply the rule on can be (,) for any version or an open range (1,4) or closed [1,4] or one version [1]", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validation.ToDiagFunc( + validation.StringMatch(regexp.MustCompile(`((^(\(|\[)((\d+\.)?(\d+\.)?(\*|\d+)|(\s*))\,((\d+\.)?(\d+\.)?(\*|\d+)|(\s*))(\)|\])$|^\[(\d+\.)?(\d+\.)?(\*|\d+)\]$))`), "invalid Range, must be one of the follows: Any Version: (,) or Specific Version: [1.2], [3] or Range: (1,), [,1.2.3], (4.5.0,6.5.2]"), + ), + }, + }, } return &schema.Resource{ @@ -144,6 +166,10 @@ var criteriaMaliciousPkgDiff = func(ctx context.Context, diff *schema.ResourceDi vulnerabilityIDs := criterion["vulnerability_ids"].(*schema.Set).List() maliciousPackage := criterion["malicious_package"].(bool) exposures := criterion["exposures"].([]interface{}) + package_name := criterion["package_name"].(string) + package_type := criterion["package_type"].(string) + package_versions := criterion["package_versions"].(*schema.Set).List() + isPackageSet := len(package_name) > 0 || len(package_type) > 0 || len(package_versions) > 0 //if one of them is not defined the API will return an error guiding which one is missing if len(exposures) > 0 && maliciousPackage || (len(exposures) > 0 && len(cvssRange) > 0) || (len(exposures) > 0 && len(minSeverity) > 0) || (len(exposures) > 0 && len(vulnerabilityIDs) > 0) { @@ -165,5 +191,15 @@ var criteriaMaliciousPkgDiff = func(ctx context.Context, diff *schema.ResourceDi return fmt.Errorf("vulnerability_ids can't be set together with with malicious_package, min_severity, cvss_range and exposures") } + if (isPackageSet && len(vulnerabilityIDs) > 0) || (isPackageSet && maliciousPackage) || + (isPackageSet && len(cvssRange) > 0) || (isPackageSet && len(minSeverity) > 0) || + (isPackageSet && len(exposures) > 0) { + return fmt.Errorf("package_name, package_type and package versions can't be set together with with vulnerability_ids, malicious_package, min_severity, cvss_range and exposures") + } + + if isPackageSet && fixVersionDependant { + return fmt.Errorf("fix_version_dependant must be set to false if package type policy is used") + } + return nil } diff --git a/pkg/xray/resource/resource_xray_security_policy_test.go b/pkg/xray/resource/resource_xray_security_policy_test.go index 228586fa..3ae51ba9 100644 --- a/pkg/xray/resource/resource_xray_security_policy_test.go +++ b/pkg/xray/resource/resource_xray_security_policy_test.go @@ -20,6 +20,7 @@ const criteriaTypeSeverity = "severity" const criteriaTypeMaliciousPkg = "malicious_package" const criteriaTypeVulnerabilityIds = "vulnerability_ids" const criteriaTypeExposures = "exposures" +const criteriaTypePackageName = "package_name" var testDataSecurity = map[string]string{ "resource_name": "", @@ -664,6 +665,7 @@ func TestAccSecurityPolicy_conflictingAttributesFail(t *testing.T) { "malicious_package = true", "min_severity = \"High\"", "exposures {\nmin_severity = \"High\" \nsecrets = true \n applications = true \n services = true \n iac = true\n}", + "package_name = \"nuget://RazorEngine\"", } for _, testAttribute := range testAttributes { @@ -760,6 +762,96 @@ func TestAccSecurityPolicy_exposures(t *testing.T) { }) } +func TestAccSecurityPolicy_Packages(t *testing.T) { + _, fqrn, resourceName := testutil.MkNames("policy-", "xray_security_policy") + testData := sdk.MergeMaps(testDataSecurity) + + testData["resource_name"] = resourceName + testData["policy_name"] = fmt.Sprintf("terraform-security-policy-10-%d", testutil.RandomInt()) + testData["rule_name"] = fmt.Sprintf("test-security-rule-10-%d", testutil.RandomInt()) + testData["block_unscanned"] = "false" + testData["block_active"] = "false" + testData["package_name"] = "nuget:RazorEngine" + testData["package_type"] = "NuGet" + testData["package_version_1"] = "(1.2.3,3.10.2)" + testData["package_version_2"] = "[3.11,)" + testData["package_version_3"] = "[4.0.0]" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: acctest.VerifyDeleted(fqrn, acctest.CheckPolicy), + ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + Steps: []resource.TestStep{ + { + Config: util.ExecuteTemplate(fqrn, securityPolicyPackages, testData), + Check: verifySecurityPolicy(fqrn, testData, criteriaTypePackageName), + }, + { + ResourceName: fqrn, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSecurityPolicy_PackagesIncorrectVersionRangeFails(t *testing.T) { + _, fqrn, resourceName := testutil.MkNames("policy-", "xray_security_policy") + testData := sdk.MergeMaps(testDataSecurity) + + for _, invalidVersionRange := range []string{"3.10.0", "[3,,4]", "(1,latest)", "[1.0.0.0]"} { + testData["resource_name"] = resourceName + testData["policy_name"] = fmt.Sprintf("terraform-security-policy-10-%d", testutil.RandomInt()) + testData["rule_name"] = fmt.Sprintf("test-security-rule-10-%d", testutil.RandomInt()) + testData["block_unscanned"] = "false" + testData["block_active"] = "false" + testData["package_name"] = "nuget://RazorEngine" + testData["package_type"] = "nuget" + testData["package_version_1"] = invalidVersionRange + testData["package_version_2"] = "(3.2.1,)" + testData["package_version_3"] = "[3.2.1,]" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: acctest.VerifyDeleted(fqrn, acctest.CheckPolicy), + ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + Steps: []resource.TestStep{ + { + Config: util.ExecuteTemplate(fqrn, securityPolicyPackages, testData), + ExpectError: regexp.MustCompile("invalid value for package_versions"), + }, + }, + }) + } +} + +func TestAccSecurityPolicy_createPackagesFail(t *testing.T) { + _, fqrn, resourceName := testutil.MkNames("policy-", "xray_security_policy") + testData := sdk.MergeMaps(testDataSecurity) + + testData["resource_name"] = resourceName + testData["policy_name"] = fmt.Sprintf("terraform-security-policy-6-%d", testutil.RandomInt()) + testData["rule_name"] = fmt.Sprintf("test-security-rule-6-%d", testutil.RandomInt()) + testData["package_name"] = "nuget:RazorEngine" + testData["package_type"] = "NuGet" + testData["package_version_1"] = "(1.2.3,3.10.2)" + testData["package_version_2"] = "[3.11,)" + testData["package_version_3"] = "[4.0.0]" + testData["fix_version_dependant"] = "true" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: acctest.VerifyDeleted(fqrn, acctest.CheckPolicy), + ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + Steps: []resource.TestStep{ + { + Config: util.ExecuteTemplate(fqrn, securityPolicyPackagesFixVersionDep, testData), + ExpectError: regexp.MustCompile("fix_version_dependant must be set to false if package type policy is used"), + }, + }, + }) +} + func testAccXraySecurityPolicy_badSecurityType(name, description, ruleName string, rangeTo int) string { return fmt.Sprintf(` resource "xray_security_policy" "test" { @@ -859,6 +951,14 @@ func verifySecurityPolicy(fqrn string, testData map[string]string, criteriaType resource.TestCheckResourceAttr(fqrn, "rule.0.criteria.0.exposures.0.iac", testData["exposures_iac"]), ) } + if criteriaType == criteriaTypePackageName { + return resource.ComposeTestCheckFunc( + commonCheckList, + resource.TestCheckResourceAttr(fqrn, "rule.0.criteria.0.package_name", testData["package_name"]), + resource.TestCheckResourceAttr(fqrn, "rule.0.criteria.0.package_type", testData["package_type"]), + resource.TestCheckTypeSetElemAttr(fqrn, "rule.0.criteria.0.package_versions.*", testData["package_version_1"]), + ) + } return nil } @@ -1168,3 +1268,58 @@ const securityPolicyMaliciousPkgFixVersionDep = `resource "xray_security_policy" } } }` + +const securityPolicyPackagesFixVersionDep = `resource "xray_security_policy" "{{ .resource_name }}" { + name = "{{ .policy_name }}" + description = "{{ .policy_description }}" + type = "security" + rule { + name = "{{ .rule_name }}" + priority = 1 + criteria { + package_name = "{{ .package_name }}" + package_type = "{{ .package_type }}" + package_versions = ["{{ .package_version_1 }}", "{{ .package_version_2 }}", "{{ .package_version_3 }}"] + fix_version_dependant = {{ .fix_version_dependant }} + } + actions { + block_release_bundle_distribution = {{ .block_release_bundle_distribution }} + fail_build = {{ .fail_build }} + notify_watch_recipients = {{ .notify_watch_recipients }} + notify_deployer = {{ .notify_deployer }} + create_ticket_enabled = {{ .create_ticket_enabled }} + build_failure_grace_period_in_days = {{ .grace_period_days }} + block_download { + unscanned = {{ .block_unscanned }} + active = {{ .block_active }} + } + } + } +}` + +const securityPolicyPackages = `resource "xray_security_policy" "{{ .resource_name }}" { + name = "{{ .policy_name }}" + description = "{{ .policy_description }}" + type = "security" + rule { + name = "{{ .rule_name }}" + priority = 1 + criteria { + package_name = "{{ .package_name }}" + package_type = "{{ .package_type }}" + package_versions = ["{{ .package_version_1 }}", "{{ .package_version_2 }}", "{{ .package_version_3 }}"] + } + actions { + block_release_bundle_distribution = {{ .block_release_bundle_distribution }} + fail_build = {{ .fail_build }} + notify_watch_recipients = {{ .notify_watch_recipients }} + notify_deployer = {{ .notify_deployer }} + create_ticket_enabled = {{ .create_ticket_enabled }} + build_failure_grace_period_in_days = {{ .grace_period_days }} + block_download { + unscanned = {{ .block_unscanned }} + active = {{ .block_active }} + } + } + } +}`