Reference Architectures
Production-ready reference architectures with security controls annotated at every layer. Use these as starting points for your own designs — adapt them to your specific threat model.
How to Use These Architectures
1. Secure Three-Tier Web Application
The classic architecture for web applications — presentation, application logic, and data layers with security controls at each tier and trust boundary.
Secure Three-Tier Architecture
Key Security Controls
Network Controls
- • Security Groups: least-privilege port access
- • NACLs: subnet-level deny rules
- • No direct internet access to app/data tier
- • VPC Flow Logs enabled
Application Controls
- • JWT validation at API gateway
- • RBAC/ABAC at service level
- • Input validation + output encoding
- • Rate limiting per authenticated user
Terraform — VPC with Security Groups
# Secure VPC with isolated subnets per tier
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "secure-app-vpc" }
}
# Public subnet (ALB only - no application code here)
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = false # No automatic public IPs
}
# Private subnet for application tier
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.10.0/24"
}
# Isolated subnet for database (no internet route)
resource "aws_subnet" "data" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.20.0/24"
}
# Security Group: ALB - only HTTPS from internet
resource "aws_security_group" "alb" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
}
# Security Group: App - only from ALB
resource "aws_security_group" "app" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
}
# Security Group: DB - only from App on port 5432
resource "aws_security_group" "db" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
# No egress to internet
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = [] }
}# Secure VPC with isolated subnets per tier
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "secure-app-vpc" }
}
# Public subnet (ALB only - no application code here)
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = false # No automatic public IPs
}
# Private subnet for application tier
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.10.0/24"
}
# Isolated subnet for database (no internet route)
resource "aws_subnet" "data" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.20.0/24"
}
# Security Group: ALB - only HTTPS from internet
resource "aws_security_group" "alb" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
}
# Security Group: App - only from ALB
resource "aws_security_group" "app" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
}
# Security Group: DB - only from App on port 5432
resource "aws_security_group" "db" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
# No egress to internet
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = [] }
}Common Misconfigurations
Database subnet has route to internet gateway
Data tier should have NO internet access. Use VPC endpoints for AWS services.
Security groups use 0.0.0.0/0 for app tier ingress
App tier should only accept traffic from the load balancer security group.
RDS instance publicly accessible
Set publicly_accessible = false. Access DB only through app tier or bastion/SSM.
2. Secure Cloud Landing Zone (Hub-Spoke)
A multi-account / multi-subscription architecture for enterprise cloud deployments. Central shared services hub with isolated spoke environments for each workload.
Hub-Spoke Cloud Landing Zone
Landing Zone Security Controls
Account-Level Controls
- • SCPs preventing disabling CloudTrail/GuardDuty
- • Mandatory encryption on S3/EBS/RDS
- • Deny public S3 buckets at account level
- • Require MFA for all IAM users
Network Controls
- • All egress through centralized inspection
- • DNS filtering via Route 53 Resolver
- • VPC Flow Logs to central SIEM
- • No direct internet access in spokes
Service Control Policy — Deny Dangerous Actions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDisablingCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail"
],
"Resource": "*"
},
{
"Sid": "DenyPublicS3",
"Effect": "Deny",
"Action": "s3:PutBucketPublicAccessBlock",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:PublicAccessBlockConfiguration/BlockPublicAcls": "true"
}
}
},
{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDisablingCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail"
],
"Resource": "*"
},
{
"Sid": "DenyPublicS3",
"Effect": "Deny",
"Action": "s3:PutBucketPublicAccessBlock",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:PublicAccessBlockConfiguration/BlockPublicAcls": "true"
}
}
},
{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}3. Zero Trust Reference Architecture
Based on NIST SP 800-207. Every access request is authenticated, authorized, and encrypted — regardless of network location.
NIST 800-207 Zero Trust Architecture
Cloudflare Access — Zero Trust App Protection
# Cloudflare Access application (ZTNA - replaces VPN)
resource "cloudflare_access_application" "internal_app" {
zone_id = var.zone_id
name = "Internal Dashboard"
domain = "dashboard.internal.example.com"
session_duration = "1h"
# Require device posture check
auto_redirect_to_identity = true
}
# Access policy: require corporate identity + device posture
resource "cloudflare_access_policy" "corp_policy" {
application_id = cloudflare_access_application.internal_app.id
zone_id = var.zone_id
name = "Corporate Access"
precedence = 1
decision = "allow"
include {
login_method = ["oidc"] # SSO via Okta/Entra
}
require {
device_posture = [
cloudflare_device_posture_rule.disk_encryption.id,
cloudflare_device_posture_rule.os_version.id,
cloudflare_device_posture_rule.firewall_enabled.id
]
}
}
# Device posture: require disk encryption
resource "cloudflare_device_posture_rule" "disk_encryption" {
account_id = var.account_id
type = "disk_encryption"
name = "Require disk encryption"
input { require_all = true }
}# Cloudflare Access application (ZTNA - replaces VPN)
resource "cloudflare_access_application" "internal_app" {
zone_id = var.zone_id
name = "Internal Dashboard"
domain = "dashboard.internal.example.com"
session_duration = "1h"
# Require device posture check
auto_redirect_to_identity = true
}
# Access policy: require corporate identity + device posture
resource "cloudflare_access_policy" "corp_policy" {
application_id = cloudflare_access_application.internal_app.id
zone_id = var.zone_id
name = "Corporate Access"
precedence = 1
decision = "allow"
include {
login_method = ["oidc"] # SSO via Okta/Entra
}
require {
device_posture = [
cloudflare_device_posture_rule.disk_encryption.id,
cloudflare_device_posture_rule.os_version.id,
cloudflare_device_posture_rule.firewall_enabled.id
]
}
}
# Device posture: require disk encryption
resource "cloudflare_device_posture_rule" "disk_encryption" {
account_id = var.account_id
type = "disk_encryption"
name = "Require disk encryption"
input { require_all = true }
}4. Microservices with Service Mesh
Kubernetes-native architecture with Istio service mesh providing automatic mTLS, authorization policies, and observability for all service-to-service communication.
Microservices with Service Mesh (Istio)
Istio Authorization Policy
# Enforce mTLS for all traffic in the mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # All traffic must be mTLS
---
# Only allow order-svc to call payment-svc
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-svc-policy
namespace: production
spec:
selector:
matchLabels:
app: payment-svc
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/order-svc"]
to:
- operation:
methods: ["POST"]
paths: ["/api/v1/charges", "/api/v1/refunds"]
---
# Deny all other traffic to payment-svc
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-svc-deny-all
namespace: production
spec:
selector:
matchLabels:
app: payment-svc
action: DENY
rules: [] # Empty = deny everything not explicitly allowed# Enforce mTLS for all traffic in the mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # All traffic must be mTLS
---
# Only allow order-svc to call payment-svc
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-svc-policy
namespace: production
spec:
selector:
matchLabels:
app: payment-svc
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/order-svc"]
to:
- operation:
methods: ["POST"]
paths: ["/api/v1/charges", "/api/v1/refunds"]
---
# Deny all other traffic to payment-svc
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-svc-deny-all
namespace: production
spec:
selector:
matchLabels:
app: payment-svc
action: DENY
rules: [] # Empty = deny everything not explicitly allowedKubernetes Network Policy
# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow order-svc to reach payment-svc on port 8080 only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-order-to-payment
namespace: production
spec:
podSelector:
matchLabels:
app: payment-svc
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: order-svc
ports:
- protocol: TCP
port: 8080# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow order-svc to reach payment-svc on port 8080 only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-order-to-payment
namespace: production
spec:
podSelector:
matchLabels:
app: payment-svc
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: order-svc
ports:
- protocol: TCP
port: 80805. Secure CI/CD Pipeline
Security gates at every stage of the pipeline — from commit to production. Every artifact is scanned, signed, and verified before deployment.
Secure CI/CD Pipeline — Security Gates
GitHub Actions — Secure Pipeline
name: Secure CI/CD Pipeline
on: [push, pull_request]
permissions:
contents: read
security-events: write
jobs:
gate-1-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gate-2-sast:
needs: gate-1-secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/secrets
- name: Dependency scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: CRITICAL,HIGH
exit-code: 1 # Fail on critical/high
gate-3-container:
needs: gate-2-sast
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan container image
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: app:${{ github.sha }}
format: spdx-json
- name: Sign image with Cosign
uses: sigstore/cosign-installer@v3
- run: cosign sign --yes app:${{ github.sha }}
gate-4-deploy:
needs: gate-3-container
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production # Requires approval
steps:
- name: Verify image signature
run: cosign verify app:${{ github.sha }}
- name: Deploy to production
run: kubectl set image deployment/app app=app:${{ github.sha }}name: Secure CI/CD Pipeline
on: [push, pull_request]
permissions:
contents: read
security-events: write
jobs:
gate-1-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gate-2-sast:
needs: gate-1-secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/secrets
- name: Dependency scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: CRITICAL,HIGH
exit-code: 1 # Fail on critical/high
gate-3-container:
needs: gate-2-sast
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan container image
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: app:${{ github.sha }}
format: spdx-json
- name: Sign image with Cosign
uses: sigstore/cosign-installer@v3
- run: cosign sign --yes app:${{ github.sha }}
gate-4-deploy:
needs: gate-3-container
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production # Requires approval
steps:
- name: Verify image signature
run: cosign verify app:${{ github.sha }}
- name: Deploy to production
run: kubectl set image deployment/app app=app:${{ github.sha }}6. Hybrid / Multi-Cloud Security
Interconnecting on-premises, AWS, Azure, and GCP environments while maintaining consistent security policies and identity federation.
Hybrid / Multi-Cloud Architecture
Identity Federation
- • Single IdP as source of truth (Entra ID / Okta)
- • SAML/OIDC federation to all cloud providers
- • No local cloud accounts — federated only
- • Consistent MFA policy across all environments
Network Security
- • Encrypted site-to-site VPN or Direct Connect
- • Consistent DNS security across environments
- • Centralized egress inspection
- • Uniform firewall rules via IaC
Data Sovereignty
- • Data residency requirements per region
- • Encryption keys in jurisdiction-appropriate KMS
- • GDPR / data localization compliance
- • Cross-border transfer agreements
Unified Observability
- • Central SIEM aggregating all logs
- • Consistent alert rules across providers
- • CSPM tools covering all clouds
- • Single pane of glass for incidents
Security Architecture Decision Record (ADR) Template
Document every security architecture decision so future teams understand the context. Use this template for each significant decision.
# ADR-{number}: {Title}
## Status
Proposed | Accepted | Deprecated | Superseded by ADR-{n}
## Context
What is the problem or situation that requires a decision?
What are the security requirements and constraints?
## Decision Drivers
- Compliance requirement (e.g., SOC 2, PCI DSS)
- Threat model finding (reference TM-{n})
- Performance / scalability requirement
- Operational complexity budget
## Considered Options
1. **Option A** — Description, pros, cons
2. **Option B** — Description, pros, cons
3. **Option C** — Description, pros, cons
## Decision
We chose **Option B** because...
## Security Implications
- **Risk accepted**: What residual risk remains?
- **Controls required**: What must be implemented alongside?
- **Monitoring**: What should we watch for?
## Consequences
- Positive: What improves?
- Negative: What trade-offs are we making?
- Related decisions: Links to dependent ADRs# ADR-{number}: {Title}
## Status
Proposed | Accepted | Deprecated | Superseded by ADR-{n}
## Context
What is the problem or situation that requires a decision?
What are the security requirements and constraints?
## Decision Drivers
- Compliance requirement (e.g., SOC 2, PCI DSS)
- Threat model finding (reference TM-{n})
- Performance / scalability requirement
- Operational complexity budget
## Considered Options
1. **Option A** — Description, pros, cons
2. **Option B** — Description, pros, cons
3. **Option C** — Description, pros, cons
## Decision
We chose **Option B** because...
## Security Implications
- **Risk accepted**: What residual risk remains?
- **Controls required**: What must be implemented alongside?
- **Monitoring**: What should we watch for?
## Consequences
- Positive: What improves?
- Negative: What trade-offs are we making?
- Related decisions: Links to dependent ADRsUse These as Starting Points