diff --git a/modules/folder/README.md b/modules/folder/README.md
index c3bcf5c19..c917a982d 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -17,6 +17,8 @@ This module allows the creation and management of folders, including support for
- [KMS Autokey](#kms-autokey)
- [Custom Security Health Analytics Modules](#custom-security-health-analytics-modules)
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
+- [Security Command Center Mute Configs](#security-command-center-mute-configs)
+ - [Security Command Center Mute Configs Factory](#security-command-center-mute-configs-factory)
- [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds)
- [Tags](#tags)
- [Files](#files)
@@ -569,6 +571,52 @@ cloudkmKeyRotationPeriod:
- "cloudkms.googleapis.com/CryptoKey"
```
+## Security Command Center Mute Configs
+
+[Security Command Center Mute Configs](https://cloud.google.com/security-command-center/docs/how-to-mute-findings) can be defined via the `scc_mute_configs` variable:
+
+```hcl
+module "folder" {
+ source = "./fabric/modules/folder"
+ parent = var.folder_id
+ name = "Folder name"
+ scc_mute_configs = {
+ muteHighSeverity = {
+ description = "Mute high severity findings"
+ filter = "severity=\"HIGH\""
+ type = "DYNAMIC"
+ }
+ }
+}
+# tftest modules=1 inventory=scc-mute-configs.yaml
+```
+
+### Security Command Center Mute Configs Factory
+
+Mute configs can also be specified via a factory. Each file is mapped to a mute config, where the config ID defaults to the file name.
+
+Mute configs defined via the variable are merged with those coming from the factory, and override them in case of duplicate names.
+
+```hcl
+module "folder" {
+ source = "./fabric/modules/folder"
+ parent = var.folder_id
+ name = "Folder name"
+ factories_config = {
+ scc_mute_configs = "data/scc_mute_configs"
+ }
+}
+# tftest modules=1 files=mute-config-1 inventory=scc-mute-configs.yaml
+```
+
+```yaml
+# tftest-file id=mute-config-1 path=data/scc_mute_configs/muteHighSeverity.yaml schema=scc-mute-config.schema.json
+muteHighSeverity:
+ description: "Mute high severity findings"
+ filter: "severity=\"HIGH\""
+ type: "DYNAMIC"
+```
+
## Cloud Asset Inventory Feeds
Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the folder level will monitor all resources within the folder and its subfolders.
@@ -645,6 +693,7 @@ module "folder" {
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [pam.tf](./pam.tf) | None | google_privileged_access_manager_entitlement |
+| [scc-mute-configs.tf](./scc-mute-configs.tf) | Folder-level SCC mute configurations. | google_scc_v2_folder_mute_config |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Folder-level Custom modules with Security Health Analytics. | google_scc_management_folder_security_health_analytics_custom_module |
| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
| [tags.tf](./tags.tf) | None | google_tags_tag_binding |
@@ -665,26 +714,27 @@ module "folder" {
| [contacts](variables.tf#L122) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} |
| [context](variables.tf#L141) | Context-specific interpolations. | object({…}) | | {} |
| [deletion_protection](variables.tf#L161) | Deletion protection setting for this folder. | bool | | false |
-| [factories_config](variables.tf#L167) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
-| [firewall_policy](variables.tf#L178) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null |
-| [folder_create](variables.tf#L187) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
+| [factories_config](variables.tf#L167) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [firewall_policy](variables.tf#L179) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null |
+| [folder_create](variables.tf#L188) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} |
| [iam_by_principals_conditional](variables-iam.tf#L68) | Authoritative IAM binding in {PRINCIPAL => {roles = [roles], condition = {cond}}} format. Principals need to be statically defined to avoid errors. Condition is required. | map(object({…})) | | {} |
-| [id](variables.tf#L197) | Folder ID in case you use folder_create=false. | string | | null |
+| [id](variables.tf#L198) | Folder ID in case you use folder_create=false. | string | | null |
| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} |
| [logging_exclusions](variables-logging.tf#L28) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} |
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null |
| [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the folder. | map(object({…})) | | {} |
-| [name](variables.tf#L203) | Folder name. | string | | null |
-| [org_policies](variables.tf#L209) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} |
+| [name](variables.tf#L204) | Folder name. | string | | null |
+| [org_policies](variables.tf#L210) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
-| [parent](variables.tf#L237) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
-| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
-| [tag_bindings](variables.tf#L251) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
+| [parent](variables.tf#L238) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
+| [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} |
+| [scc_sha_custom_modules](variables-scc.tf#L27) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
+| [tag_bindings](variables.tf#L252) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
## Outputs
diff --git a/modules/folder/scc-mute-configs.tf b/modules/folder/scc-mute-configs.tf
new file mode 100644
index 000000000..89e6a6c04
--- /dev/null
+++ b/modules/folder/scc-mute-configs.tf
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * 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.
+ */
+
+# tfdoc:file:description Folder-level SCC mute configurations.
+
+locals {
+ _scc_mute_configs_factory_path = pathexpand(coalesce(var.factories_config.scc_mute_configs, "-"))
+ _scc_mute_configs_factory_data_raw = merge([
+ for f in try(fileset(local._scc_mute_configs_factory_path, "*.yaml"), []) :
+ yamldecode(file("${local._scc_mute_configs_factory_path}/${f}"))
+ ]...)
+ _scc_mute_configs_factory_data = {
+ for k, v in local._scc_mute_configs_factory_data_raw :
+ k => {
+ description = try(v.description, null)
+ filter = v.filter
+ type = try(v.type, "DYNAMIC")
+ }
+ }
+ _scc_mute_configs = merge(
+ local._scc_mute_configs_factory_data,
+ var.scc_mute_configs
+ )
+ scc_mute_configs = {
+ for k, v in local._scc_mute_configs :
+ k => merge(v, {
+ name = k
+ parent = local.folder_id
+ })
+ }
+}
+
+resource "google_scc_v2_folder_mute_config" "scc_mute_configs" {
+ for_each = local.scc_mute_configs
+ folder = replace(local.folder_id, "folders/", "")
+ location = "global"
+ mute_config_id = each.key
+ description = each.value.description
+ filter = each.value.filter
+ type = each.value.type
+}
diff --git a/modules/folder/schemas/scc-mute-config.schema.json b/modules/folder/schemas/scc-mute-config.schema.json
new file mode 100644
index 000000000..6a46cb581
--- /dev/null
+++ b/modules/folder/schemas/scc-mute-config.schema.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "SCC Mute Configurations",
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z]+$": {
+ "type": "object",
+ "required": [
+ "filter"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "filter": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "DYNAMIC",
+ "STATIC"
+ ],
+ "default": "DYNAMIC"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/folder/schemas/scc-mute-config.schema.md b/modules/folder/schemas/scc-mute-config.schema.md
new file mode 100644
index 000000000..15db0d6ce
--- /dev/null
+++ b/modules/folder/schemas/scc-mute-config.schema.md
@@ -0,0 +1,11 @@
+# SCC Mute Configurations
+
+
+
+## Properties
+
+- **`^[a-zA-Z]+$`**: *object*
+ - **description**: *string*
+ - ⁺**filter**: *string*
+ - **type**: *string*
+ - enum: `DYNAMIC`, `STATIC`
diff --git a/modules/folder/variables-scc.tf b/modules/folder/variables-scc.tf
index 115d7967f..afa476c8a 100644
--- a/modules/folder/variables-scc.tf
+++ b/modules/folder/variables-scc.tf
@@ -14,6 +14,16 @@
* limitations under the License.
*/
+variable "scc_mute_configs" {
+ description = "SCC mute configurations keyed by name."
+ type = map(object({
+ description = optional(string)
+ filter = string
+ type = optional(string, "DYNAMIC")
+ }))
+ default = {}
+ nullable = false
+}
variable "scc_sha_custom_modules" {
description = "SCC custom modules keyed by module name."
type = map(object({
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 68114ea4d..0de8855cb 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -169,6 +169,7 @@ variable "factories_config" {
type = object({
org_policies = optional(string)
pam_entitlements = optional(string)
+ scc_mute_configs = optional(string)
scc_sha_custom_modules = optional(string)
})
nullable = false
diff --git a/modules/organization/README.md b/modules/organization/README.md
index cbabee0fe..7ad46d206 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -31,6 +31,8 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
- [Custom Roles Factory](#custom-roles-factory)
- [Custom Security Health Analytics Modules](#custom-security-health-analytics-modules)
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
+- [Security Command Center Mute Configs](#security-command-center-mute-configs)
+ - [Security Command Center Mute Configs Factory](#security-command-center-mute-configs-factory)
- [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds)
- [Tags](#tags)
- [Tags Factory](#tags-factory)
@@ -579,6 +581,50 @@ cloudkmKeyRotationPeriod:
- "cloudkms.googleapis.com/CryptoKey"
```
+## Security Command Center Mute Configs
+
+[Security Command Center Mute Configs](https://cloud.google.com/security-command-center/docs/how-to-mute-findings) can be defined via the `scc_mute_configs` variable:
+
+```hcl
+module "org" {
+ source = "./fabric/modules/organization"
+ organization_id = var.organization_id
+ scc_mute_configs = {
+ muteHighSeverity = {
+ description = "Mute high severity findings"
+ filter = "severity=\"HIGH\""
+ type = "DYNAMIC"
+ }
+ }
+}
+# tftest modules=1 resources=1 inventory=scc-mute-configs.yaml
+```
+
+### Security Command Center Mute Configs Factory
+
+Mute configs can also be specified via a factory. Each file is mapped to a mute config, where the config ID defaults to the file name.
+
+Mute configs defined via the variable are merged with those coming from the factory, and override them in case of duplicate names.
+
+```hcl
+module "org" {
+ source = "./fabric/modules/organization"
+ organization_id = var.organization_id
+ factories_config = {
+ scc_mute_configs = "data/scc_mute_configs"
+ }
+}
+# tftest modules=1 resources=1 files=mute-config-1 inventory=scc-mute-configs.yaml
+```
+
+```yaml
+# tftest-file id=mute-config-1 path=data/scc_mute_configs/mute-high-severity.yaml schema=scc-mute-config.schema.json
+muteHighSeverity:
+ description: "Mute high severity findings"
+ filter: "severity=\"HIGH\""
+ type: "DYNAMIC"
+```
+
## Cloud Asset Inventory Feeds
Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the organization level will monitor all resources within the organization.
@@ -847,6 +893,7 @@ module "org" {
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_org_policy_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [pam.tf](./pam.tf) | None | google_privileged_access_manager_entitlement |
+| [scc-mute-configs.tf](./scc-mute-configs.tf) | Organization-level SCC mute configurations. | google_scc_v2_organization_mute_config |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Organization-level Custom modules with Security Health Analytics. | google_scc_management_organization_security_health_analytics_custom_module |
| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
| [tags.tf](./tags.tf) | Manages GCP Secure Tags, keys, values, and IAM. | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_key_iam_member · google_tags_tag_value · google_tags_tag_value_iam_binding · google_tags_tag_value_iam_member |
@@ -863,13 +910,13 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [organization_id](variables.tf#L161) | Organization id in organizations/nnnnnn format. | string | ✓ | |
+| [organization_id](variables.tf#L162) | Organization id in organizations/nnnnnn format. | string | ✓ | |
| [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | map(object({…})) | | {} |
| [contacts](variables.tf#L51) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} |
| [context](variables.tf#L69) | Context-specific interpolations. | object({…}) | | {} |
| [custom_roles](variables.tf#L89) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
-| [factories_config](variables.tf#L96) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
-| [firewall_policy](variables.tf#L110) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null |
+| [factories_config](variables.tf#L96) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [firewall_policy](variables.tf#L111) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null |
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
@@ -881,10 +928,11 @@ module "org" {
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null |
| [logging_sinks](variables-logging.tf#L46) | Logging sinks to create for the organization. | map(object({…})) | | {} |
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
-| [org_policies](variables.tf#L119) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
-| [org_policy_custom_constraints](variables.tf#L147) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} |
+| [org_policies](variables.tf#L120) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
+| [org_policy_custom_constraints](variables.tf#L148) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
-| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
+| [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} |
+| [scc_sha_custom_modules](variables-scc.tf#L28) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
| [tag_bindings](variables-tags.tf#L89) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} |
| [tags](variables-tags.tf#L96) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [tags_config](variables-tags.tf#L161) | Fine-grained control on tag resource and IAM creation. | object({…}) | | {} |
@@ -904,10 +952,11 @@ module "org" {
| [organization_id](outputs.tf#L81) | Organization id dependent on module resources. | |
| [organization_policies_ids](outputs.tf#L98) | Map of ORGANIZATION_POLICIES => ID in the organization. | |
| [scc_custom_sha_modules_ids](outputs.tf#L103) | Map of SCC CUSTOM SHA MODULES => ID in the organization. | |
-| [service_agents](outputs.tf#L108) | Identities of all organization-level service agents. | |
-| [sink_writer_identities](outputs.tf#L113) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L121) | Tag key resources. | |
-| [tag_values](outputs.tf#L130) | Tag value resources. | |
-| [workforce_identity_provider_names](outputs.tf#L138) | Workforce Identity provider names. | |
-| [workforce_identity_providers](outputs.tf#L145) | Workforce Identity provider attributes. | |
+| [scc_mute_configs](outputs.tf#L108) | SCC mute configurations. | |
+| [service_agents](outputs.tf#L113) | Identities of all organization-level service agents. | |
+| [sink_writer_identities](outputs.tf#L118) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L126) | Tag key resources. | |
+| [tag_values](outputs.tf#L135) | Tag value resources. | |
+| [workforce_identity_provider_names](outputs.tf#L143) | Workforce Identity provider names. | |
+| [workforce_identity_providers](outputs.tf#L150) | Workforce Identity provider attributes. | |
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index d29c690e3..a77041b2e 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -105,6 +105,11 @@ output "scc_custom_sha_modules_ids" {
value = { for k, v in google_scc_management_organization_security_health_analytics_custom_module.scc_organization_custom_module : k => v.id }
}
+output "scc_mute_configs" {
+ description = "SCC mute configurations."
+ value = google_scc_v2_organization_mute_config.scc_mute_configs
+}
+
output "service_agents" {
description = "Identities of all organization-level service agents."
value = local.service_agents
diff --git a/modules/organization/scc-mute-configs.tf b/modules/organization/scc-mute-configs.tf
new file mode 100644
index 000000000..89e182b57
--- /dev/null
+++ b/modules/organization/scc-mute-configs.tf
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * 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.
+ */
+
+# tfdoc:file:description Organization-level SCC mute configurations.
+
+locals {
+ _scc_mute_configs_factory_path = pathexpand(coalesce(var.factories_config.scc_mute_configs, "-"))
+ _scc_mute_configs_factory_data_raw = merge([
+ for f in try(fileset(local._scc_mute_configs_factory_path, "*.yaml"), []) :
+ yamldecode(file("${local._scc_mute_configs_factory_path}/${f}"))
+ ]...)
+ _scc_mute_configs_factory_data = {
+ for k, v in local._scc_mute_configs_factory_data_raw :
+ k => {
+ description = try(v.description, null)
+ filter = v.filter
+ type = try(v.type, "DYNAMIC")
+ }
+ }
+ _scc_mute_configs = merge(
+ local._scc_mute_configs_factory_data,
+ var.scc_mute_configs
+ )
+ scc_mute_configs = {
+ for k, v in local._scc_mute_configs :
+ k => merge(v, {
+ name = k
+ parent = var.organization_id
+ })
+ }
+}
+
+resource "google_scc_v2_organization_mute_config" "scc_mute_configs" {
+ for_each = local.scc_mute_configs
+ organization = replace(var.organization_id, "organizations/", "")
+ location = "global"
+ mute_config_id = each.key
+ description = each.value.description
+ filter = each.value.filter
+ type = each.value.type
+
+ depends_on = [
+ google_organization_iam_binding.authoritative,
+ google_organization_iam_binding.bindings,
+ google_organization_iam_member.bindings,
+ ]
+}
diff --git a/modules/organization/schemas/scc-mute-config.schema.json b/modules/organization/schemas/scc-mute-config.schema.json
new file mode 100644
index 000000000..6a46cb581
--- /dev/null
+++ b/modules/organization/schemas/scc-mute-config.schema.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "SCC Mute Configurations",
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z]+$": {
+ "type": "object",
+ "required": [
+ "filter"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "filter": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "DYNAMIC",
+ "STATIC"
+ ],
+ "default": "DYNAMIC"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/organization/schemas/scc-mute-config.schema.md b/modules/organization/schemas/scc-mute-config.schema.md
new file mode 100644
index 000000000..15db0d6ce
--- /dev/null
+++ b/modules/organization/schemas/scc-mute-config.schema.md
@@ -0,0 +1,11 @@
+# SCC Mute Configurations
+
+
+
+## Properties
+
+- **`^[a-zA-Z]+$`**: *object*
+ - **description**: *string*
+ - ⁺**filter**: *string*
+ - **type**: *string*
+ - enum: `DYNAMIC`, `STATIC`
diff --git a/modules/organization/variables-scc.tf b/modules/organization/variables-scc.tf
index 115d7967f..f0a76382b 100644
--- a/modules/organization/variables-scc.tf
+++ b/modules/organization/variables-scc.tf
@@ -14,6 +14,17 @@
* limitations under the License.
*/
+variable "scc_mute_configs" {
+ description = "SCC mute configurations keyed by name."
+ type = map(object({
+ description = optional(string)
+ filter = string
+ type = optional(string, "DYNAMIC")
+ }))
+ default = {}
+ nullable = false
+}
+
variable "scc_sha_custom_modules" {
description = "SCC custom modules keyed by module name."
type = map(object({
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index fcda66109..ac814cac0 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -100,6 +100,7 @@ variable "factories_config" {
org_policies = optional(string)
org_policy_custom_constraints = optional(string)
pam_entitlements = optional(string)
+ scc_mute_configs = optional(string)
scc_sha_custom_modules = optional(string)
tags = optional(string)
})
diff --git a/modules/project/README.md b/modules/project/README.md
index ebb7a5521..33e9b91a9 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -24,6 +24,8 @@ This module implements the creation and management of one GCP project including
- [Cloud KMS Encryption Keys](#cloud-kms-encryption-keys)
- [Custom Security Health Analytics Modules](#custom-security-health-analytics-modules)
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
+- [Security Command Center Mute Configs](#security-command-center-mute-configs)
+ - [Security Command Center Mute Configs Factory](#security-command-center-mute-configs-factory)
- [Tags](#tags)
- [Tags Factory](#tags-factory)
- [Tag Bindings](#tag-bindings)
@@ -1029,6 +1031,56 @@ cloudkmKeyRotationPeriod:
- "cloudkms.googleapis.com/CryptoKey"
```
+## Security Command Center Mute Configs
+
+[Security Command Center Mute Configs](https://cloud.google.com/security-command-center/docs/how-to-mute-findings) can be defined via the `scc_mute_configs` variable:
+
+```hcl
+module "project" {
+ source = "./fabric/modules/project"
+ billing_account = var.billing_account_id
+ name = "project"
+ prefix = var.prefix
+ parent = var.folder_id
+ scc_mute_configs = {
+ muteHighSeverity = {
+ description = "Mute high severity findings"
+ filter = "severity=\"HIGH\""
+ type = "DYNAMIC"
+ }
+ }
+}
+# tftest modules=1 inventory=scc-mute-configs.yaml
+```
+
+### Security Command Center Mute Configs Factory
+
+Mute configs can also be specified via a factory. Each file is mapped to a mute config, where the config ID defaults to the file name.
+
+Mute configs defined via the variable are merged with those coming from the factory, and override them in case of duplicate names.
+
+```hcl
+module "project" {
+ source = "./fabric/modules/project"
+ billing_account = var.billing_account_id
+ name = "project"
+ prefix = var.prefix
+ parent = var.folder_id
+ factories_config = {
+ scc_mute_configs = "data/scc_mute_configs"
+ }
+}
+# tftest modules=1 files=mute-config-1 inventory=scc-mute-configs.yaml
+```
+
+```yaml
+# tftest-file id=mute-config-1 path=data/scc_mute_configs/muteHighSeverity.yaml schema=scc-mute-config.schema.json
+muteHighSeverity:
+ description: "Mute high severity findings"
+ filter: "severity=\"HIGH\""
+ type: "DYNAMIC"
+```
+
## Tags
```
@@ -2186,6 +2238,7 @@ module "project" {
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [pam.tf](./pam.tf) | None | google_privileged_access_manager_entitlement |
| [quotas.tf](./quotas.tf) | None | google_cloud_quotas_quota_preference |
+| [scc-mute-configs.tf](./scc-mute-configs.tf) | Project-level SCC mute configurations. | google_scc_v2_project_mute_config |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Project-level Custom modules with Security Health Analytics. | google_scc_management_project_security_health_analytics_custom_module |
| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | google_project_default_service_accounts · google_project_iam_member · google_project_service_identity |
| [shared-vpc.tf](./shared-vpc.tf) | Shared VPC project-level configuration. | google_compute_shared_vpc_host_project · google_compute_shared_vpc_service_project · google_compute_subnetwork_iam_member · google_project_iam_member |
@@ -2205,7 +2258,7 @@ module "project" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L237) | Project name and id suffix. | string | ✓ | |
+| [name](variables.tf#L238) | Project name and id suffix. | string | ✓ | |
| [alerts](variables-observability.tf#L17) | Monitoring alerts. | map(object({…})) | | {} |
| [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | map(object({…})) | | {} |
| [auto_create_network](variables.tf#L51) | Whether to create the default network for the project. | bool | | false |
@@ -2219,16 +2272,16 @@ module "project" {
| [default_service_account](variables.tf#L161) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string | | "keep" |
| [deletion_policy](variables.tf#L174) | Deletion policy setting for this project. | string | | "DELETE" |
| [descriptive_name](variables.tf#L185) | Descriptive project name. Set when name differs from project id. | string | | null |
-| [factories_config](variables.tf#L191) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [factories_config](variables.tf#L191) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} |
| [iam_by_principals_conditional](variables-iam.tf#L68) | Authoritative IAM binding in {PRINCIPAL => {roles = [roles], condition = {cond}}} format. Principals need to be statically defined to avoid errors. Condition is required. | map(object({…})) | | {} |
-| [kms_autokeys](variables.tf#L206) | KMS Autokey key handles. | map(object({…})) | | {} |
-| [labels](variables.tf#L224) | Resource labels. | map(string) | | {} |
-| [lien_reason](variables.tf#L231) | If non-empty, creates a project lien with this description. | string | | null |
+| [kms_autokeys](variables.tf#L207) | KMS Autokey key handles. | map(object({…})) | | {} |
+| [labels](variables.tf#L225) | Resource labels. | map(string) | | {} |
+| [lien_reason](variables.tf#L232) | If non-empty, creates a project lien with this description. | string | | null |
| [log_scopes](variables-observability.tf#L117) | Log scopes under this project. | map(object({…})) | | {} |
| [logging_data_access](variables-observability.tf#L127) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} |
| [logging_exclusions](variables-observability.tf#L138) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} |
@@ -2237,25 +2290,26 @@ module "project" {
| [metric_scopes](variables-observability.tf#L216) | List of projects that will act as metric scopes for this project. | list(string) | | [] |
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [notification_channels](variables-observability.tf#L223) | Monitoring notification channels. | map(object({…})) | | {} |
-| [org_policies](variables.tf#L242) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} |
+| [org_policies](variables.tf#L243) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
-| [parent](variables.tf#L270) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
-| [prefix](variables.tf#L284) | Optional prefix used to generate project id and name. | string | | null |
-| [project_reuse](variables.tf#L294) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null |
+| [parent](variables.tf#L271) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
+| [prefix](variables.tf#L285) | Optional prefix used to generate project id and name. | string | | null |
+| [project_reuse](variables.tf#L295) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null |
| [quotas](variables-quotas.tf#L17) | Service quota configuration. | map(object({…})) | | {} |
-| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
-| [service_agents_config](variables.tf#L314) | Automatic service agent configuration options. | object({…}) | | {} |
-| [service_config](variables.tf#L325) | Configure service API activation. | object({…}) | | {…} |
-| [service_encryption_key_ids](variables.tf#L337) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} |
-| [services](variables.tf#L344) | Service APIs to enable. | list(string) | | [] |
-| [shared_vpc_host_config](variables.tf#L350) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null |
-| [shared_vpc_service_config](variables.tf#L360) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
-| [skip_delete](variables.tf#L397) | Deprecated. Use deletion_policy. | bool | | null |
+| [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} |
+| [scc_sha_custom_modules](variables-scc.tf#L28) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
+| [service_agents_config](variables.tf#L315) | Automatic service agent configuration options. | object({…}) | | {} |
+| [service_config](variables.tf#L326) | Configure service API activation. | object({…}) | | {…} |
+| [service_encryption_key_ids](variables.tf#L338) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} |
+| [services](variables.tf#L345) | Service APIs to enable. | list(string) | | [] |
+| [shared_vpc_host_config](variables.tf#L351) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null |
+| [shared_vpc_service_config](variables.tf#L361) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
+| [skip_delete](variables.tf#L398) | Deprecated. Use deletion_policy. | bool | | null |
| [tag_bindings](variables-tags.tf#L89) | Tag bindings for this project, in key => tag value id format. | map(string) | | null |
| [tags](variables-tags.tf#L96) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [tags_config](variables-tags.tf#L161) | Fine-grained control on tag resource and IAM creation. | object({…}) | | {} |
-| [universe](variables.tf#L409) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null |
-| [vpc_sc](variables.tf#L420) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null |
+| [universe](variables.tf#L410) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null |
+| [vpc_sc](variables.tf#L421) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null |
| [workload_identity_pools](variables-identity-providers.tf#L17) | Workload Identity Federation pools and providers. | map(object({…})) | | {} |
## Outputs
diff --git a/modules/project/scc-mute-configs.tf b/modules/project/scc-mute-configs.tf
new file mode 100644
index 000000000..5b3c9e7d9
--- /dev/null
+++ b/modules/project/scc-mute-configs.tf
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * 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.
+ */
+
+# tfdoc:file:description Project-level SCC mute configurations.
+
+locals {
+ _scc_mute_configs_factory_path = pathexpand(coalesce(var.factories_config.scc_mute_configs, "-"))
+ _scc_mute_configs_factory_data_raw = merge([
+ for f in try(fileset(local._scc_mute_configs_factory_path, "*.yaml"), []) :
+ yamldecode(file("${local._scc_mute_configs_factory_path}/${f}"))
+ ]...)
+ _scc_mute_configs_factory_data = {
+ for k, v in local._scc_mute_configs_factory_data_raw :
+ k => {
+ description = try(v.description, null)
+ filter = v.filter
+ type = try(v.type, "DYNAMIC")
+ }
+ }
+ _scc_mute_configs = merge(
+ local._scc_mute_configs_factory_data,
+ var.scc_mute_configs
+ )
+ scc_mute_configs = {
+ for k, v in local._scc_mute_configs :
+ k => merge(v, {
+ name = k
+ parent = "projects/${local.project.project_id}"
+ })
+ }
+}
+
+resource "google_scc_v2_project_mute_config" "scc_mute_configs" {
+ for_each = local.scc_mute_configs
+ project = local.project.project_id
+ location = "global"
+ mute_config_id = each.key
+ description = each.value.description
+ filter = each.value.filter
+ type = each.value.type
+}
diff --git a/modules/project/schemas/scc-mute-config.schema.json b/modules/project/schemas/scc-mute-config.schema.json
new file mode 100644
index 000000000..6a46cb581
--- /dev/null
+++ b/modules/project/schemas/scc-mute-config.schema.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "SCC Mute Configurations",
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z]+$": {
+ "type": "object",
+ "required": [
+ "filter"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "filter": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "DYNAMIC",
+ "STATIC"
+ ],
+ "default": "DYNAMIC"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/project/schemas/scc-mute-config.schema.md b/modules/project/schemas/scc-mute-config.schema.md
new file mode 100644
index 000000000..15db0d6ce
--- /dev/null
+++ b/modules/project/schemas/scc-mute-config.schema.md
@@ -0,0 +1,11 @@
+# SCC Mute Configurations
+
+
+
+## Properties
+
+- **`^[a-zA-Z]+$`**: *object*
+ - **description**: *string*
+ - ⁺**filter**: *string*
+ - **type**: *string*
+ - enum: `DYNAMIC`, `STATIC`
diff --git a/modules/project/variables-scc.tf b/modules/project/variables-scc.tf
index 115d7967f..f0a76382b 100644
--- a/modules/project/variables-scc.tf
+++ b/modules/project/variables-scc.tf
@@ -14,6 +14,17 @@
* limitations under the License.
*/
+variable "scc_mute_configs" {
+ description = "SCC mute configurations keyed by name."
+ type = map(object({
+ description = optional(string)
+ filter = string
+ type = optional(string, "DYNAMIC")
+ }))
+ default = {}
+ nullable = false
+}
+
variable "scc_sha_custom_modules" {
description = "SCC custom modules keyed by module name."
type = map(object({
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 098ec8f68..22471b1ea 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -196,6 +196,7 @@ variable "factories_config" {
org_policies = optional(string)
pam_entitlements = optional(string)
quotas = optional(string)
+ scc_mute_configs = optional(string)
scc_sha_custom_modules = optional(string)
tags = optional(string)
})
diff --git a/tests/modules/folder/examples/scc-mute-configs.yaml b/tests/modules/folder/examples/scc-mute-configs.yaml
new file mode 100644
index 000000000..6470e3753
--- /dev/null
+++ b/tests/modules/folder/examples/scc-mute-configs.yaml
@@ -0,0 +1,34 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+
+
+values:
+ module.folder.google_folder.folder[0]:
+ deletion_protection: false
+ display_name: Folder name
+ parent: folders/1122334455
+ tags: null
+ timeouts: null
+ module.folder.google_scc_v2_folder_mute_config.scc_mute_configs["muteHighSeverity"]:
+ description: Mute high severity findings
+ filter: severity="HIGH"
+ location: global
+ mute_config_id: muteHighSeverity
+ type: DYNAMIC
+
+counts:
+ google_folder: 1
+ google_scc_v2_folder_mute_config: 1
+ modules: 1
+ resources: 2
diff --git a/tests/modules/organization/examples/scc-mute-configs.yaml b/tests/modules/organization/examples/scc-mute-configs.yaml
new file mode 100644
index 000000000..bbfd4adc2
--- /dev/null
+++ b/tests/modules/organization/examples/scc-mute-configs.yaml
@@ -0,0 +1,27 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+
+values:
+ module.org.google_scc_v2_organization_mute_config.scc_mute_configs["muteHighSeverity"]:
+ description: Mute high severity findings
+ filter: severity="HIGH"
+ location: global
+ mute_config_id: muteHighSeverity
+ organization: '1122334455'
+ type: DYNAMIC
+
+counts:
+ google_scc_v2_organization_mute_config: 1
+ modules: 1
+ resources: 1
diff --git a/tests/modules/project/examples/scc-mute-configs.yaml b/tests/modules/project/examples/scc-mute-configs.yaml
new file mode 100644
index 000000000..c8bfcee13
--- /dev/null
+++ b/tests/modules/project/examples/scc-mute-configs.yaml
@@ -0,0 +1,44 @@
+# Copyright 2025 Google LLC
+#
+# 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.
+
+values:
+ module.project.google_project.project[0]:
+ auto_create_network: false
+ billing_account: 123456-123456-123456
+ deletion_policy: DELETE
+ effective_labels:
+ goog-terraform-provisioned: 'true'
+ folder_id: '1122334455'
+ labels: null
+ name: test-project
+ org_id: null
+ project_id: test-project
+ tags: null
+ terraform_labels:
+ goog-terraform-provisioned: 'true'
+ timeouts: null
+ module.project.google_scc_v2_project_mute_config.scc_mute_configs["muteHighSeverity"]:
+ description: Mute high severity findings
+ filter: severity="HIGH"
+ location: global
+ mute_config_id: muteHighSeverity
+ project: test-project
+ timeouts: null
+ type: DYNAMIC
+
+counts:
+ google_project: 1
+ google_scc_v2_project_mute_config: 1
+ modules: 1
+ resources: 2