diff --git a/modules/project/README.md b/modules/project/README.md
index 62bb6fea6d..5a71b1332d 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -29,6 +29,7 @@ This module implements the creation and management of one GCP project including
- [VPC Service Controls](#vpc-service-controls)
- [Project Related Outputs](#project-related-outputs)
- [Managing project related configuration without creating it](#managing-project-related-configuration-without-creating-it)
+- [API Alerts](#api-alerts)
- [Files](#files)
- [Variables](#variables)
- [Outputs](#outputs)
@@ -1357,15 +1358,54 @@ module "bucket" {
parent = var.project_id
id = "${var.prefix}-bucket"
}
+
# tftest inventory=data.yaml e2e
```
+## API Alerts
+There are events within Google Cloud that should be monitored and alerted on to ensure that you are aware of any potential security issues.
+These actions are typically seen in cases of security breaches, or potential security breaches, although they can be genuine actions that are not security related, but are still important to monitor.
+These events are typically
+- Owner Role Assignment/Changes
+- Audit Logging Configuration Changes
+- Custom Roles Creation/editing/deletion
+- VPC Network Firewall Rule Changes
+- VPC Network Route Changes
+- VPC Network Changes
+- Cloud Storage IAM Permission Changes
+- SQL Instances Configuration Changes
+
+
+Although you may not use the services listed above, such as SQL, it is still important to monitor these events for compliance purposes
+
+To enable these alerts by default on all projects created, it is recommended to default the variable `api_slerts` within `variables.tf` to true, along with adding a default email address.
+
+You can alternatively enable these alerts on a per-project basis by setting the variable `enable_api_alerts` to true on the module, along with the `default_api_alerts_email` variable
+
+```terraform
+module "project" {
+ source = "./fabric/modules/project"
+ billing_account = var.billing_account_id
+ name = "project"
+ parent = var.folder_id
+ prefix = var.prefix
+ services = [
+ "stackdriver.googleapis.com"
+ ]
+ api_alerts = {
+ enable_api_alerts = true
+ default_api_alerts_email = "monitoring@company.com"
+ }
+}
+```
+
## Files
| name | description | resources |
|---|---|---|
+| [api_metrics_alerts.tf](./api_metrics_alerts.tf) | Add API Alerts | google_logging_metric
· google_monitoring_alert_policy
· google_monitoring_notification_channel
|
| [cmek.tf](./cmek.tf) | Service Agent IAM Bindings for CMEK | google_kms_crypto_key_iam_member
|
| [iam.tf](./iam.tf) | IAM bindings. | google_project_iam_binding
· google_project_iam_custom_role
· google_project_iam_member
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member
· google_logging_project_exclusion
· google_logging_project_sink
· google_project_iam_audit_config
· google_project_iam_member
· google_pubsub_topic_iam_member
· google_storage_bucket_iam_member
|
@@ -1387,42 +1427,43 @@ module "bucket" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L165) | Project name and id suffix. | string
| ✓ | |
-| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | bool
| | false
|
-| [billing_account](variables.tf#L23) | Billing account id. | string
| | null
|
-| [compute_metadata](variables.tf#L29) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string)
| | {}
|
-| [contacts](variables.tf#L36) | 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))
| | {}
|
-| [custom_roles](variables.tf#L43) | Map of role name => list of permissions to create in this project. | map(list(string))
| | {}
|
-| [default_service_account](variables.tf#L50) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string
| | "keep"
|
-| [deletion_policy](variables.tf#L64) | Deletion policy setting for this project. | string
| | "DELETE"
|
-| [descriptive_name](variables.tf#L75) | Name of the project name. Used for project name instead of `name` variable. | string
| | null
|
-| [factories_config](variables.tf#L81) | Paths to data files and folders that enable factory functionality. | object({…})
| | {}
|
+| [name](variables.tf#L174) | Project name and id suffix. | string
| ✓ | |
+| [api_alerts](variables.tf#L17) | Enable default API alerts for the project. | object({…})
| | {}
|
+| [auto_create_network](variables.tf#L26) | Whether to create the default network for the project. | bool
| | false
|
+| [billing_account](variables.tf#L32) | Billing account id. | string
| | null
|
+| [compute_metadata](variables.tf#L38) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string)
| | {}
|
+| [contacts](variables.tf#L45) | 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))
| | {}
|
+| [custom_roles](variables.tf#L52) | Map of role name => list of permissions to create in this project. | map(list(string))
| | {}
|
+| [default_service_account](variables.tf#L59) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string
| | "keep"
|
+| [deletion_policy](variables.tf#L73) | Deletion policy setting for this project. | string
| | "DELETE"
|
+| [descriptive_name](variables.tf#L84) | Name of the project name. Used for project name instead of `name` variable. | string
| | null
|
+| [factories_config](variables.tf#L90) | 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#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string))
| | {}
|
-| [labels](variables.tf#L92) | Resource labels. | map(string)
| | {}
|
-| [lien_reason](variables.tf#L99) | If non-empty, creates a project lien with this description. | string
| | null
|
-| [logging_data_access](variables.tf#L105) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
-| [logging_exclusions](variables.tf#L120) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string)
| | {}
|
-| [logging_sinks](variables.tf#L127) | Logging sinks to create for this project. | map(object({…}))
| | {}
|
-| [metric_scopes](variables.tf#L158) | List of projects that will act as metric scopes for this project. | list(string)
| | []
|
+| [labels](variables.tf#L101) | Resource labels. | map(string)
| | {}
|
+| [lien_reason](variables.tf#L108) | If non-empty, creates a project lien with this description. | string
| | null
|
+| [logging_data_access](variables.tf#L114) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
+| [logging_exclusions](variables.tf#L129) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string)
| | {}
|
+| [logging_sinks](variables.tf#L136) | Logging sinks to create for this project. | map(object({…}))
| | {}
|
+| [metric_scopes](variables.tf#L167) | 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({…}))
| | {}
|
-| [org_policies](variables.tf#L170) | Organization policies applied to this project keyed by policy name. | map(object({…}))
| | {}
|
-| [parent](variables.tf#L197) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
-| [prefix](variables.tf#L207) | Optional prefix used to generate project id and name. | string
| | null
|
-| [project_create](variables.tf#L217) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
+| [org_policies](variables.tf#L179) | Organization policies applied to this project keyed by policy name. | map(object({…}))
| | {}
|
+| [parent](variables.tf#L206) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
+| [prefix](variables.tf#L216) | Optional prefix used to generate project id and name. | string
| | null
|
+| [project_create](variables.tf#L226) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
| [quotas](variables-quotas.tf#L17) | Service quota configuration. | map(object({…}))
| | {}
|
-| [service_agents_config](variables.tf#L223) | Automatic service agent configuration options. | object({…})
| | {}
|
-| [service_config](variables.tf#L234) | Configure service API activation. | object({…})
| | {…}
|
-| [service_encryption_key_ids](variables.tf#L246) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string))
| | {}
|
-| [services](variables.tf#L253) | Service APIs to enable. | list(string)
| | []
|
-| [shared_vpc_host_config](variables.tf#L259) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
-| [shared_vpc_service_config](variables.tf#L268) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | {…}
|
-| [skip_delete](variables.tf#L296) | Deprecated. Use deletion_policy. | bool
| | null
|
+| [service_agents_config](variables.tf#L232) | Automatic service agent configuration options. | object({…})
| | {}
|
+| [service_config](variables.tf#L243) | Configure service API activation. | object({…})
| | {…}
|
+| [service_encryption_key_ids](variables.tf#L255) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string))
| | {}
|
+| [services](variables.tf#L262) | Service APIs to enable. | list(string)
| | []
|
+| [shared_vpc_host_config](variables.tf#L268) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
+| [shared_vpc_service_config](variables.tf#L277) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | {…}
|
+| [skip_delete](variables.tf#L305) | Deprecated. Use deletion_policy. | bool
| | null
|
| [tag_bindings](variables-tags.tf#L81) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
| [tags](variables-tags.tf#L88) | 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({…}))
| | {}
|
-| [vpc_sc](variables.tf#L308) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…})
| | null
|
+| [vpc_sc](variables.tf#L317) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…})
| | null
|
## Outputs
diff --git a/modules/project/api_metrics_alerts.tf b/modules/project/api_metrics_alerts.tf
new file mode 100644
index 0000000000..6b36ae714d
--- /dev/null
+++ b/modules/project/api_metrics_alerts.tf
@@ -0,0 +1,378 @@
+/**
+ * Copyright 2024 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 Add API Alerts
+resource "google_monitoring_notification_channel" "email" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ display_name = "Default Email Notification"
+ type = "email"
+ labels = {
+ email_address = var.api_alerts.email
+ }
+}
+
+#
+# Route Changes Metric and Policy
+#
+resource "google_logging_metric" "route_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "resource.type=\"gce_route\" AND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")"
+ name = "network-route-config-changes"
+ description = "Monitor VPC network route configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "route_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "Network Route Changes"
+ conditions {
+ display_name = "Network Route Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "resource.type = \"global\" AND metric.type = \"logging.googleapis.com/user/${google_logging_metric.route_changes[count.index].name}\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# Firewall Changes Metric and Policy
+#
+resource "google_logging_metric" "firewall_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")"
+ name = "network-firewall-config-changes"
+ description = "Monitor VPC network firewall configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "firewall_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "VPC Network Firewalls Changes"
+ conditions {
+ display_name = "VPC Network Firewalls Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "resource.type = \"global\" AND metric.type = \"logging.googleapis.com/user/${google_logging_metric.firewall_changes[count.index].name}\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# VPC Changes Metric and Policy
+#
+resource "google_logging_metric" "vpc_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")"
+ name = "vpc-network-config-changes"
+ description = "Monitor VPC network configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "vpc_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "VPC Network Changes"
+ conditions {
+ display_name = "VPC Network Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "resource.type = \"global\" AND metric.type = \"logging.googleapis.com/user/${google_logging_metric.vpc_changes[count.index].name}\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# CloudSQL Changes
+#
+resource "google_logging_metric" "cloudsql_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\""
+ name = "cloudsql-changes"
+ description = "Monitor Cloud SQL configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "cloudsql_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "CloudSQL Changes"
+ conditions {
+ display_name = "CloudSQL Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "metric.type = \"logging.googleapis.com/user/${google_logging_metric.cloudsql_changes[count.index].name}\" AND resource.type=\"global\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# Cloud Storage IAM Changes
+#
+resource "google_logging_metric" "cloudstorage_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "resource.type=gcs_bucket AND protoPayload.methodName=\"storage.setIamPermissions\""
+ name = "cloudstorage-changes"
+ description = "Monitor Cloud Storage IAM configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "cloudstorage_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "CloudStorage IAM Changes"
+ conditions {
+ display_name = "CloudStorage IAM Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "resource.type = \"gcs_bucket\" AND metric.type = \"logging.googleapis.com/user/${google_logging_metric.cloudstorage_changes[count.index].name}\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# Custom Role IAM Changes
+#
+resource "google_logging_metric" "customrole_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")"
+ name = "customrole-changes"
+ description = "Monitor IAM Custom Role configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "customrole_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "IAM Custom Role Changes"
+ conditions {
+ display_name = "IAM Custom Role Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "metric.type = \"logging.googleapis.com/user/${google_logging_metric.customrole_changes[count.index].name}\" AND resource.type=\"global\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# Audit Configuration Changes
+#
+resource "google_logging_metric" "audit_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*"
+ name = "audit-changes"
+ description = "Monitor Audit configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "audit_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "Audit Configuration Changes"
+ conditions {
+ display_name = "Audit Configuration Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "metric.type = \"logging.googleapis.com/user/${google_logging_metric.audit_changes[count.index].name}\" AND resource.type=\"global\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_MEAN"
+ cross_series_reducer = "REDUCE_COUNT"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
+
+#
+# IAM Owner Configuration Changes
+#
+resource "google_logging_metric" "owner_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ filter = "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")"
+ name = "iam-owner-changes"
+ description = "Monitor IAM Owner configuration changes inside GCP projects"
+ metric_descriptor {
+ metric_kind = "DELTA"
+ value_type = "INT64"
+ }
+}
+
+resource "google_monitoring_alert_policy" "owner_changes" {
+ count = var.api_alerts.enabled ? 1 : 0
+ project = local.project.project_id
+ combiner = "OR"
+ display_name = "Owner IAM Configuration Changes"
+ conditions {
+ display_name = "Owner IAM Configuration Changed"
+ condition_threshold {
+ comparison = "COMPARISON_GT"
+ duration = "0s"
+ filter = "metric.type = \"logging.googleapis.com/user/${google_logging_metric.owner_changes[count.index].name}\" AND resource.type=\"global\""
+ trigger {
+ count = 1
+ }
+ aggregations {
+ per_series_aligner = "ALIGN_DELTA"
+ cross_series_reducer = "REDUCE_SUM"
+ alignment_period = "600s"
+ }
+ }
+ }
+ notification_channels = [
+ google_monitoring_notification_channel.email[count.index].id,
+ ]
+ alert_strategy {
+ auto_close = "604800s"
+ }
+}
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 88f59763a0..4a9954d4a2 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -14,6 +14,15 @@
* limitations under the License.
*/
+variable "api_alerts" {
+ description = "Enable default API alerts for the project."
+ type = object({
+ enabled = optional(bool, false)
+ email = optional(string, null)
+ })
+ default = {}
+}
+
variable "auto_create_network" {
description = "Whether to create the default network for the project."
type = bool