-
Notifications
You must be signed in to change notification settings - Fork 95
Security Model
The security model is built around private-first access, explicit public exposure, layered network controls, CI policy gates, and guarded Terraform state.
Important source files:
- variables.tf
- landing-zones/security
- landing-zones/management/core
- landing-zones/management/workload
- policies/security.rego
- policies/terraform.rego
| Principle | Repository implementation |
|---|---|
| No broad public management by default | RDP CIDR validation and Load Balancer RDP NAT disabled |
| Private data plane first | Private Endpoints and Private DNS for Key Vault, Storage, SQL |
| Explicit public exposure | App Gateway, Load Balancer, VPN, NAT, and jumpbox public IP are toggles |
| Central inspection | Azure Firewall and route tables available when enabled |
| Least privilege | Custom RBAC roles available through governance module |
| Policy as code | OPA checks and Azure Policy module |
| Observable controls | Diagnostics and alerts wired through management and post-deployment diagnostics |
| Secret minimization | GitHub OIDC instead of long-lived Azure credentials JSON |
| Threat | Lab control | Validation evidence |
|---|---|---|
| Internet scanning of RDP | Public RDP rejected by validation and NAT rules disabled by default | Plan review and NSG/public IP review |
| Public PaaS data-plane exposure | Private endpoints, Private DNS, SQL public access disabled by default | Azure Portal private endpoint and network settings |
| Secret leakage in repository | Gitleaks and no AZURE_CREDENTIALS client secret JSON |
CI secret-scan result |
| Overprivileged deployment identity | OIDC and documented least-privilege guidance | Entra app and role assignment review |
| Drift from imperative commands | Terraform-managed App Gateway backend and stateful plan/apply | Plan shows backend pool changes |
| Missing diagnostics | Management and post-deployment diagnostic settings | Diagnostic settings export |
| Cost-driven security bypass |
cheap-lab profile and explicit expensive toggles |
Profile and cost estimate review |
Public RDP is controlled by four variables:
| Variable | Default intent | Risk if changed |
|---|---|---|
enable_jumpbox_public_ip |
Disable direct public jumpbox access | Exposes management host to internet |
allowed_jumpbox_source_ips |
Empty or trusted CIDRs only |
0.0.0.0/0 allows global RDP attempts |
allow_public_rdp_from_internet |
false |
Break-glass override for unsafe RDP CIDR |
enable_lb_rdp_nat_rules |
false |
Directly exposes workload VMs through public LB NAT |
Source:
- variables.tf public RDP variables
- landing-zones/management/core/main.tf
- landing-zones/management/workload/main.tf
Recommended management access path:
- Prefer VPN or private management path.
- If public access is temporarily required, use a specific trusted CIDR.
- Do not use
0.0.0.0/0. - Disable public access immediately after testing.
Shared services are deployed from:
- landing-zones/security/shared-services
- modules/keyvault
- modules/storage
- modules/sql
- modules/private-endpoint
Hardening behavior:
| Service | Hardened behavior |
|---|---|
| Key Vault | Public network access controlled; network ACL default deny when Private Endpoints are enabled |
| SQL | Public network access disabled by default; "Allow Azure Services" disabled by default |
| Storage | HTTPS required; blob public access disabled; network rules support private-first access |
| Private DNS | Blob, Key Vault, and SQL zones can be linked to the hub and spokes |
| Private Endpoints | Key Vault, Storage, and SQL private endpoints are available in the shared PE subnet |
Validate private-first behavior after apply:
az network private-endpoint list --query "[].{name:name, rg:resourceGroup, state:provisioningState}" -o table
az network private-dns zone list --query "[].{name:name, rg:resourceGroup}" -o tablePass conditions:
| Service | Pass condition |
|---|---|
| Key Vault | Private endpoint exists when enabled and public access posture matches profile |
| Storage | HTTPS-only and anonymous public blob access disabled |
| SQL | Public network access disabled unless there is an explicit lab exception |
| DNS | Private DNS zones are linked to VNets that need to resolve private endpoints |
Test private DNS from a VM or runner inside the peered network. Public internet clients should not be used as proof that private endpoints work.
NSGs are created through modules/networking/nsg.
NSG locations:
- Hub management subnet
- App Gateway subnet
- Identity domain controller subnet
- Management jumpbox subnet
- Shared services app subnet
- Workload web subnet
- Workload app subnet
- Workload data subnet
- Optional AKS subnet
- Optional on-prem simulation subnet
Validation and testing:
- Terratest checks management NSG existence in tests/landing_zone_test.go.
- OPA warns on broad inbound rules in policies/security.rego.
The pipeline enforces:
- Terraform format check
- Terraform validate
- tfsec
- Checkov
- Gitleaks
- OPA policy check
- Module version analysis
Workflow source: .github/workflows/terraform.yml
Custom actions:
Use least privilege for real subscriptions. Common review patterns:
| Persona | Expected role direction |
|---|---|
| Platform network operator | Network Contributor scoped to networking resource groups where possible |
| Security reviewer | Reader plus Security Reader or policy-specific access |
| Monitoring operator | Monitoring Reader or Monitoring Contributor depending on action scope |
| Backup operator | Backup Contributor scoped to backup vault/resource group |
| CI/CD identity | Minimum deployment roles required for selected modules and profile |
Do not use broad Owner assignments as a permanent CI/CD solution. If temporary Owner is needed for bootstrap, remove it after role assignments and state backend are working.
Never commit:
- Tenant IDs if you treat them as sensitive
- Subscription IDs if your organization classifies them
- Client secrets
- Passwords
- Private keys
- Admin usernames/passwords
- SAS tokens
- Backend access keys
- Terraform state
Use GitHub secrets for:
AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_IDTF_STATE_RGTF_STATE_SA- Optional
INFRACOST_API_KEY
Before apply:
-
enable_jumpbox_public_ip = falseunless there is a trusted CIDR requirement. -
allowed_jumpbox_source_ipsdoes not contain0.0.0.0/0. -
enable_lb_rdp_nat_rules = false. - SQL public network access is not enabled.
- Key Vault public access is not opened without a reason.
- Storage public blob access is disabled.
- Private Endpoints and Private DNS are enabled for shared services.
- GitHub Actions uses OIDC, not
AZURE_CREDENTIALS. - Destroy environments require approval.
For operational procedures, incident response, and pull request review, see Security Operations.
Next page: Security Operations
- Architecture Overview
- Full Environment Inventory
- Network Topology
- Shared Services
- Workloads
- Module Reference
- Security Model
- Security Operations
- Identity and Access
- Governance and Policy
- Production Readiness Review
- Monitoring and Diagnostics
- Operations Runbooks
- Azure Portal Validation Evidence
- Cost Management
- Disaster Recovery and Resilience