How do I do an OR condition in an S3 bucket policy? - json

I'm working on an S3 bucket policy. The idea is to explicitly deny access to all IAM users within the account, except for those explicitly granted.
I found a blog post that explains how to restrict access to a specific user. It works well. However, I want to extend the syntax to include a second IAM user that will be allowed access. This is, in effect, an OR condition.
But, I've very new to JSON, and I'm not sure how to go about that.
Here is the policy that works for restricting access to a single user:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AIDA<obfuscated id>:*",
"AIDA<obfuscated id>",
"111111111111"
]
}
}
}
]
}
Can anyone help me edit the above JSON to allow for an OR condition where I could specify an additional userid that would be allowed access?
AdvThanksance!

Ok, I figured this out.
First, I tried adding a second StringgNotLike clause to the Condition, but that didn't work.
After doing as bit more reading, I realized the Condition clause accepts multiple key/value pairs. In fact, the original policy I showed in my question already did that. I just needed to add more values to the array that was already there.
The policy that works, looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-private-bucket",
"arn:aws:s3:::my-private-bucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AIDA<obfuscated-id-1>:*",
"AIDA<obfuscated-id-1>",
"AIDA<obfuscated-id-2>:*",
"AIDA<obfuscated-id-2>",
"111111111111"
]
}
}
}
]
}
When I realized that the key had already specified an array of values, I just added the second user id to the array, and it worked great.

Related

IAM Policy with action: autoscaling:AttachLoadBalancerTargetGroups, Filters access based on the ARN of a target group is not working

We have an autoscaling group with 2 target groups. We want to allow a team to Detach/Attach one of these target groups and prevent them from detaching/attaching the other one. So we build this IAM Policy Statement:
{
"Sid": "AsgPolicy",
"Effect": "Allow",
"Action": [
"autoscaling:AttachLoadBalancerTargetGroups",
"autoscaling:DetachLoadBalancerTargetGroups"
],
"Resource": "arn:aws:autoscaling:*:${account}:autoScalingGroup:*:autoScalingGroupName/app-${env}-frontend",
"Condition": {
"StringLike": {
"autoscaling:TargetGroupARNs": "arn:aws:elasticloadbalancing:*:${account}:targetgroup/app-${env}-target/*"
}
}
}
Even though everything seems correct, whenever I use the role with this policy to detach/attach the target group in the condition I get this error:
Detaching target groups failed
User: arn:aws:sts::11111111111:assumed-role/role-name/username is not authorized to perform: autoscaling:DetachLoadBalancerTargetGroups on resource: arn:aws:autoscaling:eu-west-1:11111111111:autoScalingGroup:ad6d28fa-b472-44e9-9ec4-e39bab5cd364:autoScalingGroupName/app-dev-frontend because no identity-based policy allows the autoscaling:DetachLoadBalancerTargetGroups action
I don't understand where is the issue.
The condition is important because only one target should be updated and not the other one.
Thanks
For permission statement to work properly, ”Resource” resource types specified should be supported by actions listed under “Actions”. Not every resource type can be specified with every action. If you specify a resource that is not valid for the action, any request to use that action fails, and the statement's Effect does not apply.
In your case, actions "autoscaling:AttachLoadBalancerTargetGroups" and "autoscaling:DetachLoadBalancerTargetGroups" does not support resource-level permissions (shown as blank in the actions table under “Resource types” column of Actions Table) and you must specify all resources ("*") in your policy.
You can try with this policy:
{
"Sid": "AsgPolicy",
"Effect": "Allow",
"Action": [
"autoscaling:AttachLoadBalancerTargetGroups",
"autoscaling:DetachLoadBalancerTargetGroups"
],
"Resource": "*",
"Condition": {
"StringLike": {
"autoscaling:TargetGroupARNs": "arn:aws:elasticloadbalancing:*:${account}:targetgroup/app-${env}-target/*"
}
}
}
Here is the solution for this issue, it worked perfectly:
{
"Sid": "AsgDetach",
"Effect": "Allow",
"Action": [
"autoscaling:AttachLoadBalancerTargetGroups",
"autoscaling:DetachLoadBalancerTargetGroups"
],
"Resource": "*",
"Condition": {
"ForAnyValue:StringLike": {
"autoscaling:TargetGroupARNs": "arn:aws:elasticloadbalancing:*:*xxx"
}
}
}

