.NET · .net-core · API-Management-Service · APIM · Architecture · Azure CLI · azure-resource-manager-templates · azure-web-app · azure-web-apps · AzureFunctions · Bicep · CI-CD · Infrastructure As Code · managed-identity · Private DNS Zone · Private Endpoint · Private Link

End-to-End Security: Function, Azure SQL, and API Management

Introduction

In the dynamic realm of today’s digital environment, safeguarding your applications and data holds utmost importance. This blog post is designed to lead you through the steps of establishing comprehensive security measures for an Azure Function App, Azure SQL database, and API Management. Our approach involves leveraging Azure Virtual Network (VNet) for robust network protection, employing Private Link for secure communication, and utilizing Azure Bicep for infrastructure as code. Additionally, we’ll implement Private DNS zones for both the SQL and Function app, facilitating easy access through Private Endpoints.

Quick start

You need following to get started.

  • Azure Subscription
  • Azure CLI configured

Here is the GitHub repository that contains all the Bicep templates and accompanied bash scripts that helps provisioning the entire stack.

Into the Platform folder we have a main.bicep that stiches all the granular Bicep modules.

You need to perform the following:

  1. Clone the repo. (e.g. git clone https://github.com/MoimHossain/secure-api-functions.git)
  2. Navigate to Platform folder (e.g. cd /src/Infrastructure/Platform)
  3. Run the bash script create-core-infra.sh. (e.g. ./create-core-infra.sh)

That is all you need. It will take a while and provision the entire stack protected with network restrictions.

If you want to change the names of the resource group, and/or resources please change them in the script, or bicep params – as applicable.

Breakdowns

Creating Azure Virtual Network (VNet)

We will create a VNet with 3 subnets within it, one for data, one for function and one for API management. Here’s the crucial part of it in below snippet:

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: [
      {
        name: dataSubnetName
        properties: {
          addressPrefixes: [
            dataSubnetAddressPrefix
          ]          
        }
      }
      {
        name: gatewaySubnetName
        properties: {
          addressPrefixes: [
            gatewaySubnetAddressPrefix
          ]
          networkSecurityGroup: {
            id: nsgApim.outputs.nsgId
          }
        }
      }
      {
        name: backendSubnetName
        properties: {
          addressPrefixes: [
            backendAddressPrefix
          ]
          delegations: [
            {
              name: 'delegation'              
              properties: {
                serviceName: 'Microsoft.Web/serverfarms'
              }
              type: 'Microsoft.Network/virtualNetworks/subnets/delegations'
            }
          ]
        }
      }      
    ]
  }
}

Notice, the backend subnet is delegated to Microsoft.Web/serverfarms so that we can later use Virtual network integration for the Function.

Azure SQL Database with Private Endpoint

Now we can provision the SQL server and database with a private endpoint.

module sqlServer 'modules/data/sql-server.bicep' = {
  name: sqlServerName
  params: {
    serverName: sqlServerName
    location: location
    
    sqlAdminUserAssignedIdentityName: sqlAdminUAMI.name
    azureADOnlyAuthentication: false
    publicNetworkAccess: 'Disabled'
  }
}

module sqlDatabase 'modules/data/sql-database.bicep' = {
  name: sqlDatabaseName
  params: {
    databaseName: sqlDatabaseName
    location: location
    serverName: sqlServer.outputs.sqlServerName
  }
}
module sqlPrivateEndpoint 'modules/network/private-endpoints/private-endpoint.bicep' = {
  name: sqlPrivateEndpointName
  params: {
    location: location
    vnetId: virtualNetwork.outputs.vnetId
    createPrivateDns: true
    zoneFqdn: sqlZoneName
    zoneGroupName: sqlZoneGroupName
    privateEndpointName: sqlPrivateEndpointName
    privateLinkServiceId: sqlServer.outputs.sqlServerResourceId
    subnetId: virtualNetwork.outputs.dataSubnetId
    targetSubResource: 'sqlServer' 
  }
}

Here, the SQL server public network access is completely disabled and only allowed access via the private endpoint. Here we only showed the modules but into the repo you will notice we also create the private DNS for the private endpoint.

Azure Function App with Private Endpoint

Like SQL, we will do the similar for the function. It will Disable all public internet access and access will be only granted with a private endpoint, also a Private DNS zone will be created for the function domain name.

