Skip to content

Commit 2fb2d6c

Browse files
feat: restrict backend API to private access in WAF mode #39405
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 146be79 commit 2fb2d6c

5 files changed

Lines changed: 122 additions & 5 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: kmgs-ingress-internal
5+
namespace: ns-km
6+
annotations:
7+
nginx.ingress.kubernetes.io/proxy-body-size: "0"
8+
nginx.ingress.kubernetes.io/ssl-redirect: "false"
9+
nginx.ingress.kubernetes.io/use-regex: "true"
10+
nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600"
11+
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
12+
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
13+
nginx.ingress.kubernetes.io/send-timeout: "3600"
14+
nginx.ingress.kubernetes.io/rewrite-target: /$2
15+
spec:
16+
ingressClassName: webapprouting.kubernetes.azure.com
17+
rules:
18+
- http:
19+
paths:
20+
- path: /backend(/|$)(.*)
21+
pathType: Prefix
22+
backend:
23+
service:
24+
name: aiservice-service
25+
port:
26+
number: 9001
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: kmgs-ingress
5+
namespace: ns-km
6+
annotations:
7+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
8+
nginx.ingress.kubernetes.io/proxy-body-size: "0"
9+
nginx.ingress.kubernetes.io/ssl-redirect: "false"
10+
nginx.ingress.kubernetes.io/use-regex: "true"
11+
nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600"
12+
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
13+
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
14+
nginx.ingress.kubernetes.io/send-timeout: "3600"
15+
nginx.ingress.kubernetes.io/rewrite-target: /$2
16+
spec:
17+
ingressClassName: webapprouting.kubernetes.azure.com
18+
defaultBackend:
19+
service:
20+
name: frontapp-service
21+
port:
22+
number: 5900
23+
rules:
24+
- host: {{ fqdn }}
25+
http:
26+
paths:
27+
- path: /()(.*)
28+
pathType: Prefix
29+
backend:
30+
service:
31+
name: frontapp-service
32+
port:
33+
number: 5900
34+
tls:
35+
- hosts:
36+
- {{ fqdn }}
37+
secretName: secret-kmgs