Permission Boundary IAM role denying attaching administrator policy

Can anyone point me how to accomplish the instruction below. I am trying to find it playing with roles and policy but I can't find any way to accomplish a granular approach to deny attaching administrator policy and maintaining other IAM rights.
Set an IAM permission boundary on the development IAM role that explicitly denies attaching the administrator policy
You need to combine multiple conditions to achieve this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IAMPermissions",
"Effect": "Allow",
"Action": [
"iam:*"
],
"Resource": "*"
},
{
"Sid": "DenyAttachAdministratorPolicy",
"Effect": "Deny",
"Action": [
"iam:AttachRolePolicy"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::012345678912:policy/MyPermissionBoundary"
},
"ArnEquals": {
"iam:PolicyARN": "arn:aws:iam::aws:policy/AdministratorAccess"
}
}
}
]
}
(You would need to update the PB ARN mentioned in the policy)
Note that this might not handle other edge cases where a malicious user could potentially attach AdministratorAccess to something else and escalate their privileges (e.g. via a Lambda function or container maybe?).

How do you dynamically create an AWS IAM policy document with a variable number of resource blocks using terraform?

In my current terraform configuration I am using a static JSON file and importing into terraform using the file function to create an AWS IAM policy.
Terraform code:
resource "aws_iam_policy" "example" {
policy = "${file("policy.json")}"
}
AWS IAM Policy definition in JSON file (policy.json):
{
"Version": "2012-10-17",
"Id": "key-consolepolicy-2",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::777788889999:root"
]
},
"Action": [
"kms:Decrypt"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::444455556666:root"
]
},
"Action": [
"kms:Decrypt"
],
"Resource": "*"
}
]
}
My goal is to use a list of account numbers stored in a terraform variable and use that to dynamically build the aws_iam_policy resource in terraform. My first idea was to try and use the terraform jsonencode function. However, it looks like there might be a way to implement this using the new terraform dynamic expressions foreach loop.
The sticking point seems to be appending a variable number of resource blocks in the IAM policy.
Pseudo code below:
var account_number_list = ["123","456","789"]
policy = {"Statement":[]}
for each account_number in account_number_list:
policy["Statement"].append(policy block with account_number var reference)
Any help is appreciated.
Best,
Andrew
The aws_iam_policy_document data source from aws gives you a way to create json policies all in terraform, without needing to import raw json from a file or from a multiline string.
Because you define your policy statements all in terraform, it has the benefit of letting you use looping/filtering on your principals array.
In your example, you could do something like:
data "aws_iam_policy_document" "example_doc" {
statement {
sid = "Enable IAM User Permissions"
effect = "Allow"
actions = [
"kms:*"
]
resources = [
"*"
]
principals {
type = "AWS"
identifiers = [
for account_id in account_number_list:
account_id
]
}
}
statement {
...other statements...
}
}
resource "aws_iam_policy" "example" {
// For terraform >=0.12
policy = data.aws_iam_policy_document.example_doc.json
// For terraform <0.12
policy = "${data.aws_iam_policy_document.example_doc.json}"
}
1st option:
if you don't want to rebuild the policy in aws_iam_policy_document you can use templatefile see https://www.terraform.io/docs/language/functions/templatefile.html
resource "aws_iam_policy" "example" {
policy = templatefile("policy.json",{account_number_list = ["123","456","789"]})
}
...
%{ for account in account_number_list ~}
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${account}:root"
},
"Action": "kms:*",
"Resource": "*"
},
%{ endfor ~}
...
2nd option:
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html#policy-vars-infotouse
AWS's IAM policy document syntax allows for replacement of policy
variables within a statement using ${...}-style notation, which
conflicts with Terraform's interpolation syntax. In order to use AWS
policy variables with this data source, use &{...} notation for
interpolations that should be processed by AWS rather than by
Terraform.
...
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::&{aws:userid}:root"
},
"Action": "kms:*",
"Resource": "*"
},
Like in: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
This was great and is a good pattern to be able to hold onto. Unfortunately, I ran into an issue with it going up against the quota limit:
Assume Role Policy: LimitExceeded: Cannot exceed quota for ACLSizePerRole: 2048
You can request an increase on this quota size but supposedly the max is 4098. the assume role policy I am attempting to create is needed for every AWS account we have so we will eventually hit that limit as well.
It's unfortunate that you can use wild cards within arns of an assume role policy but you can use "*" which I would argue is much much riskier.