module serverFarm 'modules/web/server-farm.bicep' = {
  name: serverFarmName
  params: {
    location: location
    name: serverFarmName
  }
}
module functionApp 'modules/web/function-app.bicep' = {
  name: functionAppName
  params: {
    location: location
    name: functionAppName
    delegatedSubnetResourceId: virtualNetwork.outputs.backendSubnetId
    functionUAMIName: functionIdentity.name
    serverFarmId: serverFarm.outputs.serverfarmId    
  }
}
module functionPrivateEndpoint 'modules/network/private-endpoints/private-endpoint.bicep' = {
  name: funcPrivateEndpointName
  params: {
    location: location
    vnetId: virtualNetwork.outputs.vnetId
    createPrivateDns: true
    zoneFqdn: funcZoneName
    zoneGroupName: funcZoneGroupName
    privateEndpointName: funcPrivateEndpointName
    privateLinkServiceId: functionApp.outputs.functionAppId
    subnetId: virtualNetwork.outputs.apimSubnetId
    targetSubResource: 'sites' 
  }
}

API Management Configuration

We will create a External API management which has a public endpoint but the backends are connected via private endpoint.

module apimService 'modules/api-management/apim.bicep' = {
  name: apimServiceName
  params: {
    apimServiceName: apimServiceName
    location: location
    sku: sku
    skuCount: skuCount
    publisherEmail: publisherEmail
    publisherName: publisherName
    publicIpAddressName: publicIpAddressName
    publicIpDnsLabel: publicIpDnsLabel
    subnetName: virtualNetwork.outputs.apimSubnetName
    virtualNetworkName: virtualNetwork.name
  }
}

Code deployment via Pipeline

One caveat with network locked Azure Functions or web apps is, once they are network protected, you can’t deploy code via Kudo site. You need to have an agent or VM that is connected to that VNet to perform code deployment.

I will propose a rather simplified way to achieve the same without VM or Self-Hosted pipeline agents.

Here is the trick, in our pipeline before we do the code publish to Azure Function, we will change the network rules for the Functions Kudo site, Just in time, creating a network rule that allows traffic from the pipeline agents host IP (exclusively to the Kudo site). Once the deployment/publish is done, we will remove that exemption to keep it protected as it was before.

Here is the bash script that we can run before the code publish:

## To enable the public network access and add the IP security restrictions to the function app

# Get local machine IP address
localMachineIP=$(curl -s ifconfig.me)
echo "Local Machine IP: $localMachineIP"

bodyJson=$(cat <<EOF
{
  "properties": {
    "publicNetworkAccess": "Enabled",
    "siteConfig": {
        "ipSecurityRestrictionsDefaultAction": "Deny",
        "scmIpSecurityRestrictionsUseMain": false,
        "scmIpSecurityRestrictionsDefaultAction": "Deny",
        "scmIpSecurityRestrictions": [
              {
                "ipAddress": "$localMachineIP/32",
                "action": "Allow",
                "tag": "Default",
                "priority": 100,
                "name": "LOCALMACHINE",
                "description": "LocalmachineIP",
                "vnetSubnetResourceId": null,
                "headers": {},
                "serviceEndpointDisabled": false,
                "id": "5"
              }
            ],
        "ipSecurityRestrictions": []
    }
  }
}   
EOF
)
# Apply the changes to the function app
az resource patch \
        --name <FUNC-NAME>\
        --resource-group <RESOURCE-GROUP> \
        --resource-type Microsoft.web/sites \
        --is-full-object \
        --properties "$bodyJson"

And once the publish is completed we will remove the exemption:

## To Disable the public network access to the function app
bodyJson=$(cat <<EOF
{
  "properties": {
    "publicNetworkAccess": "Disabled"
  }
}   
EOF
)
# Apply the changes to the function app
az resource patch \
        --name <FUNC-NAME>\
        --resource-group <RESOURCE-GROUP> \
        --resource-type Microsoft.web/sites \
        --is-full-object \
        --properties "$bodyJson"

That is it!

Conclusion:

By implementing these Azure Bicep snippets, you have established a secure end-to-end architecture for your Azure resources, ensuring the confidentiality and integrity of your data and applications. Following these best practices helps protect your assets in the cloud environment.

Full code can be found here: https://github.com/MoimHossain/secure-api-functions

Leave a comment