From dc5862ed56725266c3c2f514c771b12d89ad5606 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Thu, 24 Jul 2025 20:39:48 +1000 Subject: [PATCH 01/12] Some terraform changes --- function.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/function.tf b/function.tf index 7324727..8a7c64d 100644 --- a/function.tf +++ b/function.tf @@ -20,6 +20,7 @@ resource "azurerm_application_insights" "stacklet" { resource_group_name = azurerm_resource_group.stacklet_rg.name application_type = "web" tags = local.tags + retention_in_days = 90 } resource "azurerm_service_plan" "stacklet" { @@ -92,7 +93,7 @@ resource "local_file" "function_app_versioned" { } resource "azurerm_linux_function_app" "stacklet" { - name = "stacklet-${var.prefix}-function-app-${substr(random_string.storage_account_suffix.result, 0, 15)}" + name = "${var.prefix}-relay-app" # -${substr(random_string.storage_account_suffix.result, 0, 15)}" resource_group_name = azurerm_resource_group.stacklet_rg.name location = azurerm_resource_group.stacklet_rg.location service_plan_id = azurerm_service_plan.stacklet.id From a7106713d6cefe6707f8ff9941fd68ee91cfdc4a Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 4 Jul 2025 17:00:11 +1200 Subject: [PATCH 02/12] Clean up a few comments. --- function.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/function.tf b/function.tf index 8a7c64d..411c135 100644 --- a/function.tf +++ b/function.tf @@ -93,7 +93,7 @@ resource "local_file" "function_app_versioned" { } resource "azurerm_linux_function_app" "stacklet" { - name = "${var.prefix}-relay-app" # -${substr(random_string.storage_account_suffix.result, 0, 15)}" + name = "${var.prefix}-relay-app" resource_group_name = azurerm_resource_group.stacklet_rg.name location = azurerm_resource_group.stacklet_rg.location service_plan_id = azurerm_service_plan.stacklet.id From 1a3a34935091c7e177b83c44833d05dd2253f4cb Mon Sep 17 00:00:00 2001 From: William Grant Date: Wed, 23 Jul 2025 19:52:55 +1000 Subject: [PATCH 03/12] Restore resource_group_name suffix lost in rebase --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index fb66127..e47cc0f 100644 --- a/main.tf +++ b/main.tf @@ -28,7 +28,7 @@ data "azurerm_role_definition" "builtin" { resource "random_uuid" "app_role_uuid" {} locals { - resource_group_name = coalesce(var.resource_group_name, var.prefix) + resource_group_name = coalesce(var.resource_group_name, "${var.prefix}-stacklet-relay") } resource "azurerm_resource_group" "stacklet_rg" { From 92c784e02cdef3f7d9df44fce9009751a2e9178a Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 11 Jul 2025 14:34:04 +1200 Subject: [PATCH 04/12] Attempts to get around a new issue that just arrived. --- function-app-v1/ProviderRelay/__init__.py | 1 + locals.tf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/function-app-v1/ProviderRelay/__init__.py b/function-app-v1/ProviderRelay/__init__.py index cd76574..dda7082 100644 --- a/function-app-v1/ProviderRelay/__init__.py +++ b/function-app-v1/ProviderRelay/__init__.py @@ -32,6 +32,7 @@ def get_session(client_id, audience, role_arn): managed_identity_client=client_id, exclude_environment_credential=True ) token = creds.get_token(audience) + logging.info(f"token: {token}") try: res = client.assume_role_with_web_identity( WebIdentityToken=token.token, diff --git a/locals.tf b/locals.tf index 776a47b..b507709 100644 --- a/locals.tf +++ b/locals.tf @@ -17,7 +17,7 @@ locals { app_role_id = var.azuread_application == null ? random_uuid.app_role_uuid.id : data.azuread_application.stacklet_application[0].app_role_ids.AssumeRoleWithWebIdentity - audience = "api://stacklet/provider/azure/${var.aws_target_prefix}" + audience = "api://${data.azurerm_subscription.current.tenant_id}/stacklet/provider/azure/${var.aws_target_prefix}" _tags = { "stacklet:app" : "Azure Relay" From 7955a8d27b5db98a4bf193a2d1cfb196c72c59c5 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 4 Jul 2025 16:45:04 +1200 Subject: [PATCH 05/12] Deployed and appears to be working. --- function.tf | 53 ++++++++++++++++++++++++++++++++++++++++++- main.tf | 21 +++++++++++++++++ provider.tf | 3 +++ storage.tf | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++- vars.tf | 6 +++++ 5 files changed, 146 insertions(+), 2 deletions(-) diff --git a/function.tf b/function.tf index 411c135..7eba37f 100644 --- a/function.tf +++ b/function.tf @@ -100,10 +100,14 @@ resource "azurerm_linux_function_app" "stacklet" { storage_account_name = azurerm_storage_account.stacklet.name storage_account_access_key = azurerm_storage_account.stacklet.primary_access_key + # replaces storage_account_access_key + # storage_uses_managed_identity = true # Use managed identity instead of access keys # Enforce HTTPS on the HTTP endpoint even though the data plane aspect of it # is unused, to avoid showing up in security checks. https_only = true + # client_certificate_enabled = false + # public_network_access_enabled = true # TODO: Set to false after deployment for security # Deploy from zip file zip_deploy_file = local_file.function_app_versioned.filename @@ -116,7 +120,33 @@ resource "azurerm_linux_function_app" "stacklet" { } # More somewhat pointless HTTP security; the HTTP data plane is unused. - minimum_tls_version = "1.3" + # always_on = false # Not needed for consumption plan + ftps_state = "Disabled" # Disable FTP/FTPS + http2_enabled = true # Enable HTTP/2 + minimum_tls_version = "1.3" + remote_debugging_enabled = false # Disable remote debugging + scm_minimum_tls_version = "1.2" # SCM also uses TLS 1.2+ + # scm_use_main_ip_restriction = true # Apply IP restrictions to SCM + # vnet_route_all_enabled = false # No VNet routing needed + websockets_enabled = false # Disable WebSockets + + # IP restrictions - temporarily removed for deployment + # TODO: Re-enable after deployment + # ip_restriction { + # action = "Deny" + # priority = 100 + # ip_address = "0.0.0.0/0" + # name = "DenyAll" + # } + + # SCM IP restrictions - temporarily simplified for deployment + # TODO: Re-enable stricter restrictions after deployment + # scm_ip_restriction { + # action = "Allow" + # priority = 100 + # service_tag = "AzureCloud" + # name = "AllowAzureCloud" + # } } app_settings = { @@ -127,6 +157,22 @@ resource "azurerm_linux_function_app" "stacklet" { # default "Your Functions 4.0 app is up and running" page. AzureWebJobsDisableHomepage = true + # Security settings + # WEBSITES_ENABLE_APP_SERVICE_STORAGE = false # Use container storage + # WEBSITE_DISABLE_OVERLAPPED_RECYCLING = true # Prevent overlapped recycling + # WEBSITE_RUN_FROM_PACKAGE = "1" # Run from package for security + + # # Storage connection using managed identity + # AzureWebJobsStorage__accountName = azurerm_storage_account.stacklet.name + # AzureWebJobsStorage__credential = "managedidentity" + + # # Disable unnecessary features + # WEBSITE_ENABLE_SYNC_UPDATE_SITE = false + # WEBSITE_HTTPLOGGING_RETENTION_DAYS = "7" # Limit log retention + + # # Function runtime settings + # PYTHON_ISOLATE_WORKER_DEPENDENCIES = "1" + # Application configuration AZURE_CLIENT_ID = azurerm_user_assigned_identity.stacklet_identity.client_id AZURE_AUDIENCE = local.audience @@ -138,6 +184,11 @@ resource "azurerm_linux_function_app" "stacklet" { AWS_TARGET_EVENT_BUS = var.aws_target_event_bus } + # # Authentication disabled since no HTTP access + # auth_settings { + # enabled = false + # } + identity { type = "UserAssigned" identity_ids = [azurerm_user_assigned_identity.stacklet_identity.id] diff --git a/main.tf b/main.tf index e47cc0f..f5d16f7 100644 --- a/main.tf +++ b/main.tf @@ -44,6 +44,27 @@ resource "azurerm_user_assigned_identity" "stacklet_identity" { tags = local.tags } +# Role assignment for storage queue access +resource "azurerm_role_assignment" "storage_queue_data_contributor" { + scope = azurerm_storage_account.stacklet.id + role_definition_name = "Storage Queue Data Contributor" + principal_id = azurerm_user_assigned_identity.stacklet_identity.principal_id +} + +# Role assignment for storage blob access (for function app packages) +resource "azurerm_role_assignment" "storage_blob_data_contributor" { + scope = azurerm_storage_account.stacklet.id + role_definition_name = "Storage Blob Data Contributor" + principal_id = azurerm_user_assigned_identity.stacklet_identity.principal_id +} + +# Role assignment for Application Insights +resource "azurerm_role_assignment" "monitoring_contributor" { + scope = azurerm_application_insights.stacklet.id + role_definition_name = "Monitoring Contributor" + principal_id = azurerm_user_assigned_identity.stacklet_identity.principal_id +} + resource "azuread_application" "stacklet_application" { count = var.azuread_application == null ? 1 : 0 display_name = "${var.prefix}-application" diff --git a/provider.tf b/provider.tf index 63161fa..b3ae789 100644 --- a/provider.tf +++ b/provider.tf @@ -34,4 +34,7 @@ provider "azurerm" { } subscription_id = var.subscription_id + + # Use Azure AD authentication for storage operations (required when access keys are disabled) + storage_use_azuread = true } diff --git a/storage.tf b/storage.tf index 0a009d0..0890c12 100644 --- a/storage.tf +++ b/storage.tf @@ -33,10 +33,73 @@ resource "azurerm_storage_account" "stacklet" { location = azurerm_resource_group.stacklet_rg.location account_tier = "Standard" account_replication_type = "LRS" - tags = local.tags + + # # Security hardening + # https_traffic_only_enabled = true # Enforce HTTPS only + # min_tls_version = "TLS1_2" # Enforce TLS 1.2+ + # allow_nested_items_to_be_public = false # Prevent public blob access + # public_network_access_enabled = true # Enable for selective access + # shared_access_key_enabled = false # Disable access keys, use managed identity only + + # # Disable unnecessary protocols + # nfsv3_enabled = false + # sftp_enabled = false + + # # Network restrictions - deny by default, allow specific access + # network_rules { + # default_action = "Deny" # Block general public access + # bypass = ["AzureServices", "Logging", "Metrics"] # Allow Azure services + + # # Allow access from specified IP addresses (Terraform, admin access, etc.) + # ip_rules = var.allowed_ip_addresses + + # # No VNet access needed for this use case + # virtual_network_subnet_ids = [] + # } + + # # Blob properties for additional security + # blob_properties { + # versioning_enabled = false # Not needed for function storage + # change_feed_enabled = false # Not needed + + # delete_retention_policy { + # days = 7 # Short retention for security + # } + + # container_delete_retention_policy { + # days = 7 # Short retention for security + # } + # } + + tags = local.tags } resource "azurerm_storage_queue" "stacklet" { name = "${azurerm_storage_account.stacklet.name}-queue" storage_account_name = azurerm_storage_account.stacklet.name } + +# # Separate queue properties resource (replaces deprecated queue_properties block) +# resource "azurerm_storage_account_queue_properties" "stacklet" { +# storage_account_id = azurerm_storage_account.stacklet.id + +# logging { +# delete = true +# read = true +# write = true +# version = "1.0" +# retention_policy_days = 7 # Short retention for security +# } + +# minute_metrics { +# version = "1.0" +# include_apis = true +# retention_policy_days = 7 +# } + +# hour_metrics { +# version = "1.0" +# include_apis = true +# retention_policy_days = 7 +# } +# } diff --git a/vars.tf b/vars.tf index 1dbbc6c..a5f9979 100644 --- a/vars.tf +++ b/vars.tf @@ -111,3 +111,9 @@ variable "azuread_application" { description = "Azure AD Application. One per tenant." default = null } + +variable "allowed_ip_addresses" { + type = list(string) + description = "List of IP addresses allowed to access the storage account for Terraform and admin access" + default = [] +} From cec2a2aec25a73c162a6da4f83d9a5f6323ef0d9 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 4 Jul 2025 16:53:22 +1200 Subject: [PATCH 06/12] Remove public network access, and enforce https. --- function.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/function.tf b/function.tf index 7eba37f..36ea7d7 100644 --- a/function.tf +++ b/function.tf @@ -106,8 +106,8 @@ resource "azurerm_linux_function_app" "stacklet" { # Enforce HTTPS on the HTTP endpoint even though the data plane aspect of it # is unused, to avoid showing up in security checks. https_only = true - # client_certificate_enabled = false - # public_network_access_enabled = true # TODO: Set to false after deployment for security + client_certificate_enabled = false + public_network_access_enabled = false # Deploy from zip file zip_deploy_file = local_file.function_app_versioned.filename From d093027aef5efe24529c17a81e1eff2c221a6847 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 4 Jul 2025 16:56:08 +1200 Subject: [PATCH 07/12] Remove the unnecessary pieces. --- function.tf | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/function.tf b/function.tf index 36ea7d7..e6237ea 100644 --- a/function.tf +++ b/function.tf @@ -120,33 +120,12 @@ resource "azurerm_linux_function_app" "stacklet" { } # More somewhat pointless HTTP security; the HTTP data plane is unused. - # always_on = false # Not needed for consumption plan ftps_state = "Disabled" # Disable FTP/FTPS http2_enabled = true # Enable HTTP/2 minimum_tls_version = "1.3" remote_debugging_enabled = false # Disable remote debugging scm_minimum_tls_version = "1.2" # SCM also uses TLS 1.2+ - # scm_use_main_ip_restriction = true # Apply IP restrictions to SCM - # vnet_route_all_enabled = false # No VNet routing needed - websockets_enabled = false # Disable WebSockets - - # IP restrictions - temporarily removed for deployment - # TODO: Re-enable after deployment - # ip_restriction { - # action = "Deny" - # priority = 100 - # ip_address = "0.0.0.0/0" - # name = "DenyAll" - # } - - # SCM IP restrictions - temporarily simplified for deployment - # TODO: Re-enable stricter restrictions after deployment - # scm_ip_restriction { - # action = "Allow" - # priority = 100 - # service_tag = "AzureCloud" - # name = "AllowAzureCloud" - # } + websockets_enabled = false # Disable WebSockets } app_settings = { @@ -157,22 +136,10 @@ resource "azurerm_linux_function_app" "stacklet" { # default "Your Functions 4.0 app is up and running" page. AzureWebJobsDisableHomepage = true - # Security settings - # WEBSITES_ENABLE_APP_SERVICE_STORAGE = false # Use container storage - # WEBSITE_DISABLE_OVERLAPPED_RECYCLING = true # Prevent overlapped recycling - # WEBSITE_RUN_FROM_PACKAGE = "1" # Run from package for security - # # Storage connection using managed identity # AzureWebJobsStorage__accountName = azurerm_storage_account.stacklet.name # AzureWebJobsStorage__credential = "managedidentity" - # # Disable unnecessary features - # WEBSITE_ENABLE_SYNC_UPDATE_SITE = false - # WEBSITE_HTTPLOGGING_RETENTION_DAYS = "7" # Limit log retention - - # # Function runtime settings - # PYTHON_ISOLATE_WORKER_DEPENDENCIES = "1" - # Application configuration AZURE_CLIENT_ID = azurerm_user_assigned_identity.stacklet_identity.client_id AZURE_AUDIENCE = local.audience From 72b2c9a5d73fea24f15ff1dc3818afc4639a7392 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Wed, 9 Jul 2025 12:00:13 +1200 Subject: [PATCH 08/12] Fix the python line endings, and fix the redeploy on hash change. --- function.tf | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/function.tf b/function.tf index e6237ea..2d59313 100644 --- a/function.tf +++ b/function.tf @@ -85,13 +85,6 @@ data "archive_file" "function_app" { output_path = "${path.module}/function-app.zip" } -# azurerm_linux_function_app's zip_deploy_file will only redeploy the function -# when the path changes, so copy the zip to a versioned filename. -resource "local_file" "function_app_versioned" { - filename = "${path.module}/function-app-${data.archive_file.function_app.output_sha256}.zip" - source = "${path.module}/function-app.zip" -} - resource "azurerm_linux_function_app" "stacklet" { name = "${var.prefix}-relay-app" resource_group_name = azurerm_resource_group.stacklet_rg.name @@ -109,9 +102,6 @@ resource "azurerm_linux_function_app" "stacklet" { client_certificate_enabled = false public_network_access_enabled = false - # Deploy from zip file - zip_deploy_file = local_file.function_app_versioned.filename - site_config { application_insights_key = azurerm_application_insights.stacklet.instrumentation_key @@ -169,3 +159,37 @@ resource "azurerm_linux_function_app" "stacklet" { ] } } + +# In order to get the underlying function in the application to be redeployed +# when the zip hash changes, we need use fork out to the azure cli. +# We also need to temporarily enable public access to allow the zip file to be deployed. +resource "null_resource" "deploy_function_app" { + depends_on = [azurerm_linux_function_app.stacklet] + + provisioner "local-exec" { + command = <<-EOT + # Temporarily enable public access + az functionapp update \ + --name ${azurerm_linux_function_app.stacklet.name} \ + --resource-group ${azurerm_resource_group.stacklet_rg.name} \ + --set publicNetworkAccess=Enabled + + # Deploy the code + az functionapp deployment source config-zip \ + --name ${azurerm_linux_function_app.stacklet.name} \ + --resource-group ${azurerm_resource_group.stacklet_rg.name} \ + --src ${data.archive_file.function_app.output_path} \ + --build-remote true + + # Disable public access again + az functionapp update \ + --name ${azurerm_linux_function_app.stacklet.name} \ + --resource-group ${azurerm_resource_group.stacklet_rg.name} \ + --set publicNetworkAccess=Disabled + EOT + } + + triggers = { + build_hash = data.archive_file.function_app.output_sha256 + } +} From dbf114f4f0fbfe4594ab78db98c692dd08817bd4 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Thu, 10 Jul 2025 12:14:48 +1200 Subject: [PATCH 09/12] Delete the unnecessary commented out code. --- storage.tf | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/storage.tf b/storage.tf index 0890c12..fb68617 100644 --- a/storage.tf +++ b/storage.tf @@ -57,19 +57,6 @@ resource "azurerm_storage_account" "stacklet" { # virtual_network_subnet_ids = [] # } - # # Blob properties for additional security - # blob_properties { - # versioning_enabled = false # Not needed for function storage - # change_feed_enabled = false # Not needed - - # delete_retention_policy { - # days = 7 # Short retention for security - # } - - # container_delete_retention_policy { - # days = 7 # Short retention for security - # } - # } tags = local.tags } @@ -78,28 +65,3 @@ resource "azurerm_storage_queue" "stacklet" { name = "${azurerm_storage_account.stacklet.name}-queue" storage_account_name = azurerm_storage_account.stacklet.name } - -# # Separate queue properties resource (replaces deprecated queue_properties block) -# resource "azurerm_storage_account_queue_properties" "stacklet" { -# storage_account_id = azurerm_storage_account.stacklet.id - -# logging { -# delete = true -# read = true -# write = true -# version = "1.0" -# retention_policy_days = 7 # Short retention for security -# } - -# minute_metrics { -# version = "1.0" -# include_apis = true -# retention_policy_days = 7 -# } - -# hour_metrics { -# version = "1.0" -# include_apis = true -# retention_policy_days = 7 -# } -# } From bfe69eea55bf790135914612d085eb985bff0367 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Thu, 10 Jul 2025 12:32:16 +1200 Subject: [PATCH 10/12] This is working. --- function.tf | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/function.tf b/function.tf index 2d59313..05da51d 100644 --- a/function.tf +++ b/function.tf @@ -93,8 +93,7 @@ resource "azurerm_linux_function_app" "stacklet" { storage_account_name = azurerm_storage_account.stacklet.name storage_account_access_key = azurerm_storage_account.stacklet.primary_access_key - # replaces storage_account_access_key - # storage_uses_managed_identity = true # Use managed identity instead of access keys + # storage_uses_managed_identity = true # Enforce HTTPS on the HTTP endpoint even though the data plane aspect of it # is unused, to avoid showing up in security checks. @@ -103,19 +102,16 @@ resource "azurerm_linux_function_app" "stacklet" { public_network_access_enabled = false site_config { - application_insights_key = azurerm_application_insights.stacklet.instrumentation_key + application_insights_key = azurerm_application_insights.stacklet.instrumentation_key + application_insights_connection_string = azurerm_application_insights.stacklet.connection_string application_stack { python_version = "3.12" } # More somewhat pointless HTTP security; the HTTP data plane is unused. - ftps_state = "Disabled" # Disable FTP/FTPS - http2_enabled = true # Enable HTTP/2 - minimum_tls_version = "1.3" - remote_debugging_enabled = false # Disable remote debugging - scm_minimum_tls_version = "1.2" # SCM also uses TLS 1.2+ - websockets_enabled = false # Disable WebSockets + minimum_tls_version = "1.3" + http2_enabled = true } app_settings = { @@ -126,10 +122,6 @@ resource "azurerm_linux_function_app" "stacklet" { # default "Your Functions 4.0 app is up and running" page. AzureWebJobsDisableHomepage = true - # # Storage connection using managed identity - # AzureWebJobsStorage__accountName = azurerm_storage_account.stacklet.name - # AzureWebJobsStorage__credential = "managedidentity" - # Application configuration AZURE_CLIENT_ID = azurerm_user_assigned_identity.stacklet_identity.client_id AZURE_AUDIENCE = local.audience @@ -141,11 +133,6 @@ resource "azurerm_linux_function_app" "stacklet" { AWS_TARGET_EVENT_BUS = var.aws_target_event_bus } - # # Authentication disabled since no HTTP access - # auth_settings { - # enabled = false - # } - identity { type = "UserAssigned" identity_ids = [azurerm_user_assigned_identity.stacklet_identity.id] From bae00bf3efcbf66883ae357c686fbe3fdd211be9 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Thu, 10 Jul 2025 12:36:27 +1200 Subject: [PATCH 11/12] Clean up storage commented out code. --- storage.tf | 25 +++---------------------- vars.tf | 6 ------ 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/storage.tf b/storage.tf index fb68617..95804f6 100644 --- a/storage.tf +++ b/storage.tf @@ -34,29 +34,10 @@ resource "azurerm_storage_account" "stacklet" { account_tier = "Standard" account_replication_type = "LRS" - # # Security hardening - # https_traffic_only_enabled = true # Enforce HTTPS only - # min_tls_version = "TLS1_2" # Enforce TLS 1.2+ - # allow_nested_items_to_be_public = false # Prevent public blob access + # Defaults to https only, and min tls version is 1.2 + # allow_nested_items_to_be_public = false # Prevent public blob access # public_network_access_enabled = true # Enable for selective access - # shared_access_key_enabled = false # Disable access keys, use managed identity only - - # # Disable unnecessary protocols - # nfsv3_enabled = false - # sftp_enabled = false - - # # Network restrictions - deny by default, allow specific access - # network_rules { - # default_action = "Deny" # Block general public access - # bypass = ["AzureServices", "Logging", "Metrics"] # Allow Azure services - - # # Allow access from specified IP addresses (Terraform, admin access, etc.) - # ip_rules = var.allowed_ip_addresses - - # # No VNet access needed for this use case - # virtual_network_subnet_ids = [] - # } - + # shared_access_key_enabled = false # Disable access keys, use managed identity only tags = local.tags } diff --git a/vars.tf b/vars.tf index a5f9979..1dbbc6c 100644 --- a/vars.tf +++ b/vars.tf @@ -111,9 +111,3 @@ variable "azuread_application" { description = "Azure AD Application. One per tenant." default = null } - -variable "allowed_ip_addresses" { - type = list(string) - description = "List of IP addresses allowed to access the storage account for Terraform and admin access" - default = [] -} From 199881bf1260fea24702c1870ae46ee8e52fd42c Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 11 Jul 2025 14:32:07 +1200 Subject: [PATCH 12/12] Add comments. --- function.tf | 7 +++++++ storage.tf | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/function.tf b/function.tf index 05da51d..f757046 100644 --- a/function.tf +++ b/function.tf @@ -94,6 +94,13 @@ resource "azurerm_linux_function_app" "stacklet" { storage_account_name = azurerm_storage_account.stacklet.name storage_account_access_key = azurerm_storage_account.stacklet.primary_access_key # storage_uses_managed_identity = true + # After the function has been deployed, this value can be turned on, and + # the storage_account_access_key removed. However, doing this also stops + # future redeployment of the function code from working as the azure cli + # looks for the `AzureWebJobsStorage` application setting. When using the + # managed identity, this value is not set, and instead + # `AzureWebJobsStorage__accountKey` is set. This causes the azure cli to + # fail to deploy the function code. # Enforce HTTPS on the HTTP endpoint even though the data plane aspect of it # is unused, to avoid showing up in security checks. diff --git a/storage.tf b/storage.tf index 95804f6..fc29324 100644 --- a/storage.tf +++ b/storage.tf @@ -35,9 +35,22 @@ resource "azurerm_storage_account" "stacklet" { account_replication_type = "LRS" # Defaults to https only, and min tls version is 1.2 - # allow_nested_items_to_be_public = false # Prevent public blob access - # public_network_access_enabled = true # Enable for selective access - # shared_access_key_enabled = false # Disable access keys, use managed identity only + + # allow_nested_items_to_be_public = false + # Setting this value to false prevents the deployment of the function app + # using the azure cli. + + # public_network_access_enabled = true + # Setting this value stops terraform being able to query the state of the + # storage account. It seems that the control plane and data plane are + # effectively configured together. In order to turn this value on, the + # storage account needs to configure either selective IP allow lists or + # selective VPCs. + + # shared_access_key_enabled = false + # Turning this value on seems to prevent the servers running the function + # app from acquiring some form of internal lock that is necessary, so the + # azure function wrapping code is never able to query the storage queue. tags = local.tags }