Skip to content

Commit

Permalink
Function identity and storage network access (#228)
Browse files Browse the repository at this point in the history
* upgrade to linux app and use identity based storage connection for function app

* add back config

* remove settings for remote build, enable local build

* Set default deny network action on SA

* Add IP to tf state storage firewall

* Add subscription to shared access key setting

* Function debugging in new premium service plan

* Assign function subnet to output storage

* Cleanup

---------

Co-authored-by: elay <[email protected]>
Co-authored-by: Rob Emanuele <[email protected]>
  • Loading branch information
3 people authored Jul 1, 2024
1 parent a08f05e commit cfaf0a8
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 124 deletions.
23 changes: 18 additions & 5 deletions deployment/bin/deploy
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function usage() {
Deploys the project infrastructure.
-t TERRAFORM_DIR: The terraform directory. Required.
-y: Auto approve the terraform changes.
--plan: Only run Terraform plan.
--skip-tf: Skips Terraform apply. Will still gather terraform output
"
Expand All @@ -37,6 +38,10 @@ while [[ "$#" -gt 0 ]]; do case $1 in
PLAN_ONLY=1
shift
;;
-y)
AUTO_APPROVE=-auto-approve
shift
;;
--help)
usage
exit 0
Expand Down Expand Up @@ -64,6 +69,14 @@ SAK_STORAGE_ACCOUNTS=(
["pcfilestest"]="pc-test-manual-resources"
)

# Add client IP to firewall for storage accounts that must have properties read
# [storage_account]=resource_group
declare -A FW_STORAGE_ACCOUNTS
FW_STORAGE_ACCOUNTS=(
["pctesttfstate"]="pc-test-manual-resources"
["pctapisstagingsa"]="pct-apis-westeurope-staging_rg"
)

if [[ -z ${TERRAFORM_DIR} ]]; then
echo "Must pass in TERRAFORM_DIR with -t"
exit 1
Expand Down Expand Up @@ -95,10 +108,10 @@ fi
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then

#########################
# Add IP to KV firewall #
# Add IP to firewalls #
#########################

bin/kv_add_ip
add_ip_to_firewalls

#####################
# Deploy Terraform #
Expand All @@ -118,7 +131,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
exit 0
fi

terraform apply -auto-approve
terraform apply "$AUTO_APPROVE"
fi

# Gather terraform output
Expand All @@ -127,10 +140,10 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
popd

##############################
# Remove IP from KV firewall #
# Remove IP from firewalls #
##############################

bin/kv_rmv_ip
remove_ip_from_firewalls

############################
# Render Helm chart values #
Expand Down
38 changes: 0 additions & 38 deletions deployment/bin/kv_add_ip

This file was deleted.

38 changes: 0 additions & 38 deletions deployment/bin/kv_rmv_ip

This file was deleted.

