diff --git a/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml b/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml new file mode 100644 index 000000000..632be9ef2 --- /dev/null +++ b/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml @@ -0,0 +1,63 @@ +# Copyright 2026 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. + +# yaml-language-server: $schema=../../../../schemas/observability.schema.json + +notification_channels: + email-security: + type: email + display_name: Security Team Email + labels: + email_address: $email_addresses:gcp-organization-admins + enabled: true + +logging_metrics: + sa-impersonation: + filter: | + protoPayload.serviceName="iamcredentials.googleapis.com" + (protoPayload.methodName="GenerateAccessToken" OR protoPayload.methodName="GenerateIdToken") + label_extractors: + email_id: EXTRACT(resource.labels.email_id) + metric_descriptor: + metric_kind: DELTA + value_type: INT64 + unit: "1" + display_name: Service Account Impersonation + labels: + - key: email_id + value_type: STRING + +alerts: + sa-impersonation-alert: + display_name: Service Account Impersonation Alert + combiner: OR + conditions: + - display_name: Impersonation Detected + condition_threshold: + filter: | + metric.type="logging.googleapis.com/user/sa-impersonation" AND + resource.type="global" + comparison: COMPARISON_GT + threshold_value: 0 + duration: 60s + trigger: + count: 1 + aggregations: + alignment_period: 60s + per_series_aligner: ALIGN_COUNT + cross_series_reducer: REDUCE_SUM + group_by_fields: ["metric.label.email_id"] + notification_channels: + - email-security + enabled: true diff --git a/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml b/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml index 0b234f28f..cb1ba257e 100644 --- a/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml +++ b/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -83,6 +83,8 @@ org_policies: - https://token.actions.githubusercontent.com - https://gitlab.com - https://app.terraform.io +factories_config: + observability: datasets/classic/observability/iac-0 data_access_logs: storage.googleapis.com: DATA_READ: {} @@ -90,6 +92,10 @@ data_access_logs: sts.googleapis.com: DATA_READ: {} DATA_WRITE: {} + # required to capture service account impersonation events + iam.googleapis.com: + DATA_READ: {} + DATA_WRITE: {} buckets: # Terraform state bucket for this stage iac-org-state: diff --git a/fast/stages/0-org-setup/schemas/observability.schema.json b/fast/stages/0-org-setup/schemas/observability.schema.json new file mode 100644 index 000000000..cf3eb2f0a --- /dev/null +++ b/fast/stages/0-org-setup/schemas/observability.schema.json @@ -0,0 +1,514 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Observability Schema", + "type": "object", + "additionalProperties": false, + "properties": { + "alerts": { + "$ref": "#/$defs/alerts" + }, + "logging_metrics": { + "$ref": "#/$defs/logging_metrics" + }, + "notification_channels": { + "$ref": "#/$defs/notification_channels" + } + }, + "$defs": { + "alerts": { + "title": "Alerts", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-zA-Z0-9-]+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "combiner": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "notification_channels": { + "type": "array", + "items": { + "type": "string" + } + }, + "severity": { + "type": "string" + }, + "user_labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "alert_strategy": { + "type": "object", + "additionalProperties": false, + "properties": { + "auto_close": { + "type": "string" + }, + "notification_prompts": { + "type": "string" + }, + "notification_rate_limit": { + "type": "object", + "additionalProperties": false, + "properties": { + "period": { + "type": "string" + } + } + }, + "notification_channel_strategy": { + "type": "object", + "additionalProperties": false, + "properties": { + "notification_channel_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "renotify_interval": { + "type": "string" + } + } + } + } + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/$defs/condition" + } + }, + "documentation": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string" + }, + "mime_type": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "links": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "display_name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + } + } + } + }, + "required": [ + "combiner" + ] + } + } + }, + "logging_metrics": { + "title": "Logging Metrics", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-zA-Z0-9-]+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "label_extractors": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "value_extractor": { + "type": "string" + }, + "bucket_options": { + "type": "object", + "additionalProperties": false, + "properties": { + "explicit_buckets": { + "type": "object", + "additionalProperties": false, + "properties": { + "bounds": { + "type": "array", + "items": { + "type": "number" + } + } + } + }, + "exponential_buckets": { + "type": "object", + "additionalProperties": false, + "properties": { + "num_finite_buckets": { + "type": "number" + }, + "growth_factor": { + "type": "number" + }, + "scale": { + "type": "number" + } + } + }, + "linear_buckets": { + "type": "object", + "additionalProperties": false, + "properties": { + "num_finite_buckets": { + "type": "number" + }, + "width": { + "type": "number" + }, + "offset": { + "type": "number" + } + } + } + } + }, + "metric_descriptor": { + "type": "object", + "additionalProperties": false, + "properties": { + "metric_kind": { + "type": "string" + }, + "value_type": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value_type": { + "type": "string" + } + }, + "required": [ + "key" + ] + } + } + }, + "required": [ + "metric_kind", + "value_type" + ] + } + }, + "required": [ + "filter" + ] + } + } + }, + "notification_channels": { + "title": "Notification Channels", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-zA-Z0-9-]+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "user_labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "sensitive_labels": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth_token": { + "type": "string" + }, + "password": { + "type": "string" + }, + "service_key": { + "type": "string" + } + } + } + }, + "required": [ + "type" + ] + } + } + }, + "condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "display_name": { + "type": "string" + }, + "condition_absent": { + "$ref": "#/$defs/absent_condition" + }, + "condition_matched_log": { + "$ref": "#/$defs/matched_log_condition" + }, + "condition_monitoring_query_language": { + "$ref": "#/$defs/monitoring_query_condition" + }, + "condition_prometheus_query_language": { + "$ref": "#/$defs/prometheus_query_condition" + }, + "condition_threshold": { + "$ref": "#/$defs/threshold_condition" + } + }, + "required": [ + "display_name" + ] + }, + "absent_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "duration": { + "type": "string" + }, + "filter": { + "type": "string" + }, + "aggregations": { + "$ref": "#/$defs/aggregations" + }, + "trigger": { + "$ref": "#/$defs/trigger" + } + }, + "required": [ + "duration" + ] + }, + "matched_log_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string" + }, + "label_extractors": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "filter" + ] + }, + "monitoring_query_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "duration": { + "type": "string" + }, + "query": { + "type": "string" + }, + "evaluation_missing_data": { + "type": "string" + }, + "trigger": { + "$ref": "#/$defs/trigger" + } + }, + "required": [ + "duration", + "query" + ] + }, + "prometheus_query_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "query": { + "type": "string" + }, + "alert_rule": { + "type": "string" + }, + "disable_metric_validation": { + "type": "boolean" + }, + "duration": { + "type": "string" + }, + "evaluation_interval": { + "type": "string" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "rule_group": { + "type": "string" + } + }, + "required": [ + "query" + ] + }, + "threshold_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "comparison": { + "type": "string" + }, + "duration": { + "type": "string" + }, + "denominator_filter": { + "type": "string" + }, + "evaluation_missing_data": { + "type": "string" + }, + "filter": { + "type": "string" + }, + "threshold_value": { + "type": "number" + }, + "aggregations": { + "$ref": "#/$defs/aggregations" + }, + "denominator_aggregations": { + "$ref": "#/$defs/aggregations" + }, + "forecast_options": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecast_horizon": { + "type": "string" + } + } + }, + "trigger": { + "$ref": "#/$defs/trigger" + } + }, + "required": [ + "comparison", + "duration" + ] + }, + "aggregations": { + "type": "object", + "additionalProperties": false, + "properties": { + "per_series_aligner": { + "type": "string" + }, + "group_by_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "cross_series_reducer": { + "type": "string" + }, + "alignment_period": { + "type": "string" + } + } + }, + "trigger": { + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "number" + }, + "percent": { + "type": "number" + } + } + } + } +} diff --git a/fast/stages/0-org-setup/schemas/observability.schema.md b/fast/stages/0-org-setup/schemas/observability.schema.md new file mode 100644 index 000000000..e3e411782 --- /dev/null +++ b/fast/stages/0-org-setup/schemas/observability.schema.md @@ -0,0 +1,166 @@ +# Observability Schema + + + +## Properties + +*additional properties: false* + +- **alerts**: *reference([alerts](#refs-alerts))* +- **logging_metrics**: *reference([logging_metrics](#refs-logging_metrics))* +- **notification_channels**: *reference([notification_channels](#refs-notification_channels))* + +## Definitions + +- **alerts**: *object* +
*additional properties: false* + - **`^[a-zA-Z0-9-]+$`**: *object* +
*additional properties: false* + - ⁺**combiner**: *string* + - **display_name**: *string* + - **enabled**: *boolean* + - **notification_channels**: *array* + - items: *string* + - **severity**: *string* + - **user_labels**: *object* +
*additional properties: string* + - **alert_strategy**: *object* +
*additional properties: false* + - **auto_close**: *string* + - **notification_prompts**: *string* + - **notification_rate_limit**: *object* +
*additional properties: false* + - **period**: *string* + - **notification_channel_strategy**: *object* +
*additional properties: false* + - **notification_channel_names**: *array* + - items: *string* + - **renotify_interval**: *string* + - **conditions**: *array* + - items: *reference([condition](#refs-condition))* + - **documentation**: *object* +
*additional properties: false* + - **content**: *string* + - **mime_type**: *string* + - **subject**: *string* + - **links**: *array* + - items: *object* +
*additional properties: false* + - **display_name**: *string* + - **url**: *string* +- **logging_metrics**: *object* +
*additional properties: false* + - **`^[a-zA-Z0-9-]+$`**: *object* +
*additional properties: false* + - ⁺**filter**: *string* + - **bucket_name**: *string* + - **description**: *string* + - **disabled**: *boolean* + - **label_extractors**: *object* +
*additional properties: string* + - **value_extractor**: *string* + - **bucket_options**: *object* +
*additional properties: false* + - **explicit_buckets**: *object* +
*additional properties: false* + - **bounds**: *array* + - items: *number* + - **exponential_buckets**: *object* +
*additional properties: false* + - **num_finite_buckets**: *number* + - **growth_factor**: *number* + - **scale**: *number* + - **linear_buckets**: *object* +
*additional properties: false* + - **num_finite_buckets**: *number* + - **width**: *number* + - **offset**: *number* + - **metric_descriptor**: *object* +
*additional properties: false* + - ⁺**metric_kind**: *string* + - ⁺**value_type**: *string* + - **display_name**: *string* + - **unit**: *string* + - **labels**: *array* + - items: *object* +
*additional properties: false* + - ⁺**key**: *string* + - **description**: *string* + - **value_type**: *string* +- **notification_channels**: *object* +
*additional properties: false* + - **`^[a-zA-Z0-9-]+$`**: *object* +
*additional properties: false* + - ⁺**type**: *string* + - **description**: *string* + - **display_name**: *string* + - **enabled**: *boolean* + - **labels**: *object* +
*additional properties: string* + - **user_labels**: *object* +
*additional properties: string* + - **sensitive_labels**: *object* +
*additional properties: false* + - **auth_token**: *string* + - **password**: *string* + - **service_key**: *string* +- **condition**: *object* +
*additional properties: false* + - ⁺**display_name**: *string* + - **condition_absent**: *reference([absent_condition](#refs-absent_condition))* + - **condition_matched_log**: *reference([matched_log_condition](#refs-matched_log_condition))* + - **condition_monitoring_query_language**: *reference([monitoring_query_condition](#refs-monitoring_query_condition))* + - **condition_prometheus_query_language**: *reference([prometheus_query_condition](#refs-prometheus_query_condition))* + - **condition_threshold**: *reference([threshold_condition](#refs-threshold_condition))* +- **absent_condition**: *object* +
*additional properties: false* + - ⁺**duration**: *string* + - **filter**: *string* + - **aggregations**: *reference([aggregations](#refs-aggregations))* + - **trigger**: *reference([trigger](#refs-trigger))* +- **matched_log_condition**: *object* +
*additional properties: false* + - ⁺**filter**: *string* + - **label_extractors**: *object* +
*additional properties: string* +- **monitoring_query_condition**: *object* +
*additional properties: false* + - ⁺**duration**: *string* + - ⁺**query**: *string* + - **evaluation_missing_data**: *string* + - **trigger**: *reference([trigger](#refs-trigger))* +- **prometheus_query_condition**: *object* +
*additional properties: false* + - ⁺**query**: *string* + - **alert_rule**: *string* + - **disable_metric_validation**: *boolean* + - **duration**: *string* + - **evaluation_interval**: *string* + - **labels**: *object* +
*additional properties: string* + - **rule_group**: *string* +- **threshold_condition**: *object* +
*additional properties: false* + - ⁺**comparison**: *string* + - ⁺**duration**: *string* + - **denominator_filter**: *string* + - **evaluation_missing_data**: *string* + - **filter**: *string* + - **threshold_value**: *number* + - **aggregations**: *reference([aggregations](#refs-aggregations))* + - **denominator_aggregations**: *reference([aggregations](#refs-aggregations))* + - **forecast_options**: *object* +
*additional properties: false* + - **forecast_horizon**: *string* + - **trigger**: *reference([trigger](#refs-trigger))* +- **aggregations**: *object* +
*additional properties: false* + - **per_series_aligner**: *string* + - **group_by_fields**: *array* + - items: *string* + - **cross_series_reducer**: *string* + - **alignment_period**: *string* +- **trigger**: *object* +
*additional properties: false* + - **count**: *number* + - **percent**: *number* diff --git a/modules/project/alerts.tf b/modules/project/alerts.tf index ef2139c06..a28a67dc4 100644 --- a/modules/project/alerts.tf +++ b/modules/project/alerts.tf @@ -135,7 +135,7 @@ resource "google_monitoring_alert_policy" "alerts" { # first try to get a channel created by this module google_monitoring_notification_channel.channels[x].name, # otherwise check the context - var.context.notification_channels[x], + local.ctx.notification_channels[x], # if nothing else, use the provided channel as is x ) diff --git a/modules/project/logging-metrics.tf b/modules/project/logging-metrics.tf index 59dc7088e..42ba6f29d 100644 --- a/modules/project/logging-metrics.tf +++ b/modules/project/logging-metrics.tf @@ -1,5 +1,5 @@ /** - * Copyright 2025 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ resource "google_logging_metric" "metrics" { disabled = each.value.disabled bucket_name = try( # first try to check the context - var.context.log_buckets[each.value.bucket_name], + local.ctx.log_buckets[each.value.bucket_name], # if nothing else, use the provided channel as is each.value.bucket_name ) diff --git a/modules/project/notification-channels.tf b/modules/project/notification-channels.tf index 45d1678c6..1827b07f9 100644 --- a/modules/project/notification-channels.tf +++ b/modules/project/notification-channels.tf @@ -1,5 +1,5 @@ /** - * Copyright 2025 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,16 @@ resource "google_monitoring_notification_channel" "channels" { description = each.value.description display_name = each.value.display_name enabled = each.value.enabled - labels = each.value.labels - user_labels = each.value.user_labels + labels = each.value.labels == null ? null : { + for k, v in each.value.labels : + # allow interpolation of email addresses and pubsub topics + k => try( + local.ctx.email_addresses[v], + local.ctx.pubsub_topics[v], + v + ) + } + user_labels = each.value.user_labels dynamic "sensitive_labels" { for_each = each.value.sensitive_labels[*] content { diff --git a/tests/fast/stages/s0_org_setup/simple.yaml b/tests/fast/stages/s0_org_setup/simple.yaml index c9c855a3a..16395beb7 100644 --- a/tests/fast/stages/s0_org_setup/simple.yaml +++ b/tests/fast/stages/s0_org_setup/simple.yaml @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -65,6 +66,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -97,6 +99,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -129,6 +132,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -161,6 +165,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -193,6 +198,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -211,6 +217,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -228,6 +235,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -245,6 +253,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -357,6 +366,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -1112,6 +1122,14 @@ values: condition: [] project: ft0-prod-billing-exp-0 role: roles/viewer + module.factory.module.projects-iam["iac-0"].google_project_iam_audit_config.default["iam.googleapis.com"]: + audit_log_config: + - exempted_members: [] + log_type: DATA_READ + - exempted_members: [] + log_type: DATA_WRITE + project: ft0-prod-iac-core-0 + service: iam.googleapis.com module.factory.module.projects-iam["iac-0"].google_project_iam_audit_config.default["storage.googleapis.com"]: audit_log_config: - exempted_members: [] @@ -1241,6 +1259,82 @@ values: module.factory.module.projects["iac-0"].data.google_storage_project_service_account.gcs_sa[0]: project: ft0-prod-iac-core-0 user_project: null + module.factory.module.projects["iac-0"].google_logging_metric.metrics["sa-impersonation"]: + bucket_name: null + bucket_options: [] + description: null + disabled: null + filter: 'protoPayload.serviceName="iamcredentials.googleapis.com" + + (protoPayload.methodName="GenerateAccessToken" OR protoPayload.methodName="GenerateIdToken") + + ' + label_extractors: + email_id: EXTRACT(resource.labels.email_id) + metric_descriptor: + - display_name: Service Account Impersonation + labels: + - description: '' + key: email_id + value_type: STRING + metric_kind: DELTA + unit: '1' + value_type: INT64 + name: sa-impersonation + project: ft0-prod-iac-core-0 + timeouts: null + value_extractor: null + module.factory.module.projects["iac-0"].google_monitoring_alert_policy.alerts["sa-impersonation-alert"]: + alert_strategy: [] + combiner: OR + conditions: + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: [] + condition_prometheus_query_language: [] + condition_sql: [] + condition_threshold: + - aggregations: + - alignment_period: 60s + cross_series_reducer: REDUCE_SUM + group_by_fields: + - metric.label.email_id + per_series_aligner: ALIGN_COUNT + comparison: COMPARISON_GT + denominator_aggregations: [] + denominator_filter: null + duration: 60s + evaluation_missing_data: null + filter: 'metric.type="logging.googleapis.com/user/sa-impersonation" AND + + resource.type="global" + + ' + forecast_options: [] + threshold_value: 0 + trigger: + - count: 1 + percent: null + display_name: Impersonation Detected + display_name: Service Account Impersonation Alert + documentation: [] + enabled: true + project: ft0-prod-iac-core-0 + severity: null + timeouts: null + user_labels: null + module.factory.module.projects["iac-0"].google_monitoring_notification_channel.channels["email-security"]: + description: null + display_name: Security Team Email + enabled: true + force_delete: false + labels: + email_address: $email_addresses:gcp-organization-admins + project: ft0-prod-iac-core-0 + sensitive_labels: [] + timeouts: null + type: email + user_labels: null module.factory.module.projects["iac-0"].google_org_policy_policy.default["iam.workloadIdentityPoolProviders"]: dry_run_spec: [] name: projects/ft0-prod-iac-core-0/policies/iam.workloadIdentityPoolProviders @@ -2862,6 +2956,7 @@ values: stage: GA title: Custom role tagViewer module.organization[0].google_tags_tag_key.default["context"]: + allowed_values_regex: null description: Organization-level contexts. parent: organizations/1234567890 purpose: null @@ -2869,6 +2964,7 @@ values: short_name: context timeouts: null module.organization[0].google_tags_tag_key.default["environment"]: + allowed_values_regex: null description: Organization-level environments. parent: organizations/1234567890 purpose: null @@ -2876,6 +2972,7 @@ values: short_name: environment timeouts: null module.organization[0].google_tags_tag_key.default["org-policies"]: + allowed_values_regex: null description: Organization policy condition tags. parent: organizations/1234567890 purpose: null @@ -2910,7 +3007,6 @@ values: input: null output: null triggers_replace: null - counts: google_bigquery_dataset: 1 google_bigquery_default_service_account: 2 @@ -2918,17 +3014,20 @@ counts: google_essential_contacts_contact: 1 google_folder: 10 google_folder_iam_binding: 44 + google_logging_metric: 1 google_logging_organization_settings: 1 google_logging_organization_sink: 3 google_logging_project_bucket_config: 3 google_logging_project_settings: 2 + google_monitoring_alert_policy: 1 + google_monitoring_notification_channel: 1 google_org_policy_custom_constraint: 1 google_org_policy_policy: 37 google_organization_iam_audit_config: 1 google_organization_iam_binding: 37 google_organization_iam_custom_role: 9 google_project: 3 - google_project_iam_audit_config: 2 + google_project_iam_audit_config: 3 google_project_iam_binding: 17 google_project_iam_member: 15 google_project_service: 33 @@ -2948,5 +3047,5 @@ counts: google_tags_tag_value_iam_binding: 4 local_file: 9 modules: 50 - resources: 324 + resources: 328 terraform_data: 4 diff --git a/tests/modules/project/context.tfvars b/tests/modules/project/context.tfvars index 476f74134..db7277832 100644 --- a/tests/modules/project/context.tfvars +++ b/tests/modules/project/context.tfvars @@ -31,6 +31,12 @@ context = { tag_values = { "test/one" = "tagValues/1234567890" } + log_buckets = { + audit = "logging.googleapis.com/projects/my-project/locations/global/buckets/audit-bucket" + } + notification_channels = { + email = "projects/my-project/notificationChannels/12345" + } vpc_sc_perimeters = { default = "accessPolicies/888933661165/servicePerimeters/default" } @@ -38,6 +44,41 @@ context = { test = "projects/test-prod-audit-logs-0/topics/audit-logs" } } +alerts = { + test-alert = { + combiner = "OR" + display_name = "Test Alert" + conditions = [{ + display_name = "test-condition" + condition_threshold = { + comparison = "COMPARISON_GT" + duration = "60s" + filter = "resource.type=\"gce_instance\" AND metric.type=\"compute.googleapis.com/instance/cpu/utilization\"" + } + }] + notification_channels = ["$notification_channels:email"] + } +} +logging_metrics = { + test-metric = { + filter = "resource.type=\"gce_instance\"" + bucket_name = "$log_buckets:audit" + } +} +notification_channels = { + new-email = { + type = "email" + labels = { + email_address = "$email_addresses:default" + } + } + new-pubsub = { + type = "pubsub" + labels = { + topic = "$pubsub_topics:test" + } + } +} asset_feeds = { test = { billing_project = "test-project" diff --git a/tests/modules/project/context.yaml b/tests/modules/project/context.yaml index dd888c331..b45ea1424 100644 --- a/tests/modules/project/context.yaml +++ b/tests/modules/project/context.yaml @@ -44,6 +44,17 @@ values: condition: [] crypto_key_id: projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute role: roles/cloudkms.cryptoKeyEncrypterDecrypter + google_logging_metric.metrics["test-metric"]: + bucket_name: logging.googleapis.com/projects/my-project/locations/global/buckets/audit-bucket + bucket_options: [] + description: null + disabled: null + filter: resource.type="gce_instance" + label_extractors: null + name: test-metric + project: my-project + timeouts: null + value_extractor: null google_logging_project_sink.sink["test-pubsub"]: custom_writer_identity: null description: test-pubsub (Terraform-managed). @@ -54,6 +65,60 @@ values: name: test-pubsub project: my-project unique_writer_identity: true + google_monitoring_alert_policy.alerts["test-alert"]: + alert_strategy: [] + combiner: OR + conditions: + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: [] + condition_prometheus_query_language: [] + condition_sql: [] + condition_threshold: + - aggregations: [] + comparison: COMPARISON_GT + denominator_aggregations: [] + denominator_filter: null + duration: 60s + evaluation_missing_data: null + filter: resource.type="gce_instance" AND metric.type="compute.googleapis.com/instance/cpu/utilization" + forecast_options: [] + threshold_value: null + trigger: [] + display_name: test-condition + display_name: Test Alert + documentation: [] + enabled: true + notification_channels: + - projects/my-project/notificationChannels/12345 + project: my-project + severity: null + timeouts: null + user_labels: null + google_monitoring_notification_channel.channels["new-email"]: + description: null + display_name: null + enabled: true + force_delete: false + labels: + email_address: foo@example.com + project: my-project + sensitive_labels: [] + timeouts: null + type: email + user_labels: null + google_monitoring_notification_channel.channels["new-pubsub"]: + description: null + display_name: null + enabled: true + force_delete: false + labels: + topic: projects/test-prod-audit-logs-0/topics/audit-logs + project: my-project + sensitive_labels: [] + timeouts: null + type: pubsub + user_labels: null google_privileged_access_manager_entitlement.default["net-admins"]: additional_notification_targets: [] approval_workflow: @@ -253,7 +318,10 @@ counts: google_compute_shared_vpc_service_project: 1 google_essential_contacts_contact: 1 google_kms_crypto_key_iam_member: 1 + google_logging_metric: 1 google_logging_project_sink: 1 + google_monitoring_alert_policy: 1 + google_monitoring_notification_channel: 2 google_privileged_access_manager_entitlement: 1 google_project: 1 google_project_iam_audit_config: 1 @@ -267,4 +335,4 @@ counts: google_tags_tag_value_iam_binding: 2 google_tags_tag_value_iam_member: 1 modules: 0 - resources: 32 + resources: 36 diff --git a/tools/duplicate-diff.py b/tools/duplicate-diff.py index 479b75540..d014d9f21 100755 --- a/tools/duplicate-diff.py +++ b/tools/duplicate-diff.py @@ -60,6 +60,10 @@ duplicates = [ "fast/stages/2-security/schemas/folder.schema.json", "modules/project-factory/schemas/folder.schema.json", ], + [ + "fast/stages/0-org-setup/schemas/observability.schema.json", + "modules/project/schemas/observability.schema.json", + ], [ "fast/stages/1-vpcsc/schemas/ingress-policy.schema.json", "modules/vpc-sc/schemas/ingress-policy.schema.json",