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:

  1. User authenticates (with MFA)
  2. System issues temporary credentials (valid for 1-12 hours)
  3. User uses temporary credentials
  4. 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:

  1. Security is iterative: Don’t try to implement everything at once. Start with the highest-risk areas and expand.

  2. Automate everything: Manual security processes don’t scale and are error-prone.

  3. Default to secure: Make the secure option the default. Make the insecure option hard.

  4. Assume breach: Design assuming attackers will get in. Focus on limiting damage and detecting intrusions quickly.

  5. 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.