Security Architecture
Last Updated: 2025-10-25
Architecture: 4-Layer Deployment (Foundation → Substrate → AI Models → Apps)
Identity: Single User-Assigned Managed Identity (ADR-047)
Deployment: Azure Container Instances with VNet integration (ADR-049)
Access: Azure Bastion for secure browser-based remote access (ADR-050)
Overview
The Loan Defenders platform implements a defense-in-depth security strategy aligned with the Azure Well-Architected Framework's Security pillar. This document provides a comprehensive reference for all security controls, identity management, RBAC permissions, and access patterns across the 4-layer deployment architecture.
Security Principles
1. Single Managed Identity (ADR-047)
- ONE user-assigned managed identity used across all layers
- Assigned to all containers for unified authentication
- Simplifies RBAC management and eliminates timing issues
- No service principals, API keys, or passwords anywhere
2. Defense in Depth
- Multiple layers of security controls
- Network isolation with Azure VNet and NSGs
- Private endpoints for all PaaS services
- Application-level authentication via managed identity
- Data encryption in transit (TLS 1.2+) and at rest
3. Zero Trust Architecture
- Verify explicitly with managed identities and RBAC
- Assume breach with network segmentation
- Least privileged access - each resource gets only needed roles
- Azure Bastion for secure human access (no VPN client required)
4. RBAC Over Access Keys (ADR-047)
- No admin user accounts or access keys enabled
- All authentication via Azure RBAC built-in roles
- Resource-level scope (never subscription-level)
- Automatic credential rotation by Azure platform
Identity Architecture
Single Managed Identity Strategy (ADR-047)
The platform uses ONE user-assigned managed identity shared across all containers:
Managed Identity: {deploymentPrefix}-{environment}-identity
├── Created in: Foundation Layer (security.bicep)
├── Used by: All containers in Apps Layer (API, UI, 3× MCP servers)
│
├── Permissions Granted:
│ ├── ACR: AcrPull
│ ├── Storage: Storage Blob Data Contributor
│ ├── Key Vault: Key Vault Secrets Officer + Reader
│ ├── AI Services: Azure AI User + Cognitive Services User
│ ├── AI Foundry Hub: Azure AI Administrator
│ ├── AI Foundry Project: Azure AI Administrator
│ └── AI Search (optional): Search Index Data Contributor + Search Service Contributor
│
└── Eliminates:
❌ Service principals
❌ API keys or connection strings
❌ Admin user accounts
❌ RBAC timing issues
Why ONE User-Assigned Identity?
Per Azure Well-Architected Framework and ADR-047:
✅ Unified permissions: All containers need same Azure resource access
✅ Pre-authorization: RBAC assigned before containers start (no timing issues)
✅ Simplified management: 1 identity with 8 role assignments vs 5 identities with 40 assignments
✅ Better audit trail: Single identity for all application operations
✅ Faster deployments: No RBAC propagation delays
Result: - Before: Multiple system-assigned identities + complex RBAC + timing issues - After: 1 user-assigned identity + clean RBAC + zero timing issues
RBAC (Role-Based Access Control)
Overview
RBAC permissions are distributed across deployment layers using a module ownership pattern - each Bicep module assigns roles for the resources it creates. This keeps security controls co-located with resource definitions.
Layer-by-Layer RBAC Breakdown
Foundation Layer (foundation.bicep)
Module: security.bicep - Identity, Storage, Key Vault
File: infrastructure/bicep/modules/security.bicep
Resources Created: - User-Assigned Managed Identity - Storage Account (with private endpoints) - Key Vault (for deployment outputs per ADR-048)
RBAC Assignments:
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| Managed Identity | Storage Account | Storage Blob Data Contributor | Store application data, documents, audit logs |
| Managed Identity | Key Vault | Key Vault Secrets Officer | Full secret management |
| Managed Identity | Key Vault | Key Vault Reader | List vaults and metadata |
| Current User/SP | Key Vault | Key Vault Secrets Officer | Store deployment outputs (ADR-048) |
Module: bastion-vm.bicep - Secure Remote Access
File: infrastructure/bicep/modules/bastion-vm.bicep
Resources Created: - Azure Bastion (browser-based RDP/SSH) - Windows Jump Box VM
RBAC Assignments: - ⚠️ None - VM has no managed identity by design - Jump box is for human interactive access - Users authenticate with their own Azure AD credentials - See "Human Access" section below for details
Substrate Layer (substrate.bicep)
Module: container-registry.bicep - Container Image Storage
File: infrastructure/bicep/modules/container-registry.bicep
Resources Created: - Azure Container Registry (ACR)
RBAC Assignments:
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| Managed Identity | ACR | AcrPull | Pull container images |
Module: ai-services.bicep - AI Infrastructure
File: infrastructure/bicep/modules/ai-services.bicep
Resources Created: - Azure AI Services (multi-service Cognitive Services) - Azure AI Foundry Hub (with system-assigned identity) - Azure AI Foundry Project (with system-assigned identity) - Azure AI Search (optional, with system-assigned identity) - Log Analytics + Application Insights
RBAC Assignments - Managed Identity Access:
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| Managed Identity | AI Services | Azure AI User | Call OpenAI models |
| Managed Identity | AI Services | Cognitive Services User | General AI services access |
| Managed Identity | AI Foundry Hub | Azure AI Administrator | Manage AI Foundry Hub |
| Managed Identity | AI Foundry Project | Azure AI Administrator | Manage AI Foundry Project |
| Managed Identity | AI Search* | Search Index Data Contributor | Query search indexes |
| Managed Identity | AI Search* | Search Service Contributor | Manage search service |
*If deployAISearch = true
RBAC Assignments - AI Foundry Hub System Identity:
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| AI Hub Identity | AI Services | Cognitive Services Contributor | Model management |
| AI Hub Identity | Storage Account | Storage Blob Data Contributor | Store artifacts |
| AI Hub Identity | AI Search* | Search Service Contributor | Manage indexes |
| AI Hub Identity | AI Search* | Search Index Data Contributor | Index data access |
RBAC Assignments - AI Foundry Project System Identity:
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| AI Project Identity | AI Services | Cognitive Services OpenAI Contributor | Deploy models |
| AI Project Identity | Storage Account | Storage Blob Data Contributor | Store model files |
| AI Project Identity | AI Search* | Search Index Data Contributor | RAG scenarios |
RBAC Assignments - Developer Users (optional via developerPrincipalIds array):
| From Identity | To Resource | Role | Purpose |
|---|---|---|---|
| Developer Users | AI Foundry Hub | Azure AI Developer | AI Foundry portal access |
| Developer Users | AI Foundry Project | Azure AI Developer | AI Foundry portal access |
Apps Layer (apps.bicep)
Module: container-group-aci.bicep - Application Containers
File: infrastructure/bicep/modules/container-group-aci.bicep
Resources Created: - Azure Container Instances (ACI) container group containing: - API container - UI container - 3× MCP server containers
RBAC Assignments: - ✅ None needed - Containers use the managed identity created in Foundation layer - Container group configured with user-assigned managed identity - Inherits ALL permissions granted to the managed identity
Container Authentication Pattern:
# Containers automatically use assigned managed identity
from azure.identity import ManagedIdentityCredential
from azure.ai.inference import ChatCompletionsClient
credential = ManagedIdentityCredential()
client = ChatCompletionsClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=credential # Uses container's managed identity
)
Complete RBAC Summary Table
| Resource | Identity Type | Role | Module | Layer |
|---|---|---|---|---|
| Storage Account | User-Assigned Managed Identity | Storage Blob Data Contributor | security.bicep | Foundation |
| Key Vault | User-Assigned Managed Identity | Key Vault Secrets Officer + Reader | security.bicep | Foundation |
| Key Vault | Current User/SP | Key Vault Secrets Officer | security.bicep | Foundation |
| ACR | User-Assigned Managed Identity | AcrPull | container-registry.bicep | Substrate |
| AI Services | User-Assigned Managed Identity | Azure AI User + User | ai-services.bicep | Substrate |
| AI Foundry Hub | User-Assigned Managed Identity | Azure AI Administrator | ai-services.bicep | Substrate |
| AI Foundry Project | User-Assigned Managed Identity | Azure AI Administrator | ai-services.bicep | Substrate |
| AI Search (optional) | User-Assigned Managed Identity | Search Index/Service Contributor | ai-services.bicep | Substrate |
| AI Services | AI Hub System Identity | Cognitive Services Contributor | ai-services.bicep | Substrate |
| Storage | AI Hub System Identity | Storage Blob Data Contributor | ai-services.bicep | Substrate |
| AI Search (optional) | AI Hub System Identity | Search Service/Data Contributor | ai-services.bicep | Substrate |
| AI Services | AI Project System Identity | Cognitive Services OpenAI Contributor | ai-services.bicep | Substrate |
| Storage | AI Project System Identity | Storage Blob Data Contributor | ai-services.bicep | Substrate |
| AI Search (optional) | AI Project System Identity | Search Index Data Contributor | ai-services.bicep | Substrate |
| AI Hub | Developer Users (optional) | Azure AI Developer | ai-services.bicep | Substrate |
| AI Project | Developer Users (optional) | Azure AI Developer | ai-services.bicep | Substrate |
| ACI Containers | User-Assigned Managed Identity | (Inherits all above) | container-group-aci.bicep | Apps |
Azure Built-in Roles Reference
All roles are Azure built-in roles maintained by Microsoft:
| Role Name | Permission Level |
|---|---|
| AcrPull | Pull container images |
| Azure AI User | Call OpenAI models (inference only) |
| Cognitive Services User | General AI services access |
| Cognitive Services Contributor | Manage AI resources |
| Cognitive Services OpenAI Contributor | Deploy OpenAI models |
| Storage Blob Data Contributor | Read/write/delete blobs |
| Key Vault Secrets Officer | Manage secrets |
| Key Vault Reader | List vaults and metadata |
| Azure AI Administrator | Manage AI Foundry resources |
| Azure AI Developer | Access AI Foundry portal |
| Search Service Contributor | Manage search service |
| Search Index Data Contributor | Read/write search data |
Permission Scope Strategy
✅ All RBAC assignments use resource-level scope (not subscription-level):
// ✅ GOOD: Scoped to specific resource
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceId, principalId, roleId)
scope: specificResource // ← Resource-level scope
properties: {
principalId: managedIdentity.properties.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
}
}
// ❌ BAD: Subscription-level scope
resource badAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: subscription() // ← Too broad! Avoid this!
}
Benefits: - Least privilege principle enforced - Blast radius limited to specific resources - Easier security audits and compliance - Follows Azure Well-Architected Framework guidance
Network Security
VNet Architecture
{deploymentPrefix}-{environment}-vnet (10.0.0.0/16)
├── compute-subnet (10.0.0.0/23)
│ ├── ACI Container Group (VNet-integrated per ADR-049)
│ │ ├── API Container (port 8000)
│ │ ├── UI Container (port 3000)
│ │ ├── MCP Application Verification (port 8010)
│ │ ├── MCP Document Processing (port 8011)
│ │ └── MCP Financial Calculations (port 8012)
│ └── Jump Box VM (for Bastion access)
│
├── data-subnet (10.0.3.0/24)
│ ├── AI Services Private Endpoint
│ ├── AI Foundry Hub Private Endpoint
│ ├── Storage Account Private Endpoints (blob + file)
│ └── Key Vault Private Endpoint (future)
│
├── apim-subnet (10.0.2.0/24)
│ └── (Reserved for future API Management)
│
└── AzureBastionSubnet (10.0.4.0/26)
└── Azure Bastion (browser-based RDP/SSH)
Subnet Delegation
Compute Subnet:
- Delegated to: Microsoft.ContainerInstance/containerGroups (ADR-049)
- Purpose: Enables ACI VNet integration
- Service endpoints: Storage, Key Vault, Cognitive Services
Network Security Groups (NSGs)
Compute Subnet NSG: - Inbound: - Internal VNet traffic allowed (10.0.0.0/16) - External access blocked (containers are private) - Outbound: - Azure services via service endpoints - Internet access for package updates (optional)
Data Subnet NSG (Private Endpoints): - Inbound: - Traffic from compute subnet only - Outbound: - Restricted to Azure backbone
APIM Subnet NSG (Reserved): - Configured when API Management is deployed
Bastion Subnet NSG: - Managed by Azure (required NSG rules auto-configured) - Allows inbound HTTPS (443) for Bastion access - Allows outbound RDP/SSH to VNet resources
Private Endpoints & DNS
Private DNS Zones (auto-configured):
- privatelink.services.ai.azure.com (AI Foundry Hub)
- privatelink.cognitiveservices.azure.com (AI Services)
- privatelink.openai.azure.com (OpenAI API)
- privatelink.api.azureml.ms (AI Foundry management API)
- privatelink.notebooks.azure.net (AI Foundry UI)
- privatelink.blob.core.windows.net (Storage blob)
- privatelink.file.core.windows.net (Storage file)
Resolution: - VNet-linked DNS zones resolve private endpoints - Containers access Azure services via private IPs - No internet egress for PaaS service access
Container Network Access
ACI Container Group (ADR-049):
ipAddress: {
type: 'Private' // ✅ No public IP - VNet-only access
ports: [
{ port: 8000, protocol: 'TCP' } // API
{ port: 3000, protocol: 'TCP' } // UI
{ port: 8010, protocol: 'TCP' } // MCP App Verification
{ port: 8011, protocol: 'TCP' } // MCP Document Processing
{ port: 8012, protocol: 'TCP' } // MCP Financial Calculations
]
}
Access Patterns: - ✅ Containers → Azure Services: Via private endpoints (no internet) - ✅ Jump Box → Containers: Direct access via private IPs - ✅ Container-to-Container: Localhost (same container group) - ❌ Internet → Containers: Blocked (no public ingress)
Human Access Security
Azure Bastion + Jump Box (ADR-050)
Replaces VPN Gateway for $150/month savings and simpler developer experience.
Architecture:
Developer's Browser
↓ HTTPS (443)
Azure Bastion (managed PaaS)
↓ RDP/SSH via Private IP
Windows Jump Box VM (in compute-subnet)
↓ Private VNet access
Azure Resources (AI Foundry portal, containers, databases)
Jump Box Configuration: - OS: Windows Server 2022 Datacenter Azure Edition - Size: Standard_D2s_v3 (2 vCPU, 8GB RAM) - Network: Private IP in compute-subnet - Identity: ⚠️ None (by design - users authenticate as themselves) - Authentication: Azure AD credentials (no local accounts)
Why Jump Box Has NO Managed Identity
Design Decision (ADR-050): - Jump boxes are for interactive human access, not automation - Users should authenticate with their personal Azure AD credentials - Better audit trail (tracks individual user actions) - No risk of shared VM identity with excessive permissions
AI Foundry Portal Access from Jump Box
Prerequisites:
1. User must be in developerPrincipalIds array (Substrate deployment parameter)
2. User gets Azure AI Developer role on AI Foundry Hub/Project
Access Flow: 1. Navigate to Azure Portal → VM → Connect → Bastion 2. Enter Azure AD credentials (RDP into VM via browser) 3. Inside VM: Open browser (Edge/Chrome) 4. Navigate to https://ai.azure.com 5. Login with Azure AD credentials 6. Portal authenticates as the user (not the VM) 7. User sees AI Foundry resources based on their RBAC roles
What Works:
- ✅ AI Foundry portal access (ai.azure.com)
- ✅ Azure Portal access
- ✅ Interactive Azure CLI (az login with user credentials)
- ✅ Visual Studio Code with Azure extensions
- ✅ Access to container private IPs for testing
What Doesn't Work (and why it's okay):
- ❌ az login --identity (VM has no managed identity)
- ❌ Automated scripts using VM identity (use GitHub Actions instead)
- ❌ DefaultAzureCredential without user login (intentional security control)
Developer RBAC Requirements
To access AI Foundry portal from Jump Box or any machine:
# Find your Azure AD principal ID
az ad signed-in-user show --query id -o tsv
# Add to substrate deployment parameters
# File: infrastructure/bicep/environments/dev-substrate.parameters.json
{
"developerPrincipalIds": {
"value": [
"your-azure-ad-principal-id-guid"
]
}
}
This grants:
- Azure AI Developer role on AI Foundry Hub
- Azure AI Developer role on AI Foundry Project
- Full portal access to manage models and deployments
Bastion Security Features
Advantages over VPN: - ✅ No client-side VPN software required - ✅ Browser-based access (works on any device) - ✅ No VPN Gateway maintenance ($150/month saved) - ✅ Azure-managed security updates - ✅ Multi-factor authentication via Azure AD - ✅ Session recording (optional for compliance)
Security Controls: - Bastion managed by Azure (auto-patched) - Public IP for Bastion only (not VMs) - RDP/SSH traffic stays on Azure backbone - No direct VM public IPs - Integration with Azure AD Conditional Access
Data Protection
Encryption in Transit
External Communication: - Azure Bastion: HTTPS/TLS 1.3 (443)
Azure Service Communication: - AI Services API: HTTPS over private endpoint - Storage Account: HTTPS over private endpoint - Key Vault: HTTPS over private endpoint - ACR: HTTPS with managed identity auth
Internal VNet Communication: - Container-to-container: HTTP over localhost (same pod) - Jump Box to containers: HTTP over private VNet (isolated network) - Rationale: TLS overhead unnecessary within isolated VNet
Encryption at Rest
All Azure PaaS services use encryption at rest by default:
| Service | Encryption | Key Management |
|---|---|---|
| Storage Account | AES-256 | Microsoft-managed keys |
| ACR (images) | AES-256 | Microsoft-managed keys |
| AI Services | AES-256 | Microsoft-managed keys |
| AI Foundry | AES-256 | Microsoft-managed keys |
| Key Vault | FIPS 140-2 Level 2 | Azure Key Vault HSM |
| VM OS Disk | AES-256 | Microsoft-managed keys |
Sensitive Data Handling
PII Protection Rules:
✅ DO:
- Use applicant_id (UUID) in all MCP tool calls
- Encrypt loan application documents in storage
- Log access events without PII values
- Use private endpoints for all data transmission
❌ DON'T:
- Pass SSN to MCP servers (use applicant_id instead)
- Log full loan application JSON with PII
- Store plaintext sensitive data in environment variables
- Transmit PII over public internet
SSN Handling Example:
# ✅ CORRECT: Use UUID for tool calls
mcp_tool.verify_credit(applicant_id="550e8400-e29b-41d4-a716-446655440000")
# ❌ WRONG: Never pass SSN to tools
mcp_tool.verify_credit(ssn="123-45-6789") # DON'T DO THIS!
Audit Trail: - Application Insights logs all requests (without PII) - Storage access logs enabled (who/when, not what) - AI model calls logged (prompts redacted if containing PII) - Agent decisions logged with reasoning (for fair lending compliance)
Application Security
Container Security
Base Images: - Official Python 3.11+ images from Docker Hub - Regular security updates via automated builds - Minimal attack surface (no unnecessary packages)
Runtime Security:
# ✅ Run as non-root user
USER appuser
# ✅ Resource limits enforced by ACI
resources:
requests:
cpu: 1
memoryInGB: 2
limits:
cpu: 2
memoryInGB: 4
Image Pull Security (ADR-047): - ✅ Managed identity for ACR authentication - ✅ No registry credentials in config - ✅ Private ACR (publicNetworkAccess: Disabled for production) - ✅ AcrPull role scoped to specific registry
API Security
Authentication: - ✅ Service-to-Service: Managed identity with RBAC - ✅ Internal APIs: Network isolation (VNet-only access)
Authorization: - ✅ Network-level isolation (containers in private subnet) - ✅ RBAC for Azure resource access
Secrets Management
Current State: - ✅ NO secrets in environment variables - ✅ NO connection strings (managed identity for everything) - ✅ NO API keys (RBAC-based authentication) - ✅ Deployment outputs stored in Key Vault (ADR-048)
Key Vault Integration (when needed):
from azure.identity import ManagedIdentityCredential
from azure.keyvault.secrets import SecretClient
credential = ManagedIdentityCredential()
client = SecretClient(
vault_url=os.environ["KEY_VAULT_URI"],
credential=credential
)
# Retrieve secrets at runtime (never in env vars)
secret = client.get_secret("third-party-api-key")
Dependency Security
Supply Chain Security:
- uv.lock for Python dependency pinning
- package-lock.json for Node.js dependency pinning
- Automated Dependabot alerts via GitHub
Validation:
# Check for known vulnerabilities
uv pip check
npm audit
# Update dependencies
uv pip install --upgrade
npm update
Security Monitoring & Auditing
Current Monitoring (Implemented)
Application Insights: - ✅ Request tracking and performance monitoring - ✅ Error logging and exception tracking - ✅ Dependency call tracing (AI Services, Storage) - ✅ Custom event logging for loan processing workflow
Azure Monitor Logs: - ✅ Resource activity logs (RBAC changes, network events) - ✅ Storage access logs (blob operations) - ✅ AI Services request logs (model calls, token usage)
Log Analytics Workspace: - ✅ Centralized log aggregation - ✅ 30-day retention (configurable) - ✅ Kusto queries for security investigation
Audit Trail Requirements
For Fair Lending Compliance:
Tracked Events:
├── Loan application submission
├── Agent assessment decisions
│ ├── Credit agent reasoning
│ ├── Income agent verification
│ └── Risk agent final decision
├── LoanDecision with audit trail
├── Model deployment changes
└── RBAC permission modifications
NOT Logged (PII Protection):
├── Full SSN values
├── Bank account numbers
└── Detailed financial documents
Audit Log Format:
{
"timestamp": "2025-10-25T14:17:00Z",
"event": "loan_application_processed",
"application_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "approved",
"agents": ["intake", "credit", "income", "risk"],
"reasoning": "Strong credit profile, stable income, low DTI",
"model_version": "gpt-4o-2024-11-20",
"user_identity": "loan-officers-managed-identity",
"compliance_flags": []
}
Security Alerts
Azure Monitor Alerts (configured as needed): - Unauthorized ACR access attempts - Excessive failed AI Service calls - Storage account access anomalies - RBAC permission changes - Key Vault secret access spikes - Container restart failures
Microsoft Defender for Cloud (enabled per compliance requirements): - Container vulnerability alerts - Storage threat detection - Network anomaly detection - Security recommendations
Kusto Query Examples
Find all loan decisions for regulatory review:
traces
| where customDimensions.event == "loan_decision_final"
| where timestamp > ago(90d)
| project timestamp, application_id, decision, reasoning
| order by timestamp desc
Detect suspicious RBAC changes:
AzureActivity
| where OperationNameValue contains "roleAssignments/write"
| where ActivityStatusValue != "Success"
| project TimeGenerated, Caller, ResourceId, ActivityStatusValue
Monitor AI Service usage:
dependencies
| where target contains "openai.azure.com"
| summarize TotalCalls=count(), AvgDuration=avg(duration) by bin(timestamp, 1h)
| render timechart
Security Testing & Validation
Pre-Deployment Validation
Bicep Linting:
# Security misconfigurations check
az bicep lint --file infrastructure/bicep/foundation.bicep
az bicep lint --file infrastructure/bicep/substrate.bicep
az bicep lint --file infrastructure/bicep/apps.bicep
# Check for security warnings
grep -i "security\|rbac\|identity" bicep-lint-output.txt
RBAC Validation:
# Verify no subscription-level role assignments
az role assignment list --scope /subscriptions/<sub-id> --query "[?scope == '/subscriptions/<sub-id>']"
# Verify resource-level scoping
az role assignment list -g <rg-name> --query "[].{Principal:principalName, Role:roleDefinitionName, Scope:scope}"
Post-Deployment Security Checks
Identity Verification:
# Verify managed identity created
az identity show -n <identity-name> -g <rg-name> -o table
# Check identity assignments on containers
az container show -n <container-group-name> -g <rg-name> --query "identity"
RBAC Verification:
# List all RBAC assignments in resource group
az role assignment list -g <rg-name> -o table
# Verify specific role assignment
az role assignment list \
--assignee <identity-principal-id> \
--scope <resource-id> \
--query "[].roleDefinitionName"
Network Security Checks:
# Verify private endpoints created
az network private-endpoint list -g <rg-name> -o table
# Check NSG rules
az network nsg rule list -g <rg-name> --nsg-name <nsg-name> -o table
# Verify ACR public access disabled (production)
az acr show -n <acr-name> --query "publicNetworkAccess" -o tsv
# Should return: Disabled
Container Security:
# Verify containers using managed identity
az container show -n <container-group-name> -g <rg-name> \
--query "identity.type" -o tsv
# Should return: UserAssigned
# Check no public IP assigned
az container show -n <container-group-name> -g <rg-name> \
--query "ipAddress.type" -o tsv
# Should return: Private
Automated Security Validation Script
File: scripts/validate-security.sh (to be created)
#!/bin/bash
# Security validation checklist
echo "=== Security Validation ==="
# 1. Verify managed identity
echo "✓ Checking managed identity..."
az identity show -n $IDENTITY_NAME -g $RG_NAME > /dev/null
if [ $? -eq 0 ]; then
echo " ✅ Managed identity exists"
else
echo " ❌ Managed identity missing"
exit 1
fi
# 2. Verify RBAC assignments
echo "✓ Checking RBAC assignments..."
ROLE_COUNT=$(az role assignment list -g $RG_NAME --query "length([])")
if [ $ROLE_COUNT -gt 0 ]; then
echo " ✅ RBAC assignments configured ($ROLE_COUNT roles)"
else
echo " ❌ No RBAC assignments found"
exit 1
fi
# 3. Verify private endpoints
echo "✓ Checking private endpoints..."
PE_COUNT=$(az network private-endpoint list -g $RG_NAME --query "length([])")
if [ $PE_COUNT -gt 0 ]; then
echo " ✅ Private endpoints configured ($PE_COUNT endpoints)"
else
echo " ⚠️ No private endpoints found"
fi
# 4. Verify container security
echo "✓ Checking container security..."
IP_TYPE=$(az container show -n $CONTAINER_GROUP -g $RG_NAME --query "ipAddress.type" -o tsv)
if [ "$IP_TYPE" == "Private" ]; then
echo " ✅ Containers use private IP"
else
echo " ❌ Containers exposed publicly"
exit 1
fi
echo "=== All security checks passed ==="
Incident Response
Standard incident response procedures follow Azure security best practices:
- Detection: Azure Monitor alerts + Application Insights anomaly detection
- Isolation: Disable compromised identity via Azure Portal or Azure CLI
- Investigation: Review audit logs in Log Analytics, trace request flows
- Remediation: Rotate credentials (automatic for managed identities), patch vulnerabilities
- Post-mortem: Update security controls, document learnings in ADRs
Key Contacts: - Azure Support: Via Azure Portal (support.azure.com) - Security Team: Defined per organization
Compliance and Regulations
Fair Lending Compliance
ECOA (Equal Credit Opportunity Act): - ✅ Complete audit trails for all loan decisions - ✅ Agent reasoning stored in LoanDecision model - ✅ No discriminatory data (race, religion) in processing - ✅ Explainable AI with transparent decision logic
FCRA (Fair Credit Reporting Act): - ✅ Credit report access logged with applicant_id - ✅ Adverse action notices trackable - ✅ Consumer dispute handling via audit trail - ✅ Credit data retention policy (7 years)
GLBA (Gramm-Leach-Bliley Act): - ✅ PII encryption in transit (TLS 1.2+) and at rest (AES-256) - ✅ Access controls via RBAC (least privilege) - ✅ Audit logging for all data access
Data Retention Policies
| Data Type | Retention Period | Storage Location | Encryption |
|---|---|---|---|
| Loan Applications | 7 years | Azure Storage (blob) | AES-256 |
| Audit Logs | 7 years | Log Analytics | Microsoft-managed |
| AI Model Decisions | 7 years | Storage (JSON) | AES-256 |
| AI Model Versions | Indefinite | AI Foundry | AES-256 |
| Container Logs | 30 days | Application Insights | Microsoft-managed |
| RBAC Changes | 90 days | Azure Activity Log | Microsoft-managed |
Security Compliance Matrix
| Control | Requirement | Implementation | Status |
|---|---|---|---|
| Identity Management | Managed identities only | User-assigned managed identity | ✅ Implemented |
| Access Control | RBAC with least privilege | Resource-level RBAC | ✅ Implemented |
| Network Security | Private network isolation | VNet + Private Endpoints | ✅ Implemented |
| Data Encryption | TLS 1.2+ in transit | HTTPS/TLS 1.3 | ✅ Implemented |
| Data Encryption | AES-256 at rest | Azure platform default | ✅ Implemented |
| Audit Logging | All access tracked | Application Insights + Azure Monitor | ✅ Implemented |
| Secret Management | No plaintext secrets | Managed identity + Key Vault | ✅ Implemented |
| PII Protection | SSN never in logs | applicant_id UUID pattern | ✅ Implemented |
Future Security Enhancements
Authentication & Authorization
- Azure AD B2C for end-user authentication
- API-level RBAC (role-based loan officer permissions)
- Customer-managed encryption keys (CMK)
Network & Infrastructure
- Azure Application Gateway or API Management for public ingress
- Web Application Firewall (WAF)
- Azure Front Door with DDoS protection
- Azure Sentinel for SIEM
Monitoring & Threat Detection
- Microsoft Defender for Containers (vulnerability scanning)
- Automated security alerts (ACR access, RBAC changes, anomalies)
- Third-party penetration testing (annual)
- OWASP Top 10 validation
Compliance & Auditing
- Customer privacy notices (GLBA)
- Just-in-time (JIT) VM access
- Bastion session recording
- Automated compliance reporting
References
Architecture Decision Records (ADRs)
- ADR-047: Single Managed Identity - Single user-assigned identity for all containers
- ADR-048: Key Vault for Outputs - Cross-layer dependency management
- ADR-049: ACI vs Container Apps - Container deployment strategy
- ADR-050: Bastion vs VPN - Secure remote access approach
Microsoft Documentation
- Azure Well-Architected Framework - Security
- Managed Identity Best Practices
- RBAC Best Practices
- Azure Built-in Roles
- Container Instances Security
Last Updated: 2025-10-25
Review Cycle: Quarterly
Maintained By: Infrastructure Team + Security Team