AWS IAM Policy To Restrict S3 Access (Prefix) Based On IAM User's Tag

A variety of IAM users are sharing access to an S3 bucket. The S3 bucket has content separated by user so each user has a unique area they have access to.
For instance:
S3 Bucket: example-bucket.
IAM User: UserOne. This user is tagged with sampleTag=u11111.
IAM User: UserTwo. This user is tagged with sampleTag=u22222.
Many more tagged IAM users.
I'd like to write an IAM policy such that:
UserOne has access to read+write content to s3://example-bucket/u11111/* and read content from s3://example-bucket/config/u11111/
UserTwo has access to read+write content to s3://example-bucket/u22222/* and read content from s3://example-bucket/config/u22222/
Etc...
Note that the S3 key includes the value of the sampleTag in the path.
I'd like this single policy to be able to be applied to the entire group of IAM users without need to include an individual policy for each user.
I expected this to be possible thanks to ${aws:PrincipalTag/sampleTag} which I thought would inject the tag value in that location in the resource strings. But after playing with the policy simulator, it doesn't seem to accomplish this.
Current policy looks like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket-test"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket-test"
],
"Condition": {
"StringEquals": {
"s3:prefix": [
"",
"${aws:PrincipalTag/sampleTag}/"
],
"s3:delimiter": ["/"]
}
}
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket-test"
],
"Condition": {
"StringLike": {
"s3:prefix": ["${aws:PrincipalTag/sampleTag}/*"]
}
}
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectAcl",
"s3:GetObjectVersion",
"s3:GetObjectVersionAcl"
],
"Resource": [
"arn:aws:s3:::example-bucket/config/${aws:PrincipalTag/sampleTag}/*",
"arn:aws:s3:::example-bucket-test/config/${aws:PrincipalTag/sampleTag}/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectAcl",
"s3:GetObjectVersion",
"s3:GetObjectVersionAcl",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::example-bucket/${aws:PrincipalTag/sampleTag}/*",
"arn:aws:s3:::example-bucket-test/${aws:PrincipalTag/sampleTag}/*"
]
}
]
}
I think the last two policies to not work. I can't find documentation to say if you can embed variables into the Resource strings or not, but s3:prefix doesn't seem to be available for GetObject or PutObject operations -- so I'm not sure how else to restrict the scope of those permissions.
Any ideas as to what is wrong or how to accomplish this would be appreciated!
I think it is possible to use ${aws:PrincipalTag} in the Ressource property of a policy. Just look into the docs of IAM. The example uses the PrincipalTag as last part of the Ressource value.
(Just use Strg + F and type PrincipalTag on the docs website an you find the example)
You are right, the last two policies will not work.
According to documentation aws:PrincipalTag/tag-key works with string operators, hence aws:PrincipalTag/tag-key usage will only works inside Condition policy elements.
Also, s3:prefix condition key only work for ListBucket & ListBucketVersions actions: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazons3.html#amazons3-policy-keys
You can have PrincipalTag as part of the resource.
This works.
{
"Sid": "FullPermissionOnlyForPrefix",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::mybucket/${aws:PrincipalTag/team}*",
"arn:aws:s3:::mybucket/${aws:PrincipalTag/team}/*"
]
}

Create a single IAM user to access only specific S3 bucket

I have many S3 buckets in my AWS account. But now I created an IAM user and a new S3 bucket, I would like to give this user the ability to access the new S3 bucket using a client like CyberDuck.
I tried to create so many policies. But after that this user getting permission to list all my other buckets also. How can I give access to listing and writing access to a single S3 bucket?
First you create a Policy to allow access to a single S3 bucket (IAM -> Policies -> Create Policy). You can use AWS Policy Generator (http://awspolicygen.s3.amazonaws.com/policygen.html), it should look something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1528735049406",
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:HeadBucket",
"s3:ListBucket",
"s3:ListObjects",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::YOURBUCKETNAME"
}
]
}
Save the policy and note the name you gave to it, then go to IAM -> Users and select the desired user. In the permissions tab, click 'Add permissions', then select 'Attach existing policies directly' near the top. Find your policy by its name, tick its checkbox and complete the process.
Per this ( https://aws.amazon.com/blogs/security/writing-iam-policies-grant-access-to-user-specific-folders-in-an-amazon-s3-bucket/ )
they’ll need to be able to at least list all the buckets. But other than that, this also provides an example policy, which I just used last night for my own account, so I can confirm that it works.
Update
Okay, I've tested and confirmed using CyberDuck that the following policy (customized to your environment of course) will prevent users from viewing all root buckets, and only allow them access to the bucket you specify:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllInBucket",
"Action": [
"s3:*"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::bucket-for-single-user"
}
]
}
Just make sure that when you specify the path in CyberDuck, that you enter it as: bucket-for-single-user.s3.amazonaws.com.
Also, only START unrestricted like that, just to make sure it's working for you (since access appears to be an issue). After that, apply restrictions, you know...least privilege and all.
According to Cyberduck Help / Howto / Amazon S3, it supports directly entering the Bucket name, as <bucketname>.s3.amazonaws.com. If this is possible with the client you are using, you don't need s3:ListAllMyBuckets permissions.
Actions should be grouped by the Resources that they can parse
(Conditions are also potentially different per Action).
This IAM policy will allow full control of all the content (aka in the bucket)
without controlling of the S3 bucket subresources (aka of the bucket):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BucketOperations",
"Effect": "Allow",
"Action": "s3:ListBucket*",
"Resource": "arn:aws:s3:::<bucketname>"
},
{
"Sid": "ObjectOperations",
"Effect": "Allow",
"Action": [
"s3:AbortMultipartUpload",
"s3:ListMultipartUploads",
"s3:DeleteObject*",
"s3:GetObject*",
"s3:PutObject*"
],
"Resource": "arn:aws:s3:::<bucketname>/*"
},
{
"Sid": "DenyAllOthers",
"Effect": "Deny",
"Action": "s3:*",
"NotResource": [
"arn:aws:s3:::<bucketname>",
"arn:aws:s3:::<bucketname>/*"
]
}
]
}
If you aren't specifically trying to lock the IAM user out of every
possible public S3 bucket, you can leave the "DenyAllOthers" Sid off,
without granting additional permissions to the users.
FYI, the AWS ReadOnlyAccess policy automatically gives s3:* to
anything it's attached to. I recommend ViewOnlyAccess (which will
unfortunately grant s3:ListAllMyBuckets without the DenyAllOthers).
Create my own policy and working for me. The IAM user can just list all bucket. But cant do anything on another bucket. The user can only get access to the specific bucket with reading, write, delete files privileges.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "<EXAMPLE_SID>",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::<MYBUCKET>"
},
{
"Sid": "<EXAMPLE_SID>",
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "*"
}, {
"Sid": "<EXAMPLE_SID>",
"Effect": "Deny",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::<MYotherBUCKET>"
}, {
"Sid": "<EXAMPLE_SID>",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::<MYBUCKET>/*"
}
]
}
Then add this policy also to this user. This policy will restrict all type of operation to listed other s3 bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "<EXAMPLE_SID>",
"Effect": "Deny",
"Action": [
"s3:PutAnalyticsConfiguration",
"s3:GetObjectVersionTagging",
"s3:CreateBucket",
"s3:ReplicateObject",
"s3:GetObjectAcl",
"s3:DeleteBucketWebsite",
"s3:PutLifecycleConfiguration",
"s3:GetObjectVersionAcl",
"s3:PutBucketAcl",
"s3:PutObjectTagging",
"s3:DeleteObject",
"s3:GetIpConfiguration",
"s3:DeleteObjectTagging",
"s3:GetBucketWebsite",
"s3:PutReplicationConfiguration",
"s3:DeleteObjectVersionTagging",
"s3:GetBucketNotification",
"s3:PutBucketCORS",
"s3:DeleteBucketPolicy",
"s3:GetReplicationConfiguration",
"s3:ListMultipartUploadParts",
"s3:PutObject",
"s3:GetObject",
"s3:PutBucketNotification",
"s3:PutBucketLogging",
"s3:PutObjectVersionAcl",
"s3:GetAnalyticsConfiguration",
"s3:GetObjectVersionForReplication",
"s3:GetLifecycleConfiguration",
"s3:ListBucketByTags",
"s3:GetInventoryConfiguration",
"s3:GetBucketTagging",
"s3:PutAccelerateConfiguration",
"s3:DeleteObjectVersion",
"s3:GetBucketLogging",
"s3:ListBucketVersions",
"s3:ReplicateTags",
"s3:RestoreObject",
"s3:GetAccelerateConfiguration",
"s3:GetBucketPolicy",
"s3:PutEncryptionConfiguration",
"s3:GetEncryptionConfiguration",
"s3:GetObjectVersionTorrent",
"s3:AbortMultipartUpload",
"s3:PutBucketTagging",
"s3:GetBucketRequestPayment",
"s3:GetObjectTagging",
"s3:GetMetricsConfiguration",
"s3:DeleteBucket",
"s3:PutBucketVersioning",
"s3:PutObjectAcl",
"s3:ListBucketMultipartUploads",
"s3:PutMetricsConfiguration",
"s3:PutObjectVersionTagging",
"s3:GetBucketVersioning",
"s3:GetBucketAcl",
"s3:PutInventoryConfiguration",
"s3:PutIpConfiguration",
"s3:GetObjectTorrent",
"s3:ObjectOwnerOverrideToBucketOwner",
"s3:PutBucketWebsite",
"s3:PutBucketRequestPayment",
"s3:GetBucketCORS",
"s3:PutBucketPolicy",
"s3:GetBucketLocation",
"s3:ReplicateDelete",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::<MYotherBUCKET>/*",
"arn:aws:s3:::<MYotherBUCKET>"
]
}
]
}
I was recently able to get this to work using Amazon's documentation. The key for me was to point the IAM User to the specific bucket NOT the S3 console. Per the documentation, "Warning: After you change these permissions, the user gets an Access Denied error when they access the main Amazon S3 console. The main console link is similar to the following:
https://s3.console.aws.amazon.com/s3/home
Instead, the user must access the bucket using a direct console link to the bucket, similar to the following:
https://s3.console.aws.amazon.com/s3/buckets/awsexamplebucket/"
My policy is below:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1589486662000",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::AWSEXAMPLEBUCKET",
"arn:aws:s3:::AWSEXAMPLEBUCKET/*"
]
}
]
}