From cc329ebdfbfa7e81d95e2f9def4eed3f548606d2 Mon Sep 17 00:00:00 2001 From: "Stephen Lewis (Burrows)" Date: Tue, 19 Sep 2023 14:58:07 -0700 Subject: [PATCH] Added support for project-level custom modules (#8993) * Added support for project-level custom modules Related to b/296259216 * gofmt * Added client-side validation of display_name * Fixed expressions in update test * Added mutex --- .../securitycenter/ProjectCustomModule.yaml | 197 ++++++++++++++++++ .../scc_project_custom_module_basic.tf.erb | 17 ++ .../scc_project_custom_module_full.tf.erb | 31 +++ ...resource_scc_project_custom_module_test.go | 77 +++++++ 4 files changed, 322 insertions(+) create mode 100644 mmv1/products/securitycenter/ProjectCustomModule.yaml create mode 100644 mmv1/templates/terraform/examples/scc_project_custom_module_basic.tf.erb create mode 100644 mmv1/templates/terraform/examples/scc_project_custom_module_full.tf.erb create mode 100644 mmv1/third_party/terraform/services/securitycenter/resource_scc_project_custom_module_test.go diff --git a/mmv1/products/securitycenter/ProjectCustomModule.yaml b/mmv1/products/securitycenter/ProjectCustomModule.yaml new file mode 100644 index 000000000000..c723c8c710a5 --- /dev/null +++ b/mmv1/products/securitycenter/ProjectCustomModule.yaml @@ -0,0 +1,197 @@ +# Copyright 2023 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Resource +name: 'ProjectCustomModule' +description: | + Represents an instance of a Security Health Analytics custom module, including + its full module name, display name, enablement state, and last updated time. + You can create a custom module at the organization, folder, or project level. + Custom modules that you create at the organization or folder level are inherited + by the child folders and projects. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Overview of custom modules for Security Health Analytics': 'https://cloud.google.com/security-command-center/docs/custom-modules-sha-overview' + api: 'https://cloud.google.com/security-command-center/docs/reference/rest/v1/projects.securityHealthAnalyticsSettings.customModules' +base_url: 'projects/{{project}}/securityHealthAnalyticsSettings/customModules' +self_link: 'projects/{{project}}/securityHealthAnalyticsSettings/customModules/{{name}}' +mutex: 'projects/{{project}}/securityHealthAnalyticsSettings/customModules' +update_verb: :PATCH +update_mask: true +examples: + - !ruby/object:Provider::Terraform::Examples + name: "scc_project_custom_module_basic" + primary_resource_id: "example" + - !ruby/object:Provider::Terraform::Examples + name: "scc_project_custom_module_full" + primary_resource_id: "example" + +properties: + - !ruby/object:Api::Type::String + name: 'name' + output: true + custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb + description: | + The resource name of the custom module. Its format is "projects/{project}/securityHealthAnalyticsSettings/customModules/{customModule}". + The id {customModule} is server-generated and is not user settable. It will be a numeric id containing 1-20 digits. + - !ruby/object:Api::Type::String + name: 'displayName' + immutable: true + required: true + # API error for invalid display names is just "INVALID_ARGUMENT" with no details + validation: !ruby/object:Provider::Terraform::Validation + function: 'verify.ValidateRegexp(`^[a-z][\w_]{0,127}$`)' + description: | + The display name of the Security Health Analytics custom module. This + display name becomes the finding category for all findings that are + returned by this custom module. The display name must be between 1 and + 128 characters, start with a lowercase letter, and contain alphanumeric + characters or underscores only. + - !ruby/object:Api::Type::Enum + name: 'enablementState' + required: true + description: | + The enablement state of the custom module. + values: + - :ENABLED + - :DISABLED + - !ruby/object:Api::Type::String + name: 'updateTime' + output: true + description: | + The time at which the custom module was last updated. + + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and + up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z". + - !ruby/object:Api::Type::String + name: 'lastEditor' + output: true + description: | + The editor that last updated the custom module. + - !ruby/object:Api::Type::String + name: 'ancestorModule' + output: true + description: | + If empty, indicates that the custom module was created in the organization,folder, + or project in which you are viewing the custom module. Otherwise, ancestor_module + specifies the organization or folder from which the custom module is inherited. + - !ruby/object:Api::Type::NestedObject + name: 'customConfig' + required: true + description: | + The user specified custom configuration for the module. + properties: + - !ruby/object:Api::Type::NestedObject + name: 'predicate' + required: true + description: | + The CEL expression to evaluate to produce findings. When the expression evaluates + to true against a resource, a finding is generated. + properties: + - !ruby/object:Api::Type::String + name: 'expression' + required: true + description: | + Textual representation of an expression in Common Expression Language syntax. + - !ruby/object:Api::Type::String + name: 'title' + description: | + Title for the expression, i.e. a short string describing its purpose. This can + be used e.g. in UIs which allow to enter the expression. + - !ruby/object:Api::Type::String + name: 'description' + description: | + Description of the expression. This is a longer text which describes the + expression, e.g. when hovered over it in a UI. + - !ruby/object:Api::Type::String + name: 'location' + description: | + String indicating the location of the expression for error reporting, e.g. a + file name and a position in the file. + - !ruby/object:Api::Type::NestedObject + name: 'customOutput' + description: | + Custom output properties. + properties: + - !ruby/object:Api::Type::Array + name: 'properties' + description: | + A list of custom output properties to add to the finding. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the property for the custom output. + - !ruby/object:Api::Type::NestedObject + name: 'valueExpression' + description: | + The CEL expression for the custom output. A resource property can be specified + to return the value of the property or a text string enclosed in quotation marks. + properties: + - !ruby/object:Api::Type::String + name: 'expression' + required: true + description: | + Textual representation of an expression in Common Expression Language syntax. + - !ruby/object:Api::Type::String + name: 'title' + description: | + Title for the expression, i.e. a short string describing its purpose. This can + be used e.g. in UIs which allow to enter the expression. + - !ruby/object:Api::Type::String + name: 'description' + description: | + Description of the expression. This is a longer text which describes the + expression, e.g. when hovered over it in a UI. + - !ruby/object:Api::Type::String + name: 'location' + description: | + String indicating the location of the expression for error reporting, e.g. a + file name and a position in the file. + - !ruby/object:Api::Type::NestedObject + name: 'resourceSelector' + required: true + description: | + The resource types that the custom module operates on. Each custom module + can specify up to 5 resource types. + properties: + - !ruby/object:Api::Type::Array + name: 'resourceTypes' + required: true + description: | + The resource types to run the detector on. + item_type: Api::Type::String + - !ruby/object:Api::Type::Enum + name: 'severity' + required: true + description: | + The severity to assign to findings generated by the module. + values: + - :CRITICAL + - :HIGH + - :MEDIUM + - :LOW + - !ruby/object:Api::Type::String + name: 'description' + description: | + Text that describes the vulnerability or misconfiguration that the custom + module detects. This explanation is returned with each finding instance to + help investigators understand the detected issue. The text must be enclosed in quotation marks. + - !ruby/object:Api::Type::String + name: 'recommendation' + required: true + description: | + An explanation of the recommended steps that security teams can take to resolve + the detected issue. This explanation is returned with each finding generated by + this module in the nextSteps property of the finding JSON. diff --git a/mmv1/templates/terraform/examples/scc_project_custom_module_basic.tf.erb b/mmv1/templates/terraform/examples/scc_project_custom_module_basic.tf.erb new file mode 100644 index 000000000000..f52bff0780da --- /dev/null +++ b/mmv1/templates/terraform/examples/scc_project_custom_module_basic.tf.erb @@ -0,0 +1,17 @@ +resource "google_scc_project_custom_module" "<%= ctx[:primary_resource_id] %>" { + display_name = "basic_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/scc_project_custom_module_full.tf.erb b/mmv1/templates/terraform/examples/scc_project_custom_module_full.tf.erb new file mode 100644 index 000000000000..2fa6803b27e7 --- /dev/null +++ b/mmv1/templates/terraform/examples/scc_project_custom_module_full.tf.erb @@ -0,0 +1,31 @@ +resource "google_scc_project_custom_module" "<%= ctx[:primary_resource_id] %>" { + display_name = "full_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/securitycenter/resource_scc_project_custom_module_test.go b/mmv1/third_party/terraform/services/securitycenter/resource_scc_project_custom_module_test.go new file mode 100644 index 000000000000..37e73a4e6cce --- /dev/null +++ b/mmv1/third_party/terraform/services/securitycenter/resource_scc_project_custom_module_test.go @@ -0,0 +1,77 @@ +package securitycenter_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(context), + }, + { + ResourceName: "google_scc_project_custom_module.example", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(context), + }, + { + ResourceName: "google_scc_project_custom_module.example", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_project_custom_module" "example" { + display_name = "full_custom_module" + enablement_state = "DISABLED" + custom_config { + predicate { + expression = "resource.name == \"updated-name\"" + title = "Updated expression title" + description = "Updated description of the expression" + location = "Updated location of the expression" + } + custom_output { + properties { + name = "violation" + value_expression { + expression = "resource.name" + title = "Updated expression title" + description = "Updated description of the expression" + location = "Updated location of the expression" + } + } + } + resource_selector { + resource_types = [ + "compute.googleapis.com/Instance", + ] + } + severity = "CRITICAL" + description = "Updated description of the custom module" + recommendation = "Updated steps to resolve violation" + } +} +`, context) +}