Writing IAM Policies CAREfully

Condition, Action, Resource, Effect

Posted by Ryan S. Brown on Sun, Nov 20, 2016
In General
Tags: security, iam, aws

This isn’t a tutorial, just a conceptual framework that’s helped me write better IAM policies. It’s extra useful when an app needs a group of services like DynamoDB, S3, and Kinesis. The method is called “CARE,” and it’s an acronym based on the four parts of an IAM policy statement.

Writing Policies

CARE started out as a mnemonic to use when writing policy statements, standing for:

  • Condition
  • Action
  • Resource
  • Effect

Moving further towards serverless apps typically means taking advantage of more and more external services, and a need for granular RBAC. Managing the right levels of access for each function is great for limiting the blast radius of a bug, since code can’t ruin what it can’t change.

Serverless meant writing more IAM policies than usual, and remembering CARE meant I could skip looking up the policy structure in the docs every single time.

Note: The team at Cloudonaut have a great IAM reference site that’s often quicker to use than finding the AWS policy/action documentation.

CARE, Evolved

At first, a single role per “service” in my application seemed right. Every function wouldn’t have its own role, and it felt familiar to how I’d delegate permissions to a regular web service. It’s also pretty low-management, since a full application might only have a couple roles along these lines:

  • A role for frontend type functions that talk to DynamoDB or a SQL database. Typically this would be extra-restrictive since untrusted input could be coming in from the outside world.
  • A role for backend functions that handle Kinesis or DynamoDB stream events, but don’t take untrusted input. Data handled here would have been sanitized and validated by public-facing frontend functions.
  • A role for administrivia functions that handled infra tasks like scheduled jobs. These sometimes need super-privileges like managing ECS containers or kicking off tasks.

That worked well enough, but sometimes I’d end up with functions that needed a special (somewhat powerful) permission. Usually it would get tacked on to the administrivia role. Unfortunately, that meant the administrivia role ended up grossly over-permissioned. It could terminate RDS and EC2 instances, nuke CloudFormation stacks, redeploy other Lambdas, and change VPC’s. Not even close to the principle of least privilege.

Now CARE is part of how I divide permission statements between managed policies. When redesigning the permission scheme to break up the overpowered administrivia role, I switched to making one IAM role per function and using a regular naming scheme. Every function would have a role named {app}-{environment}-{function}, for example: myapp-prod-dbbackup. Each role can have up to 5 managed policies attached, so permissions can be quite granular.

The four parts of an individual IAM statement are a nice lens to look through when building a shared policy. A managed policy has similar principles, applied at a higher level. Since policies are shared, consider whether all the permissions granted are cohesive in covering a specific goal. Going over my policies, here are a few custom ones I use:

  • ProdAppDataStoreAccess
  • ProdLambdaVPCAttachment
  • ProdFrontendApiRead
  • ProdFrontendApiWrite

Before writing one, I describe it in CARE terms so it covers everything about the policy. Take the ApplicationDataStoreAccess policy for example:

  • Condition: The environment has to be production, and the function has to be in the VPC for RDS access.
  • Action: Read and write to DynamoDB tables, a Kinesis stream, and an RDS database.
  • Resource: The prod-users and prod-queues DynamoDB tables and the prod-clickstream Kinesis stream. RDS is excluded because that’s handled by the security group, since RDS connections aren’t IAM.
  • Effect: Allow specified, deny everything else.

Wrapping Up

And that’s CARE. I hope this helps you when you’re writing your own IAM policies. Remember: one role per function, but reuse policies where it makes sense. The fewer places you need to update security rules, the more likely you are to get it right.

If you like this sort of thing, subscribe to the Serverless Code mailing list. Send questions them to ryan@serverlesscode.com or @ryan_sb on Twitter.

Tweet this, send to Hackernews, or post on Reddit