There are multiple parts to writing IAM least privilege policies. This document attempts to touch on the best ways to do each of these tasks. The assumption is that you’ve never written an IAM policy before, which is probably false but starting there is probably useful for a newbie developer.
Know what you need
Only you as a dev will know what data your code needs access to. For example: At a minimum you’ll need to know that your new feature plans to read patient records, identify the non-encrypted ones, encrypt them, delete the old records and store the new encrypted records, while maintaining an audit trail. No tool can tell you this, right?
Only you as a dev will know what data your code needs access to. For example: At a minimum you’ll need to know that your new feature plans to read patient records, identify the non-encrypted ones, encrypt them, delete the old records and store the new encrypted records, while maintaining an audit trail. No tool can tell you this, right?
- My records are in DynamoDB. I need read access to DynamoDB.
- I need to look at the metadata of each record to see if it's encrypted. So I need read access to all metadata.
- Then I need to encrypt them. I need a key to do this. I need to be able to use an existing KMS key OR be able to create a new one to encrypt my data.
- I need to delete the old records. I need write access to DynamoDB.
- I need to store the new records. I don’t need any more access, as I already got write access in the previous step.
- Oh wait. Not all tables - just Table A :)
- I need to log all my actions somewhere. I need write access to Cloudtrail.
How do I implement the above?
- Now you know you’re going to probably have a StepFunction or a Lambda function where that code will live. Which means there’s an execution role that comes into play. It’s a good idea to use a new role for each function coz:
- It’s cleaner and you know exactly what that function and only that function needs
- It’s easier to change and not worry what else will break in some other function that shares that role
- Security will whine less as you’ll probably follow least privilege when you assign permissions
- Okay good. You create a new execution role and now need to assign permissions. You need to map all those Read/Write statements that made sense intuitively into an IAM policy, which is what controls access to all the roles.
- You add a lot of permissions, see that it works, dial down a bit, see it still works, dial down some more and see it break and repeat this increasingly painful and irritating exercise till everything works. Of course then security complains that it's not least privilege, gives you a link to what’s expected and rejects the request. The link’s somewhat helpful but not a lot, and you end up repeating all that crap again till they’re happy.
- You get better at this over time but at one point feel you’re wasting a lot of time tweaking policies, when you should be writing code instead. And then wonder if there is a better way to write IAM policies.
Some ideas to get better
- Definitely use a PolicyGenerator. Don’t write a policy by hand.
- AWS Console: You need to know exactly what you want. If you do, it works great
- AWS PolicyGen: Older version of Console but with a simpler interface. Not sure its maintained
- PolicySentry: This looks like a fantastic tool that does a lot of the policy generation for you. It is worth spending the time learning all its detailed options.
- If you know which resource you want to control access to, use a CRUD template
- If you know which actions you need, use an Actions template
- To clean up policies generated by PolicySentry, remember what you really need and refer to the AWS docs. You’ll need to do this as you almost certainly do not need everything PolicySentry gives you. Pay heed to the Dependent Actions column as well and don’t forget to grant access to those actions. Here is a sample table for CloudTrail.
- Create as many templates as possible for services that you often use and make them easily reusable or to reference, across your engineering team.
- Use a linter such as Parliament to check your policy once it’s generated for obvious typos as well as some security misconfigurations. Integrate it into your pipeline.
- Use the AWS policy simulator to verify if your execution role has and doesn’t have access to the expected resources. Think of this as a confirmation of your existing policies. The good thing with this is it doesn’t make any changes to your actual stack.
Policy Sentry templates
CRUD:
policy_sentry create-template --name TestRole --output-file crud.yml --template-type crud
Edit the YML template that gets generated, tweaking it to remove whatever you don't want.
Policy Sentry query commands
policy_sentry query action-table --service cloudwatch --wildcard-only
policy_sentry query arn-table --service dynamodb
policy_sentry query condition-table --service lambda
Parliament sample bad policies
Blank resource field where a "*" or an ARN is needed:
Mismatched condition where the condition is not valid for the chosen action: