diff --git a/examples/autokey_example/main.tf b/examples/autokey_example/main.tf index bb95e2d..df8c26c 100644 --- a/examples/autokey_example/main.tf +++ b/examples/autokey_example/main.tf @@ -16,7 +16,7 @@ module "autokey" { source = "terraform-google-modules/kms/google//modules/autokey" - version = "3.1.0" + version = "~> 3.2" project_id = var.project_id autokey_folder_number = var.folder_id diff --git a/examples/import_only_example/main.tf b/examples/import_only_example/main.tf index f0a7745..3ac1c99 100644 --- a/examples/import_only_example/main.tf +++ b/examples/import_only_example/main.tf @@ -21,7 +21,8 @@ resource "random_pet" "main" { } module "kms" { - source = "../.." + source = "terraform-google-modules/kms/google" + version = "~> 3.2" project_id = var.project_id keyring = random_pet.main.id diff --git a/examples/monitoring_alerts/README.md b/examples/monitoring_alerts/README.md new file mode 100644 index 0000000..5c1bec9 --- /dev/null +++ b/examples/monitoring_alerts/README.md @@ -0,0 +1,24 @@ +# Monitoring Alert Example + +This example provides monitoring e-mail alerts for KMS key versions scheduled for destruction. If multiple key versions are deleted in less than 5 minutes, a single notification will be sent. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| email\_addresses\_to\_be\_notified | Email addresses used for sending notifications to. | `list(string)` | n/a | yes | +| location | Location to create the KMS key and keyring. | `string` | `"us-central1"` | no | +| monitor\_all\_keys\_in\_the\_project | True for all KMS key versions under the same project to be monitored, false for only the KMS key version created in this example to be monitored. Default: false. | `bool` | n/a | yes | +| project\_id | The ID of the project in which to provision resources. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| key | The version of the created KMS key. | +| keyring | The keyring created. | +| notification\_channel\_names | Notification channel names. | +| project\_id | GCP Project ID where key version was created. | + + diff --git a/examples/monitoring_alerts/main.tf b/examples/monitoring_alerts/main.tf new file mode 100644 index 0000000..260f40a --- /dev/null +++ b/examples/monitoring_alerts/main.tf @@ -0,0 +1,82 @@ +/** + * 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. + */ + +/** + * Send a warning email when a KMS key version is scheduled for destruction. + * If multiple key versions are deleted in less than 5 minutes, a single notification will be sent. + */ + +# See all the request types available for google.cloud.kms.v1 here: https://cloud.google.com/kms/docs/reference/rpc/google.cloud.kms.v1. For this example specifically we are monitoring and alerting DestroyCryptoKeyVersionRequest. +locals { + all_keys_filter = "protoPayload.request.@type=\"type.googleapis.com/google.cloud.kms.v1.DestroyCryptoKeyVersionRequest\"" + single_key_filter = "${local.all_keys_filter} AND protoPayload.request.name=~\"${values(module.kms.keys)[0]}/.*\"" + # It's possible to replace "${values(module.kms.keys)[0]}" with your own existing KMS key's name. It's not required to create a new KMS key to take leverage from this example. +} + +resource "random_string" "suffix" { + length = 4 + special = false + upper = false +} + +module "kms" { + source = "terraform-google-modules/kms/google" + version = "~> 3.2" + + project_id = var.project_id + keyring = "alert-keyring-${random_string.suffix.result}" + location = var.location + keys = ["alert-key"] + prevent_destroy = false +} + +resource "google_monitoring_alert_policy" "main" { + project = var.project_id + display_name = "KMS Key Version Destruction Alert" + documentation { + content = "KMS Key Version alert: one or more key versions from ${var.project_id} project were scheduled for destruction." + } + combiner = "OR" + conditions { + display_name = "Destroy condition" + condition_matched_log { + filter = var.monitor_all_keys_in_the_project ? local.all_keys_filter : local.single_key_filter + } + } + + alert_strategy { + notification_rate_limit { + period = "300s" + } + } + + notification_channels = [for email_ch in google_monitoring_notification_channel.email_channel : email_ch.name] + + severity = "WARNING" +} + +resource "google_monitoring_notification_channel" "email_channel" { + for_each = toset(var.email_addresses_to_be_notified) + + project = var.project_id + display_name = "KMS version scheduled for destruction alert channel" + type = "email" + description = "Sends email notifications for KMS key versions scheduled for destruction alerts" + + labels = { + email_address = each.value + } +} diff --git a/examples/monitoring_alerts/outputs.tf b/examples/monitoring_alerts/outputs.tf new file mode 100644 index 0000000..e8993f9 --- /dev/null +++ b/examples/monitoring_alerts/outputs.tf @@ -0,0 +1,35 @@ +/** + * 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. + */ + +output "key" { + value = values(module.kms.keys)[0] + description = "The version of the created KMS key." +} + +output "keyring" { + value = module.kms.keyring_name + description = "The keyring created." +} + +output "project_id" { + value = var.project_id + description = "GCP Project ID where key version was created." +} + +output "notification_channel_names" { + value = [for channel in google_monitoring_notification_channel.email_channel : channel.name] + description = "Notification channel names." +} diff --git a/examples/monitoring_alerts/variables.tf b/examples/monitoring_alerts/variables.tf new file mode 100644 index 0000000..75317c3 --- /dev/null +++ b/examples/monitoring_alerts/variables.tf @@ -0,0 +1,36 @@ +/** + * 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. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = string +} + +variable "monitor_all_keys_in_the_project" { + type = bool + description = "True for all KMS key versions under the same project to be monitored, false for only the KMS key version created in this example to be monitored. Default: false." +} + +variable "email_addresses_to_be_notified" { + type = list(string) + description = "Email addresses used for sending notifications to." +} + +variable "location" { + type = string + description = "Location to create the KMS key and keyring." + default = "us-central1" +} diff --git a/test/fixtures/monitoring_alerts_on_project/main.tf b/test/fixtures/monitoring_alerts_on_project/main.tf new file mode 100644 index 0000000..cbb4339 --- /dev/null +++ b/test/fixtures/monitoring_alerts_on_project/main.tf @@ -0,0 +1,23 @@ +/** + * 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. + */ + +module "monitoring_alert_on_project" { + source = "../../../examples/monitoring_alerts" + + monitor_all_keys_in_the_project = true + project_id = var.project_id + email_addresses_to_be_notified = ["email@example.com", "email2@example.com"] +} diff --git a/test/fixtures/monitoring_alerts_on_project/outputs.tf b/test/fixtures/monitoring_alerts_on_project/outputs.tf new file mode 100644 index 0000000..dd076c4 --- /dev/null +++ b/test/fixtures/monitoring_alerts_on_project/outputs.tf @@ -0,0 +1,35 @@ +/** + * 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. + */ + +output "key" { + value = module.monitoring_alert_on_project.key + description = "The version of the created KMS key." +} + +output "keyring" { + value = module.monitoring_alert_on_project.keyring + description = "The keyring created." +} + +output "project_id" { + value = module.monitoring_alert_on_project.project_id + description = "GCP Project ID where key version was created." +} + +output "notification_channel_names" { + value = module.monitoring_alert_on_project.notification_channel_names + description = "Notification channel names." +} diff --git a/test/fixtures/monitoring_alerts_on_project/variables.tf b/test/fixtures/monitoring_alerts_on_project/variables.tf new file mode 100644 index 0000000..561b5a3 --- /dev/null +++ b/test/fixtures/monitoring_alerts_on_project/variables.tf @@ -0,0 +1,20 @@ +/** + * 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. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = string +} diff --git a/test/fixtures/monitoring_alerts_specific_key/main.tf b/test/fixtures/monitoring_alerts_specific_key/main.tf new file mode 100644 index 0000000..1b48fd6 --- /dev/null +++ b/test/fixtures/monitoring_alerts_specific_key/main.tf @@ -0,0 +1,23 @@ +/** + * 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. + */ + +module "monitoring_alert_specific_key" { + source = "../../../examples/monitoring_alerts" + + monitor_all_keys_in_the_project = false + project_id = var.project_id + email_addresses_to_be_notified = ["email@example.com", "email2@example.com"] +} diff --git a/test/fixtures/monitoring_alerts_specific_key/outputs.tf b/test/fixtures/monitoring_alerts_specific_key/outputs.tf new file mode 100644 index 0000000..837746d --- /dev/null +++ b/test/fixtures/monitoring_alerts_specific_key/outputs.tf @@ -0,0 +1,35 @@ +/** + * 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. + */ + +output "key" { + value = module.monitoring_alert_specific_key.key + description = "The version of the created KMS key." +} + +output "keyring" { + value = module.monitoring_alert_specific_key.keyring + description = "The keyring created." +} + +output "project_id" { + value = module.monitoring_alert_specific_key.project_id + description = "GCP Project ID where key version was created." +} + +output "notification_channel_names" { + value = module.monitoring_alert_specific_key.notification_channel_names + description = "Notification channel names." +} diff --git a/test/fixtures/monitoring_alerts_specific_key/variables.tf b/test/fixtures/monitoring_alerts_specific_key/variables.tf new file mode 100644 index 0000000..561b5a3 --- /dev/null +++ b/test/fixtures/monitoring_alerts_specific_key/variables.tf @@ -0,0 +1,20 @@ +/** + * 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. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = string +} diff --git a/test/integration/monitoring_alerts/monitoring_alerts_test.go b/test/integration/monitoring_alerts/monitoring_alerts_test.go new file mode 100644 index 0000000..0d7562e --- /dev/null +++ b/test/integration/monitoring_alerts/monitoring_alerts_test.go @@ -0,0 +1,127 @@ +// 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. + +package monitoring_alert + +import ( + "errors" + "fmt" + "strings" + "testing" + "time" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils" + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" +) + +func TestMonitoringAlertKeyVersion(t *testing.T) { + + // This test will run 2 iterations based on the following TfInputs variable. + // Map's key (monitor_all_keys_in_the_project): + // - "true" means we are testing the use case where we monitor all the KMS keys in the project. + // - "false" means we are testing the use case where we monitor a single KMS key in the project. + // Map's values (fixture_path): + // We are loading the fixture instead of the example directly because we need to pass the mentioned + // above's boolean to terraform input in order to have the described behavior. + + TfInputs := map[bool]string{ + true: "../../fixtures/monitoring_alerts_on_project", + false: "../../fixtures/monitoring_alerts_specific_key", + } + + for monitor_all_keys_in_the_project, fixture_path := range TfInputs { + + kmsAlertT := tft.NewTFBlueprintTest(t, + tft.WithTFDir(fixture_path), + ) + + kmsAlertT.DefineVerify(func(assert *assert.Assertions) { + kmsAlertT.DefaultVerify(assert) + + projectId := kmsAlertT.GetStringOutput("project_id") + keyVersion := kmsAlertT.GetStringOutput("key") + keyring := kmsAlertT.GetStringOutput("keyring") + notificationChannelNames := kmsAlertT.GetJsonOutput("notification_channel_names").Array() + + assert.Len(notificationChannelNames, 2) + notificationChannelEmailAddresses := []string{} + notificationChannelStringNames := []string{} + for _, notificationChannelName := range notificationChannelNames { + notificationChannelStringNames = append(notificationChannelStringNames, notificationChannelName.String()) + monitoringChannel := gcloud.Runf(t, "beta monitoring channels list --project %s --filter name='%s'", projectId, notificationChannelName.String()).Array() + assert.Len(monitoringChannel, 1) + notificationChannelEmailAddresses = append(notificationChannelEmailAddresses, monitoringChannel[0].Get("labels.email_address").String()) + } + assert.ElementsMatch([]string{"email@example.com", "email2@example.com"}, notificationChannelEmailAddresses) + + var expectedFilter string + if monitor_all_keys_in_the_project { + expectedFilter = fmt.Sprintf("protoPayload.request.@type=\"type.googleapis.com/google.cloud.kms.v1.DestroyCryptoKeyVersionRequest\"") + } else { + expectedFilter = fmt.Sprintf("protoPayload.request.@type=\"type.googleapis.com/google.cloud.kms.v1.DestroyCryptoKeyVersionRequest\" AND protoPayload.request.name=~\"%s/.*\"", keyVersion) + } + + monitoringAlerts := gcloud.Runf(t, "alpha monitoring policies list --project %s", projectId).Array() + var monitoringAlert gjson.Result + for _, monitoringAlertLoop := range monitoringAlerts { + conditions := monitoringAlertLoop.Get("conditions").Array() + if len(conditions) > 0 && conditions[0].Get("conditionMatchedLog.filter").String() == expectedFilter { + monitoringAlert = monitoringAlertLoop + break + } + } + alertCondition := monitoringAlert.Get("conditions").Array() + assert.Len(alertCondition, 1) + assert.Equal(expectedFilter, alertCondition[0].Get("conditionMatchedLog.filter").String()) + notificationChannels := monitoringAlert.Get("notificationChannels").Array() + for _, notificationChannel := range notificationChannels { + assert.Contains(notificationChannelStringNames, notificationChannel.String()) + } + assert.Equal("WARNING", monitoringAlert.Get("severity").String()) + assert.Equal("300s", monitoringAlert.Get("alertStrategy.notificationRateLimit.period").String()) + assert.True(monitoringAlert.Get("enabled").Bool()) + + if !monitor_all_keys_in_the_project { + time.Sleep(1 * time.Minute) + // Deleting a key will be tested just for a specific key use case in order + // to avoid increasing too much the testing runtime. + + gcloud.Runf(t, fmt.Sprintf("kms keys versions destroy 1 --location us-central1 --keyring %s --key alert-key --project %s", keyring, projectId)) + utils.Poll(t, func() (bool, error) { + alertingLogs := gcloud.Runf(t, "logging read logName:\"projects/%s/logs/monitoring.googleapis.com\" --freshness=2m --project %s", projectId, projectId).Array() + for _, log := range alertingLogs { + expectedLogMessage := "Log match condition fired for Cloud KMS CryptoKeyVersion" + logMessage := log.Get("labels.verbose_message").String() + expectedLogName := fmt.Sprintf("projects/%s/logs/monitoring.googleapis.com", projectId) + logName := log.Get("logName").String() + if strings.Contains(logMessage, expectedLogMessage) && strings.Contains(logName, expectedLogName) { + // Test succeded. + return false, nil + } + } + return true, errors.New("Alert wasn't fired correctly.") + }, + // Wait for the alert trigger to be fired into logs. + // Timeout will occour after 20 retries of 10 seconds + 20, + 10*time.Second) + } + }) + kmsAlertT.Test() + + } +} diff --git a/test/setup/main.tf b/test/setup/main.tf index 02108ec..c94e36f 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -39,6 +39,8 @@ module "project_ci_kms" { "cloudkms.googleapis.com", "serviceusage.googleapis.com", "cloudresourcemanager.googleapis.com", + "monitoring.googleapis.com", + "logging.googleapis.com" ] activate_api_identities = [{