diff --git a/.github/workflows/_terraformDestroyTemplate.yml b/.github/workflows/_terraformDestroyTemplate.yml new file mode 100644 index 00000000..9da21126 --- /dev/null +++ b/.github/workflows/_terraformDestroyTemplate.yml @@ -0,0 +1,100 @@ +name: Terraform Destroy Template + +on: + workflow_call: + inputs: + environment: + required: true + type: string + default: "dev" + description: "Specifies the environment of the deployment." + config: + required: true + type: string + description: "Specifies the configuration folder for the deployment." + terraform_version: + required: true + type: string + description: "Specifies the terraform version." + node_version: + required: true + type: number + description: "Specifies the node version." + working_directory: + required: true + type: string + description: "Specifies the working directory." + tenant_id: + required: true + type: string + description: "Specifies the tenant id of the deployment." + subscription_id: + required: true + type: string + description: "Specifies the subscription id of the deployment." + secrets: + CLIENT_ID: + required: true + description: "Specifies the client id." + MY_SAMPLE_SECRET: + required: true + description: "Specifies a sample secret." + +permissions: + id-token: write + contents: read + +jobs: + deployment: + name: Terraform Destroy + runs-on: [self-hosted] + continue-on-error: false + environment: ${{ inputs.environment }} + if: github.event_name == 'push' || github.event_name == 'release' + concurrency: + group: terraform-${{ inputs.config }}-${{ inputs.environment }} + cancel-in-progress: false + + env: + ARM_TENANT_ID: ${{ inputs.tenant_id }} + ARM_SUBSCRIPTION_ID: ${{ inputs.subscription_id }} + ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }} + ARM_USE_OIDC: true + + steps: + # Setup Node + - name: Setup Node + id: node_setup + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + + # Setup Terraform + - name: Setup Terraform + id: terraform_setup + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: true + + # Check Out Repository + - name: Check Out Repository + id: checkout_repository + uses: actions/checkout@v4 + + # Terraform Init + - name: Terraform Init + working-directory: ${{ inputs.working_directory }} + run: | + terraform init -backend-config=../../config/${CONFIG}/azurerm.tfbackend + env: + CONFIG: ${{ inputs.config }} + + # Terraform Destroy + - name: Terraform Destroy + working-directory: ${{ inputs.working_directory }} + run: | + terraform apply -var-file="../../config/${CONFIG}/vars.tfvars" -auto-approve -input=false -destroy + env: + CONFIG: ${{ inputs.config }} + TF_VAR_my_secret: ${{ secrets.MY_SAMPLE_SECRET }} diff --git a/.github/workflows/_terraformEnvironmentTemplate.yml b/.github/workflows/_terraformEnvironmentTemplate.yml index 4e15156f..b0571a8f 100644 --- a/.github/workflows/_terraformEnvironmentTemplate.yml +++ b/.github/workflows/_terraformEnvironmentTemplate.yml @@ -35,9 +35,6 @@ on: CLIENT_ID: required: true description: "Specifies the client id." - CLIENT_SECRET: - required: true - description: "Specifies the client secret." MY_SAMPLE_SECRET: required: true description: "Specifies a sample secret." @@ -101,15 +98,14 @@ jobs: environment: ${{ inputs.environment }} needs: [lint] concurrency: - group: terraform-apply-${{ inputs.config }}-${{ inputs.environment }} + group: terraform-${{ inputs.config }}-${{ inputs.environment }} cancel-in-progress: false env: ARM_TENANT_ID: ${{ inputs.tenant_id }} ARM_SUBSCRIPTION_ID: ${{ inputs.subscription_id }} ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - ARM_USE_OIDC: false + ARM_USE_OIDC: true steps: # Setup Node @@ -196,18 +192,17 @@ jobs: runs-on: [self-hosted] continue-on-error: false environment: ${{ inputs.environment }} - if: github.event_name == 'push' || github.event_name == 'release' + # if: github.event_name == 'push' || github.event_name == 'release' needs: [plan] concurrency: - group: terraform-apply-${{ inputs.config }}-${{ inputs.environment }} + group: terraform-${{ inputs.config }}-${{ inputs.environment }} cancel-in-progress: false env: ARM_TENANT_ID: ${{ inputs.tenant_id }} ARM_SUBSCRIPTION_ID: ${{ inputs.subscription_id }} ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - ARM_USE_OIDC: false + ARM_USE_OIDC: true steps: # Setup Node diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index 6f3f4791..972836a4 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -27,12 +27,27 @@ jobs: with: environment: "dev" config: "PerfectThymeTech" - terraform_version: "1.6.6" - node_version: 18 + terraform_version: "1.9.6" + node_version: 20 working_directory: "./code/infra" - tenant_id: "3556be79-2979-4b19-a1af-4dd4e6d9ed7e" - subscription_id: "8f171ff9-2b5b-4f0f-aed5-7fa360a1d094" + tenant_id: "37963dd4-f4e6-40f8-a7d6-24b97919e452" + subscription_id: "1fdab118-1638-419a-8b12-06c9543714a0" + secrets: + CLIENT_ID: ${{ secrets.CLIENT_ID }} + MY_SAMPLE_SECRET: ${{ secrets.MY_SAMPLE_SECRET }} + + terraform_dev_destroy: + uses: ./.github/workflows/_terraformDestroyTemplate.yml + name: "Dev - Destroy" + needs: [terraform_dev] + with: + environment: "dev" + config: "PerfectThymeTech" + terraform_version: "1.9.6" + node_version: 20 + working_directory: "./code/infra" + tenant_id: "37963dd4-f4e6-40f8-a7d6-24b97919e452" + subscription_id: "1fdab118-1638-419a-8b12-06c9543714a0" secrets: CLIENT_ID: ${{ secrets.CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} MY_SAMPLE_SECRET: ${{ secrets.MY_SAMPLE_SECRET }} diff --git a/code/infra/alerts.tf b/code/infra/alerts.tf index ed2c8fc4..508aedc6 100644 --- a/code/infra/alerts.tf +++ b/code/infra/alerts.tf @@ -1,5 +1,6 @@ resource "azurerm_monitor_activity_log_alert" "monitor_activity_log_alert_service_health" { name = "${local.prefix}-alert-servicehealth" + location = "global" resource_group_name = azurerm_resource_group.logging_rg.name tags = var.tags diff --git a/code/infra/applicationinsights.tf b/code/infra/applicationinsights.tf new file mode 100644 index 00000000..7df761e8 --- /dev/null +++ b/code/infra/applicationinsights.tf @@ -0,0 +1,14 @@ +module "application_insights" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/applicationinsights?ref=main" + providers = { + azurerm = azurerm + } + + location = var.location + resource_group_name = azurerm_resource_group.logging_rg.name + tags = var.tags + application_insights_name = "${local.prefix}-appi001" + application_insights_application_type = "other" + application_insights_log_analytics_workspace_id = var.log_analytics_workspace_id + diagnostics_configurations = [] # local.diagnostics_configurations # Disabled to avoid duplicate logs in LAW and App Insights +} diff --git a/code/infra/appserviceplan.tf b/code/infra/appserviceplan.tf new file mode 100644 index 00000000..1f92b389 --- /dev/null +++ b/code/infra/appserviceplan.tf @@ -0,0 +1,18 @@ +module "app_service_plan" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/appserviceplan?ref=main" + providers = { + azurerm = azurerm + } + + location = var.location + resource_group_name = azurerm_resource_group.app_rg.name + tags = var.tags + service_plan_name = "${local.prefix}-asp001" + service_plan_maximum_elastic_worker_count = null + service_plan_os_type = "Linux" + service_plan_per_site_scaling_enabled = false + service_plan_sku_name = var.function_sku + service_plan_worker_count = 1 # Update to '3' for production + service_plan_zone_balancing_enabled = false # Update to 'true' for production + diagnostics_configurations = local.diagnostics_configurations +} diff --git a/code/infra/function.tf b/code/infra/function.tf index 0b48fd3f..2309064b 100644 --- a/code/infra/function.tf +++ b/code/infra/function.tf @@ -1,44 +1,3 @@ -resource "azurerm_service_plan" "service_plan" { - name = "${local.prefix}-asp001" - location = var.location - resource_group_name = azurerm_resource_group.app_rg.name - tags = var.tags - - # maximum_elastic_worker_count = 20 - os_type = "Linux" - per_site_scaling_enabled = false - sku_name = var.function_sku - worker_count = 1 # Update to '3' for production - zone_balancing_enabled = false # Update to 'true' for production -} - -data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_service_plan" { - resource_id = azurerm_service_plan.service_plan.id -} - -resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_service_plan" { - name = "logAnalytics" - target_resource_id = azurerm_service_plan.service_plan.id - log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - - dynamic "enabled_log" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_service_plan.log_category_groups - content { - category_group = entry.value - } - } - - dynamic "metric" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_service_plan.metrics - content { - category = entry.value - enabled = true - } - } -} - resource "azapi_resource" "function" { type = "Microsoft.Web/sites@2022-09-01" parent_id = azurerm_resource_group.app_rg.id @@ -65,7 +24,7 @@ resource "azapi_resource" "function" { redundancyMode = "None" reserved = true scmSiteAlsoStopped = true - serverFarmId = azurerm_service_plan.service_plan.id + serverFarmId = module.app_service_plan.service_plan_id storageAccountRequired = false vnetContentShareEnabled = true vnetImagePullEnabled = false # Set to 'true' when pulling image from private Azure Container Registry @@ -91,7 +50,7 @@ resource "azapi_resource" "function" { appSettings = [ { name = "APPLICATIONINSIGHTS_CONNECTION_STRING" - value = azurerm_application_insights.application_insights.connection_string + value = module.application_insights.application_insights_connection_string }, { name = "AZURE_SDK_TRACING_IMPLEMENTATION" @@ -123,7 +82,7 @@ resource "azapi_resource" "function" { }, { name = "WEBSITE_OS_TYPE" - value = azurerm_service_plan.service_plan.os_type + value = module.app_service_plan.service_plan_os_type }, { name = "WEBSITE_RUN_FROM_PACKAGE" @@ -131,7 +90,7 @@ resource "azapi_resource" "function" { }, { name = "AzureWebJobsStorage__accountName" - value = azurerm_storage_account.storage.name + value = module.storage_account.storage_account_name }, { name = "AzureWebJobsSecretStorageType" @@ -139,7 +98,7 @@ resource "azapi_resource" "function" { }, { name = "AzureWebJobsSecretStorageKeyVaultUri" - value = azurerm_key_vault.key_vault.vault_uri + value = module.key_vault.key_vault_uri }, { name = "WEBSITES_ENABLE_APP_SERVICE_STORAGE" # Disable when not running a container @@ -219,11 +178,8 @@ resource "azapi_resource" "function" { # "properties.siteConfig.appSettings" # ] depends_on = [ - azurerm_private_endpoint.key_vault_private_endpoint, - azurerm_private_endpoint.storage_private_endpoint_blob, - azurerm_private_endpoint.storage_private_endpoint_file, - azurerm_private_endpoint.storage_private_endpoint_queue, - azurerm_private_endpoint.storage_private_endpoint_table, + module.key_vault.key_vault_setup_completed, + module.storage_account.storage_setup_completed, ] } @@ -234,7 +190,7 @@ data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_function" { resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_function" { name = "logAnalytics" target_resource_id = azapi_resource.function.id - log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + log_analytics_workspace_id = var.log_analytics_workspace_id dynamic "enabled_log" { iterator = entry @@ -267,11 +223,20 @@ resource "azurerm_private_endpoint" "function_private_endpoint" { private_connection_resource_id = azapi_resource.function.id subresource_names = ["sites"] } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azapi_resource.function.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_sites + subnet_id = azapi_resource.subnet_private_endpoints.id + dynamic "private_dns_zone_group" { + for_each = var.private_dns_zone_id_sites == "" ? [] : [1] + content { + name = "${azapi_resource.function.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_sites + ] + } + } + + lifecycle { + ignore_changes = [ + private_dns_zone_group ] } } diff --git a/code/infra/keyvault.tf b/code/infra/keyvault.tf index a29d74ce..799b9512 100644 --- a/code/infra/keyvault.tf +++ b/code/infra/keyvault.tf @@ -1,85 +1,30 @@ -resource "azurerm_key_vault" "key_vault" { - name = "${local.prefix}-vault001" - location = var.location - resource_group_name = azurerm_resource_group.app_rg.name - tags = var.tags - - access_policy = [] - enable_rbac_authorization = true - enabled_for_deployment = false - enabled_for_disk_encryption = false - enabled_for_template_deployment = false - network_acls { - bypass = "AzureServices" - default_action = "Deny" - ip_rules = [] - virtual_network_subnet_ids = [] +module "key_vault" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/keyvault?ref=main" + providers = { + azurerm = azurerm + time = time } - public_network_access_enabled = false - purge_protection_enabled = true - sku_name = "standard" - soft_delete_retention_days = 7 - tenant_id = data.azurerm_client_config.current.tenant_id + + location = var.location + resource_group_name = azurerm_resource_group.app_rg.name + tags = var.tags + key_vault_name = "${local.prefix}-kv001" + key_vault_sku_name = "standard" + key_vault_soft_delete_retention_days = 7 + diagnostics_configurations = local.diagnostics_configurations + subnet_id = azapi_resource.subnet_private_endpoints.id + connectivity_delay_in_seconds = var.connectivity_delay_in_seconds + private_dns_zone_id_vault = var.private_dns_zone_id_key_vault } resource "azurerm_key_vault_secret" "key_vault_secret_sample" { name = "MySampleSecret" - key_vault_id = azurerm_key_vault.key_vault.id + key_vault_id = module.key_vault.key_vault_id content_type = "text/plain" value = var.my_secret depends_on = [ - azurerm_role_assignment.current_role_assignment_key_vault, - azurerm_private_endpoint.key_vault_private_endpoint + module.key_vault.key_vault_setup_completed, ] } - -data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_key_vault" { - resource_id = azurerm_key_vault.key_vault.id -} - -resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_key_vault" { - name = "logAnalytics" - target_resource_id = azurerm_key_vault.key_vault.id - log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - - dynamic "enabled_log" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_key_vault.log_category_groups - content { - category_group = entry.value - } - } - - dynamic "metric" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_key_vault.metrics - content { - category = entry.value - enabled = true - } - } -} - -resource "azurerm_private_endpoint" "key_vault_private_endpoint" { - name = "${azurerm_key_vault.key_vault.name}-pe" - location = var.location - resource_group_name = azurerm_key_vault.key_vault.resource_group_name - tags = var.tags - - custom_network_interface_name = "${azurerm_key_vault.key_vault.name}-nic" - private_service_connection { - name = "${azurerm_key_vault.key_vault.name}-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_key_vault.key_vault.id - subresource_names = ["vault"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_key_vault.key_vault.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_key_vault - ] - } -} diff --git a/code/infra/locals.tf b/code/infra/locals.tf index 09920e43..d74d535e 100644 --- a/code/infra/locals.tf +++ b/code/infra/locals.tf @@ -1,18 +1,38 @@ locals { + # General locals prefix = "${lower(var.prefix)}-${var.environment}" + resource_providers_to_register = [ + "Microsoft.Authorization", + "Microsoft.Insights", + "Microsoft.KeyVault", + "Microsoft.Network", + "Microsoft.Resources", + "Microsoft.Storage", + "Microsoft.Web", + ] + # Resource locals virtual_network = { resource_group_name = split("/", var.vnet_id)[4] name = split("/", var.vnet_id)[8] } - network_security_group = { resource_group_name = split("/", var.nsg_id)[4] name = split("/", var.nsg_id)[8] } - route_table = { resource_group_name = split("/", var.route_table_id)[4] name = split("/", var.route_table_id)[8] } + + # Logging locals + diagnostics_configurations = [ + { + log_analytics_workspace_id = var.log_analytics_workspace_id + storage_account_id = "" + } + ] + + # CMK locals + customer_managed_key = null } diff --git a/code/infra/logging.tf b/code/infra/logging.tf deleted file mode 100644 index c43f02a3..00000000 --- a/code/infra/logging.tf +++ /dev/null @@ -1,133 +0,0 @@ -resource "azurerm_application_insights" "application_insights" { - name = "${local.prefix}-appi001" - location = var.location - resource_group_name = azurerm_resource_group.logging_rg.name - tags = var.tags - - application_type = "other" - daily_data_cap_notifications_disabled = false - disable_ip_masking = false - force_customer_storage_for_profiler = false - internet_ingestion_enabled = true - internet_query_enabled = true - local_authentication_disabled = false - retention_in_days = 90 - sampling_percentage = 100 - workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id -} - -# data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_application_insights" { # Disable to avoid duplicate logs in Application Insights -# resource_id = azurerm_application_insights.application_insights.id -# } - -# resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_application_insights" { -# name = "logAnalytics" -# target_resource_id = azurerm_application_insights.application_insights.id -# log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - -# dynamic "enabled_log" { -# iterator = entry -# for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_application_insights.log_category_groups -# content { -# category_group = entry.value -# } -# } - -# dynamic "metric" { -# iterator = entry -# for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_application_insights.metrics -# content { -# category = entry.value -# enabled = true -# } -# } -# } - -resource "azurerm_log_analytics_workspace" "log_analytics_workspace" { - name = "${local.prefix}-log001" - location = var.location - resource_group_name = azurerm_resource_group.logging_rg.name - tags = var.tags - - allow_resource_only_permissions = true - cmk_for_query_forced = false - daily_quota_gb = -1 - internet_ingestion_enabled = true - internet_query_enabled = true - local_authentication_disabled = true - retention_in_days = 30 - sku = "PerGB2018" -} - -data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_log_analytics_workspace" { - resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id -} - -resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_log_analytics_workspace" { - name = "logAnalytics" - target_resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - - dynamic "enabled_log" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_log_analytics_workspace.log_category_groups - content { - category_group = entry.value - } - } - - dynamic "metric" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_log_analytics_workspace.metrics - content { - category = entry.value - enabled = true - } - } -} - -resource "azurerm_monitor_private_link_scope" "mpls" { - name = "${local.prefix}-ampls001" - resource_group_name = azurerm_resource_group.logging_rg.name - tags = var.tags -} - -resource "azurerm_monitor_private_link_scoped_service" "mpls_application_insights" { - name = "ampls-${azurerm_application_insights.application_insights.name}" - resource_group_name = azurerm_monitor_private_link_scope.mpls.resource_group_name - scope_name = azurerm_monitor_private_link_scope.mpls.name - linked_resource_id = azurerm_application_insights.application_insights.id -} - -resource "azurerm_monitor_private_link_scoped_service" "mpls_log_analytics_workspace" { - name = "ampls-${azurerm_log_analytics_workspace.log_analytics_workspace.name}" - resource_group_name = azurerm_monitor_private_link_scope.mpls.resource_group_name - scope_name = azurerm_monitor_private_link_scope.mpls.name - linked_resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id -} - -resource "azurerm_private_endpoint" "mpls_private_endpoint" { - name = "${azurerm_monitor_private_link_scope.mpls.name}-pe" - location = var.location - resource_group_name = azurerm_monitor_private_link_scope.mpls.resource_group_name - tags = var.tags - - custom_network_interface_name = "${azurerm_monitor_private_link_scope.mpls.name}-nic" - private_service_connection { - name = "${azurerm_monitor_private_link_scope.mpls.name}-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_monitor_private_link_scope.mpls.id - subresource_names = ["azuremonitor"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_monitor_private_link_scope.mpls.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_monitor, - var.private_dns_zone_id_oms_opinsights, - var.private_dns_zone_id_ods_opinsights, - var.private_dns_zone_id_automation_agents, - var.private_dns_zone_id_blob - ] - } -} diff --git a/code/infra/network.tf b/code/infra/network.tf index 7bd58906..e66efed4 100644 --- a/code/infra/network.tf +++ b/code/infra/network.tf @@ -5,7 +5,7 @@ resource "azapi_resource" "subnet_function" { body = jsonencode({ properties = { - addressPrefix = tostring(cidrsubnet(data.azurerm_virtual_network.virtual_network.address_space[0], 27 - tonumber(reverse(split("/", data.azurerm_virtual_network.virtual_network.address_space[0]))[0]), 2)) + addressPrefix = var.subnet_cidr_function delegations = [ { name = "FunctionDelegation" @@ -29,14 +29,14 @@ resource "azapi_resource" "subnet_function" { }) } -resource "azapi_resource" "subnet_services" { +resource "azapi_resource" "subnet_private_endpoints" { type = "Microsoft.Network/virtualNetworks/subnets@2022-07-01" name = "PeSubnet" parent_id = data.azurerm_virtual_network.virtual_network.id body = jsonencode({ properties = { - addressPrefix = tostring(cidrsubnet(data.azurerm_virtual_network.virtual_network.address_space[0], 27 - tonumber(reverse(split("/", data.azurerm_virtual_network.virtual_network.address_space[0]))[0]), 3)) + addressPrefix = var.subnet_cidr_private_endpoints delegations = [] ipAllocations = [] networkSecurityGroup = { diff --git a/code/infra/providers.tf b/code/infra/providers.tf new file mode 100644 index 00000000..7995ceaa --- /dev/null +++ b/code/infra/providers.tf @@ -0,0 +1,27 @@ +provider "azurerm" { + disable_correlation_request_id = false + environment = "public" + resource_provider_registrations = "none" + resource_providers_to_register = local.resource_providers_to_register + storage_use_azuread = true + + features { + key_vault { + recover_soft_deleted_key_vaults = true + recover_soft_deleted_certificates = true + recover_soft_deleted_keys = true + recover_soft_deleted_secrets = true + } + resource_group { + prevent_deletion_if_contains_resources = true + } + } +} + +provider "azapi" { + default_location = var.location + default_tags = var.tags + disable_correlation_request_id = false + environment = "public" + skip_provider_registration = false +} diff --git a/code/infra/roleassignments.tf b/code/infra/roleassignments.tf index ba7b169c..345ac85f 100644 --- a/code/infra/roleassignments.tf +++ b/code/infra/roleassignments.tf @@ -1,23 +1,17 @@ -resource "azurerm_role_assignment" "current_role_assignment_key_vault" { - scope = azurerm_key_vault.key_vault.id - role_definition_name = "Key Vault Administrator" - principal_id = data.azurerm_client_config.current.object_id -} - resource "azurerm_role_assignment" "function_role_assignment_storage_blob_data_owner" { - scope = azurerm_storage_account.storage.id + scope = module.storage_account.storage_account_id role_definition_name = "Storage Blob Data Owner" principal_id = azapi_resource.function.identity[0].principal_id } # resource "azurerm_role_assignment" "function_role_assignment_storage_account_contributor" { # Enable when using blob triggers -# scope = azurerm_storage_account.storage.id +# scope = module.storage_account.storage_account_id # role_definition_name = "Storage Account Contributor" # principal_id = azapi_resource.function.identity[0].principal_id # } # resource "azurerm_role_assignment" "function_role_assignment_storage_queue_data_contributor" { # Enable when using blob triggers -# scope = azurerm_storage_account.storage.id +# scope = module.storage_account.storage_account_id # role_definition_name = "Storage Queue Data Contributor" # principal_id = azapi_resource.function.identity[0].principal_id # } @@ -26,13 +20,13 @@ resource "azurerm_role_assignment" "function_role_assignment_storage_blob_data_o # For more details, refer to: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference?tabs=blob&pivots=programming-language-python#grant-permission-to-the-identity resource "azurerm_role_assignment" "function_role_assignment_key_vault" { - scope = azurerm_key_vault.key_vault.id + scope = module.key_vault.key_vault_id role_definition_name = "Key Vault Secrets Officer" principal_id = azapi_resource.function.identity[0].principal_id } # resource "azurerm_role_assignment" "function_role_assignment_application_insights" { # Enable to rely on Entra ID-based authentication to Application Insights -# scope = azurerm_application_insights.application_insights.id +# scope = module.application_insights.application_insights_id # role_definition_name = "Monitoring Metrics Publisher" # principal_id = azapi_resource.function.identity[0].principal_id # } diff --git a/code/infra/storage.tf b/code/infra/storage.tf index 63dd8bb7..ff6e4fe1 100644 --- a/code/infra/storage.tf +++ b/code/infra/storage.tf @@ -1,168 +1,45 @@ -resource "azurerm_storage_account" "storage" { - name = replace("${local.prefix}-stg001", "-", "") - location = var.location - resource_group_name = azurerm_resource_group.app_rg.name - tags = var.tags - - access_tier = "Hot" - account_kind = "StorageV2" - account_replication_type = "ZRS" - account_tier = "Standard" - allow_nested_items_to_be_public = false - allowed_copy_scope = "AAD" - blob_properties { - change_feed_enabled = false - container_delete_retention_policy { - days = 7 - } - delete_retention_policy { - days = 7 - } - default_service_version = "2020-06-12" - last_access_time_enabled = false - versioning_enabled = false - } - cross_tenant_replication_enabled = false - default_to_oauth_authentication = true - enable_https_traffic_only = true - infrastructure_encryption_enabled = true - is_hns_enabled = true - large_file_share_enabled = false - min_tls_version = "TLS1_2" - network_rules { - bypass = ["None"] - default_action = "Deny" - ip_rules = [] - virtual_network_subnet_ids = [] - private_link_access { - endpoint_resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Security/datascanners/storageDataScanner" - endpoint_tenant_id = data.azurerm_client_config.current.tenant_id - } - } - nfsv3_enabled = false - public_network_access_enabled = false - queue_encryption_key_type = "Account" - table_encryption_key_type = "Account" - routing { - choice = "MicrosoftRouting" - publish_internet_endpoints = false - publish_microsoft_endpoints = false - } - sftp_enabled = false - shared_access_key_enabled = false # Required to be set to 'true' when creating a Windows host -} - -data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_storage" { - resource_id = azurerm_storage_account.storage.id -} - -resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_storage" { - name = "logAnalytics" - target_resource_id = azurerm_storage_account.storage.id - log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id - - dynamic "enabled_log" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_storage.log_category_groups - content { - category_group = entry.value - } +module "storage_account" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/storage?ref=main" + providers = { + azurerm = azurerm + time = time } - dynamic "metric" { - iterator = entry - for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_storage.metrics - content { - category = entry.value - enabled = true - } - } -} - -resource "azurerm_private_endpoint" "storage_private_endpoint_blob" { - name = "${azurerm_storage_account.storage.name}-blob-pe" location = var.location - resource_group_name = azurerm_storage_account.storage.resource_group_name - tags = var.tags - - custom_network_interface_name = "${azurerm_storage_account.storage.name}-blob-nic" - private_service_connection { - name = "${azurerm_storage_account.storage.name}-blob-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_storage_account.storage.id - subresource_names = ["blob"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_storage_account.storage.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_blob - ] - } -} - -resource "azurerm_private_endpoint" "storage_private_endpoint_file" { - name = "${azurerm_storage_account.storage.name}-file-pe" - location = var.location - resource_group_name = azurerm_storage_account.storage.resource_group_name - tags = var.tags - - custom_network_interface_name = "${azurerm_storage_account.storage.name}-file-nic" - private_service_connection { - name = "${azurerm_storage_account.storage.name}-file-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_storage_account.storage.id - subresource_names = ["file"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_storage_account.storage.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_file - ] - } -} - -resource "azurerm_private_endpoint" "storage_private_endpoint_queue" { - name = "${azurerm_storage_account.storage.name}-queue-pe" - location = var.location - resource_group_name = azurerm_storage_account.storage.resource_group_name - tags = var.tags - - custom_network_interface_name = "${azurerm_storage_account.storage.name}-queue-nic" - private_service_connection { - name = "${azurerm_storage_account.storage.name}-queue-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_storage_account.storage.id - subresource_names = ["queue"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_storage_account.storage.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_queue - ] - } -} - -resource "azurerm_private_endpoint" "storage_private_endpoint_table" { - name = "${azurerm_storage_account.storage.name}-table-pe" - location = var.location - resource_group_name = azurerm_storage_account.storage.resource_group_name + resource_group_name = azurerm_resource_group.app_rg.name tags = var.tags - custom_network_interface_name = "${azurerm_storage_account.storage.name}-table-nic" - private_service_connection { - name = "${azurerm_storage_account.storage.name}-table-pe" - is_manual_connection = false - private_connection_resource_id = azurerm_storage_account.storage.id - subresource_names = ["table"] - } - subnet_id = azapi_resource.subnet_services.id - private_dns_zone_group { - name = "${azurerm_storage_account.storage.name}-arecord" - private_dns_zone_ids = [ - var.private_dns_zone_id_table - ] - } + storage_account_name = replace("${local.prefix}-stg001", "-", "") + storage_access_tier = "Hot" + storage_account_type = "StorageV2" + storage_account_tier = "Standard" + storage_account_replication_type = "ZRS" + storage_blob_change_feed_enabled = false + storage_blob_container_delete_retention_in_days = 7 + storage_blob_delete_retention_in_days = 7 + storage_blob_cors_rules = {} + storage_blob_last_access_time_enabled = false + storage_blob_versioning_enabled = false + storage_is_hns_enabled = false + storage_network_bypass = ["None"] + storage_network_private_link_access = [ + "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Security/datascanners/storageDataScanner" + ] + storage_public_network_access_enabled = false + storage_nfsv3_enabled = false + storage_sftp_enabled = false + storage_shared_access_key_enabled = false # Required to be set to 'true' when creating a Windows host + storage_container_names = [] + storage_static_website = [] + diagnostics_configurations = local.diagnostics_configurations + subnet_id = azapi_resource.subnet_private_endpoints.id + connectivity_delay_in_seconds = var.connectivity_delay_in_seconds + private_endpoint_subresource_names = ["blob", "file", "queue", "table"] + private_dns_zone_id_blob = var.private_dns_zone_id_blob + private_dns_zone_id_file = var.private_dns_zone_id_file + private_dns_zone_id_table = var.private_dns_zone_id_table + private_dns_zone_id_queue = var.private_dns_zone_id_queue + private_dns_zone_id_web = "" + private_dns_zone_id_dfs = "" + customer_managed_key = local.customer_managed_key } diff --git a/code/infra/terraform.tf b/code/infra/terraform.tf index b66f4c10..62f8b6ce 100644 --- a/code/infra/terraform.tf +++ b/code/infra/terraform.tf @@ -4,12 +4,16 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.116.0" + version = "4.3.0" } azapi = { source = "azure/azapi" version = "1.15.0" } + time = { + source = "hashicorp/time" + version = "0.12.1" + } } backend "azurerm" { @@ -21,32 +25,3 @@ terraform { use_azuread_auth = true } } - -provider "azurerm" { - disable_correlation_request_id = false - environment = "public" - skip_provider_registration = false - storage_use_azuread = true - # use_oidc = true - - features { - key_vault { - recover_soft_deleted_key_vaults = true - recover_soft_deleted_certificates = true - recover_soft_deleted_keys = true - recover_soft_deleted_secrets = true - } - resource_group { - prevent_deletion_if_contains_resources = true - } - } -} - -provider "azapi" { - default_location = var.location - default_tags = var.tags - disable_correlation_request_id = false - environment = "public" - skip_provider_registration = false - # use_oidc = true -} diff --git a/code/infra/variables.tf b/code/infra/variables.tf index a34bc971..2680609b 100644 --- a/code/infra/variables.tf +++ b/code/infra/variables.tf @@ -102,7 +102,17 @@ variable "my_secret" { } } -# Monitoring variables +# Logging & Monitoring variables +variable "log_analytics_workspace_id" { + description = "Specifies the resource ID of the log analytics workspace used for collecting logs." + type = string + sensitive = false + validation { + condition = length(split("/", var.log_analytics_workspace_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + variable "alert_endpoints" { description = "Specifies the alert details." type = object({ @@ -119,6 +129,18 @@ variable "alert_endpoints" { } # Network variables +variable "connectivity_delay_in_seconds" { + description = "Specifies the delay in seconds after the private endpoint deployment (required for the DNS automation via Policies)." + type = number + sensitive = false + nullable = false + default = 120 + validation { + condition = var.connectivity_delay_in_seconds >= 0 + error_message = "Please specify a valid non-negative number." + } +} + variable "vnet_id" { description = "Specifies the resource ID of the Vnet used for the Azure Function." type = string @@ -149,6 +171,26 @@ variable "route_table_id" { } } +variable "subnet_cidr_function" { + description = "Specifies the subnet cidr range for the function app subnet." + type = string + sensitive = false + validation { + condition = length(split("/", var.subnet_cidr_function)) == 2 + error_message = "Please specify a valid subnet cidr range." + } +} + +variable "subnet_cidr_private_endpoints" { + description = "Specifies the subnet cidr range for private endpoints." + type = string + sensitive = false + validation { + condition = length(split("/", var.subnet_cidr_private_endpoints)) == 2 + error_message = "Please specify a valid subnet cidr range." + } +} + # DNS variables variable "private_dns_zone_id_blob" { description = "Specifies the resource ID of the private DNS zone for Azure Storage blob endpoints. Not required if DNS A-records get created via Azue Policy." @@ -215,47 +257,3 @@ variable "private_dns_zone_id_sites" { error_message = "Please specify a valid resource ID for the private DNS Zone." } } - -variable "private_dns_zone_id_monitor" { - description = "Specifies the resource ID of the private DNS zone for Azure Monitor. Not required if DNS A-records get created via Azue Policy." - type = string - sensitive = false - default = "" - validation { - condition = var.private_dns_zone_id_monitor == "" || (length(split("/", var.private_dns_zone_id_monitor)) == 9 && endswith(var.private_dns_zone_id_monitor, "privatelink.monitor.azure.com")) - error_message = "Please specify a valid resource ID for the private DNS Zone." - } -} - -variable "private_dns_zone_id_oms_opinsights" { - description = "Specifies the resource ID of the private DNS zone for Azure Monitor OMS Insights. Not required if DNS A-records get created via Azue Policy." - type = string - sensitive = false - default = "" - validation { - condition = var.private_dns_zone_id_oms_opinsights == "" || (length(split("/", var.private_dns_zone_id_oms_opinsights)) == 9 && endswith(var.private_dns_zone_id_oms_opinsights, "privatelink.oms.opinsights.azure.com")) - error_message = "Please specify a valid resource ID for the private DNS Zone." - } -} - -variable "private_dns_zone_id_ods_opinsights" { - description = "Specifies the resource ID of the private DNS zone for Azure Monitor ODS Insights. Not required if DNS A-records get created via Azue Policy." - type = string - sensitive = false - default = "" - validation { - condition = var.private_dns_zone_id_ods_opinsights == "" || (length(split("/", var.private_dns_zone_id_ods_opinsights)) == 9 && endswith(var.private_dns_zone_id_ods_opinsights, "privatelink.ods.opinsights.azure.com")) - error_message = "Please specify a valid resource ID for the private DNS Zone." - } -} - -variable "private_dns_zone_id_automation_agents" { - description = "Specifies the resource ID of the private DNS zone for Azure Monitor Automation Agents. Not required if DNS A-records get created via Azue Policy." - type = string - sensitive = false - default = "" - validation { - condition = var.private_dns_zone_id_automation_agents == "" || (length(split("/", var.private_dns_zone_id_automation_agents)) == 9 && endswith(var.private_dns_zone_id_automation_agents, "privatelink.agentsvc.azure-automation.net")) - error_message = "Please specify a valid resource ID for the private DNS Zone." - } -} diff --git a/config/PerfectThymeTech/azurerm.tfbackend b/config/PerfectThymeTech/azurerm.tfbackend index 15418ba9..809ed7d5 100644 --- a/config/PerfectThymeTech/azurerm.tfbackend +++ b/config/PerfectThymeTech/azurerm.tfbackend @@ -1,7 +1,7 @@ environment = "public" -subscription_id = "8f171ff9-2b5b-4f0f-aed5-7fa360a1d094" -resource_group_name = "mycrp-prd-cicd" -storage_account_name = "mycrpprdstg001" +subscription_id = "e82c5267-9dc4-4f45-ac13-abdd5e130d27" +resource_group_name = "rg-terraform" +storage_account_name = "terraformststg001" container_name = "function" key = "terraform.tfstate" use_azuread_auth = true diff --git a/config/PerfectThymeTech/vars.tfvars b/config/PerfectThymeTech/vars.tfvars index 71f10282..6e5f2f3b 100644 --- a/config/PerfectThymeTech/vars.tfvars +++ b/config/PerfectThymeTech/vars.tfvars @@ -1,7 +1,7 @@ # General variables location = "northeurope" environment = "dev" -prefix = "myfunc" +prefix = "secfunc" tags = {} # Function variables @@ -11,22 +11,21 @@ function_sku = "EP1" function_sku_cpus = 1 function_health_path = "/v1/health/heartbeat" -# Network variables -vnet_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/virtualNetworks/mycrp-prd-function-vnet001" -nsg_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/networkSecurityGroups/mycrp-prd-function-nsg001" -route_table_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/routeTables/mycrp-prd-function-rt001" - # Monitoring variables -alert_endpoints = {} +log_analytics_workspace_id = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-logging-rg/providers/Microsoft.OperationalInsights/workspaces/ptt-dev-log001" +alert_endpoints = {} + +# Network variables +vnet_id = "/subscriptions/1fdab118-1638-419a-8b12-06c9543714a0/resourceGroups/ptt-dev-networking-rg/providers/Microsoft.Network/virtualNetworks/spoke-ptt-dev-vnet001" +nsg_id = "/subscriptions/1fdab118-1638-419a-8b12-06c9543714a0/resourceGroups/ptt-dev-networking-rg/providers/Microsoft.Network/networkSecurityGroups/ptt-dev-default-nsg001" +route_table_id = "/subscriptions/1fdab118-1638-419a-8b12-06c9543714a0/resourceGroups/ptt-dev-networking-rg/providers/Microsoft.Network/routeTables/ptt-dev-default-rt001" +subnet_cidr_function = "10.3.0.192/26" +subnet_cidr_private_endpoints = "10.3.1.0/26" # DNS variables -private_dns_zone_id_blob = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" -private_dns_zone_id_queue = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.queue.core.windows.net" -private_dns_zone_id_table = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.table.core.windows.net" -private_dns_zone_id_file = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" -private_dns_zone_id_key_vault = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" -private_dns_zone_id_sites = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.azurewebsites.net" -private_dns_zone_id_monitor = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.monitor.azure.com" -private_dns_zone_id_oms_opinsights = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.oms.opinsights.azure.com" -private_dns_zone_id_ods_opinsights = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.ods.opinsights.azure.com" -private_dns_zone_id_automation_agents = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.agentsvc.azure-automation.net" +private_dns_zone_id_blob = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" +private_dns_zone_id_queue = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.queue.core.windows.net" +private_dns_zone_id_table = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.table.core.windows.net" +private_dns_zone_id_file = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" +private_dns_zone_id_key_vault = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" +private_dns_zone_id_sites = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.azurewebsites.net"