Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions function-app-v1/ProviderRelay/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
61 changes: 49 additions & 12 deletions function.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -84,38 +85,40 @@ 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"
}

# More somewhat pointless HTTP security; the HTTP data plane is unused.
minimum_tls_version = "1.3"
http2_enabled = true
}

app_settings = {
Expand Down Expand Up @@ -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
}
}
23 changes: 22 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand All @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
21 changes: 20 additions & 1 deletion storage.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down