57 changes: 55 additions & 2 deletions deployment/bin/lib
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ function disable_shared_access_keys() {
--name ${SAK_STORAGE_ACCOUNT} \
--resource-group ${SAK_RESOURCE_GROUP} \
--allow-shared-key-access false \
--subscription ${ARM_SUBSCRIPTION_ID} \
--output none

if [ $? -ne 0 ]; then
Expand All @@ -154,20 +155,72 @@ function disable_shared_access_keys() {
}

function enable_shared_access_keys() {
echo "Enabling shared key access for storage account..."
# Terraform isn't able to read all resources from a storage account if shared key access is disabled
# so while we're deploying, we need to enable it. Since we haven't run TF yet, we don't have the name of the account
# so they are hardcoded here. This is a temporary workaround until this is resolved
# https://github.com/hashicorp/terraform-provider-azurerm/issues/25218

echo "Enabling shared key access for storage accounts..."
for SAK_STORAGE_ACCOUNT in "${!SAK_STORAGE_ACCOUNTS[@]}"; do
SAK_RESOURCE_GROUP=${SAK_STORAGE_ACCOUNTS[$SAK_STORAGE_ACCOUNT]}

echo " - enabling ${SAK_STORAGE_ACCOUNT} / ${SAK_RESOURCE_GROUP}"
echo " - ${SAK_RESOURCE_GROUP}.${SAK_STORAGE_ACCOUNT}"
az storage account update \
--name ${SAK_STORAGE_ACCOUNT} \
--resource-group ${SAK_RESOURCE_GROUP} \
--allow-shared-key-access true \
--subscription ${ARM_SUBSCRIPTION_ID} \
--output none
done

sleep 10
}

function add_ip_to_firewalls() {
cidr=$(get_cidr_range)

echo "Adding IP $cidr to Key Vault firewall allow list..."
az keyvault network-rule add \
-g "${KEY_VAULT_RESOURCE_GROUP_NAME}" \
-n "${KEY_VAULT_NAME}" \
--ip-address "$cidr" \
--subscription "${ARM_SUBSCRIPTION_ID}" \
--output none

# Also add the IP to the terraform state storage account
for FW_STORAGE_ACCOUNT in "${!FW_STORAGE_ACCOUNTS[@]}"; do
FW_RESOURCE_GROUP=${FW_STORAGE_ACCOUNTS[$FW_STORAGE_ACCOUNT]}
echo "Adding IP $cidr to ${FW_STORAGE_ACCOUNT} Storage firewall allow list..."
az storage account network-rule add \
-g "${FW_RESOURCE_GROUP}" \
-n "${FW_STORAGE_ACCOUNT}" \
--ip-address "$cidr" \
--subscription "${ARM_SUBSCRIPTION_ID}" \
--output none
done

sleep 10
}

function remove_ip_from_firewalls() {
cidr=$(get_cidr_range)

echo "Removing IP $cidr from Key Vault firewall allow list..."
az keyvault network-rule remove \
-g ${KEY_VAULT_RESOURCE_GROUP_NAME} \
-n ${KEY_VAULT_NAME} \
--ip-address $cidr \
--subscription ${ARM_SUBSCRIPTION_ID} \
--output none

for FW_STORAGE_ACCOUNT in "${!FW_STORAGE_ACCOUNTS[@]}"; do
FW_RESOURCE_GROUP=${FW_STORAGE_ACCOUNTS[$FW_STORAGE_ACCOUNT]}
echo "Removing IP $cidr from ${FW_STORAGE_ACCOUNT} Storage firewall allow list..."
az storage account network-rule remove \
-g ${FW_RESOURCE_GROUP} \
-n ${FW_STORAGE_ACCOUNT} \
--ip-address $cidr \
--subscription ${ARM_SUBSCRIPTION_ID} \
--output none
done
}
87 changes: 48 additions & 39 deletions deployment/terraform/resources/functions.tf
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
resource "azurerm_app_service_plan" "pc" {
name = "plan-${local.prefix}"
resource "azurerm_service_plan" "pc" {
name = "app-plan-${local.prefix}"
location = azurerm_resource_group.pc.location
resource_group_name = azurerm_resource_group.pc.name
kind = "functionapp"
reserved = true
os_type = "Linux"

sku_name = "EP1"

sku {
tier = "Dynamic"
size = "Y1"
}
}

resource "azurerm_function_app" "pcfuncs" {
name = "func-${local.prefix}"
location = azurerm_resource_group.pc.location
resource_group_name = azurerm_resource_group.pc.name
app_service_plan_id = azurerm_app_service_plan.pc.id
storage_account_name = azurerm_storage_account.pc.name
storage_account_access_key = azurerm_storage_account.pc.primary_access_key
https_only = true
resource "azurerm_linux_function_app" "pcfuncs" {
name = "func-${local.prefix}"
location = azurerm_resource_group.pc.location
resource_group_name = azurerm_resource_group.pc.name
service_plan_id = azurerm_service_plan.pc.id
storage_account_name = azurerm_storage_account.pc.name

virtual_network_subnet_id = azurerm_subnet.function_subnet.id

ftp_publish_basic_authentication_enabled = false
webdeploy_publish_basic_authentication_enabled = false


storage_uses_managed_identity = true
https_only = true

identity {
type = "SystemAssigned"
}

app_settings = {
"ENABLE_ORYX_BUILD" = "true",
"SCM_DO_BUILD_DURING_DEPLOYMENT" = "true",
"FUNCTIONS_WORKER_RUNTIME" = "python",
"APP_INSIGHTS_IKEY" = azurerm_application_insights.pc_application_insights.instrumentation_key,
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.pc_application_insights.instrumentation_key,
"FUNCTIONS_WORKER_RUNTIME" = "python",
"APP_INSIGHTS_IKEY" = azurerm_application_insights.pc_application_insights.instrumentation_key,

# Remote build
"BUILD_FLAGS" = "UseExpressBuild",
"ENABLE_ORYX_BUILD" = "true"
"SCM_DO_BUILD_DURING_DEPLOYMENT" = "1",
"XDG_CACHE_HOME" = "/tmp/.cache"
"AzureWebJobsDisableHomepage" = true,

# Animation Function
Expand All @@ -48,48 +55,50 @@ resource "azurerm_function_app" "pcfuncs" {
"LOG_ANALYTICS_WORKSPACE_ID" = var.prod_log_analytics_workspace_id,
}

os_type = "linux"
version = "~4"
site_config {
linux_fx_version = "PYTHON|3.9"
use_32_bit_worker_process = false
ftps_state = "Disabled"
vnet_route_all_enabled = true
application_insights_key = azurerm_application_insights.pc_application_insights.instrumentation_key
ftps_state = "Disabled"

cors {
allowed_origins = ["*"]
}
application_stack {
python_version = "3.9"
}
}

lifecycle {
ignore_changes = [
tags
]
}
}

# Note: this must be in the same subscription as the rest of the deployed infrastructure
data "azurerm_storage_container" "output" {
name = var.output_container_name
storage_account_name = var.output_storage_account_name


resource "azurerm_role_assignment" "function-app-storage-account-access" {
scope = azurerm_storage_account.pc.id
role_definition_name = "Storage Blob Data Owner"
principal_id = azurerm_linux_function_app.pcfuncs.identity[0].principal_id
}

resource "azurerm_role_assignment" "function-app-animation-container-access" {
scope = data.azurerm_storage_container.output.resource_manager_id
scope = data.azurerm_storage_account.output-storage-account.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_function_app.pcfuncs.identity[0].principal_id
principal_id = azurerm_linux_function_app.pcfuncs.identity[0].principal_id

depends_on = [
azurerm_function_app.pcfuncs
azurerm_linux_function_app.pcfuncs
]
}

resource "azurerm_role_assignment" "function-app-storage-table-data-contributor" {
scope = azurerm_storage_account.pc.id
role_definition_name = "Storage Table Data Contributor"
principal_id = azurerm_function_app.pcfuncs.identity[0].principal_id
principal_id = azurerm_linux_function_app.pcfuncs.identity[0].principal_id

depends_on = [
azurerm_function_app.pcfuncs
azurerm_linux_function_app.pcfuncs
]
}

Expand All @@ -102,9 +111,9 @@ data "azurerm_log_analytics_workspace" "prod_log_analytics_workspace" {
resource "azurerm_role_assignment" "function-app-log-analytics-access" {
scope = data.azurerm_log_analytics_workspace.prod_log_analytics_workspace.id
role_definition_name = "Log Analytics Reader"
principal_id = azurerm_function_app.pcfuncs.identity[0].principal_id
principal_id = azurerm_linux_function_app.pcfuncs.identity[0].principal_id

depends_on = [
azurerm_function_app.pcfuncs
azurerm_linux_function_app.pcfuncs
]
}
}
2 changes: 1 addition & 1 deletion deployment/terraform/resources/output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,5 @@ output "redis_port" {
# Functions

output "function_app_name" {
value = azurerm_function_app.pcfuncs.name
value = azurerm_linux_function_app.pcfuncs.name
}
Loading

0 comments on commit cfaf0a8

Please sign in to comment.