AWS IAM Examples
Below are some example configurations that I find useful in restricting applications to doing only what they need to do.
Lock To Region
If you want a quick way to lock an IAM account down to a single region such as London:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "eu-west-2"
}
}
}
]
}
SES
SES Send Email From Specific IP
The following configuration only allows sending emails, and only if the requests for sending those emails came from the specified IP addresses. This means that even if your credentials leaked out somehow, people wouldn't be able to use them to send emails. They would have to have access to your server.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*",
"Condition": {
"ForAnyValue:IpAddress": {
"aws:SourceIp": [
"x.x.x.x/32",
"x.x.x.x/32"
]
}
}
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ses:SendRawEmail",
"Resource": "*",
"Condition": {
"ForAnyValue:IpAddress": {
"aws:SourceIp": [
"x.x.x.x/32",
"x.x.x.x/32"
]
}
}
}
]
}
If you wish to further restrict it, so that emails can only come from a specified email address, then you can add this like so:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*",
"Condition": {
"ForAnyValue:IpAddress": {
"aws:SourceIp": [
"x.x.x.x/32",
"x.x.x.x/32"
]
},
"StringEquals": {
"ses:FromAddress": "my-email@my.domain.com"
}
}
}
]
}
EC2
EC2 Snapshotting
If you need a user that just has the ability to create AMI images (which requires snapshotting of the ebs volumes), then this is what you need (courtesy of a post on serverfault):
{
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"ec2:CreateSnapshot",
"ec2:CreateImage"
],
"Resource": [
"*"
]
}
S3
S3 Access To Specific Bucket
The following configuration will give full access to everything in the my-example-bucket
bucket in S3 (as long as you own it).
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-example-bucket",
"arn:aws:s3:::my-example-bucket/*"
]
}
]
}
Below is the version I used to use:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::my-example-bucket"]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:PutObjectVersionAcl",
"s3:PutObjectAcl"
],
"Resource": ["arn:aws:s3:::my-example-bucket/*"]
}
]
}
The above (outdated) configuration won't let you upload to it through the web console. This alternative version from here did:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListAllMyBuckets"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket-name",
"arn:aws:s3:::my-bucket-name/*"
]
}
]
}
Terraform S3 Backend Permissions
This is the policy required for an IAM user/credentials that Terraform would need in order to use S3 as a backend (store your Terraform state).
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::mybucket"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::mybucket/path/to/my/key"
}
]
}
Elastic Transcoder
Elastic Transcoder - Restricted To Single Pipeline
Use the JSON configuration below, just swapping out the identifier for the pipeline.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"elastictranscoder:ListPipelines",
"elastictranscoder:ListJobsByStatus",
"elastictranscoder:ListPresets"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"elastictranscoder:Read*",
"elastictranscoder:*Job",
"elastictranscoder:*Preset",
"elastictranscoder:List*"
],
"Resource": [
"arn:aws:elastictranscoder:*:*:job/*",
"arn:aws:elastictranscoder:*:*:pipeline/2457257325632-wysdbs",
"arn:aws:elastictranscoder:*:*:preset/*"
]
}
]
}
2457257325632-wysdbs
in the example configuration, but it should be close enough to a real name that you should recognize that you want the short identifier, not the really long ARN.
Route 53
Nginx Proxy Manager - Let's Encrypt DNS Challenge
If you are using Nginx Proxy Manager, and wish for it to be able to perform DNS challenges, then you can create a strict IAM policy for it like so:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/Z07XXXXXXXXXXXXXX",
"Condition": {
"IpAddress": {
"aws:SourceIp": "xxx.xxx.xxx.xxx/32"
},
"ForAllValues:StringLike": {
"route53:ChangeResourceRecordSetsNormalizedRecordNames": [
"_acme-challenge.*"
],
"route53:ChangeResourceRecordSetsRecordTypes": ["TXT"]
}
}
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "route53:ListHostedZones",
"Resource": "*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "xxx.xxx.xxx.xxx/32"
}
}
}
]
}
This will require the requests to come from your server, and the only types of records that can be changed are TXT records, whose values must start with _acme-challenge.
,
which all the DNS challenges do.
xxx.xxx.xxx.xxx/32
to the CIDR of your Nginx Proxy Manager server, and arn:aws:route53:::hostedzone/Z07XXXXXXXXXXXXXX
to the ARN of
the hosted zone for the domain you wish to change. If you wish to manage multiple zones, then you need to change this to an array of ARNs.
References
- AWS Docs - Controlling access to Amazon SES
- AWS: Restrict users to access services to a specific region using IAM policy
- paulgalow.com - AWS Route 53 least privilege IAM policy for Let's Encrypt DNS challenge
First published: 18th August 2020