From AKS to ACA: Securing Private Azure Container Apps with Application Gateway and Bicep Automation

Categories: Azure CloudNative DigitalTransformation AzureContainerApps ApplicationGateway Bicep InfrastructureAsCode Devsecops
Tags: Azure CloudNative DigitalTransformation AzureContainerApps ApplicationGateway Bicep InfrastructureAsCode Devsecops
From AKS to ACA: Securing Private Azure Container Apps with Application Gateway and Bicep Automation
As enterprise architects and cloud consultants, our work often revolves around bridging the gap between strategic business objectives and robust technical implementations. A recent engagement with a client, undergoing significant digital transformation, perfectly illustrates this. Their challenge was a common one: how to securely expose private microservices, initially built on Azure Kubernetes Service (AKS), in a scalable and manageable way, ultimately aiming for the simplified operational model of Azure Container Apps.
From an Enterprise Architecture perspective, guided by principles like TOGAF, our goal was clear: establish a secure, performant, and highly automated application layer. This meant ensuring stringent security controls at the perimeter while providing seamless, reliable access to internal, privately deployed applications. The transition to Azure Container Apps was driven by a business case focused on reducing operational overhead and letting development teams focus on core business logic, embracing a serverless Kubernetes paradigm.
This blog post will delve into the technical solution: leveraging Azure Application Gateway to protect private Azure Container Apps, and crucially, how we achieved highly repeatable and user-friendly deployments using Bicep loops.
The Architecture: Private Container Apps, Public Gateway
The core architectural pattern involved deploying Azure Container Apps exclusively within a private Azure Virtual Network (VNet). This ensures they are not directly accessible from the public internet, significantly reducing their attack surface. To provide controlled public access, we positioned an Azure Application Gateway in a dedicated subnet within the same VNet.
Here’s the logical flow:
- External Request: A user’s request (e.g.,
clientA.companyAsolutions.com
) hits the Application Gateway’s public IP address. - Application Gateway Processing:
- SSL Termination: The Application Gateway handles SSL termination for the custom domain.
- WAF Inspection: The Web Application Firewall (WAF) inspects the request for common web vulnerabilities (e.g., SQL injection, XSS) before forwarding it.
- Host-Based Routing: Based on the incoming hostname (e.g.,
clientA.companyAsolutions.com
), the Application Gateway routes the traffic.
- Internal Routing: The request is forwarded to the correct, private Azure Container App using its internal FQDN (e.g.,
internal.mydomain.org
as seen in our Bicep parameters, typically resolved via Private DNS Zones). - Container App Response: The Container App processes the request and sends the response back through the Application Gateway to the user.
This pattern provides:
- Enhanced Security: Container Apps remain private, shielded by the Application Gateway’s WAF and centralized SSL. Access to the Application Gateway itself is managed via Microsoft Entra ID, RBAC, and granular roles, ensuring a tight security perimeter.
- Centralized Control: A single point for traffic management, monitoring, and security policy enforcement.
- Simplified DNS: External DNS records point only to the Application Gateway’s public IP.
- Scalability & Resiliency: Application Gateway provides built-in load balancing and high availability.
The Automation: Bicep Loops for Dynamic Configuration
The real challenge in managing such an architecture, especially for a client with numerous microservices, is maintaining consistency and reducing manual effort. This is where Bicep and its powerful looping constructs became indispensable.
Instead of writing repetitive code for each microservice’s configuration on the Application Gateway, we parameterized the solution using an array of objects. Each object in this array represents a distinct microservice (or Container App) and its specific requirements:
"containerApps": {
"type": "Array",
"value": [
{
"certName": "clientA-cert",
"customDomainName": "internal.mydomain.org", // Represents the internal FQDN of the Container App
"fqdn": "clientA.companyAsolutions.com", // Public FQDN exposed via AGW
"name": "clientA",
"probeHost": "clientA.companyAsolutions.com", // Host header for AGW health probes
"timeoutSeconds": 30
},
{
"certName": "clientB-cert",
"customDomainName": "internal.mydomain.org",
"fqdn": "clientB.companyAsolutions.com",
"name": "clientB",
"probeHost": "clientB.companyAsolutions.com",
"timeoutSeconds": 30
}
]
},
This containerApps
array is the driving force behind our Bicep deployment. We then used Bicep’s for
loops to dynamically provision the required Application Gateway components for each entry in this array.
Here’s how the Bicep template leveraged these loops to automate what would otherwise be a tedious and error-prone manual configuration:
Dynamic Application Gateway Configuration with Bicep Loops
-
HTTPS Listeners: For each
containerApp
definition, we dynamically create a dedicated HTTPS listener. This listener is configured with the public-facingfqdn
from our parameters (e.g.,clientA.companyAsolutions.com
). It then references the appropriate SSL certificate (certName
), which would typically be securely managed in Azure Key Vault and seamlessly linked to the Application Gateway.httpListeners: [for app in containerApps: { name: '${app.name}-listener' properties: { // ... (frontend IP, port, protocol are defined elsewhere) sslCertificate: { id: resourceId('Microsoft.Network/applicationGateways/sslCertificates', appGatewayName, app.certName) } hostNames: [ app.fqdn ] } }]
-
Backend Address Pools: We provision a unique backend address pool for every Container App. Since these apps reside privately within the VNet, their internal FQDNs (like
internal.mydomain.org
in our example, which resolves through Azure Private DNS Zones) are used as targets. This ensures all traffic remains securely within your virtual network boundary.backendAddressPools: [for app in containerApps: { name: '${app.name}-backendPool' properties: { backendAddresses: [ { fqdn: app.customDomainName // Assumes this is the internal FQDN of the Container App } ] } }]
-
HTTP Settings & Health Probes: Each backend pool is paired with corresponding HTTP settings. These define the protocol (HTTPS), port (usually 443), and, critically, link to a dynamically created health probe. The
probeHost
parameter in ourcontainerApps
array is vital here; it ensures the Application Gateway sends health checks with the correctHost
header, allowing accurate health assessment for each specific Container App instance. ThetimeoutSeconds
gives us granular control over how long the probe waits for a response.backendHttpSettingsCollection: [for app in containerApps: { name: '${app.name}-httpSettings' properties: { port: 443 protocol: 'Https' cookieBasedAffinity: 'Disabled' requestTimeout: app.timeoutSeconds probe: { id: resourceId('Microsoft.Network/applicationGateways/probes', appGatewayName, '${app.name}-healthProbe') } hostName: app.probeHost // Critical for specific host header on probes } }] probes: [for app in containerApps: { name: '${app.name}-healthProbe' properties: { protocol: 'Https' host: app.probeHost path: '/health' // Standard health endpoint, customize as needed interval: 30 timeout: app.timeoutSeconds unhealthyThreshold: 3 } }]
-
Request Routing Rules: Finally, routing rules act as the glue, connecting our listeners to their respective backend pools and HTTP settings. Each rule is configured with a basic rule type, ensuring incoming traffic is mapped to the correct backend based on the hostname.
requestRoutingRules: [for app in containerApps: { name: '${app.name}-routingRule' properties: { ruleType: 'Basic' httpListener: { id: resourceId('Microsoft.Network/applicationGateways/httpListeners', appGatewayName, '${app.name}-listener') } backendAddressPool: { id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', appGatewayName, '${app.name}-backendPool') } backendHttpSettings: { id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', appGatewayName, '${app.name}-httpSettings') } } }]
This looped approach dramatically simplifies the Bicep template. Adding a new microservice to be exposed merely involves extending the containerApps
parameter array, not writing dozens of lines of redundant Bicep code. This aligns perfectly with DevOps principles of automation and repeatability.
Application Gateway WAF: Best Practices for Robust Security
Beyond basic routing, the Application Gateway’s integrated Web Application Firewall (WAF) is a critical component for protecting web applications. For this solution, we implemented WAF in Prevention mode, leveraging the latest CRS (Core Rule Set) version. This is non-negotiable for enterprise-grade security.
Here are the Key WAF Best Practices we applied:
- Prevention Mode First: The WAF was configured to actively block detected threats rather than just logging them. This proactive stance is essential for immediate protection.
- Latest CRS Utilization: We always ensure the WAF uses the most current Core Rule Set (e.g., CRS 3.2 or newer) to guard against the latest known vulnerabilities identified by OWASP (like the OWASP Top 10). Staying updated is key.
- Targeted Custom Rules (Where Necessary): While not explicitly in the core Bicep output, in real-world production systems, custom WAF rules are vital. These are designed to address specific application vulnerabilities or fine-tune behavior, helping to reduce false positives without compromising security.
- Precision Exclusion Lists: We carefully configure exclusion lists for legitimate traffic patterns that might otherwise trigger WAF rules (e.g., specific HTTP headers or request attributes). This prevents blocking valid user requests, ensuring a smooth user experience.
- Comprehensive Logging and Monitoring: All WAF logs are integrated with Azure Monitor and Azure Sentinel. This enables real-time threat detection, detailed analysis, and automated alerting, which are crucial for a proactive cloud security posture.
- Centralized Policy Attachment: The WAF policy is centrally managed and attached to the Application Gateway, enforcing consistent security across all exposed Container Apps without requiring individual app-level configurations.
This comprehensive WAF configuration provides a strong first line of defense, mitigating common web attacks before they even reach the private Container Apps.
Navigating Container Apps Limitations
It’s important for seasoned practitioners to acknowledge that while Azure Container Apps offers significant benefits in terms of developer experience and operational simplicity compared to AKS, it does have known limitations in certain advanced, configMap, networking or traffic management scenarios. For instance, highly complex path-based routing, configMap features might be more straightforward in a full AKS environment.
However, our approach effectively worked around these limitations by offloading much of that complexity to the robust and feature-rich Azure Application Gateway. This allowed us to:
- Handle diverse custom domains seamlessly.
- Implement granular health probes tailored to each microservice’s endpoint.
- Centralize WAF protection and SSL management.
This demonstrates that by strategically combining Azure services, we can achieve enterprise-grade capabilities even when individual platform features might have specific constraints. It’s about understanding the platform’s capabilities and knowing how to build a complete, resilient solution.
Conclusion: A Blueprint for Cloud-Native Enterprise Adoption
This architectural blueprint, automated with Bicep loops, offers a powerful solution for organizations adopting Azure Container Apps for their microservices architecture. It provides the necessary security, scalability, and operational efficiency critical for digital transformation initiatives.
For our client, it meant shedding the overhead of self-managed Kubernetes and gaining valuable engineering focus. For us, it’s another example of how mialdo.com delivers tangible, high-impact cloud solutions that align technology with core business objectives. This pattern ensures that while the Container Apps remain agile and focused on application logic, the ingress and security layers are robust, centralized, and automated.
If your organization is navigating similar cloud migrations or looking to refine your Azure architecture, mialdo.com brings the field experience to turn these complexities into streamlined, secure, and performant realities.
#Azure #CloudNative #DigitalTransformation #AzureContainerApps #ApplicationGateway #Bicep #InfrastructureAsCode #CloudSecurity #Microservices #Serverless #EnterpriseArchitecture #MialdoSolutions #CloudConsulting #AzureArchitecture #DevOps #CloudMigration #ITTransformation #CloudStrategist #AzureExpert #WAF #MicrosoftEntraID #RBAC #CloudIdentity #NetworkSecurity