Deployment/resourcedeployment.ps1

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,16 @@ try {
551551

552552
Write-Host "Validation Completed" -ForegroundColor Green
553553

554+
# Detect WAF deployment mode from resource group tag (set by Bicep: Type = 'WAF' | 'Non-WAF')
555+
$isWafDeployment = $false
556+
$rgDeploymentType = az group show --name $deploymentResult.ResourceGroupName --query "tags.Type" -o tsv 2>$null
557+
if ($rgDeploymentType -eq "WAF") {
558+
$isWafDeployment = $true
559+
Write-Host "WAF deployment mode detected. Backend APIs will be restricted to private access." -ForegroundColor Cyan
560+
} else {
561+
Write-Host "Non-WAF deployment mode detected. Standard deployment will be used." -ForegroundColor Yellow
562+
}
563+
554564
# Step 1-3 Loading aiservice's configution file template then replace the placeholder with the actual values
555565
# Define the placeholders and their corresponding values for AI service configuration
556566

@@ -834,12 +844,19 @@ try {
834844
$certManagerTemplate | Set-Content -Path $certManagerPath -Force
835845

836846
# 3.2 Update deploy.ingress.yaml.template file and save as deploy.ingress.yaml
837-
# webfront / apibackend
847+
# In WAF mode, use the WAF-specific template that only exposes the frontend publicly
848+
# In non-WAF mode, use the standard template that exposes both frontend and backend
838849
$ingressPlaceholders = @{
839850
'{{ fqdn }}' = $fqdn
840851
}
841852

842-
$ingressTemplate = Get-Content -Path .\kubernetes\deploy.ingress.yaml.template -Raw
853+
if ($isWafDeployment) {
854+
$ingressTemplatePath = ".\kubernetes\deploy.ingress.waf.yaml.template"
855+
Write-Host "Using WAF ingress template (frontend-only public access)." -ForegroundColor Cyan
856+
} else {
857+
$ingressTemplatePath = ".\kubernetes\deploy.ingress.yaml.template"
858+
}
859+
$ingressTemplate = Get-Content -Path $ingressTemplatePath -Raw
843860
$ingress = Invoke-PlaceholdersReplacement $ingressTemplate $ingressPlaceholders
844861
$ingressPath = ".\kubernetes\deploy.ingress.yaml"
845862
$ingress | Set-Content -Path $ingressPath -Force
@@ -1003,6 +1020,19 @@ try {
10031020
# 5.5. Deploy Ingress Controller in Kubernetes for external access
10041021
kubectl apply -f "./kubernetes/deploy.ingress.yaml" -n $kubenamespace
10051022

1023+
# 5.6. WAF Mode: Deploy internal backend ingress and network policies
1024+
if ($isWafDeployment) {
1025+
Write-Host "Applying WAF-specific configurations: internal backend ingress and network policies..." -ForegroundColor Cyan
1026+
1027+
# Deploy internal ingress for backend services (no public exposure)
1028+
kubectl apply -f "./kubernetes/deploy.ingress.internal.yaml.template" -n $kubenamespace
1029+
1030+
# Deploy network policies to restrict backend traffic to internal only
1031+
kubectl apply -f "./kubernetes/deploy.networkpolicy.yaml" -n $kubenamespace
1032+
1033+
Write-Host "WAF network policies and internal backend ingress applied successfully." -ForegroundColor Green
1034+
}
1035+
10061036
# #####################################################################
10071037
# # Data file uploading
10081038
# Show-Banner -Title "Step 9 : Sample Data Uploading to the Backend API - https://${fqdn}/backend/Documents/ImportDocument"

docs/DeploymentGuide.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ Review the configuration options below. You can customize any settings that meet
119119
| **Use Case** | POCs, development, testing | Production workloads |
120120
| **Framework** | Basic configuration | [Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/) |
121121
| **Features** | Core functionality | Reliability, security, operational excellence |
122+
| **Backend API Access** | Public (accessible via ingress) | Private (internal-only, not exposed to public internet) |
123+
| **Network Policies** | None | Kubernetes NetworkPolicy isolates backend from external traffic |
122124

123125
**To use production configuration:**
124126

@@ -142,7 +144,28 @@ azd env set AZURE_ENV_VM_ADMIN_USERNAME <your-username>
142144
azd env set AZURE_ENV_VM_ADMIN_PASSWORD <your-password>
143145
```
144146

145-
### 3.3 Advanced Configuration (Optional)
147+
### 3.3 WAF Deployment: Network Architecture (Production Only)
148+
149+
> **Note:** This section describes the networking architecture automatically configured when using the **Production** deployment type (WAF mode).
150+
151+
When deploying with WAF configuration (`enablePrivateNetworking: true`), the following security measures are applied:
152+
153+
- **AKS Private Cluster**: The AKS API server is configured as a private cluster, not accessible from the public internet.
154+
- **Frontend-Only Public Ingress**: Only the frontend web application is exposed publicly through the WAF/Application Gateway ingress. The `/backend` API route is removed from the public ingress.
155+
- **Internal Backend Ingress**: Backend API services (`aiservice`, `kernelmemory`) are accessible only through an internal ingress that is not exposed to the public internet.
156+
- **Kubernetes Network Policies**: NetworkPolicy resources enforce traffic isolation — backend pods only accept traffic from frontend pods and the internal ingress controller within the cluster.
157+
- **Private Endpoints**: All Azure PaaS services (Cosmos DB, Storage, Search, OpenAI, etc.) use private endpoints and are not accessible from the public internet.
158+
159+
**Traffic Flow (WAF mode):**
160+
```
161+
Internet → WAF/Application Gateway → Public Ingress → Frontend (frontapp)
162+
↓ (internal)
163+
Backend (aiservice) → Azure PaaS (via Private Endpoints)
164+
↓ (internal)
165+
Kernel Memory Service → Azure PaaS (via Private Endpoints)
166+
```
167+
168+
### 3.4 Advanced Configuration (Optional)
146169

147170
<details>
148171
<summary><b>Configurable Parameters</b></summary>

infra/main.bicep

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,13 +960,14 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster:0.10.
960960
dnsPrefix: 'aks-${solutionSuffix}'
961961
enableRBAC: true
962962
disableLocalAccounts: false
963-
publicNetworkAccess: 'Enabled'
963+
// WAF aligned configuration for Private Networking
964+
publicNetworkAccess: enablePrivateNetworking ? 'SecuredByPerimeter' : 'Enabled'
964965
managedIdentities: {
965966
systemAssigned: true
966967
}
967968
serviceCidr: '10.20.0.0/16'
968969
dnsServiceIP: '10.20.0.10'
969-
enablePrivateCluster: false
970+
enablePrivateCluster: enablePrivateNetworking
970971
primaryAgentPoolProfiles: [
971972
{
972973
name: 'agentpool'

0 commit comments

Comments
 (0)