To ensure your security and compliance requirements are met, Azure Front Door offers comprehensive end-to-end TLS encryption. For more information, see End-to-end TLS with Azure Front Door support. With Front Door's TLS/SSL offload capability, the TLS connection is terminated and the incoming traffic is decrypted at the Front Door. The traffic is then re-encrypted before being forwarded to the origin, that in this project is represented by a web application hosted in an Azure Kubernetes Service cluster. The sample application is exposed via the NGINX Ingress Controller configured to use a private IP address as a frontend IP configuration of the kubernetes-internal
internal load balancer. For more information, see Create an ingress controller using an internal IP address.
To enhance security, HTTPS is configured as the forwarding protocol on Azure Front Door when connecting to the AKS-hosted workload configured as a origin. This practice ensures that end-to-end TLS encryption is enforced for the entire request process, from the client to the origin.
Azure Front Door Premium can connect to a backend application via Azure Private Link Service (PLS). For more information, see Secure your Origin with Private Link in Azure Front Door Premium. If you deploy a private origin using Azure Front Door Premium and the Azure Private Link Service (PLS), TLS/SSL offload is fully supported.
This article demonstrates how to set up end-to-end TLS encryption using Azure Front Door Premium and Azure Kubernetes Service (AKS). In addition, it shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Service(AKS). The sample application is exposed via the NGINX Ingress Controller configured to use a private IP address as a frontend IP configuration of the kubernetes-internal
internal load balancer. For more information, see Create an ingress controller using an internal IP address.
This sample also shows how to deploy an Azure Kubernetes Service cluster with the API Server VNET Integration and how to use an Azure NAT Gateway to manage outbound connections initiated by AKS-hosted workloads. AKS clusters with API Server VNET integration provide a series of advantages, for example, they can have public network access or private cluster mode enabled or disabled without redeploying the cluster. For more information, see Create an Azure Kubernetes Service cluster with API Server VNet Integration.
If it is not necessary to implement end-to-end TLS and if the Front Door route can be set up to utilize HTTP instead of HTTPS for calling the downstream AKS-hosted workload, you may refer to the following resource: How to expose NGINX Ingress Controller via Azure Front Door and Azure Private Link Service. You can find the companion code for this article in this GitHub repository.
- An active Azure subscription. If you don't have one, create a free Azure account before you begin.
- Visual Studio Code installed on one of the supported platforms along with the Bicep extension.
- Azure CLI version 2.58.0 or later installed. to install or upgrade, see Install Azure CLI.
- An existing Azure Key Vault resource with a valid TLS certificate for the sample web application.
- An existing Azure DNS Zone for the name resolution of the Azure Front Door custom domain via a CNAME record.
This sample provides a set of Bicep modules to deploy and configure an Azure Front Door Premium with an WAF Policy as global load balancer in front of a public or a private AKS cluster with API Server VNET Integration. You can can either configure your AKS cluster to use Azure CNI with Dynamic IP Allocation or Azure CNI Overlay networking. In addition, the deployment configures the AKS cluster with the Azure Key Vault provider for Secrets Store CSI Driver that allows for the integration of an Azure Key Vault as a secret store with an Azure Kubernetes Service (AKS) cluster via a CSI volume.
The following diagram shows the architecture and network topology deployed by the project when the AKS cluster is configured to use Azure CNI with Dynamic IP Allocation:
A Deployment Script is used to create the NGINX Ingress Controller, configured to use a private IP address as frontend IP configuration of the kubernetes-internal
internal load balancer, via Helm and a sample httpbin web application via YAML manifests. The script defines a SecretProviderClass
to read the TLS certificate from the source Azure Key Vault and creates a Kubernetes secret
. The deployment
and ingress
objects are configured to use the certificate contained in the Kubernetes secret.
The Origin child resource of the Azure Front Door Premium global load balancer is configured to call the sample application using the HTTP forwarding protocol via the Azure Private Link Service, the AKS the kubernetes-internal
internal load balancer, and the NGINX Ingress Controller.
Bicep modules are parametric, so you can choose any network plugin:
- Azure CNI with static IP allocation
- Azure CNI with dynamic IP allocation
- Azure CNI Powered by Cilium
- Azure CNI Overlay
- BYO CNI
- Kubenet
NOTE
The sample was tested only with Azure CNI and Azure CNI Overlay
In addition, the project shows how to deploy an Azure Kubernetes Service cluster with the following extensions and features:
- Istio-based service mesh add-on for Azure Kubernetes Service provides an officially supported and tested Istio integration for Azure Kubernetes Service (AKS).
- API Server VNET Integration allows you to enable network communication between the API server and the cluster nodes without requiring a private link or tunnel. AKS clusters with API Server VNET integration provide a series of advantages, for example, they can have public network access or private cluster mode enabled or disabled without redeploying the cluster. For more information, see Create an Azure Kubernetes Service cluster with API Server VNet Integration.
- Azure NAT Gateway to manage outbound connections initiated by AKS-hosted workloads.
- Event-driven Autoscaling (KEDA) add-on is a single-purpose and lightweight component that strives to make application autoscaling simple and is a CNCF Incubation project.
- Dapr extension for Azure Kubernetes Service (AKS) allows you to install Dapr, a portable, event-driven runtime that simplifies building resilient, stateless, and stateful applications that run on the cloud and edge and embrace the diversity of languages and developer frameworks. With its sidecar architecture, Dapr helps you tackle the challenges that come with building microservices and keeps your code platform agnostic.
- Flux V2 extension allows to deploy workloads to an Azure Kubernetes Service (AKS) cluster via GitOps. For more information, see GitOps Flux v2 configurations with AKS and Azure Arc-enabled Kubernetes
- Vertical Pod Autoscaling allows you to automatically sets resource requests and limits on containers per workload based on past usage. VPA makes certain pods are scheduled onto nodes that have the required CPU and memory resources. For more information, see Kubernetes Vertical Pod Autoscaling.
- Azure Key Vault Provider for Secrets Store CSI Driver provides a variety of methods of identity-based access to your Azure Key Vault.
- Image Cleaner to clean up stale images on your Azure Kubernetes Service cluster.
- Azure Kubernetes Service (AKS) Network Observability is an important part of maintaining a healthy and performant Kubernetes cluster. By collecting and analyzing data about network traffic, you can gain insights into how your cluster is operating and identify potential problems before they cause outages or performance degradation.
- Managed NGINX ingress with the application routing add-on.
In a production environment, we strongly recommend deploying a private AKS cluster with Uptime SLA. For more information, see private AKS cluster with a Public DNS address. Alternatively, you can deploy a public AKS cluster and secure access to the API server using authorized IP address ranges.
The Bicep modules deploy or use the following Azure resources:
- Microsoft.ContainerService/managedClusters: A public or private Azure Kubernetes Service(AKS) cluster composed of the following node pools:
- A
system
node pool in a dedicated subnet. The default node pool hosts only critical system pods and services. The worker nodes have node taint which prevents application pods from beings scheduled on this node pool. - A
user
node pool hosting user workloads and artifacts in a dedicated subnet.
- A
- Microsoft.Authorization/roleDefinitions: the Bicep modules create the following role assignments:
- a
Grafana Admin
role assignment on the Azure Managed Grafana for the Microsoft Entra ID user whose objectID is defined in theuserId
parameter. TheGrafana Admin
role provides full control of the instance including managing role assignments, viewing, editing, and configuring data sources. For more information, see How to share access to Azure Managed Grafana. - a
Key Vault Administrator
role assignment on the existing Azure Key Vault resource which contains the TLS certificate for the user-defined managed identity used by the Azure Key Vault provider for Secrets Store CSI Driver. This assignment is necessary to let the CSI driver read the certificate from the source Key Vault.
- a
- Microsoft.Cdn/profiles: an Azure Front Door Premium resource used to expose the AKS-hosted sample application via Azure Private Link Service and NGINX Ingress Controller. The Bicep module creates the following child resources to expose the workload:
- Microsoft.Cdn/profiles/originGroups: an Origin Group in Azure Front Door refers to a set of Origins that receives similar traffic for their application. You can define the Origin Group as a logical grouping of your application instances across the world that receives the same traffic and responds with an expected behavior. These Origins can be deployed across different regions or within the same region. All origins can be deployed in an Active/Active or Active/Passive configuration.
- Microsoft.Cdn/profiles/originGroups/origins: an Origin refers to the application deployment exposed via Azure Front Door. An Origin defines properties of the underlying backend application like its type, weight, priority, host header, and more. In this sample, the Origin is configured to call the httpbin web application via an Azure Private Link Service.
- Microsoft.Cdn/profiles/afdEndpoints: in Azure Front Door Standard/Premium, an endpoint is a logical grouping of one or more routes that are associated with domain names. Each endpoint is assigned a domain name by Front Door, and you can associate your own custom domains by using routes.
- Microsoft.Cdn/profiles/secrets: this resource is used to store and manage the TLS certificate from Azure Key Vault. This certificate is used by the custom domain.
- Microsoft.Cdn/profiles/customDomains: this resource allows you to configure and manage a custom domain name for the Front Door endpoint. The custom domain is configured to use the Front Door secret that contains the TLS certificate.
- Microsoft.Cdn/profiles/securityPolicies: a security policy associates a WAF policy to a list of domains and paths. For more information, see Security and Azure Front Door.
- Microsoft.Network/FrontDoorWebApplicationFirewallPolicies: Azure Web Application Firewall (WAF) on Azure Front Door provides centralized protection for your web applications. WAF defends your web services against common exploits and vulnerabilities. It keeps your service highly available for your users and helps you meet compliance requirements. You can configure a WAF policy and associate that policy to one or more Front Door front-ends for protection. The WAF policy deployed by this sample consists of three types of security rules:
- Custom rules are used to block incoming requests based on the content of the payload, querystring, HTTP request method, IP address of the caller, and more. This sample add a couple of customer rules to block calls coming from a given IP range or calls that contain the word
blockme
in the querystring. - OWASP Azure-managed rule sets provide an easy way to deploy protection against a common set of security threats like SQL injection or cross-site scripting.
- Bot protection rule set can be used to take custom actions on requests from known bot categories.
- Custom rules are used to block incoming requests based on the content of the payload, querystring, HTTP request method, IP address of the caller, and more. This sample add a couple of customer rules to block calls coming from a given IP range or calls that contain the word
- Microsoft.Network/dnsZones: this resource references an existing Azure DNS zone used for the name resolution of the Azure Front Door custom domain. You can use Azure DNS to host your DNS domain and manage your DNS records.
- Microsoft.Network/dnsZones/CNAME: this CNAME record is used to create an alias or pointer from one domain name to another. With this resource, you can configure a CNAME record to redirect DNS queries for the custom domain to the original hostname of the Azure Front Door endpoint.
- Microsoft.Network/dnsZones/TXT: this resource represents a Text (TXT) record within a DNS zone. A TXT record allows you to store arbitrary text information associated with a domain. In this project, the TXT record contains the validation token for the custom domain.
- Microsoft.Network/privateLinkServices: an Azure Private Link Service is configured to reference the
kubernetes-internal
internal load balancer of the AKS cluster.- Microsoft.Cdn/profiles/afdEndpoints/routes: a route defines properties such as custom domains, http redirect, supported protocols, and origin path that specify how to invoke the backend application. For more information, see Routing architecture overview.
- Microsoft.ContainerService/managedClusters: A public or private AKS cluster composed of a:
system
node pool in a dedicated subnet. The default node pool hosts only critical system pods and services. The worker nodes have node taint which prevents application pods from beings scheduled on this node pool.user
node pool hosting user workloads and artifacts in a dedicated subnet.
- Microsoft.Network/virtualNetworks: a new virtual network with six subnets:
SystemSubnet
: this subnet is used for the agent nodes of thesystem
node pool.UserSubnet
: this subnet is used for the agent nodes of theuser
node pool.PodSubnet
: this subnet is used to allocate private IP addresses to pods dynamically.ApiServerSubnet
: API Server VNET Integration projects the API server endpoint directly into this delegated subnet in the virtual network where the AKS cluster is deployed.AzureBastionSubnet
: a subnet for the Azure Bastion Host.VmSubnet
: a subnet for a jump-box virtual machine used to connect to the (private) AKS cluster and for the private endpoints.
- Microsoft.ManagedIdentity/userAssignedIdentities: a user-defined managed identity used by the AKS cluster to create additional resources like load balancers and managed disks in Azure.
- Microsoft.Compute/virtualMachines: Bicep modules create a jump-box virtual machine to manage the private AKS cluster.
- Microsoft.Network/bastionHosts: a separate Azure Bastion is deployed in the AKS cluster virtual network to provide SSH connectivity to both agent nodes and virtual machines.
- Microsoft.Storage/storageAccounts: this storage account is used to store the boot diagnostics logs of both the service provider and service consumer virtual machines. Boot Diagnostics is a debugging feature that allows you to view console output and screenshots to diagnose virtual machine status.
- Microsoft.ContainerRegistry/registries: an Azure Container Registry (ACR) to build, store, and manage container images and artifacts in a private registry for all container deployments.
- Microsoft.KeyVault/vaults: an existing Azure Key Vault used to store secrets, certificates, and keys that can be mounted as files by pods using Azure Key Vault Provider for Secrets Store CSI Driver. For more information, see Use the Azure Key Vault Provider for Secrets Store CSI Driver in an AKS cluster and Provide an identity to access the Azure Key Vault Provider for Secrets Store CSI Driver. In this project, the existing Key Vault resource contains the TLS certificate used by the
ingress
Kubernetes object and by the custom domain of the Azure Front Door endpoint. - Microsoft.Network/privateEndpoints: an Azure Private Endpoints is created for each of the following resources:
- Azure Container Registry
- Azure Key Vault
- Azure Storage Account
- API Server when deploying a private AKS cluster.
- Microsoft.Network/privateDnsZones: an Azure Private DNS Zone is created for each of the following resources:
- Azure Container Registry
- Azure Key Vault
- Azure Storage Account
- API Server when deploying a private AKS cluster.
- Microsoft.Network/networkSecurityGroups: subnets hosting virtual machines and Azure Bastion Hosts are protected by Azure Network Security Groups that are used to filter inbound and outbound traffic.
- Microsoft.Monitor/accounts: An Azure Monitor workspace is a unique environment for data collected by Azure Monitor. Each workspace has its own data repository, configuration, and permissions. Log Analytics workspaces contain logs and metrics data from multiple Azure resources, whereas Azure Monitor workspaces currently contain only metrics related to Prometheus. Azure Monitor managed service for Prometheus allows you to collect and analyze metrics at scale using a Prometheus-compatible monitoring solution, based on the Prometheus. This fully managed service allows you to use the Prometheus query language (PromQL) to analyze and alert on the performance of monitored infrastructure and workloads without having to operate the underlying infrastructure. The primary method for visualizing Prometheus metrics is Azure Managed Grafana. You can connect your Azure Monitor workspace to an Azure Managed Grafana to visualize Prometheus metrics using a set of built-in and custom Grafana dashboards.
- Microsoft.Dashboard/grafana: an Azure Managed Grafana instance used to visualize the Prometheus metrics generated by the Azure Kubernetes Service(AKS) cluster deployed by the Bicep modules. Azure Managed Grafana](https://learn.microsoft.com/en-us/azure/managed-grafana/overview) is a fully managed service for analytics and monitoring solutions. It's supported by Grafana Enterprise, which provides extensible data visualizations. This managed service allows to quickly and easily deploy Grafana dashboards with built-in high availability and control access with Azure security.
- Microsoft.OperationalInsights/workspaces: a centralized Azure Log Analytics workspace is used to collect the diagnostics logs and metrics from all the Azure resources:
- Azure Kubernetes Service cluster
- Azure Key Vault
- Azure Network Security Group
- Azure Container Registry
- Azure Storage Account
- Microsoft.Resources/deploymentScripts: a deployment script is used to run the
install-front-door-end-to-end-tls.sh
Bash script which installs the httpbin web application via YAML templates and the following packages to the AKS cluster via Helm. For more information on deployment scripts, see Use deployment scripts in Bicep
NOTE
AKS nodes can be referenced in the load balancer backend pools by either their IP configuration (Azure Virtual Machine Scale Sets based membership) or by their IP address only. Utilizing the IP address based backend pool membership provides higher efficiencies when updating services and provisioning load balancers, especially at high node counts. Provisioning new clusters with IP based backend pools and converting existing clusters is now supported. When combined with NAT Gateway or user-defined routing egress types, provisioning of new nodes and services are more performant. Two different pool membership types are available:
nodeIPConfiguration
: legacy Virtual Machine Scale Sets IP configuration based pool membership typenodeIP
: IP-based membership typeAzure Private Link Service does not support Azure Load balancers configured to use with backend addresses set by (virtualNetwork, ipAddress) or (subnet, ipAddress). Hence,
nodeIP
backend pool type is not currently supported if you want to create Azure Private Link Service based on an AKS load balancer. For this reason, this project adopts thenodeIPConfiguration
membership type for the backend pools.
NOTE
At the end of the deployment, thedeploy.sh
performs additional steps to approve the Azure Private Link Service connection from Azure Front Door. For more information, see Secure your Origin with Private Link in Azure Front Door Premium. If you don't use thedeploy.sh
script to deploy the Bicep modules, you must approve the private endpoint connection before traffic can pass to the origin privately. You can approve private endpoint connections by using the Azure portal, Azure CLI, or Azure PowerShell. For more information, see Manage a Private Endpoint connection.
NOTE
You can find thearchitecture.vsdx
file used for the diagram under thevisio
folder.
The following diagram illustrates the steps involved in the message flow during deployment and runtime.
The deployment time steps are as follows:
- A security engineer generates a certificate for the custom domain used by the workload and saves it in an Azure Key Vault. You can obtain a valid certificate from a well-known certification authority (CA), or use a solution like Key Vault Acmebot to acquire a certificate from one of the following ACME v2 compliant Certification Authority:
- Let's Encrypt
- Buypass Go SSL
- ZeroSSL (Requires EAB Credentials)
- Google Trust Services (Requires EAB Credentials)
- SSL.com (Requires EAB Credentials)
- Entrust (Requires EAB Credentials)
- A platform engineer specifies the necessary information in the
main.bicepparams
Bicep parameters file and deploys the Bicep modules to create the Azure resources. This includes:- A prefix for the Azure resources
- The name and resource group of the existing Azure Key Vault that holds the TLS certificate for the workload hostname and Front Door custom domain.
- The name of the certificate in the Key Vault.
- The name and resource group of the DNS zone used for resolving the Front Door custom domain.
- The Deployment Script creates the NGINX Ingress Controller and a sample httpbin web application using Helm and YAML manifests. The script defines a
SecretProviderClass
that retrieves the TLS certificate from the specified Azure Key Vault using the user-defined managed identity of the Azure Key Vault provider for Secrets Store CSI Driver, and creates a Kubernetessecret
. Thedeployment
andingress
objects are configured to use the certificate stored in the Kubernetes secret. - A Front Door secret resource is used to manage and store the TLS certificate from the Azure Key Vault. This certificate is used by the custom domain associated with the Azure Front Door endpoint.
During runtime, the message flow for a request initiated by an external client application is as follows:
- The client application sends a request to the web application using its custom domain. The DNS zone associated with the custom domain uses a CNAME record to redirect the DNS query for the custom domain to the original hostname of the Azure Front Door endpoint.
- The request is sent to one of the Azure Front Door points-of-presence.
- Azure Front Door forwards the incoming request to the Azure Private Endpoint connected to the Azure Private Link Service used to expose the AKS-hosted workload.
- The request is sent to the Azure Private Link Service.
- The request is forwarded to the
kubernetes-internal
AKS internal load balancer. - The request is sent to one of the agent nodes hosting a pod of the NGINX Ingress Controller.
- The request is handled by one of the NGINX Ingress Controller replicas
- The NGINX Ingress Controller forwards the request to one of the workload pods.
Azure Front Door supports end-to-end TLS encryption to meet security and compliance requirements. TLS/SSL offload is employed, where the TLS connection is terminated at Azure Front Door, decrypting the traffic and re-encrypting it before forwarding it to the origin. When using the origin's public IP address, configuring HTTPS as the forwarding protocol is recommended for enhanced security. This ensures enforcement of end-to-end TLS encryption throughout the request processing from client to origin. Additionally, TLS/SSL offload is supported when deploying a private origin with Azure Front Door Premium via the Azure Private Link Service (PLS) feature. For more information, see End-to-end TLS with Azure Front Door.
When configuring custom domains in Azure Front Door, you have two options: using a custom domain equal to the original hostname of the workload or using a custom domain that differs from the original hostname. Using a custom domain equal to the original hostname provides the following advantages:
- Simplified configuration without additional DNS management.
- Maintenance of search engine optimization (SEO) benefits and branding consistency.
- Hostname and custom domain consistency across Front Door and the downstream workload.
- Need for a single certificate across the Azure Front Door resource and the workload.
For HTTPS connections in Azure Front Door, the origin must present a certificate from a valid CA, with a subject name matching the origin hostname. Front Door refuses the connection if the presented certificate lacks the appropriate subject name, resulting in an error for the client. Frontend TLS connections from the client to Azure Front Door can be enabled with a certificate managed by Azure Front Door or by using your own certificate.
Azure Front Door provides certificate autorotation for managed certificates. Managed certificates are automatically rotated within 90 days of expiry for Azure Front Door managed certificates and within 45 days for Azure Front Door Standard/Premium managed certificates. For custom TLS/SSL certificates, autorotation occurs within 3-4 days when a newer version is available in the key vault. It's possible to manually select a specific version for custom certificates, but autorotation is not supported in that case. The service principal for Front Door must have access to the key vault containing the certificate. The certificate rollout operation by Azure Front Door doesn't cause any downtime, as long as the certificate's subject name or subject alternate name (SAN) remains unchanged.
You can deploy the Bicep modules in the bicep
folder using the deploy.sh
Bash script in the same folder. Specify a value for the following parameters in the deploy.sh
script and main.parameters.json
parameters file before deploying the Bicep modules.
prefix
: specifies a prefix for all the Azure resources.authenticationType
: specifies the type of authentication when accessing the Virtual Machine.sshPublicKey
is the recommended value. Allowed values:sshPublicKey
andpassword
.vmAdminUsername
: specifies the name of the administrator account of the virtual machine.vmAdminPasswordOrKey
: specifies the SSH Key or password for the virtual machine.aksClusterSshPublicKey
: specifies the SSH Key or password for AKS cluster agent nodes.aadProfileAdminGroupObjectIDs
: when deploying an AKS cluster with Azure AD and Azure RBAC integration, this array parameter contains the list of Azure AD group object IDs that will have the admin role of the cluster.subdomain
: specifies the subdomain of the workload hostname. Make sure this corresponds to the common name on the TLS certificate. If the hostname isstore.test.com
, the subdomain should betest
.dnsZoneName
: specifies name of the Azure DNS Zone, for exampletest.com
.dnsZoneResourceGroupName
: specifies the nthe name of the resource group which contains the Azure DNS zone.namespace
: specifies the namespace of the workload.keyVaultName
: specifies the name of an existing Key Vault resource holding the TLS certificate.keyVaultResourceGroupName
: specifies the name of the resource group that contains the existing Key Vault resource.keyVaultCertificateName
: specifies the name of the existing TLS certificate in Azure Key Vault.secretProviderClassName
: specifies the name of theSecretProviderClass
.secretName
: specifies the name of the Kubernetes secret containing the TLS certificate.publicDnsZoneName
: specifies the name of the public DNS zone used by the managed NGINX Ingress Controller, when enabled.publicDnsZoneResourceGroupName
: specifies the resource group name of the public DNS zone used by the managed NGINX Ingress Controller, when enabled.
We suggest reading sensitive configuration data such as passwords or SSH keys from a pre-existing Azure Key Vault resource. For more information, see Create parameters files for Bicep deployment.
#!/bin/bash
# Template
template="main.bicep"
parameters="main.bicepparam"
# AKS cluster name
prefix="Babo"
aksName="${prefix}Aks"
validateTemplate=0
useWhatIf=0
update=1
deploy=1
installExtensions=0
# Name and location of the resource group for the Azure Kubernetes Service (AKS) cluster
resourceGroupName="${prefix}RG"
location="NorthEurope"
deploymentName="main"
# Subscription id, subscription name, and tenant id of the current subscription
subscriptionId=$(az account show --query id --output tsv)
subscriptionName=$(az account show --query name --output tsv)
tenantId=$(az account show --query tenantId --output tsv)
# Install aks-preview Azure extension
if [[ $installExtensions == 1 ]]; then
echo "Checking if [aks-preview] extension is already installed..."
az extension show --name aks-preview &>/dev/null
if [[ $? == 0 ]]; then
echo "[aks-preview] extension is already installed"
# Update the extension to make sure you have the latest version installed
echo "Updating [aks-preview] extension..."
az extension update --name aks-preview &>/dev/null
else
echo "[aks-preview] extension is not installed. Installing..."
# Install aks-preview extension
az extension add --name aks-preview 1>/dev/null
if [[ $? == 0 ]]; then
echo "[aks-preview] extension successfully installed"
else
echo "Failed to install [aks-preview] extension"
exit
fi
fi
# Registering AKS feature extensions
aksExtensions=(
"AzureServiceMeshPreview"
"AKS-KedaPreview"
"RunCommandPreview"
"EnableOIDCIssuerPreview"
"EnableWorkloadIdentityPreview"
"EnableImageCleanerPreview"
"AKS-VPAPreview"
)
ok=0
registeringExtensions=()
for aksExtension in ${aksExtensions[@]}; do
echo "Checking if [$aksExtension] extension is already registered..."
extension=$(az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}" --output tsv)
if [[ -z $extension ]]; then
echo "[$aksExtension] extension is not registered."
echo "Registering [$aksExtension] extension..."
az feature register \
--name $aksExtension \
--namespace Microsoft.ContainerService \
--only-show-errors
registeringExtensions+=("$aksExtension")
ok=1
else
echo "[$aksExtension] extension is already registered."
fi
done
echo $registeringExtensions
delay=1
for aksExtension in ${registeringExtensions[@]}; do
echo -n "Checking if [$aksExtension] extension is already registered..."
while true; do
extension=$(az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}" --output tsv)
if [[ -z $extension ]]; then
echo -n "."
sleep $delay
else
echo "."
break
fi
done
done
if [[ $ok == 1 ]]; then
echo "Refreshing the registration of the Microsoft.ContainerService resource provider..."
az provider register \
--namespace Microsoft.ContainerService \
--only-show-errors
echo "Microsoft.ContainerService resource provider registration successfully refreshed"
fi
fi
# Get the last Kubernetes version available in the region
kubernetesVersion=$(az aks get-versions \
--location $location \
--query "values[?isPreview==null].version | sort(@) | [-1]" \
--output tsv \
--only-show-errors)
if [[ -n $kubernetesVersion ]]; then
echo "Successfully retrieved the last Kubernetes version [$kubernetesVersion] supported by AKS in [$location] Azure region"
else
echo "Failed to retrieve the last Kubernetes version supported by AKS in [$location] Azure region"
exit
fi
# Check if the resource group already exists
echo "Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription..."
az group show \
--name $resourceGroupName \
--only-show-errors &>/dev/null
if [[ $? != 0 ]]; then
echo "No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription"
echo "Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription..."
# Create the resource group
az group create \
--name $resourceGroupName \
--location $location \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription"
else
echo "Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription"
exit
fi
else
echo "[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription"
fi
# Get the user principal name of the current user
echo "Retrieving the user principal name of the current user from the [$tenantId] Azure AD tenant..."
userPrincipalName=$(az account show \
--query user.name \
--output tsv \
--only-show-errors)
if [[ -n $userPrincipalName ]]; then
echo "[$userPrincipalName] user principal name successfully retrieved from the [$tenantId] Azure AD tenant"
else
echo "Failed to retrieve the user principal name of the current user from the [$tenantId] Azure AD tenant"
exit
fi
# Retrieve the objectId of the user in the Azure AD tenant used by AKS for user authentication
echo "Retrieving the objectId of the [$userPrincipalName] user principal name from the [$tenantId] Azure AD tenant..."
userObjectId=$(az ad user show \
--id $userPrincipalName \
--query id \
--output tsv \
--only-show-errors 2>/dev/null)
if [[ -n $userObjectId ]]; then
echo "[$userObjectId] objectId successfully retrieved for the [$userPrincipalName] user principal name"
else
echo "Failed to retrieve the objectId of the [$userPrincipalName] user principal name"
exit
fi
# Create AKS cluster if does not exist
echo "Checking if [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group..."
az aks show \
--name $aksName \
--resource-group $resourceGroupName \
--only-show-errors &>/dev/null
notExists=$?
if [[ $notExists != 0 || $update == 1 ]]; then
if [[ $notExists != 0 ]]; then
echo "No [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group"
else
echo "[$aksName] aks cluster already exists in the [$resourceGroupName] resource group. Updating the cluster..."
fi
# Validate the Bicep template
if [[ $validateTemplate == 1 ]]; then
if [[ $useWhatIf == 1 ]]; then
# Execute a deployment What-If operation at resource group scope.
echo "Previewing changes deployed by [$template] Bicep template..."
az deployment group what-if \
--only-show-errors \
--resource-group $resourceGroupName \
--template-file $template \
--parameters $parameters \
--parameters prefix=$prefix \
location=$location \
userId=$userObjectId \
aksClusterKubernetesVersion=$kubernetesVersion
if [[ $? == 0 ]]; then
echo "[$template] Bicep template validation succeeded"
else
echo "Failed to validate [$template] Bicep template"
exit
fi
else
# Validate the Bicep template
echo "Validating [$template] Bicep template..."
output=$(az deployment group validate \
--only-show-errors \
--resource-group $resourceGroupName \
--template-file $template \
--parameters $parameters \
--parameters prefix=$prefix \
location=$location \
userId=$userObjectId \
aksClusterKubernetesVersion=$kubernetesVersion)
if [[ $? == 0 ]]; then
echo "[$template] Bicep template validation succeeded"
else
echo "Failed to validate [$template] Bicep template"
echo $output
exit
fi
fi
fi
if [[ $deploy == 1 ]]; then
# Deploy the Bicep template
echo "Deploying [$template] Bicep template..."
az deployment group create \
--only-show-errors \
--resource-group $resourceGroupName \
--only-show-errors \
--template-file $template \
--parameters $parameters \
--parameters prefix=$prefix \
location=$location \
userId=$userObjectId \
aksClusterKubernetesVersion=$kubernetesVersion 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$template] Bicep template successfully provisioned"
else
echo "Failed to provision the [$template] Bicep template"
exit
fi
else
echo "Skipping the deployment of the [$template] Bicep template"
exit
fi
else
echo "[$aksName] aks cluster already exists in the [$resourceGroupName] resource group"
fi
# Retrieve the resource id of the AKS cluster
echo "Retrieving the resource id of the [$aksName] AKS cluster..."
aksClusterId=$(az aks show \
--name "$aksName" \
--resource-group "$resourceGroupName" \
--query id \
--output tsv \
--only-show-errors 2>/dev/null)
if [[ -n $aksClusterId ]]; then
echo "Resource id of the [$aksName] AKS cluster successfully retrieved"
else
echo "Failed to retrieve the resource id of the [$aksName] AKS cluster"
exit
fi
# Assign Azure Kubernetes Service RBAC Cluster Admin role to the current user
role="Azure Kubernetes Service RBAC Cluster Admin"
echo "Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster..."
current=$(az role assignment list \
--only-show-errors \
--assignee $userObjectId \
--scope $aksClusterId \
--query "[?roleDefinitionName=='$role'].roleDefinitionName" \
--output tsv 2>/dev/null)
if [[ $current == "Owner" ]] || [[ $current == "Contributor" ]] || [[ $current == "$role" ]]; then
echo "[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster"
else
echo "[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster"
echo "Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster..."
az role assignment create \
--role "$role" \
--assignee $userObjectId \
--scope $aksClusterId \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster"
else
echo "Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster"
exit
fi
fi
# Assign Azure Kubernetes Service Cluster Admin Role role to the current user
role="Azure Kubernetes Service Cluster Admin Role"
echo "Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster..."
current=$(az role assignment list \
--only-show-errors \
--assignee $userObjectId \
--scope $aksClusterId \
--query "[?roleDefinitionName=='$role'].roleDefinitionName" \
--output tsv 2>/dev/null)
if [[ $current == "Owner" ]] || [[ $current == "Contributor" ]] || [[ $current == "$role" ]]; then
echo "[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster"
else
echo "[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster"
echo "Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster..."
az role assignment create \
--role "$role" \
--assignee $userObjectId \
--scope $aksClusterId \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster"
else
echo "Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster"
exit
fi
fi
# Get the FQDN of the Azure Front Door endpoint
azureFrontDoorEndpointFqdn=$(az deployment group show \
--name $deploymentName \
--resource-group $resourceGroupName \
--query properties.outputs.frontDoorEndpointFqdn.value \
--output tsv \
--only-show-errors)
if [[ -n $azureFrontDoorEndpointFqdn ]]; then
echo "FQDN of the Azure Front Door endpoint: $azureFrontDoorEndpointFqdn"
else
echo "Failed to get the FQDN of the Azure Front Door endpoint"
exit -1
fi
# Get the private link service name
privateLinkServiceName=$(az deployment group show \
--name $deploymentName \
--resource-group $resourceGroupName \
--query properties.outputs.privateLinkServiceName.value \
--output tsv \
--only-show-errors)
if [[ -z $privateLinkServiceName ]]; then
echo "Failed to get the private link service name"
exit -1
fi
# Get the resource id of the Private Endpoint Connection
privateEndpointConnectionId=$(az network private-endpoint-connection list \
--name $privateLinkServiceName \
--resource-group $resourceGroupName \
--type Microsoft.Network/privateLinkServices \
--query [0].id \
--output tsv \
--only-show-errors)
if [[ -n $privateEndpointConnectionId ]]; then
echo "Resource id of the Private Endpoint Connection: $privateEndpointConnectionId"
else
echo "Failed to get the resource id of the Private Endpoint Connection"
exit -1
fi
# Approve the private endpoint connection
echo "Approving [$privateEndpointConnectionId] private endpoint connection ID..."
az network private-endpoint-connection approve \
--name $privateLinkServiceName \
--resource-group $resourceGroupName \
--id $privateEndpointConnectionId \
--description "Approved" \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$privateEndpointConnectionId] private endpoint connection ID successfully approved"
else
echo "Failed to approve [$privateEndpointConnectionId] private endpoint connection ID"
exit -1
fi
The last steps of the Bash script perform the following actions:
- az deployment group show command: retrieves the name of the Azure Private Link Service from the outputs of the deployment.
- az network private-endpoint-connection list command: gets the resource id of the Azure Private Link Service.
- az network private-endpoint-connection approve approves the private endpoint connection from the Azure Front Door Premium resource.
If you miss running these steps, Azure Front Door cannot invoke the httpbin web application via the Azure Private Link Service, and the kubernetes-internal
internal load balancer of the AKS cluster.
The following table contains the code from the frontDoor.bicep
Bicep module used to deploy and configure Azure Front Door Premium.
// Parameters
@description('Specifies the name of the Azure Front Door.')
param frontDoorName string
@description('The name of the SKU to use when creating the Front Door profile.')
@allowed([
'Standard_AzureFrontDoor'
'Premium_AzureFrontDoor'
])
param frontDoorSkuName string = 'Premium_AzureFrontDoor'
@description('Specifies the send and receive timeout on forwarding request to the origin. When timeout is reached, the request fails and returns.')
param originResponseTimeoutSeconds int = 30
@description('Specifies the name of the Azure Front Door Origin Group for the web application.')
param originGroupName string
@description('Specifies the name of the Azure Front Door Origin for the web application.')
param originName string
@description('Specifies the address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported.This should be unique across all origins in an endpoint.')
param hostName string
@description('Specifies the value of the HTTP port. Must be between 1 and 65535.')
param httpPort int = 80
@description('Specifies the value of the HTTPS port. Must be between 1 and 65535.')
param httpsPort int = 443
@description('Specifies the host header value sent to the origin with each request. If you leave this blank, the request hostname determines this value. Azure Front Door origins, such as Web Apps, Blob Storage, and Cloud Services require this host header value to match the origin hostname by default. This overrides the host header defined at Endpoint.')
param originHostHeader string
@description('Specifies the priority of origin in given origin group for load balancing. Higher priorities will not be used for load balancing if any lower priority origin is healthy.Must be between 1 and 5.')
@minValue(1)
@maxValue(5)
param priority int = 1
@description('Specifies the weight of the origin in a given origin group for load balancing. Must be between 1 and 1000.')
@minValue(1)
@maxValue(1000)
param weight int = 1000
@description('Specifies whether to enable health probes to be made against backends defined under backendPools. Health probes can only be disabled if there is a single enabled backend in single enabled backend pool.')
@allowed([
'Enabled'
'Disabled'
])
param originEnabledState string = 'Enabled'
@description('Specifies the resource id of a private link service.')
param privateLinkResourceId string
@description('Specifies the number of samples to consider for load balancing decisions.')
param sampleSize int = 4
@description('Specifies the number of samples within the sample period that must succeed.')
param successfulSamplesRequired int = 3
@description('Specifies the additional latency in milliseconds for probes to fall into the lowest latency bucket.')
param additionalLatencyInMilliseconds int = 50
@description('Specifies path relative to the origin that is used to determine the health of the origin.')
param probePath string = '/'
@description('The custom domain name to associate with your Front Door endpoint.')
param customDomainName string
@description('Specifies the health probe request type.')
@allowed([
'GET'
'HEAD'
'NotSet'
])
param probeRequestType string = 'GET'
@description('Specifies the health probe protocol.')
@allowed([
'Http'
'Https'
'NotSet'
])
param probeProtocol string = 'Http'
@description('Specifies the number of seconds between health probes.Default is 240 seconds.')
param probeIntervalInSeconds int = 60
@description('Specifies whether to allow session affinity on this host. Valid options are Enabled or Disabled.')
@allowed([
'Enabled'
'Disabled'
])
param sessionAffinityState string = 'Disabled'
@description('Specifies the endpoint name reuse scope. The default value is TenantReuse.')
@allowed([
'NoReuse'
'ResourceGroupReuse'
'SubscriptionReuse'
'TenantReuse'
])
param autoGeneratedDomainNameLabelScope string = 'TenantReuse'
@description('Specifies the name of the Azure Front Door Route for the web application.')
param routeName string
@description('Specifies a directory path on the origin that Azure Front Door can use to retrieve content from, e.g. contoso.cloudapp.net/originpath.')
param originPath string = '/'
@description('Specifies the rule sets referenced by this endpoint.')
param ruleSets array = []
@description('Specifies the list of supported protocols for this route')
param supportedProtocols array = [
'Http'
'Https'
]
@description('Specifies the route patterns of the rule.')
param routePatternsToMatch array = [ '/*' ]
@description('Specifies the protocol this rule will use when forwarding traffic to backends.')
@allowed([
'HttpOnly'
'HttpsOnly'
'MatchRequest'
])
param forwardingProtocol string = 'HttpsOnly'
@description('Specifies whether this route will be linked to the default endpoint domain.')
@allowed([
'Enabled'
'Disabled'
])
param linkToDefaultDomain string = 'Enabled'
@description('Specifies whether to automatically redirect HTTP traffic to HTTPS traffic. Note that this is a easy way to set up this rule and it will be the first rule that gets executed.')
@allowed([
'Enabled'
'Disabled'
])
param httpsRedirect string = 'Enabled'
@description('Specifies the name of the Azure Front Door Endpoint for the web application.')
param endpointName string
@description('Specifies whether to enable use of this rule. Permitted values are Enabled or Disabled')
@allowed([
'Enabled'
'Disabled'
])
param endpointEnabledState string = 'Enabled'
@description('Specifies the name of the Azure Front Door WAF policy.')
param wafPolicyName string
@description('Specifies the WAF policy is in detection mode or prevention mode.')
@allowed([
'Detection'
'Prevention'
])
param wafPolicyMode string = 'Prevention'
@description('Specifies if the policy is in enabled or disabled state. Defaults to Enabled if not specified.')
param wafPolicyEnabledState string = 'Enabled'
@description('Specifies the list of managed rule sets to configure on the WAF.')
param wafManagedRuleSets array = []
@description('Specifies the list of custom rulesto configure on the WAF.')
param wafCustomRules array = []
@description('Specifies if the WAF policy managed rules will inspect the request body content.')
@allowed([
'Enabled'
'Disabled'
])
param wafPolicyRequestBodyCheck string = 'Enabled'
@description('Specifies name of the security policy.')
param securityPolicyName string
@description('Specifies the list of patterns to match by the security policy.')
param securityPolicyPatternsToMatch array = [ '/*' ]
@description('Specifies the resource id of the Log Analytics workspace.')
param workspaceId string
@description('Specifies the location.')
param location string = resourceGroup().location
@description('Specifies the resource tags.')
param tags object
@description('Specifies the name of the resource group that contains the key vault with custom domain\'s certificate.')
param keyVaultResourceGroupName string = resourceGroup().name
@description('Specifies the name of the Key Vault that contains the custom domain certificate.')
param keyVaultName string
@description('Specifies the name of the Key Vault secret that contains the custom domain certificate.')
param keyVaultCertificateName string
@description('Specifies the version of the Key Vault secret that contains the custom domain certificate. Set the value to an empty string to use the latest version.')
param keyVaultCertificateVersion string = ''
@description('Specifies the TLS protocol version that will be used for Https')
param minimumTlsVersion string = 'TLS12'
// Variables
var diagnosticSettingsName = 'diagnosticSettings'
var logCategories = [
'FrontDoorAccessLog'
'FrontDoorHealthProbeLog'
'FrontDoorWebApplicationFirewallLog'
]
var metricCategories = [
'AllMetrics'
]
var logs = [for category in logCategories: {
category: category
enabled: true
retentionPolicy: {
enabled: true
days: 0
}
}]
var metrics = [for category in metricCategories: {
category: category
enabled: true
retentionPolicy: {
enabled: true
days: 0
}
}]
// Resources
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
scope: resourceGroup(keyVaultResourceGroupName)
name: keyVaultName
resource secret 'secrets' existing = {
name: keyVaultCertificateName
}
}
resource frontDoor 'Microsoft.Cdn/profiles@2022-11-01-preview' = {
name: frontDoorName
location: 'Global'
tags: tags
sku: {
name: frontDoorSkuName
}
properties: {
originResponseTimeoutSeconds: originResponseTimeoutSeconds
}
}
resource originGroup 'Microsoft.Cdn/profiles/origingroups@2022-11-01-preview' = {
parent: frontDoor
name: originGroupName
properties: {
loadBalancingSettings: {
sampleSize: sampleSize
successfulSamplesRequired: successfulSamplesRequired
additionalLatencyInMilliseconds: additionalLatencyInMilliseconds
}
healthProbeSettings: {
probePath: probePath
probeRequestType: probeRequestType
probeProtocol: probeProtocol
probeIntervalInSeconds: probeIntervalInSeconds
}
sessionAffinityState: sessionAffinityState
}
}
resource origin 'Microsoft.Cdn/profiles/origingroups/origins@2022-11-01-preview' = {
parent: originGroup
name: originName
properties: {
hostName: hostName
httpPort: httpPort
httpsPort: httpsPort
originHostHeader: originHostHeader
priority: priority
weight: weight
enabledState: originEnabledState
sharedPrivateLinkResource: empty(privateLinkResourceId) ? {} : {
privateLink: {
id: privateLinkResourceId
}
privateLinkLocation: location
status: 'Approved'
requestMessage: 'Please approve this request to allow Front Door to access the container app'
}
enforceCertificateNameCheck: true
}
}
resource endpoint 'Microsoft.Cdn/profiles/afdEndpoints@2022-11-01-preview' = {
parent: frontDoor
name: endpointName
location: 'Global'
properties: {
autoGeneratedDomainNameLabelScope: toUpper(autoGeneratedDomainNameLabelScope)
enabledState: endpointEnabledState
}
}
resource route 'Microsoft.Cdn/profiles/afdEndpoints/routes@2022-11-01-preview' = {
parent: endpoint
name: routeName
properties: {
customDomains: [
{
id: customDomain.id
}
]
originGroup: {
id: originGroup.id
}
originPath: originPath
ruleSets: ruleSets
supportedProtocols: supportedProtocols
patternsToMatch: routePatternsToMatch
forwardingProtocol: forwardingProtocol
linkToDefaultDomain: linkToDefaultDomain
httpsRedirect: httpsRedirect
}
dependsOn: [
origin
]
}
resource secret 'Microsoft.Cdn/profiles/secrets@2023-07-01-preview' = {
name: toLower(format('{0}-{1}-latest', keyVaultName, keyVaultCertificateName))
parent: frontDoor
properties: {
parameters: {
type: 'CustomerCertificate'
useLatestVersion: (keyVaultCertificateVersion == '')
secretVersion: keyVaultCertificateVersion
secretSource: {
id: keyVault::secret.id
}
}
}
}
resource customDomain 'Microsoft.Cdn/profiles/customDomains@2023-07-01-preview' = {
name: replace(customDomainName, '.', '-')
parent: frontDoor
properties: {
hostName: customDomainName
tlsSettings: {
certificateType: 'CustomerCertificate'
minimumTlsVersion: minimumTlsVersion
secret: {
id: secret.id
}
}
}
}
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' = {
name: wafPolicyName
location: 'Global'
tags: tags
sku: {
name: frontDoorSkuName
}
properties: {
policySettings: {
enabledState: wafPolicyEnabledState
mode: wafPolicyMode
requestBodyCheck: wafPolicyRequestBodyCheck
}
managedRules: {
managedRuleSets: wafManagedRuleSets
}
customRules: {
rules: wafCustomRules
}
}
}
resource securityPolicy 'Microsoft.Cdn/profiles/securitypolicies@2022-11-01-preview' = {
parent: frontDoor
name: securityPolicyName
properties: {
parameters: {
type: 'WebApplicationFirewall'
wafPolicy: {
id: wafPolicy.id
}
associations: [
{
domains: [
{
id: endpoint.id
}
{
id: customDomain.id
}
]
patternsToMatch: securityPolicyPatternsToMatch
}
]
}
}
}
// Diagnostics Settings
resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
name: diagnosticSettingsName
scope: frontDoor
properties: {
workspaceId: workspaceId
logs: logs
metrics: metrics
}
}
// Outputs
output id string = frontDoor.id
output name string = frontDoor.name
output frontDoorEndpointFqdn string = endpoint.properties.hostName
output customDomainValidationDnsTxtRecordValue string = customDomain.properties.validationProperties.validationToken != null ? customDomain.properties.validationProperties.validationToken : ''
output customDomainValidationExpiry string = customDomain.properties.validationProperties.expirationDate
output customDomainDeploymentStatus string = customDomain.properties.deploymentStatus
output customDomainValidationState string = customDomain.properties.domainValidationState
The Bicep module creates the following resources:
- Azure Front Door origin group with the specified name (
originGroupName
). It includes load balancing settings and health probe settings. - Azure Front Door origin with the specified name (
originName
). It includes the origin's host name, HTTP and HTTPS ports, origin host header, priority, weight, enabled state, and any shared private link resource. - Azure Front Door endpoint with the specified name (
endpointName
). It includes the auto-generated domain name label scope and enabled state. - Azure Front Door route with the specified name (
routeName
). It includes the custom domains associated with the endpoint, origin group, origin path, rule sets, supported protocols, route patterns to match, forwarding protocol, link to default domain, and HTTPS redirect settings. - Key Vault secret with the custom domain certificate specified (
keyVaultCertificateName
) and the latest version of the certificate. - Azure Front Door custom domain with the specified name (
customDomainName
). It includes the custom domain host name, TLS settings with the customer certificate, and the Key Vault secret ID. - Azure Front Door WAF policy with the specified name (
wafPolicyName
). It includes the WAF policy settings, managed rule sets, and custom rules. In particular, one of the custom rules blocks incoming requests when they contain the wordblockme
in the query string. - Azure Front Door security policy with the specified name (
securityPolicyName
). It includes the security policy parameters, WAF policy association with the endpoint and custom domain, and patterns to match. - Diagnostic settings for Azure Front Door with the specified name (
diagnosticSettingsName
). It includes the workspace ID, enabled logs (FrontDoorAccessLog, FrontDoorHealthProbeLog, and FrontDoorWebApplicationFirewallLog), and enabled metrics (AllMetrics).
The module also defines several input parameters to customize the configuration, such as the Front Door name, SKU, origin group and origin names, origin details (hostname, ports, host header, etc.), custom domain name, routing settings, WAF policy details, security policy name, diagnostic settings, etc.
Finally, the module provides several output variables, including the Front Door ID and name, Front Door endpoint FQDN, custom domain validation DNS TXT record value, custom domain validation expiry date, custom domain deployment status, and custom domain validation state.
The sample makes use of a Deployment Script to run the install-front-door-end-to-end-tls.sh
Bash script which installs the httpbin web application via YAML templates and the following packages to the AKS cluster via Helm. For more information on deployment scripts, see Use deployment scripts in Bicep
# Install kubectl
az aks install-cli --only-show-errors
# Get AKS credentials
az aks get-credentials \
--admin \
--name $clusterName \
--resource-group $resourceGroupName \
--subscription $subscriptionId \
--only-show-errors
# Check if the cluster is private or not
private=$(az aks show --name $clusterName \
--resource-group $resourceGroupName \
--subscription $subscriptionId \
--query apiServerAccessProfile.enablePrivateCluster \
--output tsv)
# Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o get_helm.sh -s
chmod 700 get_helm.sh
./get_helm.sh &>/dev/null
# Add Helm repos
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add jetstack https://charts.jetstack.io
# Update Helm repos
helm repo update
# Install Prometheus
helm install prometheus prometheus-community/kube-prometheus-stack \
--create-namespace \
--namespace prometheus \
--set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
# Install NGINX ingress controller using the internal load balancer
helm install nginx-ingress ingress-nginx/ingress-nginx \
--create-namespace \
--namespace ingress-basic \
--set controller.replicaCount=3 \
--set controller.nodeSelector."kubernetes\.io/os"=linux \
--set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
--set controller.metrics.enabled=true \
--set controller.metrics.serviceMonitor.enabled=true \
--set controller.metrics.serviceMonitor.additionalLabels.release="prometheus" \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-internal"=true
# Install certificate manager
helm install cert-manager jetstack/cert-manager \
--create-namespace \
--namespace cert-manager \
--set installCRDs=true \
--set nodeSelector."kubernetes\.io/os"=linux
# Create cluster issuer
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-nginx
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: $email
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: nginx
podTemplate:
spec:
nodeSelector:
"kubernetes.io/os": linux
EOF
# Create a namespace for the application
kubectl create namespace $namespace
# Create the Secret Provider Class object
echo "Creating the [$secretProviderClassName] secret provider lass object in the [$namespace] namespace..."
cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: $secretProviderClassName
spec:
provider: azure
secretObjects:
- secretName: $secretName
type: kubernetes.io/tls
data:
- objectName: $keyVaultCertificateName
key: tls.key
- objectName: $keyVaultCertificateName
key: tls.crt
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: $clientId
keyvaultName: $keyVaultName
objects: |
array:
- |
objectName: $keyVaultCertificateName
objectType: secret
tenantId: $tenantId
EOF
# Create deployment and service in the namespace
echo "Creating the sample deployment and service in the [$namespace] namespace..."
cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 3
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: httpbin
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: httpbin
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: httpbin
image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
resources:
requests:
memory: "64Mi"
cpu: "125m"
limits:
memory: "128Mi"
cpu: "250m"
ports:
- containerPort: 80
env:
- name: PORT
value: "80"
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "$secretProviderClassName"
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: ClusterIP
selector:
app: httpbin
EOF
# Create an ingress resource for the application
echo "Creating an ingress in the [$namespace] namespace..."
cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpbin
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "360"
nginx.ingress.kubernetes.io/proxy-send-timeout: "360"
nginx.ingress.kubernetes.io/proxy-read-timeout: "360"
nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "360"
spec:
ingressClassName: nginx
tls:
- hosts:
- $hostname
secretName: $secretName
rules:
- host: $hostname
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: httpbin
port:
number: 80
EOF
# Create output as JSON file
echo '{}' |
jq --arg x 'prometheus' '.prometheus=$x' |
jq --arg x 'cert-manager' '.certManager=$x' |
jq --arg x 'ingress-basic' '.nginxIngressController=$x' >$AZ_SCRIPTS_OUTPUT_PATH
As you can note, when deploying the NGINX Ingress Controller via Helm, the service.beta.kubernetes.io/azure-load-balancer-internal to create the kubernetes-internal
internal load balancer in the node resource group of the AKS cluster and expose the ingress controller service via a private IP address.
The deployment script uses a SecretProviderClass
to retrieve the TLS certificate from Azure Key Vault and generate the Kubernetes secret for the ingress
object. The TLS certificate's common name must match the ingress
hostname and the Azure Front Door custom domain. The Secrets Store CSI Driver for Key Vault only creates the Kubernetes secret that contains the TLS certificate when the deployment
utilizing the SecretProviderClass
in a volume definition is created. For more information, see Set up Secrets Store CSI Driver to enable NGINX Ingress Controller with TLS.
The script uses YAML templates to create the deployment
and service
for the httpbin web application. You can mdofiy the script to install your own application. In particular, an ingress is used to expose the application via the NGINX Ingress Controller via the HTTPS protocol using the TLS certificate common name as a hostname. The ingress object can be easily modified to expose the server via HTTPS and provide a certificate for TLS termination.
If you want to replace the NGINX ingress controller installed via Helm by the deployment script with the managed version installed by the application routing addon, you can just replace the nginx
ingressClassName
in the ingress
object with the name of the ingress controller deployed by the application routing addon, that, by default is equal to webapprouting.kubernetes.azure.com
.
Azure Private Link Service (PLS) is an infrastructure component that allows users to privately connect via an Azure Private Endpoint (PE) in a virtual network in Azure and a Frontend IP Configuration associated with an internal or public Azure Load Balancer (ALB). With Private Link, users as service providers can securely provide their services to consumers who can connect from within Azure or on-premises without data exfiltration risks.
Before Private Link Service integration, users who wanted private connectivity from on-premises or other virtual networks to their services in an Azure Kubernetes Service(AKS) cluster were required to create a Private Link Service (PLS) to reference the cluster Azure Load Balancer, like in this sample. The user would then create an Azure Private Endpoint (PE) to connect to the PLS to enable private connectivity. With the Azure Private Link Service Integration feature, a managed Azure Private Link Service (PLS) to the AKS cluster load balancer can be created automatically, and the user would only be required to create Private Endpoint connections to it for private connectivity. You can expose a Kubernetes service via a Private Link Service using annotations. For more information, see Azure Private Link Service Integration.
Azure Private Link Service Integration simplifies the creation of a Azure Private Link Service (PLS) when deploying Kubernetes services or ingress controllers via a classic CI/CD pipeline using Azure DevOps, GitHub Actions, Jenkins, or GitLab, but even when using a GitOps approach with Argo CD or Flux v2.
For every workload that you expose via Azure Private Link Service (PLS) and Azure Front Door Premium, you need to create - Microsoft.Cdn/profiles/originGroups: an Origin Group, an Origin, endpoint, a route, and a security policy if you want to protect the workload with a WAF policy. You can accomplish this task using [az network front-door](az network front-door) Azure CLI commands in the CD pipeline used to deploy your service.
If the deployment succeeds, and the private endpoint connection from the Azure Front Door Premium instance to the Azure Private Link Service (PLS) is approved, you should be able to access the AKS-hosted httpbin web application as follows:
- Navigate to the overview page of your Front Door Premium in the Azure Portal and copy the URL from the Endpoint hostname.
- Paste and open the URL in your favorite internet browser. You should see the user interface of the httpbin application:
You can use the bicep/test.sh
Bash script to simulate a few attacks and see the managed rule set and custom rule of the Azure Web Application Firewall in action.
#!/bin/bash
# Variables
url="<Front Door Endpoint Hostname URL>"
# Call REST API
echo "Calling REST API..."
curl -I -s "$url"
# Simulate SQL injection
echo "Simulating SQL injection..."
curl -I -s "${url}?users=ExampleSQLInjection%27%20--"
# Simulate XSS
echo "Simulating XSS..."
curl -I -s "${url}?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E"
# A custom rule blocks any request with the word blockme in the querystring.
echo "Simulating query string manipulation with the 'attack' word in the query string..."
curl -I -s "${url}?task=blockme"
The Bash script should produce the following output, where the first call succeeds, while the remaining one are blocked by the WAF Policy configured in prevention mode.
Calling REST API...
HTTP/2 200
content-length: 9593
content-type: text/html; charset=utf-8
accept-ranges: bytes
vary: Accept-Encoding
access-control-allow-origin: *
access-control-allow-credentials: true
x-azure-ref: 05mwQZAAAAADma91JbmU0TJqRqS2lyFurTUlMMzBFREdFMDYwOQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=
x-cache: CONFIG_NOCACHE
date: Tue, 14 Mar 2023 12:47:33 GMT
Simulating SQL injection...
HTTP/2 403
x-azure-ref: 05mwQZAAAAABaQCSGQToQT4tifYGpmsTmTUlMMzBFREdFMDYxNQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=
date: Tue, 14 Mar 2023 12:47:34 GMT
Simulating XSS...
HTTP/2 403
x-azure-ref: 05mwQZAAAAAAJZzCrTmN4TLY+bZOxskzOTUlMMzBFREdFMDYxMwA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=
date: Tue, 14 Mar 2023 12:47:33 GMT
Simulating query string manipulation with the 'blockme' word in the query string...
HTTP/2 403
x-azure-ref: 05mwQZAAAAADAle0hOg4FTYH6Q1LHIP50TUlMMzBFREdFMDYyMAA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=
date: Tue, 14 Mar 2023 12:47:33 GMT
Front Door WAF Policies and Application Gateway WAF policies can be configured to run in the following two modes:
-
Detection mode
: When run in detection mode, WAF doesn't take any other actions other than monitors and logs the request and its matched WAF rule to WAF logs. You can turn on logging diagnostics for Front Door. When you use the portal, go to the Diagnostics section. -
Prevention mode
: In prevention mode, WAF takes the specified action if a request matches a rule. If a match is found, no further rules with lower priority are evaluated. Any matched requests are also logged in the WAF logs.
For more information, see Azure Web Application Firewall on Azure Front Door.
You can use the Azure portal or the Azure CLI to list the deployed resources in the resource group:
az resource list --resource-group <resource-group-name>
You can also use the following PowerShell cmdlet to list the deployed resources in the resource group:
Get-AzResource -ResourceGroupName <resource-group-name>
You can delete the resource group using the following Azure CLI command when you no longer need the resources you created. This will remove all the Azure resources.
az group delete --name <resource-group-name>
Alternatively, you can use the following PowerShell cmdlet to delete the resource group and all the Azure resources.
Remove-AzResourceGroup -Name <resource-group-name>