From 2fb2d6c57c2212ab4ebc0d987fe71fd96ac5414d Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Fri, 10 Apr 2026 10:54:11 +0530 Subject: [PATCH 1/4] feat: restrict backend API to private access in WAF mode #39405 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../deploy.ingress.internal.yaml.template | 26 +++++++++++++ .../deploy.ingress.waf.yaml.template | 37 +++++++++++++++++++ Deployment/resourcedeployment.ps1 | 34 ++++++++++++++++- docs/DeploymentGuide.md | 25 ++++++++++++- infra/main.bicep | 5 ++- 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 Deployment/kubernetes/deploy.ingress.internal.yaml.template create mode 100644 Deployment/kubernetes/deploy.ingress.waf.yaml.template diff --git a/Deployment/kubernetes/deploy.ingress.internal.yaml.template b/Deployment/kubernetes/deploy.ingress.internal.yaml.template new file mode 100644 index 00000000..af973f23 --- /dev/null +++ b/Deployment/kubernetes/deploy.ingress.internal.yaml.template @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kmgs-ingress-internal + namespace: ns-km + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/send-timeout: "3600" + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: webapprouting.kubernetes.azure.com + rules: + - http: + paths: + - path: /backend(/|$)(.*) + pathType: Prefix + backend: + service: + name: aiservice-service + port: + number: 9001 diff --git a/Deployment/kubernetes/deploy.ingress.waf.yaml.template b/Deployment/kubernetes/deploy.ingress.waf.yaml.template new file mode 100644 index 00000000..65bf5009 --- /dev/null +++ b/Deployment/kubernetes/deploy.ingress.waf.yaml.template @@ -0,0 +1,37 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kmgs-ingress + namespace: ns-km + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/send-timeout: "3600" + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: webapprouting.kubernetes.azure.com + defaultBackend: + service: + name: frontapp-service + port: + number: 5900 + rules: + - host: {{ fqdn }} + http: + paths: + - path: /()(.*) + pathType: Prefix + backend: + service: + name: frontapp-service + port: + number: 5900 + tls: + - hosts: + - {{ fqdn }} + secretName: secret-kmgs diff --git a/Deployment/resourcedeployment.ps1 b/Deployment/resourcedeployment.ps1 index 24dbcfaf..e9e11560 100644 --- a/Deployment/resourcedeployment.ps1 +++ b/Deployment/resourcedeployment.ps1 @@ -551,6 +551,16 @@ try { Write-Host "Validation Completed" -ForegroundColor Green + # Detect WAF deployment mode from resource group tag (set by Bicep: Type = 'WAF' | 'Non-WAF') + $isWafDeployment = $false + $rgDeploymentType = az group show --name $deploymentResult.ResourceGroupName --query "tags.Type" -o tsv 2>$null + if ($rgDeploymentType -eq "WAF") { + $isWafDeployment = $true + Write-Host "WAF deployment mode detected. Backend APIs will be restricted to private access." -ForegroundColor Cyan + } else { + Write-Host "Non-WAF deployment mode detected. Standard deployment will be used." -ForegroundColor Yellow + } + # Step 1-3 Loading aiservice's configution file template then replace the placeholder with the actual values # Define the placeholders and their corresponding values for AI service configuration @@ -834,12 +844,19 @@ try { $certManagerTemplate | Set-Content -Path $certManagerPath -Force # 3.2 Update deploy.ingress.yaml.template file and save as deploy.ingress.yaml - # webfront / apibackend + # In WAF mode, use the WAF-specific template that only exposes the frontend publicly + # In non-WAF mode, use the standard template that exposes both frontend and backend $ingressPlaceholders = @{ '{{ fqdn }}' = $fqdn } - $ingressTemplate = Get-Content -Path .\kubernetes\deploy.ingress.yaml.template -Raw + if ($isWafDeployment) { + $ingressTemplatePath = ".\kubernetes\deploy.ingress.waf.yaml.template" + Write-Host "Using WAF ingress template (frontend-only public access)." -ForegroundColor Cyan + } else { + $ingressTemplatePath = ".\kubernetes\deploy.ingress.yaml.template" + } + $ingressTemplate = Get-Content -Path $ingressTemplatePath -Raw $ingress = Invoke-PlaceholdersReplacement $ingressTemplate $ingressPlaceholders $ingressPath = ".\kubernetes\deploy.ingress.yaml" $ingress | Set-Content -Path $ingressPath -Force @@ -1003,6 +1020,19 @@ try { # 5.5. Deploy Ingress Controller in Kubernetes for external access kubectl apply -f "./kubernetes/deploy.ingress.yaml" -n $kubenamespace + # 5.6. WAF Mode: Deploy internal backend ingress and network policies + if ($isWafDeployment) { + Write-Host "Applying WAF-specific configurations: internal backend ingress and network policies..." -ForegroundColor Cyan + + # Deploy internal ingress for backend services (no public exposure) + kubectl apply -f "./kubernetes/deploy.ingress.internal.yaml.template" -n $kubenamespace + + # Deploy network policies to restrict backend traffic to internal only + kubectl apply -f "./kubernetes/deploy.networkpolicy.yaml" -n $kubenamespace + + Write-Host "WAF network policies and internal backend ingress applied successfully." -ForegroundColor Green + } + # ##################################################################### # # Data file uploading # Show-Banner -Title "Step 9 : Sample Data Uploading to the Backend API - https://${fqdn}/backend/Documents/ImportDocument" diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index 658e63b6..81369509 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -119,6 +119,8 @@ Review the configuration options below. You can customize any settings that meet | **Use Case** | POCs, development, testing | Production workloads | | **Framework** | Basic configuration | [Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/) | | **Features** | Core functionality | Reliability, security, operational excellence | +| **Backend API Access** | Public (accessible via ingress) | Private (internal-only, not exposed to public internet) | +| **Network Policies** | None | Kubernetes NetworkPolicy isolates backend from external traffic | **To use production configuration:** @@ -142,7 +144,28 @@ azd env set AZURE_ENV_VM_ADMIN_USERNAME azd env set AZURE_ENV_VM_ADMIN_PASSWORD ``` -### 3.3 Advanced Configuration (Optional) +### 3.3 WAF Deployment: Network Architecture (Production Only) + +> **Note:** This section describes the networking architecture automatically configured when using the **Production** deployment type (WAF mode). + +When deploying with WAF configuration (`enablePrivateNetworking: true`), the following security measures are applied: + +- **AKS Private Cluster**: The AKS API server is configured as a private cluster, not accessible from the public internet. +- **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. +- **Internal Backend Ingress**: Backend API services (`aiservice`, `kernelmemory`) are accessible only through an internal ingress that is not exposed to the public internet. +- **Kubernetes Network Policies**: NetworkPolicy resources enforce traffic isolation — backend pods only accept traffic from frontend pods and the internal ingress controller within the cluster. +- **Private Endpoints**: All Azure PaaS services (Cosmos DB, Storage, Search, OpenAI, etc.) use private endpoints and are not accessible from the public internet. + +**Traffic Flow (WAF mode):** +``` +Internet → WAF/Application Gateway → Public Ingress → Frontend (frontapp) + ↓ (internal) + Backend (aiservice) → Azure PaaS (via Private Endpoints) + ↓ (internal) + Kernel Memory Service → Azure PaaS (via Private Endpoints) +``` + +### 3.4 Advanced Configuration (Optional)
Configurable Parameters diff --git a/infra/main.bicep b/infra/main.bicep index fa4f5c1c..33256c05 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -960,13 +960,14 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster:0.10. dnsPrefix: 'aks-${solutionSuffix}' enableRBAC: true disableLocalAccounts: false - publicNetworkAccess: 'Enabled' + // WAF aligned configuration for Private Networking + publicNetworkAccess: enablePrivateNetworking ? 'SecuredByPerimeter' : 'Enabled' managedIdentities: { systemAssigned: true } serviceCidr: '10.20.0.0/16' dnsServiceIP: '10.20.0.10' - enablePrivateCluster: false + enablePrivateCluster: enablePrivateNetworking primaryAgentPoolProfiles: [ { name: 'agentpool' From 7d44cb9456be921bb2acf857ad8c321c6c5dc3c6 Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Thu, 16 Apr 2026 10:09:04 +0530 Subject: [PATCH 2/4] feat: implement network policy to restrict backend API access and update DNS name handling in deployment script --- .../deploy.ingress.waf.yaml.template | 7 ++++ .../deploy.networkpolicy.yaml.template | 34 +++++++++++++++++++ Deployment/resourcedeployment.ps1 | 17 +++++++--- infra/main.bicep | 2 +- 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 Deployment/kubernetes/deploy.networkpolicy.yaml.template diff --git a/Deployment/kubernetes/deploy.ingress.waf.yaml.template b/Deployment/kubernetes/deploy.ingress.waf.yaml.template index 65bf5009..672e43f1 100644 --- a/Deployment/kubernetes/deploy.ingress.waf.yaml.template +++ b/Deployment/kubernetes/deploy.ingress.waf.yaml.template @@ -24,6 +24,13 @@ spec: - host: {{ fqdn }} http: paths: + - path: /backend(/|$)(.*) + pathType: Prefix + backend: + service: + name: aiservice-service + port: + number: 9001 - path: /()(.*) pathType: Prefix backend: diff --git a/Deployment/kubernetes/deploy.networkpolicy.yaml.template b/Deployment/kubernetes/deploy.networkpolicy.yaml.template new file mode 100644 index 00000000..0356b208 --- /dev/null +++ b/Deployment/kubernetes/deploy.networkpolicy.yaml.template @@ -0,0 +1,34 @@ +# NetworkPolicy to restrict backend services (aiservice, kernelmemory) to only +# accept traffic from within the cluster — frontend pods and internal ingress. +# This ensures backend APIs are not directly accessible from the public internet +# when WAF deployment mode is enabled. +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: deny-external-to-backend + namespace: ns-km +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - aiservice + - kernelmemory + policyTypes: + - Ingress + ingress: + # Allow traffic from frontend pods in the same namespace + - from: + - podSelector: + matchLabels: + app: frontapp + # Allow traffic from ingress controller namespace (app-routing-system) + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: app-routing-system + # Allow traffic from within the ns-km namespace (inter-service communication) + - from: + - podSelector: {} diff --git a/Deployment/resourcedeployment.ps1 b/Deployment/resourcedeployment.ps1 index e9e11560..942be033 100644 --- a/Deployment/resourcedeployment.ps1 +++ b/Deployment/resourcedeployment.ps1 @@ -739,14 +739,21 @@ try { # 6-1. Get Az Network resource Name with the public IP address Write-Host "Assign DNS Name to the public IP address" -ForegroundColor Green $publicIpName=$(az network public-ip list --resource-group $aksResourceGroupName --query "[?ipAddress=='$externalIP'].name" --output tsv) - # 6-2. Generate Unique backend API fqdn Name - esgdocanalysis-3 digit random number with padding 0 - $dnsName = "kmgs$($(Get-Random -Minimum 0 -Maximum 9999).ToString("D4"))" - - # Validate if the AKS Resource Group Name, Public IP name and DNS Name are provided + # 6-2. Reuse existing DNS name if already assigned, otherwise generate a new one + # Validate if the AKS Resource Group Name and Public IP name are provided ValidateVariableIsNullOrEmpty -variableValue $aksResourceGroupName -variableName "AKS Resource Group name" ValidateVariableIsNullOrEmpty -variableValue $publicIpName -variableName "Public IP name" + $existingDnsName = az network public-ip show --resource-group $aksResourceGroupName --name $publicIpName --query "dnsSettings.domainNameLabel" --output tsv 2>$null + if ($existingDnsName) { + Write-Host "Reusing existing DNS name: $existingDnsName" -ForegroundColor Yellow + $dnsName = $existingDnsName + } else { + $dnsName = "kmgs$($(Get-Random -Minimum 0 -Maximum 9999).ToString("D4"))" + Write-Host "Generated new DNS name: $dnsName" -ForegroundColor Green + } + ValidateVariableIsNullOrEmpty -variableValue $dnsName -variableName "DNS Name" # 6-3. Assign DNS Name to the public IP address @@ -1028,7 +1035,7 @@ try { kubectl apply -f "./kubernetes/deploy.ingress.internal.yaml.template" -n $kubenamespace # Deploy network policies to restrict backend traffic to internal only - kubectl apply -f "./kubernetes/deploy.networkpolicy.yaml" -n $kubenamespace + kubectl apply -f "./kubernetes/deploy.networkpolicy.yaml.template" -n $kubenamespace Write-Host "WAF network policies and internal backend ingress applied successfully." -ForegroundColor Green } diff --git a/infra/main.bicep b/infra/main.bicep index 33256c05..74589ba3 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -967,7 +967,7 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster:0.10. } serviceCidr: '10.20.0.0/16' dnsServiceIP: '10.20.0.10' - enablePrivateCluster: enablePrivateNetworking + enablePrivateCluster: false primaryAgentPoolProfiles: [ { name: 'agentpool' From 9ff3b69e45e23058f7bae6f84adc8b5ccd143d7c Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Fri, 17 Apr 2026 16:54:23 +0530 Subject: [PATCH 3/4] feat: enhance WAF deployment with private backend access, update ingress configurations, and improve deployment documentation --- .../documentViewer/iFrameComponent.tsx | 2 +- App/frontend-app/vite.config.ts | 44 +++++++++++++------ .../deploy.ingress.internal.yaml.template | 26 ----------- .../deploy.ingress.waf.yaml.template | 7 --- .../deploy.networkpolicy.yaml.template | 16 ++++--- Deployment/resourcedeployment.ps1 | 30 ++++++++----- docs/DeploymentGuide.md | 23 ++++++---- 7 files changed, 72 insertions(+), 76 deletions(-) delete mode 100644 Deployment/kubernetes/deploy.ingress.internal.yaml.template diff --git a/App/frontend-app/src/components/documentViewer/iFrameComponent.tsx b/App/frontend-app/src/components/documentViewer/iFrameComponent.tsx index 4a89f62b..58cf45d3 100644 --- a/App/frontend-app/src/components/documentViewer/iFrameComponent.tsx +++ b/App/frontend-app/src/components/documentViewer/iFrameComponent.tsx @@ -36,7 +36,7 @@ export function IFrameComponent({ className, metadata, urlWithSasToken, iframeKe ); } case "application/pdf": { - const url = new URL(urlWithSasToken); + const url = new URL(urlWithSasToken, window.location.origin); url.searchParams.append("embed", "True"); return