July 14, 2023

Separation of Concerns vs Least Privilege: Why They're Not the Same (and How to Use Both)

Two fundamental principles that sound similar but serve completely different purposes in cloud security. Here's why the distinction matters

I was reviewing an AWS IAM policy last week when a colleague asked why we were “separating concerns” by splitting read and write permissions across different roles. They’d just conflated two different security principles that get mixed up constantly in our field.

I’ve made this same mistake. Early in my career, I threw around “separation of concerns” and “least privilege” interchangeably, as if they just meant “split things up for security.” It took a system that spectacularly failed an audit to show me these aren’t different approaches to the same problem. They solve fundamentally different problems.

The Incident That Taught Me Everything

Picture this: I’m building the IAM structure for a new microservices architecture. I carefully separated each service into its own AWS account: payment service in one, user management in another, and so on. I was proud of my “separation of concerns.” Each service was beautifully isolated.

Then I created the IAM roles. The payment service role could read from every S3 bucket, write to any DynamoDB table, and invoke any Lambda function within its account. It was “separated” from other services, so it was secure, right?

Wrong.

The auditor took one look at the payment service IAM policy and asked the question that still makes me cringe: “Why does your payment processor need to read your compliance logs?”

That’s when it clicked. I had separation of concerns at the architecture level, and nothing resembling least privilege at the permissions level.

What Separation of Concerns Means

Separation of concerns is an architectural principle. It’s about organizing your system so that different components handle different responsibilities. In AWS terms, this might look like:

  • Compute separation: Lambda functions for business logic, separate from data storage
  • Network separation: Different VPCs or subnets for different tiers
  • Account separation: Different AWS accounts for dev, staging, and production
  • Service separation: Microservices instead of monoliths

The goal isn’t security. It’s maintainability, scalability, and clarity. When payment processing logic is separate from user authentication logic, developers can work on one without breaking the other. When your database sits in a different subnet from your web servers, you can apply different network rules to each tier.

Here’s what proper separation of concerns looks like in AWS:

# Different services, different concerns
PaymentService:
  Responsibilities: 
    - Process transactions
    - Handle payment webhooks
    - Generate payment reports
  
UserService:
  Responsibilities:
    - User authentication
    - Profile management
    - Session handling

NotificationService:
  Responsibilities:
    - Send emails
    - Push notifications
    - SMS messaging

Each service has a clear, single responsibility. They’re separated not for security, but for architectural clarity.

What Least Privilege Really Means

Least privilege, by contrast, is purely a security principle: every component gets only the minimum permissions it needs to do its job. No more, no less.

This applies within each separated concern. Your payment service might be architecturally separate, but it still needs least privilege for every permission it holds:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/payments"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::payment-config/prod/config.json"
    }
  ]
}

Notice what’s NOT in this policy:

  • No s3:* permissions
  • No access to other DynamoDB tables
  • No unnecessary services like EC2 or RDS
  • No administrative permissions

The payment service can do exactly what it needs to do, nothing more.

Quick Clarifier: Three Different Principles

Before we dive deeper, let’s distinguish three concepts that often get confused:

Separation of Concerns = architectural boundaries (services, networks, accounts).
Separation of Duties = different people/roles for critical actions (e.g., deploy vs approve, KMS admin vs key user).
Least Privilege = minimum permissions per role/service.

You need all three, just for different problems.

Why The Confusion Happens

The confusion is understandable because the two principles often work together. Good security architecture involves both:

  1. Separate your concerns so different components have different responsibilities
  2. Apply least privilege so each component has only the permissions its specific responsibility needs

But you can have perfect separation of concerns with terrible least privilege, and vice versa. I’ve seen monolithic applications (poor separation of concerns) with granular IAM policies (good least privilege), and beautifully architected microservices (great separation of concerns) where every service has admin permissions (terrible least privilege).

An Implementation

Let me show you how these principles work together in practice. Consider an e-commerce platform:

Separation of Concerns:

Each service handles one concern. Payment doesn’t know about user profiles, user service doesn’t handle inventory, etc.

Least Privilege (Permissions):

// Payment Service Role
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem"],
      "Resource": "arn:aws:dynamodb:eu-west-2:123456789012:table/payments",
      "Condition": {
        "ForAllValues:StringEquals": { 
          "dynamodb:Attributes": ["payment_id", "amount", "status", "created_at", "customer_id"] 
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:eu-west-2:123456789012:secret/stripe/prod/api_key-*"
    }
  ]
}

// User Service Role  
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:UpdateItem"],
      "Resource": "arn:aws:dynamodb:eu-west-2:123456789012:table/users",
      "Condition": {
        "ForAllValues:StringEquals": { 
          "dynamodb:Attributes": ["user_id", "email", "name", "created_at", "last_login"] 
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": ["ses:SendEmail"],
      "Resource": "arn:aws:ses:eu-west-2:123456789012:identity/*",
      "Condition": {
        "StringEquals": {
          "ses:FromAddress": "noreply@yourcompany.com"
        }
      }
    }
  ]
}

Note: Stripe calls are made over HTTPS using the API key retrieved from Secrets Manager, not IAM actions.

Each service gets exactly the permissions its separated concern needs.

A Notable Checklist

When designing your AWS architecture, ask these questions:

For Separation of Concerns:

  • Does each component have a single, clear responsibility?
  • Can I modify one service without affecting others?
  • Are my deployment pipelines separate?
  • Do different teams own different services?

For Least Privilege:

  • What’s the minimum this role needs to function?
  • Can I remove any permissions and still have it work?
  • Am I using wildcards where I should be specific?
  • When did I last audit these permissions?

A Security Engineer’s Perspective

We need both principles, but for different reasons.

Separation of concerns limits blast radius when something goes wrong, keeps security reviews manageable by creating clear boundaries between components, and lets teams work independently without compromising security.

Least privilege minimizes attack surface by cutting unnecessary permissions, prevents privilege escalation by limiting what compromised credentials can reach, and keeps you compliant with regulations that mandate minimal access.

The Bottom Line

Next time someone says “separating concerns” about IAM permissions, they probably mean “least privilege.” If they say “least privilege” about microservices architecture, they might mean “separation of concerns.”

Both principles matter for building secure, maintainable systems in AWS. Understanding the distinction will make you a better security engineer and save you from the audit embarrassment that still makes me wince.

The auditors know the difference.

Back to blog