Network Architecture
📝 Updated (2025-10-24): This document has been updated to reflect current architecture: - Azure Bastion + Jump Box VM (replaces VPN Gateway) - ADR-050 - ACI Container Group (replaces Container Apps) - ADR-049 - Semantic layer names (Foundation, Substrate, AI Models, Apps) - ADR-052
Complete network design for Loan Defenders Azure deployment
Table of Contents
- Overview
- VNet Design
- Subnet Allocation
- CIDR Strategy
- Network Security Groups (NSGs)
- Private Endpoints
- Azure Bastion (Dev Environment)
- DNS Configuration
- Network Flow
- Best Practices
- Related Architecture Decisions
Overview
Loan Defenders uses a secure, private network architecture based on Azure VNet with multiple subnets for isolation, Network Security Groups for traffic control, and private endpoints for secure Azure service access.
Key Principles: - Zero Trust network architecture - Private communication by default - Subnet-based isolation - NSG-based traffic control - No public endpoints for backend services
Related Documentation: - Azure Deployment Architecture - High-level system overview - Security Architecture - Security implementation details - Bastion Dev Access - Browser-based VNet access guide
VNet Design
Network Diagram
graph TB
subgraph "Azure VNet (10.0.0.0/16)"
subgraph "Compute Subnet (10.0.1.0/24)"
ACI[ACI Container Group<br/>UI + API + 3 MCP]
end
subgraph "Private Endpoints Subnet (10.0.2.0/24)"
PE1[Private Endpoint<br/>AI Foundry]
PE2[Private Endpoint<br/>ACR]
PE3[Private Endpoint<br/>Key Vault]
end
subgraph "Bastion Subnet (10.0.3.0/26)"
Bastion[Azure Bastion<br/>Dev Only]
JumpBox[Windows Jump Box VM<br/>Dev Only]
end
end
Internet[Internet] -->|HTTPS| ACI
Devs[Developers] -->|Browser RDP| Bastion
Bastion -->|RDP| JumpBox
JumpBox -->|Access| ACI
ACI -->|Private| PE1
ACI -->|Private| PE2
ACI -->|Private| PE3
style ACI fill:#4CAF50
style PE1 fill:#2196F3
style PE2 fill:#2196F3
style PE3 fill:#2196F3
style Bastion fill:#FF9800
style JumpBox fill:#FFC107
subgraph "Application Subnet (10.0.2.0/24)"
API[API Container App<br/>Internal Only]
end
subgraph "MCP Subnet (10.0.3.0/24)"
MCP1[MCP: App Verification<br/>Port 8010]
MCP2[MCP: Document Processing<br/>Port 8011]
MCP3[MCP: Financial Calc<br/>Port 8012]
end
subgraph "AI Subnet (10.0.4.0/24)"
AI[Azure AI Services<br/>Private Endpoint]
end
subgraph "Infrastructure Subnet (10.0.0.0/24)"
ACR[Container Registry<br/>Private]
KV[Key Vault<br/>Private]
end
subgraph "Bastion Subnet (10.0.5.0/26)"
Bastion[Azure Bastion + Jump Box]
end
end
Users[Internet Users] -->|HTTPS| UI
UI -->|Internal| API
API -->|Internal| MCP1
API -->|Internal| MCP2
API -->|Internal| MCP3
API -->|Private Endpoint| AI
API -->|Private Endpoint| ACR
API -->|Private Endpoint| KV
Devs[Developers] -.->|Browser RDP| Bastion
Bastion -.->|Jump Box → VNet| API
style UI fill:#90EE90
style API fill:#FFB6C1
style MCP1 fill:#FFD700
style MCP2 fill:#FFD700
style MCP3 fill:#FFD700
style AI fill:#87CEEB
style Bastion fill:#DDA0DD
```
VNet Configuration
Address Space: 10.0.0.0/16 (65,536 addresses)
Region: Same as resource group (e.g., eastus)
DNS: Azure-provided DNS with custom DNS for private endpoints
Subnet Allocation
| Subnet | CIDR | Addresses | Purpose | NSG |
|---|---|---|---|---|
| Infrastructure | 10.0.0.0/24 | 256 | ACR, Key Vault, Managed Identities | ✅ Yes |
| Public | 10.0.1.0/24 | 256 | UI Container App (public ingress) | ✅ Yes |
| Application | 10.0.2.0/24 | 256 | API Container App (internal only) | ✅ Yes |
| MCP Services | 10.0.3.0/24 | 256 | 3 MCP Server Container Apps | ✅ Yes |
| AI Services | 10.0.4.0/24 | 256 | Azure AI Services private endpoint | ✅ Yes |
| Bastion | 10.0.5.0/26 | 64 | Azure Bastion + Jump Box | ❌ No (Azure managed) |
| Total Allocated: 1,312 addresses (2% of VNet) | ||||
| Available for Growth: 64,224 addresses (98% of VNet) | ||||
| --- | ||||
| ## CIDR Strategy | ||||
| ### Allocation Rationale | ||||
| Why /16 VNet? | ||||
| - Provides 65,536 addresses | ||||
| - Room for 256 /24 subnets | ||||
| - Future-proof for scaling | ||||
| - Standard enterprise practice | ||||
| Why /24 Subnets? | ||||
| - 256 addresses per subnet | ||||
| - Enough for auto-scaling (up to 100+ containers) | ||||
| - Standard subnet size for Azure | ||||
| - Easy to remember and manage | ||||
| Why /26 for Bastion? | ||||
| - Azure requirement for Gateway Subnet | ||||
| - Minimum recommended size | ||||
| - 32 addresses sufficient for VPN | ||||
| ### Reserved Addresses | ||||
| Azure reserves 5 addresses per subnet: | ||||
- x.x.x.0 - Network address |
||||
- x.x.x.1 - Default gateway |
||||
- x.x.x.2, x.x.x.3 - Azure DNS |
||||
- x.x.x.255 - Broadcast address |
||||
| Usable addresses per /24 subnet: 251 | ||||
| --- | ||||
| ## Network Security Groups (NSGs) | ||||
| ### NSG Rules per Subnet | ||||
| #### Infrastructure Subnet NSG | ||||
| Inbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-VNet-Inbound | VirtualNetwork | VirtualNetwork | * |
| 4096 | Deny-All-Inbound | * | * | * |
| Outbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-VNet-Outbound | VirtualNetwork | VirtualNetwork | * |
| 110 | Allow-Internet-Outbound | * | Internet | 443 |
| 4096 | Deny-All-Outbound | * | * | * |
| --- | ||||
| #### Public Subnet NSG | ||||
| Inbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-HTTPS-Inbound | Internet | * | 443 |
| 110 | Allow-VNet-Inbound | VirtualNetwork | VirtualNetwork | * |
| 4096 | Deny-All-Inbound | * | * | * |
| Outbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-VNet-Outbound | VirtualNetwork | VirtualNetwork | * |
| 110 | Allow-Internet-Outbound | * | Internet | 443 |
| 4096 | Deny-All-Outbound | * | * | * |
| --- | ||||
| #### Application & MCP Subnets NSG | ||||
| Inbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-VNet-Inbound | VirtualNetwork | VirtualNetwork | * |
| 4096 | Deny-All-Inbound | * | * | * |
| Outbound: | ||||
| Priority | Name | Source | Destination | Port |
| ---------- | ------ | -------- | ------------- | ------ |
| 100 | Allow-VNet-Outbound | VirtualNetwork | VirtualNetwork | * |
| 110 | Allow-HTTPS-Outbound | * | Internet | 443 |
| 4096 | Deny-All-Outbound | * | * | * |
| --- | ||||
| ## Private Endpoints | ||||
| ### Private Endpoint Configuration | ||||
| Services with Private Endpoints: | ||||
| 1. Azure Container Registry - Pull container images privately | ||||
| 2. Azure Key Vault - Access secrets privately | ||||
| 3. Azure AI Services - AI model inference privately | ||||
| 4. Azure Storage (if used) - Blob/file access privately | ||||
| ### Private DNS Zones | ||||
| Service | DNS Zone | Example Record | ||
| --------- | ---------- | ---------------- | ||
| Container Registry | privatelink.azurecr.io |
myacr.azurecr.io → 10.0.0.10 |
||
| Key Vault | privatelink.vaultcore.azure.net |
mykv.vault.azure.net → 10.0.0.11 |
||
| AI Services | privatelink.cognitiveservices.azure.com |
myai.cognitiveservices.azure.com → 10.0.4.10 |
||
| Storage Account | privatelink.blob.core.windows.net |
mysa.blob.core.windows.net → 10.0.0.12 |
||
| Configuration: | ||||
| - Private DNS zones automatically created by Bicep | ||||
| - Linked to VNet for resolution | ||||
| - A records point to private endpoint IPs | ||||
| - No public DNS exposure | ||||
| --- | ||||
| ## Azure Bastion (Dev Environment) | ||||
| > 📝 Updated (2025-10-24): VPN Gateway has been replaced by Azure Bastion + Windows Jump Box VM (ADR-050). | ||||
| > | ||||
| > Benefits: | ||||
| > - Browser-based RDP access (no VPN client installation) | ||||
| > - Lower cost (~$210/month vs ~$360/month for VPN) | ||||
| > - Faster provisioning (10 minutes vs 45 minutes) | ||||
| > - Windows environment for UI testing | ||||
| ### Bastion Architecture | ||||
| Azure Bastion: | ||||
| - Subnet: AzureBastionSubnet (10.0.3.0/26 - 64 addresses) | ||||
| - SKU: Standard | ||||
| - Access: Browser-based RDP from Azure Portal | ||||
| - Security: No public IP on Jump Box VM | ||||
| Windows Jump Box VM: | ||||
| - Image: Windows Server 2022 Datacenter | ||||
| - Size: Standard_D2s_v3 (2 vCPU, 8 GB RAM) | ||||
| - Network: Private subnet, no public IP | ||||
| - Access: Via Bastion only | ||||
| - Purpose: Browser access to ACI containers for UI testing | ||||
| ### Accessing VNet Resources | ||||
| 1. Navigate to Azure Portal | ||||
| 2. Go to Jump Box VM resource | ||||
| 3. Click Connect → Bastion | ||||
| 4. Enter credentials | ||||
| 5. Browser-based RDP session opens | ||||
| 6. From Jump Box, access containers via: | ||||
- UI: http://10.0.1.4:80 |
||||
- API: http://10.0.1.4:8000 |
||||
- MCP Servers: http://10.0.1.4:8010-8012 |
||||
| ### Cost Management | ||||
| Cost Optimization: | ||||
| ```bash | ||||
| # Stop VM when not in use (saves ~$70/month) | ||||
| az vm deallocate -g |
||||
| # Start VM when needed | ||||
| az vm start -g |
||||
| # Delete Bastion during extended breaks (saves ~$140/month, redeploys in 10 min) | ||||
| az network bastion delete -g |
||||
| ``` |
Monthly Costs: - Bastion: ~$140/month - Jump Box VM: ~$70/month (running), $0 (stopped) - Total: ~$210/month (vs ~$360/month for VPN Gateway)
See: ADR-050: Bastion Replaces VPN Gateway
Deprecated: VPN Gateway (Historical)
⚠️ DEPRECATED: VPN Gateway has been replaced by Azure Bastion + Jump Box VM.
This section is kept for historical reference. For current dev access, see Azure Bastion.
Historical VPN Gateway Documentation
### Overview **Purpose:** Secure developer access to internal resources **Type:** Point-to-Site (P2S) VPN **Deployed:** Dev environment only (production uses Azure Bastion or private connectivity) ### Configuration **Gateway Subnet:** `10.0.5.0/27` (32 addresses) **Gateway SKU:** VpnGw1 (Basic for dev, higher for prod) **VPN Type:** RouteBased **Client Address Pool:** `172.16.0.0/24` (256 client addresses) **Authentication:** Azure Active Directory (certificate-based alternative available) **Protocols:** OpenVPN (UDP 1194), IKEv2 **Tunnel Type:** Split tunnel (only Azure traffic goes through VPN) ### Cost **Dev Environment:** ~$140/month (VpnGw1 SKU) **Production:** ~$500-1,000/month (VpnGw2-4 SKUs with high availability) **Alternative:** Azure Bastion (~$140/month, no client required) **See:** [VPN Dev Access Guide](../deployment/vpn-dev-access.md) for setup instructions (DEPRECATED)DNS Configuration
Azure-Provided DNS
Default: 168.63.129.16 (Azure recursive resolvers)
Resolution: - Public Azure service FQDNs - Private endpoint FQDNs (via private DNS zones) - Internet domain names
Private DNS Zones
Automatic creation and linking:
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.azurecr.io'
location: 'global'
}
resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: 'vnet-link'
location: 'global'
properties: {
virtualNetwork: {
id: vnet.id
}
registrationEnabled: false
}
}
DNS Resolution Flow
graph LR
App[Container App] -->|Query: myacr.azurecr.io| AzureDNS[Azure DNS<br/>168.63.129.16]
AzureDNS -->|Check private DNS zones| PrivateDNS[Private DNS Zone<br/>privatelink.azurecr.io]
PrivateDNS -->|Return| PrivateIP[Private IP<br/>10.0.0.10]
PrivateIP --> App
App2[Container App] -->|Query: google.com| AzureDNS
AzureDNS -->|Forward to Internet DNS| PublicDNS[Public DNS]
PublicDNS -->|Return| PublicIP[Public IP]
PublicIP --> App2
Network Flow
External User → UI
Internet User (HTTPS)
↓
Azure Load Balancer (Public IP)
↓
UI Container App (10.0.1.x)
↓
Internal communication to API
UI → API → MCP
UI Container App (10.0.1.x)
↓ (Internal VNet)
API Container App (10.0.2.x)
↓ (Internal VNet)
MCP Container Apps (10.0.3.x)
↓ (Private Endpoint)
Azure AI Services (10.0.4.x)
Developer Access (Dev Environment)
Developer Laptop
↓ (OpenVPN/IKEv2)
VPN Gateway (10.0.5.x)
↓ (VNet routing)
Any internal resource (10.0.x.x)
Best Practices
Network Security
- Private by Default
- All backend services use internal communication
- Private endpoints for Azure services
-
No public IPs except UI load balancer
-
Least Privilege NSG Rules
- Deny all by default
- Allow only required traffic
-
Document every allow rule
-
Subnet Isolation
- Separate subnets per tier
- NSGs on every subnet
- No cross-tier communication except via API
Scalability
- Large VNet Address Space
- Use /16 or larger for VNet
- Allows future subnet additions
-
Room for scaling within subnets
-
Appropriately Sized Subnets
- /24 provides 251 usable addresses
- Enough for auto-scaling scenarios
-
Not wasteful of address space
-
Reserve Address Space
- Don't allocate all subnets initially
- Leave room for new services
- Plan for growth
Operations
- Consistent Naming
{prefix}-{env}-vnet{prefix}-{env}-subnet-{purpose}-
{prefix}-{env}-nsg-{subnet} -
Tag Everything
- Environment (dev, staging, prod)
- Cost center
- Owner/team
-
Application
-
Monitor Network
- Enable NSG flow logs
- Use Network Watcher
- Alert on unusual traffic
- Regular security reviews
Related Architecture Decisions
Network Design: - ADR-049: ACI vs Container Apps - Container platform decision - ADR-050: Bastion Replaces VPN Gateway - Dev access approach - ADR-052: Layer Renaming - Infrastructure organization
Security: - ADR-047: Layer-Specific RBAC - Access control architecture - ADR-048: Key Vault for Outputs - Deployment state management
Related Documentation
Architecture: - Azure Deployment Architecture - High-level overview - Security Architecture - Security implementation - 4-Layer Deployment Cake - Deployment layers
Operational Guides: - Bastion Dev Access - Browser-based VNet access - Direct Azure Deployment - Deployment instructions - GitHub CI/CD Deployment - Automated deployment
For deployment instructions, see: Direct Azure Deployment or GitHub CI/CD Deployment