Cloud computing has fundamentally changed how we build and deploy applications, but itβs also changed how we approach security. The perimeter-based security model that worked in the data center doesnβt translate well to the cloud, where resources are dynamic, distributed, and accessed over the internet.
After spending the last year architecting security solutions for cloud environments, Iβve developed a set of patterns that work well across AWS, Azure, and GCP. These arenβt product-specific recommendationsβtheyβre architectural principles that apply regardless of your cloud provider.
Defense in Depth: Layered Security
The core principle is defense in depth: multiple layers of security controls, so if one layer fails, others still protect you.
Hereβs how I think about the layers:
βββββββββββββββββββββββββββββββββββββββ
β Application Security β β Input validation, auth, OWASP
βββββββββββββββββββββββββββββββββββββββ€
β Data Security β β Encryption, key management
βββββββββββββββββββββββββββββββββββββββ€
β Compute Security β β OS hardening, patch management
βββββββββββββββββββββββββββββββββββββββ€
β Network Security β β Segmentation, firewalls
βββββββββββββββββββββββββββββββββββββββ€
β Identity & Access Management β β Least privilege, MFA
βββββββββββββββββββββββββββββββββββββββ€
β Logging & Monitoring β β Audit trails, anomaly detection
βββββββββββββββββββββββββββββββββββββββ
Letβs dive into patterns for each layer.
Network Security Patterns
Pattern 1: Network Segmentation with Security Zones
Donβt put everything in one virtual network. Segment your infrastructure into security zones based on trust levels and data sensitivity:
ββββββββββββββββββββββββββββββββββββββββββββ
β Public Zone (DMZ) β
β - Load balancers β
β - API gateways β
β - Bastion hosts β
ββββββββββββββββ¬ββββββββββββββββββββββββββββ
β Firewall rules
ββββββββββββββββΌββββββββββββββββββββββββββββ
β Application Zone β
β - Application servers β
β - Business logic β
β - No direct internet access β
ββββββββββββββββ¬ββββββββββββββββββββββββββββ
β Firewall rules
ββββββββββββββββΌββββββββββββββββββββββββββββ
β Data Zone β
β - Databases β
β - Key management services β
β - No internet access β
ββββββββββββββββββββββββββββββββββββββββββββ
Each zone has strict firewall rules controlling what traffic can flow between zones. The principle: minimize the attack surface at each layer.
Pattern 2: Private Subnets by Default
Make private subnets the default. Only resources that absolutely must be internet-facing should be in public subnets. Everything else should be in private subnets with no direct internet access.
For private resources that need outbound internet access (updates, API calls), use NAT gateways. This allows outbound connections while preventing inbound connections.
Pattern 3: Micro-Segmentation
Traditional firewalls control traffic between zones. Micro-segmentation controls traffic between individual workloads, even within the same zone.
This is especially important for multi-tenant systems. If an attacker compromises one tenantβs workload, micro-segmentation prevents lateral movement to other tenants.
Identity and Access Management Patterns
Identity is the new perimeter. When your resources are distributed across cloud providers and regions, identity determines who can access what.
Pattern 1: Least Privilege by Default
Every identity (human or service) should have the minimum permissions needed to do its job, nothing more.
Hereβs a pattern I use consistently:
1. Start with zero permissions
2. Grant specific permissions for specific resources
3. Use conditions to further restrict (time, IP, MFA)
4. Review and audit regularly
For example, instead of granting broad permissions:
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
Be specific:
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/my-prefix/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "10.0.0.0/8"
}
}
}
Pattern 2: Service Identity over Credentials
Never put long-lived credentials in your application code or configuration files. Instead, use the cloud providerβs native service identity mechanism (IAM roles in AWS, Managed Identities in Azure, Service Accounts in GCP).
The pattern:
1. Create a service identity for your application
2. Grant that identity the permissions it needs
3. Configure your compute resource to use that identity
4. Cloud provider automatically handles credential lifecycle
Your application code becomes credential-free:
// Instead of this (BAD):
s3Client := s3.New(session.New(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
}))
// Do this (GOOD):
s3Client := s3.New(session.New(&aws.Config{
// Automatically uses instance IAM role
}))
Pattern 3: Temporary Credentials
For human access, use temporary credentials with short lifetimes. Implement an authentication flow that:
- User authenticates (with MFA)
- System issues temporary credentials (valid for 1-12 hours)
- User uses temporary credentials
- Credentials automatically expire
This limits the window of exposure if credentials are compromised.
Data Security Patterns
Pattern 1: Encryption Everywhere
Encrypt data everywhere it lives:
- At rest: Databases, file storage, backups, logs
- In transit: All network communication (TLS 1.2+)
- In use: Encrypted memory for highly sensitive operations
Donβt make encryption optional or configurable. Make it the default, always on.
Pattern 2: Separate Key Management
Never store encryption keys alongside encrypted data. Use a separate key management service:
Application
β (request data encryption key)
Key Management Service
β (return encrypted DEK)
Application (stores encrypted DEK with data)
β (decrypt data when needed)
Key Management Service (decrypt DEK)
This pattern ensures that even if someone gains access to your encrypted data, they canβt decrypt it without also compromising your key management service.
Pattern 3: Data Classification and Protection
Not all data needs the same level of protection. Classify your data and apply appropriate controls:
βββ Public: No special protection needed
βββ Internal: Access controls, encrypted in transit
βββ Confidential: Encryption at rest + transit, strict access controls
βββ Restricted: Encryption everywhere, hardware key storage, extensive auditing
Compute Security Patterns
Pattern 1: Immutable Infrastructure
Donβt patch or update running instances. Instead, build new images with updates and replace running instances.
1. Build new image with security patches
2. Test the image
3. Deploy new instances with new image
4. Terminate old instances
This eliminates configuration drift and ensures all instances are in a known-good state.
Pattern 2: Minimal Base Images
Start with minimal base images that contain only whatβs necessary to run your application. Every package you include is a potential vulnerability.
I prefer Alpine Linux or distroless images for containersβthey have a much smaller attack surface than full Ubuntu or CentOS images.
Pattern 3: Runtime Security Controls
Implement controls that prevent unauthorized behavior at runtime:
- Read-only filesystems: Prevent malware from persisting
- Capability dropping: Run processes with minimal Linux capabilities
- Seccomp/AppArmor: Restrict system calls and resource access
- Resource limits: Prevent DoS via resource exhaustion
Logging and Monitoring Patterns
Security is useless if you canβt detect when itβs been breached.
Pattern 1: Centralized Logging
Send all logs to a centralized logging system. This enables:
- Correlation across services
- Long-term retention (compliance often requires 7+ years)
- Secure storage (logs themselves are sensitive)
Hereβs the architecture I use:
Applications β Log Agents β Log Aggregator β Storage
β
Alerting System
β
Security Team
Pattern 2: Audit Everything
Log every security-relevant event:
- Authentication attempts (success and failure)
- Authorization decisions
- Data access (who accessed what, when)
- Configuration changes
- Privileged operations
Structure your logs for machine parsing:
logger.Info("authentication_attempt",
"user_id", userID,
"source_ip", sourceIP,
"success", success,
"mfa_used", mfaUsed,
"timestamp", time.Now().Unix(),
)
Pattern 3: Automated Alerting and Response
Donβt rely on humans to watch logs 24/7. Implement automated detection and response:
Anomaly Detection β Alert β Automated Response
Examples:
- Unusual login location β Alert security team + require additional verification
- Multiple failed logins β Temporarily block IP address
- Sensitive data access from new location β Alert + require re-authentication
Compliance and Governance Patterns
Pattern 1: Policy as Code
Define security policies as code and enforce them automatically:
# Example policy: All S3 buckets must have encryption enabled
def check_s3_encryption(bucket):
encryption = bucket.get_encryption()
if not encryption:
return PolicyViolation("S3 bucket must have encryption enabled")
return PolicyCompliance()
Pattern 2: Automated Compliance Checking
Continuously scan your infrastructure for compliance violations. Donβt wait for annual audits.
Tools can check for:
- Unencrypted resources
- Overly permissive access controls
- Missing logging configuration
- Outdated software versions
- Non-compliant network configurations
Pattern 3: Immutable Audit Trail
Security logs should be immutableβonce written, they canβt be modified or deleted. This prevents attackers from covering their tracks.
Techniques:
- Write logs to append-only storage
- Use cryptographic hashing to detect tampering
- Replicate logs to separate accounts/environments
Putting It All Together
These patterns donβt exist in isolation. A complete cloud security architecture combines them:
Internet
β
Load Balancer (Public subnet, TLS termination)
β
API Gateway (Authentication, rate limiting)
β
Application Servers (Private subnet, service identity)
β
Database (Private subnet, encrypted, access controls)
β
Key Management Service (Separate VPC, HSM-backed)
All components:
- Use principle of least privilege
- Log all actions to centralized system
- Encrypted communication
- Automated security scanning
- Immutable infrastructure
Lessons from the Field
After implementing these patterns across multiple cloud environments:
-
Security is iterative: Donβt try to implement everything at once. Start with the highest-risk areas and expand.
-
Automate everything: Manual security processes donβt scale and are error-prone.
-
Default to secure: Make the secure option the default. Make the insecure option hard.
-
Assume breach: Design assuming attackers will get in. Focus on limiting damage and detecting intrusions quickly.
-
Keep learning: The threat landscape evolves constantly. Yesterdayβs best practices may be tomorrowβs vulnerabilities.
Looking Ahead
Cloud security is a journey, not a destination. As cloud platforms evolve and new attack vectors emerge, our security architectures must evolve too.
In upcoming posts, Iβll dive deeper into specific areas: container security, secrets management, security automation, and compliance-as-code.
The patterns Iβve shared here are the foundation. Build on them, adapt them to your specific context, and always be questioning whether your security controls are still effective against current threats.
Stay secure out there.