From cffa994e093baa13ace816263176a555b86c2e2f Mon Sep 17 00:00:00 2001 From: rmvangun <85766511+rmvangun@users.noreply.github.com> Date: Tue, 13 May 2025 09:40:18 -0400 Subject: [PATCH 1/2] AzureRM Terraform backend (#420) * Add azurerm backend * Allow configurable public access * Checkov skip * fmt comment out outputs default public access true --- terraform/backend/azurerm/.terraform.lock.hcl | 41 ++++++ terraform/backend/azurerm/main.tf | 135 ++++++++++++++++++ terraform/backend/azurerm/outputs.tf | 15 ++ .../backend/azurerm/templates/backend.tftpl | 3 + terraform/backend/azurerm/variables.tf | 88 ++++++++++++ terraform/backend/s3/main.tf | 26 ++-- terraform/backend/s3/outputs.tf | 32 ++--- terraform/backend/s3/templates/backend.tftpl | 4 + terraform/cluster/azure-aks/outputs.tf | 26 ++-- terraform/network/azure-vnet/outputs.tf | 26 ++-- 10 files changed, 344 insertions(+), 52 deletions(-) create mode 100644 terraform/backend/azurerm/.terraform.lock.hcl create mode 100644 terraform/backend/azurerm/main.tf create mode 100644 terraform/backend/azurerm/outputs.tf create mode 100644 terraform/backend/azurerm/templates/backend.tftpl create mode 100644 terraform/backend/azurerm/variables.tf create mode 100644 terraform/backend/s3/templates/backend.tftpl diff --git a/terraform/backend/azurerm/.terraform.lock.hcl b/terraform/backend/azurerm/.terraform.lock.hcl new file mode 100644 index 000000000..d8aa9eeba --- /dev/null +++ b/terraform/backend/azurerm/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.90.0" + constraints = "3.90.0" + hashes = [ + "h1:TpB4R7pYGxbXHC7wI7pZmhxZU/8O0JokORGlBfIpoRw=", + "zh:194a4342620958403beabf4d57d552133ca6ac18eef3027d6d1a98846b52f8ab", + "zh:1d8ee378aaa793e3288c9328e056763c98d0f2e8560357296bc3446fbd3b1b9d", + "zh:24aba7903e912570e36edb03f79c68028d3e254175947b588c96521f09f89df4", + "zh:27f91fbeef9d04c6382014b6c32883a96dbe91cf7a4fa07a97be5d6b03991f95", + "zh:59eeaa2f50f698bab6f36ada0e865d6b624625ff5d76309334b3c3aa366cb692", + "zh:732af42d18fa222ee88f7f97c0898d4955ae48fde5456e22af3b8f5d324c6b41", + "zh:766034eac5e6a66cf3631580956dd584b1c2e6134167302fc8b95d6b42ebf08b", + "zh:a5b2ec52abfc3fb154047af45ea692c98c646c2b5c336b12b6341a49be95025c", + "zh:bdd72f85d770fa4a2e6ebf542858341d3df7e858a4d70c0f94df758721bcd811", + "zh:e9f15f2399c667c24b3daf8a843f1cadd13bc619becf6362b46c3216b17009b1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f73b1ec8b372bc1480ca0d93e78914f1c9cebe81395f20273d7bc99579b84809", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.3" + hashes = [ + "h1:MCzg+hs1/ZQ32u56VzJMWP9ONRQPAAqAjuHuzbyshvI=", + "zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927", + "zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e", + "zh:6243509bb208656eb9dc17d3c525c89acdd27f08def427a0dce22d5db90a4c8b", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:885d85869f927853b6fe330e235cd03c337ac3b933b0d9ae827ec32fa1fdcdbf", + "zh:bab66af51039bdfcccf85b25fe562cbba2f54f6b3812202f4873ade834ec201d", + "zh:c505ff1bf9442a889ac7dca3ac05a8ee6f852e0118dd9a61796a2f6ff4837f09", + "zh:d36c0b5770841ddb6eaf0499ba3de48e5d4fc99f4829b6ab66b0fab59b1aaf4f", + "zh:ddb6a407c7f3ec63efb4dad5f948b54f7f4434ee1a2607a49680d494b1776fe1", + "zh:e0dafdd4500bec23d3ff221e3a9b60621c5273e5df867bc59ef6b7e41f5c91f6", + "zh:ece8742fd2882a8fc9d6efd20e2590010d43db386b920b2a9c220cfecc18de47", + "zh:f4c6b3eb8f39105004cf720e202f04f57e3578441cfb76ca27611139bc116a82", + ] +} diff --git a/terraform/backend/azurerm/main.tf b/terraform/backend/azurerm/main.tf new file mode 100644 index 000000000..9454c610f --- /dev/null +++ b/terraform/backend/azurerm/main.tf @@ -0,0 +1,135 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.90.0" + } + } +} + +provider "azurerm" { + features {} +} + +#--------------------------------------------------------------------------------------------------- +# Storage Account Creation +# This section creates the Azure Storage Account used for storing Terraform state. +# It ensures that the storage account is unique per subscription and resource group. +#--------------------------------------------------------------------------------------------------- + +resource "azurerm_resource_group" "this" { + name = local.resource_group_name + location = var.location + tags = var.tags +} + +resource "azurerm_storage_account" "this" { + # checkov:skip=CKV_AZURE_33:Not needed for terraform backend + # checkov:skip=CKV_AZURE_43:Storage account name is managed by variables + # checkov:skip=CKV_AZURE_206:Using LRS for terraform state is acceptable + # checkov:skip=CKV2_AZURE_33:Private endpoint not needed for terraform backend + # checkov:skip=CKV2_AZURE_40:Shared key auth needed for terraform backend + # checkov:skip=CKV2_AZURE_47:Container access type is set to private + # checkov:skip=CKV_AZURE_190:Public access is disabled via network rules + # checkov:skip=CKV2_AZURE_41:SAS expiration not needed for terraform backend + # checkov:skip=CKV2_AZURE_1:CMK not needed for terraform state + # checkov:skip=CKV_AZURE_59:Public access needed for terraform backend + name = var.storage_account_name != "" ? var.storage_account_name : local.default_storage_account_name + resource_group_name = azurerm_resource_group.this.name + location = var.location + account_tier = "Standard" + account_replication_type = "LRS" + min_tls_version = "TLS1_2" + + dynamic "identity" { + for_each = var.enable_cmk ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.storage.id] + } + } + + # Configure customer managed key if enabled + dynamic "customer_managed_key" { + for_each = var.enable_cmk && var.key_vault_key_id != "" ? [1] : [] + content { + key_vault_key_id = var.key_vault_key_id + user_assigned_identity_id = azurerm_user_assigned_identity.storage[0].principal_id + } + } + + # Configure blob properties + blob_properties { + versioning_enabled = true + delete_retention_policy { + days = 7 + } + container_delete_retention_policy { + days = 7 + } + } + + # Configure SAS token expiration + sas_policy { + expiration_period = "1.00:00:00" + } + + tags = merge( + var.tags, + { + ManagedBy = "Terraform" + } + ) + + network_rules { + default_action = var.allow_public_access ? "Allow" : "Deny" + bypass = ["AzureServices"] + ip_rules = var.allowed_ip_ranges + } +} + +#--------------------------------------------------------------------------------------------------- +# Storage Container Creation +# This section creates the blob container within the storage account for Terraform state files. +#--------------------------------------------------------------------------------------------------- + +resource "azurerm_storage_container" "this" { + # checkov:skip=CKV2_AZURE_21:Logging configured at storage account level + name = local.container_name + storage_account_name = azurerm_storage_account.this.name + container_access_type = "private" +} + +#--------------------------------------------------------------------------------------------------- +# Local Variables +# This section defines local variables for naming conventions and configuration. +#--------------------------------------------------------------------------------------------------- + +locals { + default_storage_account_name = var.storage_account_name != "" ? var.storage_account_name : replace(lower("tfstate${var.context_id}"), "/[^a-z0-9]/", "") + resource_group_name = var.resource_group_name != "" ? var.resource_group_name : "rg-tfstate-${var.context_id}" + container_name = var.container_name != "" ? var.container_name : "tfstate-${var.context_id}" +} + +#--------------------------------------------------------------------------------------------------- +# Backend Configuration File +# This section generates the backend configuration file for Terraform. +#--------------------------------------------------------------------------------------------------- + +resource "local_file" "backend_config" { + count = var.context_path != "" ? 1 : 0 + content = templatefile("${path.module}/templates/backend.tftpl", { + resource_group_name = local.resource_group_name + storage_account_name = azurerm_storage_account.this.name + container_name = azurerm_storage_container.this.name + }) + filename = "${var.context_path}/terraform/backend.tfvars" +} + +# User-assigned identity for CMK +resource "azurerm_user_assigned_identity" "storage" { + count = var.enable_cmk ? 1 : 0 + name = "id-storage-${var.context_id}" + resource_group_name = azurerm_resource_group.this.name + location = var.location +} diff --git a/terraform/backend/azurerm/outputs.tf b/terraform/backend/azurerm/outputs.tf new file mode 100644 index 000000000..ed0c3af47 --- /dev/null +++ b/terraform/backend/azurerm/outputs.tf @@ -0,0 +1,15 @@ +# Temporary until pending CLI fix +# output "storage_account_name" { +# description = "Name of the storage account" +# value = azurerm_storage_account.this.name +# } + +# output "container_name" { +# description = "Name of the blob container" +# value = azurerm_storage_container.this.name +# } + +# output "resource_group_name" { +# description = "Name of the resource group" +# value = azurerm_resource_group.this.name +# } diff --git a/terraform/backend/azurerm/templates/backend.tftpl b/terraform/backend/azurerm/templates/backend.tftpl new file mode 100644 index 000000000..61242a554 --- /dev/null +++ b/terraform/backend/azurerm/templates/backend.tftpl @@ -0,0 +1,3 @@ +resource_group_name = "${resource_group_name}" +storage_account_name = "${storage_account_name}" +container_name = "${container_name}" diff --git a/terraform/backend/azurerm/variables.tf b/terraform/backend/azurerm/variables.tf new file mode 100644 index 000000000..68c3b560e --- /dev/null +++ b/terraform/backend/azurerm/variables.tf @@ -0,0 +1,88 @@ +#--------------------------------------------------------------------------------------------------- +# General Context +#--------------------------------------------------------------------------------------------------- + +variable "context_path" { + type = string + description = "The path to the context folder" + default = "" +} + +variable "context_id" { + description = "Context ID for the resources" + type = string +} + +#--------------------------------------------------------------------------------------------------- +# Azure Region and Resource Group +#--------------------------------------------------------------------------------------------------- + +variable "location" { + description = "Azure region where resources will be created" + type = string + default = "eastus2" +} + +variable "resource_group_name" { + description = "Name of the resource group where the storage account will be created" + type = string + default = "" +} + +#--------------------------------------------------------------------------------------------------- +# Storage Account +#--------------------------------------------------------------------------------------------------- + +variable "storage_account_name" { + description = "Name of the storage account. If not provided, a default name will be generated" + type = string + default = "" + validation { + condition = length(var.storage_account_name) <= 24 + error_message = "The storage account name must be 24 characters or less." + } +} + +variable "container_name" { + description = "Name of the blob container for Terraform state" + type = string + default = "" +} + +#--------------------------------------------------------------------------------------------------- +# Tags +#--------------------------------------------------------------------------------------------------- + +variable "tags" { + description = "Additional tags to apply to resources" + type = map(string) + default = {} +} + +#--------------------------------------------------------------------------------------------------- +# Customer Managed Key (CMK) Configuration +#--------------------------------------------------------------------------------------------------- + +variable "enable_cmk" { + description = "Enable customer managed key encryption" + type = bool + default = false +} + +variable "key_vault_key_id" { + description = "The ID of the Key Vault Key to use for CMK encryption" + type = string + default = "" +} + +variable "allow_public_access" { + description = "Allow public access to the storage account" + type = bool + default = true +} + +variable "allowed_ip_ranges" { + description = "List of IP ranges to allow access to the storage account" + type = list(string) + default = [] +} diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index 2b5222200..987f2fe3b 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -53,6 +53,10 @@ resource "aws_s3_bucket_lifecycle_configuration" "this" { id = "cleanup" status = "Enabled" + filter { + prefix = "" + } + abort_incomplete_multipart_upload { days_after_initiation = 7 } @@ -287,19 +291,17 @@ resource "aws_kms_alias" "terraform_state_alias" { } #--------------------------------------------------------------------------------------------------- -# Backend Configuration Output -# This section outputs the backend configuration to a local file in tfvars format +# Backend Configuration File +# This section generates the backend configuration file for Terraform using a template. #--------------------------------------------------------------------------------------------------- resource "local_file" "backend_config" { - count = 1 - - content = < Date: Tue, 13 May 2025 22:13:38 +0200 Subject: [PATCH 2/2] Fixes azure-aks module --- .../cluster/azure-aks/.terraform.lock.hcl | 45 ++++++++---- terraform/cluster/azure-aks/main.tf | 73 +++++++++++++------ terraform/cluster/azure-aks/variables.tf | 12 +++ 3 files changed, 93 insertions(+), 37 deletions(-) diff --git a/terraform/cluster/azure-aks/.terraform.lock.hcl b/terraform/cluster/azure-aks/.terraform.lock.hcl index 5cf536fb2..13839bbe5 100644 --- a/terraform/cluster/azure-aks/.terraform.lock.hcl +++ b/terraform/cluster/azure-aks/.terraform.lock.hcl @@ -32,21 +32,40 @@ provider "registry.terraform.io/hashicorp/azurerm" { } provider "registry.terraform.io/hashicorp/local" { - version = "2.5.2" + version = "2.5.3" hashes = [ - "h1:IyFbOIO6mhikFNL/2h1iZJ6kyN3U00jgkpCLUCThAfE=", - "zh:136299545178ce281c56f36965bf91c35407c11897f7082b3b983d86cb79b511", - "zh:3b4486858aa9cb8163378722b642c57c529b6c64bfbfc9461d940a84cd66ebea", - "zh:4855ee628ead847741aa4f4fc9bed50cfdbf197f2912775dd9fe7bc43fa077c0", - "zh:4b8cd2583d1edcac4011caafe8afb7a95e8110a607a1d5fb87d921178074a69b", - "zh:52084ddaff8c8cd3f9e7bcb7ce4dc1eab00602912c96da43c29b4762dc376038", - "zh:71562d330d3f92d79b2952ffdda0dad167e952e46200c767dd30c6af8d7c0ed3", + "h1:MCzg+hs1/ZQ32u56VzJMWP9ONRQPAAqAjuHuzbyshvI=", + "zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927", + "zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e", + "zh:6243509bb208656eb9dc17d3c525c89acdd27f08def427a0dce22d5db90a4c8b", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:805f81ade06ff68fa8b908d31892eaed5c180ae031c77ad35f82cb7a74b97cf4", - "zh:8b6b3ebeaaa8e38dd04e56996abe80db9be6f4c1df75ac3cccc77642899bd464", - "zh:ad07750576b99248037b897de71113cc19b1a8d0bc235eb99173cc83d0de3b1b", - "zh:b9f1c3bfadb74068f5c205292badb0661e17ac05eb23bfe8bd809691e4583d0e", - "zh:cc4cbcd67414fefb111c1bf7ab0bc4beb8c0b553d01719ad17de9a047adff4d1", + "zh:885d85869f927853b6fe330e235cd03c337ac3b933b0d9ae827ec32fa1fdcdbf", + "zh:bab66af51039bdfcccf85b25fe562cbba2f54f6b3812202f4873ade834ec201d", + "zh:c505ff1bf9442a889ac7dca3ac05a8ee6f852e0118dd9a61796a2f6ff4837f09", + "zh:d36c0b5770841ddb6eaf0499ba3de48e5d4fc99f4829b6ab66b0fab59b1aaf4f", + "zh:ddb6a407c7f3ec63efb4dad5f948b54f7f4434ee1a2607a49680d494b1776fe1", + "zh:e0dafdd4500bec23d3ff221e3a9b60621c5273e5df867bc59ef6b7e41f5c91f6", + "zh:ece8742fd2882a8fc9d6efd20e2590010d43db386b920b2a9c220cfecc18de47", + "zh:f4c6b3eb8f39105004cf720e202f04f57e3578441cfb76ca27611139bc116a82", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + hashes = [ + "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", ] } diff --git a/terraform/cluster/azure-aks/main.tf b/terraform/cluster/azure-aks/main.tf index 3a4642825..df5d6cb56 100644 --- a/terraform/cluster/azure-aks/main.tf +++ b/terraform/cluster/azure-aks/main.tf @@ -53,16 +53,22 @@ resource "azurerm_resource_group" "aks" { # Key Vault #----------------------------------------------------------------------------------------------------------------------- +resource "random_string" "key" { + length = 3 + special = false + upper = false +} + resource "azurerm_key_vault" "key_vault" { # checkov:skip=CKV2_AZURE_32: We are using a public cluster for testing, there is no need for private endpoints. - name = "aks-keyvault-${var.context_id}" + name = "keyvault-${var.context_id}-${random_string.key.result}" location = azurerm_resource_group.aks.location resource_group_name = azurerm_resource_group.aks.name tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" enabled_for_disk_encryption = true purge_protection_enabled = true - soft_delete_retention_days = 7 + soft_delete_retention_days = var.soft_delete_retention_days # checkov:skip=CKV_AZURE_189: We are using a public cluster for testing # private services are encouraged for production public_network_access_enabled = var.public_network_access_enabled @@ -73,26 +79,28 @@ resource "azurerm_key_vault" "key_vault" { default_action = var.network_acls_default_action bypass = "AzureServices" } +} - access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id - - key_permissions = [ - "Create", - "Delete", - "Get", - "Purge", - "Recover", - "Update", - "GetRotationPolicy", - "SetRotationPolicy" - ] +resource "azurerm_key_vault_access_policy" "key_vault_access_policy" { + key_vault_id = azurerm_key_vault.key_vault.id - secret_permissions = [ - "Set", - ] - } + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Delete", + "Get", + "Purge", + "Recover", + "Update", + "GetRotationPolicy", + "SetRotationPolicy" + ] + + secret_permissions = [ + "Set", + ] } resource "azurerm_key_vault_access_policy" "key_vault_access_policy_disk" { @@ -119,7 +127,7 @@ resource "azurerm_key_vault_access_policy" "key_vault_access_policy_disk" { resource "time_static" "expiry" {} resource "azurerm_key_vault_key" "key_vault_key" { - name = "aks-key-${var.context_id}" + name = "key-${var.context_id}-${random_string.key.result}" key_vault_id = azurerm_key_vault.key_vault.id key_type = "RSA-HSM" key_size = 2048 @@ -145,7 +153,7 @@ resource "azurerm_key_vault_key" "key_vault_key" { } resource "azurerm_disk_encryption_set" "main" { - name = "des-${var.context_id}" + name = "des-${var.context_id}-${random_string.key.result}" resource_group_name = azurerm_resource_group.aks.name location = azurerm_resource_group.aks.location key_vault_key_id = azurerm_key_vault_key.key_vault_key.id @@ -178,6 +186,12 @@ data "azurerm_subnet" "private" { virtual_network_name = var.vnet_name == null ? "windsor-vnet-${var.context_id}" : var.vnet_name } +resource "azurerm_user_assigned_identity" "cluster" { + name = "${var.context_id}-cluster-identity" + location = var.region + resource_group_name = azurerm_resource_group.aks.name +} + resource "azurerm_kubernetes_cluster" "main" { name = local.cluster_name location = azurerm_resource_group.aks.location @@ -243,12 +257,17 @@ resource "azurerm_kubernetes_cluster" "main" { } identity { - type = "SystemAssigned" + type = "UserAssigned" + identity_ids = concat( + [azurerm_user_assigned_identity.cluster.id], + var.additional_cluster_identity_ids + ) } lifecycle { ignore_changes = [ - default_node_pool[0].node_count + default_node_pool[0].upgrade_settings, + workload_autoscaler_profile ] } } @@ -269,6 +288,12 @@ resource "azurerm_kubernetes_cluster_node_pool" "autoscaled" { # checkov:skip=CKV_AZURE_168: This is set in the variable by default to 50 max_pods = var.autoscaled_node_pool.max_pods host_encryption_enabled = var.autoscaled_node_pool.host_encryption_enabled + + lifecycle { + ignore_changes = [ + upgrade_settings + ] + } } resource "local_file" "kube_config" { diff --git a/terraform/cluster/azure-aks/variables.tf b/terraform/cluster/azure-aks/variables.tf index d269fe5f9..a3b650256 100644 --- a/terraform/cluster/azure-aks/variables.tf +++ b/terraform/cluster/azure-aks/variables.tf @@ -197,3 +197,15 @@ variable "expiration_date" { description = "The expiration date for the AKS cluster's key vault" default = null } + +variable "additional_cluster_identity_ids" { + type = list(string) + description = "Additional user assigned identity IDs for the AKS cluster" + default = [] +} + +variable "soft_delete_retention_days" { + type = number + description = "The number of days to retain the AKS cluster's key vault" + default = 7 +}