Demystifying AWS IAM Policies: Unraveling De Morgan’s Laws and S3 Buckets Policy

Condition Evaluation

Before we get into it we need to review how Condition evaluation works:

The images on this page does a nice job of summarizing.

Evaluation logic for multiple context keys or values

Evaluation logic for negated matching condition operators

New IAM Deny reason Error Messages

As of September 2021 You will now get error messages that detail the source of a IAM access block for the following policy types:

  • SCP

  • VPC Endpoint policy

  • Permission Boundaries

  • Session Policies

  • Resource Based policy

  • Identity Based policy

This saves a lot of time especially if you do not have access to all of these.

When there is no additional context

For S3 if you do not have permission on the default KMS key that is configured on the bucket you will get an AccessDenied with no further information.

A good indicator of this is if you can list_objects which requires no KMS permission. If that works, but PutObject or GetObject give AccessDenied check very closely that say your lambda role has permission to use the default KMS key of the bucket.

De Morgan’s laws and IAM

We all know that multiple conditions in a IAM Condition statement are joined by logical AND. Having an understanding of using Not operator conditions in AWS IAM policy by applying De Morgans laws can give you powerful flexibility, that can be mind boggling to read and understand.

Lets take a look at this example policy. Have a think about what will happen if you access it using the named role via the internet S3 endpoint.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DenyNonVPCAccessExceptNamedRole",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::your-bucket-name",
                "arn:aws:s3:::your-bucket-name/*"
            ],
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpc": ["vpc-123", "vpc-456"]
                },
                "ArnNotEquals": {
                    "aws:PrincipalArn": "arn:aws:iam::12345678902:role/role_that_cannot_use_vpc"
                }
            }
        }
    ]
}

This Bucket policy will deny all access unless it is from the named VPC - If the role is arn:aws:iam::12345678902:role/role_that_cannot_use_vpc and not from a VPC allow it.

Lets break it down:

The StringNotEquals condition for aws:sourceVpc ensures that the request’s source VPC is not equal to vpc-123. The ArnNotEquals condition for aws:PrincipalArn checks if the request’s principal ARN is not equal to arn:aws:iam::12345678902:role/role_that_cannot_use_vpc. if you access the bucket using the role called role_that_cannot_use_vpc and do not access it from the VPCs listed in the policy:

If bucket is accessed from a VPC other than vpc-123 using the role role_that_cannot_use_vpc the StringNotEquals condition for aws:sourceVpc evaluates to true since the VPC ID does not match. However, the ArnNotEquals condition for aws:PrincipalArn evaluates to false since the principal ARN matches the specified role ARN. As a result, the deny statement is not triggered, and access to the bucket is allowed.

If bucket is accessed from VPC vpc-123 but not using the role role_that_cannot_use_vpc, the StringNotEquals condition for aws:sourceVpc evaluates to false since the VPC ID matches. The ArnNotEquals condition for aws:PrincipalArn evaluates to true since the principal ARN does not match the specified role ARN. The deny statement is not triggered, and access to the bucket is allowed.

If bucket is accessed from a VPC other than vpc-123 and using a role other than role_that_cannot_use_vpc, both conditions (StringNotEquals for aws:sourceVpc and ArnNotEquals for aws:PrincipalArn) evaluate to true. Consequently, the deny policy statement is not triggered, and access to the bucket is allowed.

Other S3

VPC Endpoint Policy does not allows listing objects in bucket:

{
	"Version": "2008-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": "*",
			"Action": "*",
			"Resource": "arn:aws:s3:::aws-logs-003422198502-ap-southeast-1/*"
		}
	]
}

ubuntu@ip-10-0-9-197:~$ aws s3 ls   aws-logs-003422198502-ap-southeast-1/elasticmapreduce

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:sts::003422198502:assumed-role/adminrole/i-0bf2c5ae5ae2b566c is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::aws-logs-003422198502-ap-southeast-1" because no VPC endpoint policy allows the s3:ListBucket action

But when we remove the /* from the resource from the endpoint policy:


{ “Version”: “2008-10-17”, “Statement”: [ { “Effect”: “Allow”, “Principal”: “”, “Action”: “”, “Resource”: “arn:aws:s3:::aws-logs-003422198502-ap-southeast-1” } ] }


It works:

ubuntu@ip-10-0-9-197:~$ aws s3 ls aws-logs-003422198502-ap-southeast-1 PRE bucet2brentvc/ PRE elasticmapreduce/

``

750263812530 org child brent-orgchild bukcet name

“aws:PrincipalOrgPaths”: “o-r2rjrevijr/r-3hbo/ou-3hbo-nmya1n36/*”

Consider the following:

      {
       "Action": [
        "s3:GetObject*",
        "s3:ListBucket"
       ],
       "Condition": {
        "StringEquals": {
         "aws:ResourceOrgId": [
          "o-r2rjrevijr"
         ],
         "aws:PrincipalOrgPaths": [
          "o-r2rjrevijr/r-3hbo/*"
         ],
         "aws:ResourceAccount": [
          "750263812530"
         ]
        }
       },
       "Effect": "Allow",
       "Principal": "*",
       "Resource": "*"
      },

aws:PrincipalOrgPaths is a multi valued key, and must be used with a set condition key operator such as ForAnyValue:StringLike it will resolve to false if used with only StringEquals

Now consider the incorrect use in the following policy:

This following is allowed is because the statement-1 evaluates to false but is not a Deny statement, and statement-2 evaluates to true and is an Allow:

{
	"Version": "2008-10-17",
	"Statement": [

		{
            "sid": "Statement-1",
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject*",
				"s3:ListBucket"
			],
			"Resource": "*",
			"Condition": {
				"StringEquals": {
					"aws:PrincipalOrgPaths": "o-r2rjrevijr/r-3hbo/*",
					"aws:ResourceAccount": "750263812530",
					"aws:ResourceOrgId": "o-r2rjrevijr"
				}
			}
		},
		{
            "sid": "Statement-2",
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject*",
				"s3:ListBucket"
			],
			"Resource": "*",
			"Condition": {
				"StringEquals": {
					"aws:ResourceOrgId": "o-r2rjrevijr"
				}
			}
		}
	]
}

However if we remove statement-2 it is denied:

  • This is because the statement-1 evaluates to false but is not a Deny statement , since there is no other policy to allow this it is denied.

{
	"Version": "2008-10-17",
	"Statement": [

		{
            "sid": "Statement-1",
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject*",
				"s3:ListBucket"
			],
			"Resource": "*",
			"Condition": {
				"StringEquals": {
					"aws:PrincipalOrgPaths": "o-r2rjrevijr/r-3hbo/*",
					"aws:ResourceAccount": "750263812530",
					"aws:ResourceOrgId": "o-r2rjrevijr"
				}
			}
		}
	]
}

Comments

comments powered by Disqus