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/function.tf b/function.tf index 7324727..f757046 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" { @@ -84,31 +85,32 @@ 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 = "stacklet-${var.prefix}-function-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 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. https_only = true - - # Deploy from zip file - zip_deploy_file = local_file.function_app_versioned.filename + client_certificate_enabled = false + 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" @@ -116,6 +118,7 @@ resource "azurerm_linux_function_app" "stacklet" { # More somewhat pointless HTTP security; the HTTP data plane is unused. minimum_tls_version = "1.3" + http2_enabled = true } app_settings = { @@ -150,3 +153,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 + } +} diff --git a/main.tf b/main.tf index fb66127..f5d16f7 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" { @@ -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..fc29324 100644 --- a/storage.tf +++ b/storage.tf @@ -33,7 +33,26 @@ resource "azurerm_storage_account" "stacklet" { location = azurerm_resource_group.stacklet_rg.location account_tier = "Standard" account_replication_type = "LRS" - tags = local.tags + + # Defaults to https only, and min tls version is 1.2 + + # 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 } resource "azurerm_storage_queue" "stacklet" {