diff --git a/infrastructure/modules/key-vault/alerts.tf b/infrastructure/modules/key-vault/alerts.tf new file mode 100644 index 00000000..2f0200ee --- /dev/null +++ b/infrastructure/modules/key-vault/alerts.tf @@ -0,0 +1,97 @@ +resource "azurerm_monitor_scheduled_query_rules_alert_v2" "kv_secret_near_expiry" { + count = var.enable_alerting == true ? 1 : 0 + + name = "${azurerm_key_vault.keyvault.name}-secret-near-expiry" + resource_group_name = var.resource_group_name_monitoring != null ? var.resource_group_name_monitoring : var.resource_group_name + location = var.location + + evaluation_frequency = var.secret_near_expiry_alert.evaluation_frequency + window_duration = var.secret_near_expiry_alert.window_duration + scopes = [azurerm_key_vault.keyvault.id] + severity = 2 + + criteria { + query = <<-QUERY +AzureDiagnostics +| where ResourceProvider == "MICROSOFT.KEYVAULT" +| where OperationName contains "SecretNearExpiry" +| project + SecretName = column_ifexists("eventGridEventProperties_data_ObjectName_s","") +| summarize Events=count() by SecretName +QUERY + + time_aggregation_method = "Total" + threshold = var.secret_near_expiry_alert.threshold + operator = "GreaterThanOrEqual" + + resource_id_column = "SecretName" + metric_measure_column = "Events" + + dimension { + name = "SecretName" + operator = "Include" + values = ["*"] + } + } + + description = "The Key Vault secret is nearing expiration." + + action { + action_groups = [var.action_group_id] + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_monitor_scheduled_query_rules_alert_v2" "kv_secret_expired" { + count = var.enable_alerting == true ? 1 : 0 + + name = "${azurerm_key_vault.keyvault.name}-secret-expired" + resource_group_name = var.resource_group_name_monitoring != null ? var.resource_group_name_monitoring : var.resource_group_name + location = var.location + + evaluation_frequency = var.secret_expired_alert.evaluation_frequency + window_duration = var.secret_expired_alert.window_duration + scopes = [azurerm_key_vault.keyvault.id] + severity = 2 + + criteria { + query = <<-QUERY +AzureDiagnostics +| where ResourceProvider == "MICROSOFT.KEYVAULT" +| where OperationName contains "SecretExpired" +| project + SecretName = column_ifexists("eventGridEventProperties_data_ObjectName_s","") +| summarize Events=count() by SecretName +QUERY + + time_aggregation_method = "Total" + threshold = var.secret_expired_alert.threshold + operator = "GreaterThanOrEqual" + + resource_id_column = "SecretName" + metric_measure_column = "Events" + + dimension { + name = "SecretName" + operator = "Include" + values = ["*"] + } + } + + description = "The Key Vault secret has expired." + + action { + action_groups = [var.action_group_id] + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} diff --git a/infrastructure/modules/key-vault/tfdocs.md b/infrastructure/modules/key-vault/tfdocs.md index 659ec910..8dd88bd2 100644 --- a/infrastructure/modules/key-vault/tfdocs.md +++ b/infrastructure/modules/key-vault/tfdocs.md @@ -131,6 +131,59 @@ Description: Resource tags to be applied throughout the deployment. Type: `map(string)` Default: `{}` + +### [resource\_group\_name\_monitoring](#input\_resource\_group\_name\_monitoring) + +Description: The name of the resource group in which to create monitoring resources for the Key Vault. Changing this forces a new resource to be created. + +Type: `string` + +Default: `null` + +### [action\_group\_id](#input\_action\_group\_id) + +Description: The ID of the Action Group to use for alerts. + +Type: `string` + +Default: `null` + +### [enable\_alerting](#input\_enable\_alerting) + +Description: Whether monitoring and alerting is enabled for the Key Vault. + +Type: `bool` + +Default: `false` + +### [secret\_near\_expiry\_alert](#input\_secret\_near\_expiry\_alert) + +Description: Configuration for the Key Vault secret near expiry alert. + +Type: + +```hcl +object({ + evaluation_frequency = string + window_duration = string + threshold = number +}) +``` + +### [secret\_expired\_alert](#input\_secret\_expired\_alert) + +Description: Configuration for the Key Vault secret expired alert. + +Type: + +```hcl +object({ + evaluation_frequency = string + window_duration = string + threshold = number +}) +``` + ## Modules The following Modules are called: diff --git a/infrastructure/modules/key-vault/variables.tf b/infrastructure/modules/key-vault/variables.tf index 4e09fa37..36e3e690 100644 --- a/infrastructure/modules/key-vault/variables.tf +++ b/infrastructure/modules/key-vault/variables.tf @@ -14,6 +14,10 @@ variable "disk_encryption" { default = true } +/* -------------------------------------------------------------------------------------------------- + Monitoring and Diagnostics Variables +-------------------------------------------------------------------------------------------------- */ + variable "log_analytics_workspace_id" { type = string description = "id of the log analytics workspace to send resource logging to via diagnostic settings" @@ -35,6 +39,72 @@ variable "monitor_diagnostic_setting_keyvault_metrics" { description = "Controls what metrics will be enabled for the keyvault" } +variable "resource_group_name_monitoring" { + type = string + description = "The name of the resource group in which to create the Monitoring resources for the Key Vault. Changing this forces a new resource to be created." + default = null +} + +variable "action_group_id" { + type = string + description = "The ID of the Action Group to use for alerts." + default = null +} + +variable "enable_alerting" { + description = "Whether monitoring and alerting is enabled for the Key Vault." + type = bool + default = false +} + +variable "secret_near_expiry_alert" { + type = object({ + evaluation_frequency = string + window_duration = string + threshold = number + }) + + validation { + condition = contains( + ["PT1M", "PT5M", "PT15M", "PT30M", "PT1H", "PT6H", "PT12H", "P1D"], + var.secret_near_expiry_alert.evaluation_frequency + ) + error_message = "secret_near_expiry_alert.evaluation_frequency must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D" + } + + validation { + condition = contains( + ["PT1M", "PT5M", "PT15M", "PT30M", "PT1H", "PT6H", "PT12H", "P1D"], + var.secret_near_expiry_alert.window_duration + ) + error_message = "secret_near_expiry_alert.window_duration must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D" + } +} + +variable "secret_expired_alert" { + type = object({ + evaluation_frequency = string + window_duration = string + threshold = number + }) + + validation { + condition = contains( + ["PT1M", "PT5M", "PT15M", "PT30M", "PT1H", "PT6H", "PT12H", "P1D"], + var.secret_expired_alert.evaluation_frequency + ) + error_message = "secret_expired_alert.evaluation_frequency must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D" + } + + validation { + condition = contains( + ["PT1M", "PT5M", "PT15M", "PT30M", "PT1H", "PT6H", "PT12H", "P1D"], + var.secret_expired_alert.window_duration + ) + error_message = "secret_expired_alert.window_duration must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D" + } +} + variable "name" { description = "The name of the Key Vault." type = string