AWS Security Blog

IAM makes it easier for you to manage permissions for AWS services accessing your resources

November 24, 2023: This post has been updated to show the differences between accessing data by way of an AWS service over public endpoints and over AWS PrivateLink (data access pattern 2).

July 7, 2023: This post had been updated to use Amazon S3 Replication as an example in Data access pattern 3b section.


Amazon Web Services (AWS) customers are storing an unprecedented amount of data on AWS for a range of use cases, including data lakes and analytics, machine learning, and enterprise applications. Customers secure their data by implementing data security controls including identity and access management, network security, and encryption. For non-public, sensitive data, customers want to make sure that it’s only accessible by authorized users from known locations. Customers implement identity and network-based data loss prevention controls to help ensure that access to sensitive data is restricted to trusted identities, such as your authorized corporate users, and expected network locations, such as your corporate network or your Amazon VPC. Some AWS services need to access your resources using their own identities and from outside of your network locations. In this post, we show how you can use aws:PrincipalIsAWSService, a new global AWS Identity and Access Management (IAM) condition key, to write policies that restrict access to your data from untrusted identities and unexpected network locations while safely granting access to AWS services. We discuss how to use the new condition key, provide sample policies that show its usage, and show how you can incorporate it into your organization’s data security strategy.

New global IAM condition key

aws:PrincipalIsAWSService is a global IAM condition key that simplifies resource-based policies (such as an Amazon S3 bucket policy) when granting access to AWS services. It gives you a shorthand for allowing AWS services to access your resources and can be used alongside other desired restrictions, such as restricting access to your networks. Since the purpose of this condition key is to simplify how AWS services interact with your resources, the examples in this post primarily cover resource-based policies. We use AWS CloudTrail to illustrate how this condition key can be used. With CloudTrail, you can log, continuously monitor, and retain account activity related to actions across your AWS infrastructure. CloudTrail allows you to create a trail, enabling ongoing delivery of events as log files to an Amazon Simple Storage Service (Amazon S3) bucket that you specify. Consider a scenario where you want to allow CloudTrail to write data to your S3 bucket directly from its service account but also want to help ensure that additional access from your identities is restricted to your network, such as your Amazon VPC, as illustrated in Figure 1.

Figure 1: Access from trusted network and from CloudTrail

Figure 1: Access from trusted network and from CloudTrail

The new aws:PrincipalIsAWSService condition key can be used to implement a bucket policy that limits access to your data from your VPC while safely granting access to an AWS service, such as CloudTrail. Replace <my-logs-bucket>, <AccountNumber>, and <vpc-111bbb22> with your information in the following example:

	 <CloudTrail standard cross account bucket policy configuration>
       …
      {
         "Sid":"expected-network+service-principal",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:PutObject*",
            "s3:GetObject*",
            "s3:ListBucket"
         ],
         "Resource":"arn:aws:s3:::<my-logs-bucket>/AWSLogs/<AccountNumber>/*",
         "Condition":{
            "StringNotEquals":{
               "aws:SourceVpc":"<vpc-111bbb22>"
            },
            "Bool":{
               "aws:PrincipalIsAWSService":"false"
            }
         }
      }

The above policy statement limits s3:PutObject and s3:GetObject actions to the VPC while exempting AWS services from this condition. The aws:PrincipalIsAWSService condition key works with Boolean condition operators that restrict access based on comparing a key to true or false. In the preceding statement, access to the bucket containing CloudTrail data is restricted unless the request originates from the specified VPC (<vpc-111bbb22>) or from an AWS service (CloudTrail in this case).

Note: the complete policy, including the necessary Allow statements for cross-account access, is covered later in this post.

aws:PrincipalIsAWSService as part of a data perimeter

You have just read about the new aws:PrincipalIsAWSService global condition key and its basic usage. Keep reading to learn how you can use the new condition key to establish a data perimeter. Simply put, a data perimeter is a set of preventive guardrails that ensures that only customers’ identities are accessing their own data in the cloud from expected network locations. One of the key data perimeter controls is to configure your resources to be accessible only by trusted identities and from expected network locations. Let’s assume you have sensitive data stored in an S3 bucket. As part of establishing a data perimeter, you want to restrict access to that data to network locations—such as your VPC or on-premises network. Let’s see how you can restrict access based on four common data access patterns while securely exempting AWS services that also need to access your resources from outside of your network. Knowing the different data access patterns will help you understand when and how to use this new condition key, along with other IAM condition keys, to securely exempt AWS services.

1. Direct access of your identities to data

The most basic data access pattern is when one of your IAM principals (roles or users) from your AWS account directly accesses data within an S3 bucket. For example, a user logs in to the AWS Management Console to upload an object to an S3 bucket as shown in Figure 2.

Figure 2: Direct access of your identities to data (data access pattern 1)

Figure 2: Direct access of your identities to data (data access pattern 1)

With this access pattern, your bucket policy can constrain the permissions you have already granted as follows. Replace <my-data-bucket> and <203.0.113.0/24> with your information.

{
   "Version":"2012-10-17",
   "Id":"S3BucketPolicyId1",
   "Statement":[
      {
         "Sid":"expected-network",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:ListBucket",
            "s3:PutObject*",
            "s3:GetObject*"
         ],
         "Resource":[
            "arn:aws:s3:::<my-data-bucket>",
            "arn:aws:s3:::<my-data-bucket>/*"
         ],
         "Condition":{
            "NotIpAddress":{
               "aws:SourceIp":"<203.0.113.0/24>"
            }
         }
      }
   ]
}

With the aws:SourceIp condition in the preceding policy, users are denied access to list, put, and get objects in or out of the S3 bucket unless the API call originates from within their corporate network.

Note: This and subsequent examples use a Deny statement to constrain the permissions you have already granted to help illustrate an effective data perimeter policy. The principals also require an identity-based policy with the appropriate Allow permissions to write to this bucket, which isn’t depicted in the examples. Similarly, for cross-account access, appropriate Allow statements must be added to the bucket policy for authorized principals.

The policies include a subset of s3 data access actions instead of s3:* to prevent unintentional lockout from your bucket, which could occur if you edit bucket policies outside of your specified network locations. You might consider expanding the action list to include other actions such as DeleteObject, RestoreObject, or PutBucketPolicy, depending on your requirements, or even s3:* as long as you can perform all actions from within your defined network perimeter.

2. Direct access to data by way of an AWS service

This second access pattern applies when you access the data via an AWS service and that service takes subsequent actions on behalf of the IAM principal. A common example of this access pattern is Amazon Athena. Athena is an interactive query service that lets you use standard SQL to query data in Amazon S3.

2a. Direct access to data by way of an AWS service – using public service endpoints

To continue the preceding example, let’s assume you want to restrict users from accessing the S3 bucket unless the API call originates from within their corporate network but also allow them to query the data via Athena, using Athena’s public endpoint. An example of this would be to access Athena using the AWS Management Console as shown in Figure 3.

Figure 3: Direct access to data by way of an AWS service using public service endpoints (data access pattern 2a)

Figure 3: Direct access to data by way of an AWS service using public service endpoints (data access pattern 2a)

In this scenario, you don’t need to modify your bucket policy, because the requester’s IP address is passed by Athena to Amazon S3. S3 then uses the requester’s IP address to make an authorization decision by comparing it with the IP address that you specify in the aws:SourceIp condition key in the S3 bucket policy.

2b. Direct access to data by way of an AWS service – using AWS PrivateLink

Let’s now assume a slightly different scenario where your users or applications need to access your data using Athena through AWS PrivateLink. An example of this would be an application that uses the AWS SDK to run an Athena query from a VPC that has the Athena VPC endpoint configured, as shown in Figure 4.

Figure 4: Direct access to data by way of an AWS service using AWS PrivateLink (data access pattern 2b)

Figure 4: Direct access to data by way of an AWS service using AWS PrivateLink (data access pattern 2b)

In this scenario, since the request comes from a host that uses a VPC endpoint to access the Athena service, the aws:SourceIp key is not available (see aws:SourceIp for further reference). Also, unlike with aws:SourceIp, the requester’s source VPC is not passed when Athena is making a call to S3. To account for this access pattern, you can update your bucket policy by adding the condition key aws:CalledViaFirst with StringNotEquals, as shown in the following example.

{
    "Version": "2012-10-17",
    "Id": "S3BucketPolicyId1",
    "Statement": [
        {
            "Sid": "expected-network+athena",
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject*",
                "s3:GetObject*"
            ],
            "Resource": [
                "arn:aws:s3:::<my-data-bucket>",
                "arn:aws:s3:::<my-data-bucket>/*"
            ],
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "<203.0.113.0/24>"
                },
                "StringNotEquals": {
                    "aws:CalledViaFirst": "athena.amazonaws.com"
                }
            }
        }
    ]
}

We now have a Deny statement with two negated condition keys. This means that both conditions must resolve to true to trigger the Deny effect. The condition statement in the preceding policy now reads as follows: deny the three S3 actions unless they originate from your corporate network (NotIpAddress with aws:SourceIp) or via the Athena service (StringNotEquals with aws:CalledViaFirst). We are using aws:CalledViaFirst (a single value key) instead of aws:CalledVia (a multivalued key), because a single value key is more simple to reason about when used with a StringNotEquals condition. For more information on how to use aws:CalledViaFirst (and aws:CalledVia), see How to define least-privileged permissions for actions called by AWS services. See also Creating a condition with multiple keys or values for more details on the evaluation logic.

3. Intermediate IAM roles for data access

A third common pattern is to use an AWS service role. In this scenario, a given AWS service assumes a service role that you created to perform actions on your behalf. Since the AWS service is using a service role rather than making a request on the principal’s behalf, you cannot use the aws:CalledViaFirst condition key from the previous example. This access pattern has two variations which will determine how we grant AWS services access to your resources.

3a. API call originates from your VPC

The first is when the API call originates from within your expected network, such as within your VPC. A good example of this is AWS Glue. AWS Glue is a serverless data integration service that makes it simpler to discover, prepare, and combine data for analytics, machine learning, and application development. To continue further with the preceding example, let’s assume you want to restrict users from accessing the data in your S3 bucket unless the API call originates from within your corporate network but also allow them to use AWS Glue to crawl the data to update the schema in the AWS Glue Data Catalog. In the case of AWS Glue, you can configure the crawler to use your network with network connection to Amazon S3 by specifying the desired VPC ID, subnet IDs, and security group as shown in Figure 5.

Figure 5: Intermediate IAM roles for data access from customer’s VPC (data access pattern 3a)

Figure 5: Intermediate IAM roles for data access from customer’s VPC (data access pattern 3a)

With this pattern, you simply extend your data perimeter to include your VPC network by adding the aws:SourceVpc condition to StringNotEquals, as shown in the following example. Replace each <placeholder> with your values.

{
   "Version":"2012-10-17",
   "Id":"S3BucketPolicyId1",
   "Statement":[
      {
         "Sid":"expected-network+athena",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:ListBucket",
            "s3:PutObject*",
            "s3:GetObject*"
         ],
         "Resource":[
            "arn:aws:s3:::<my-data-bucket>",
            "arn:aws:s3:::<my-data-bucket>/*"
         ],
         "Condition":{
            "NotIpAddress":{
               "aws:SourceIp":"<203.0.113.0/24>"
            },
            "StringNotEquals":{
               "aws:CalledViaFirst":"athena.amazonaws.com",
               "aws:SourceVpc":"<vpc-111bbb22>"
            }
         }
      }
   ]
}

The preceding policy adds the aws:SourceVPC condition in the same block as the aws:CalledViaFirst condition that was added earlier. We now have a Deny statement with three negated condition keys. This means that all three conditions must resolve to true to trigger the Deny effect. Therefore, this policy denies a call to Amazon S3 only if the call does not originate from your on-premises network, is not made via the Athena service, and does not originate from your VPC network.

3b. API call originates from outside of your VPC

While some AWS services that use a service role access your resources directly from your VPC such as AWS Glue, there’s a second variation of this access pattern when a service role needs to access the data in an S3 bucket from outside of your VPC. A good example of such a pattern is replicating objects using the Amazon S3 Replication feature. If you apply the preceding bucket policy, the replication will fail. This happens because the calls to Amazon S3 are made from outside of the VPC by a service role associated with the replication configuration. This is shown in Figure 6.

Figure 6: Intermediate IAM roles for data access from outside of the customer’s VPC (data access pattern 3b)

Figure 6: Intermediate IAM roles for data access from outside of the customer’s VPC (data access pattern 3b)

To account for this access pattern, you can update your bucket policy to include the aws:PrincipalArn condition key as part of the StringNotEquals statement as shown in the following example. Replace each <placeholder> with your values.

{
   "Version":"2012-10-17",
   "Id":"S3BucketPolicyId1",
   "Statement":[
      {
         "Sid":"expected-network+athena+service-role",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:ListBucket",
            "s3:PutObject*",
            "s3:GetObject*"
         ],
         "Resource":[
            "arn:aws:s3:::<my-data-bucket>",
            "arn:aws:s3:::<my-data-bucket>/*"
         ],
         "Condition":{
            "NotIpAddress":{
               "aws:SourceIp":"<203.0.113.0/24>"
            },
            "StringNotEquals":{
               "aws:CalledViaFirst":"athena.amazonaws.com",
               "aws:SourceVpc":"<vpc-111bbb22>",
"aws:PrincipalArn":"arn:aws:iam::<AccountNumber>:role/<AmazonS3ReplicationRole>"
            }
         }
      }
   ]
}

With the preceding policy, you’re effectively excluding the Amazon S3 replication role — <AmazonS3ReplicationRole> — associated with the replication configuration from the SourceIp and SourceVpc restrictions. You can make this exception because the Amazon S3 bucket replication role is configured with a trust policy that allows only the Amazon S3 service to assume it — only s3.amazonaws.com can assume the role. It is a best practice to apply the principle of least privilege to help that only authorized users are allowed to modify the trust policy of the role and to pass the role as part of the replication configuration.

4. AWS services with direct access to your resources

In the previous three examples, data in the bucket is accessed directly by your trusted identity, directly via an AWS service (Athena), or by a trusted intermediary service role (AWS Glue and Amazon S3). However, there’s one final access pattern where the AWS service uses its own identity — a service principal — to perform an action on behalf of the customer. A good example of this access pattern is the CloudTrail use case we introduced at the start of this blog post, shown in Figure 7.

Figure 7: AWS services with direct access to your resources (data access pattern 4)

Figure 7: AWS services with direct access to your resources (data access pattern 4)

Let’s assume your data perimeter objective is to restrict access to the logs in your S3 bucket from either your VPC or the CloudTrail service. If you craft a bucket policy that restricts access to only your VPC using aws:SourceVpc condition alone, you effectively prevent CloudTrail from writing data to your bucket. You cannot use aws:CalledViaFirst to exclude CloudTrail as shown in data access pattern #2 above because CloudTrail is using its own service principal to write data to your bucket (Note: although as part of the CalledVia condition you also specify a service principal, such as athena.amazonaws.com, CalledVia only applies when the service is making a request on behalf of the calling principal, as opposed to CloudTrail, where the service is directly writing data to your bucket). You also cannot use aws:PrincipalArn as shown in data access pattern #3b because CloudTrail uses a service principal and not an ARN. By adding the new aws:PrincipalIsAWSService condition to your bucket policy, you can achieve your data perimeter objective as follows. Replace each <placeholder> with your values.

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Sid":"grant-read-access-to-cloudtrail",
         "Effect":"Allow",
         "Principal":{
            "Service":"cloudtrail.amazonaws.com"
         },
         "Action":"s3:GetBucketAcl",
         "Resource":"arn:aws:s3:::<my-logs-bucket>"
      },
      {
         "Sid":"grant-write-access-to-cloudtrail",
         "Effect":"Allow",
         "Principal":{
            "Service":"cloudtrail.amazonaws.com"
         },
         "Action":"s3:PutObject",
         "Resource":"arn:aws:s3:::<my-logs-bucket>/AWSLogs/<AccountNumber>/*",
         "Condition":{
            "StringEquals":{
               "s3:x-amz-acl":"bucket-owner-full-control"
            }
         }
      },
      {
         "Sid":"expected-network+service-principal",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:PutObject*",
            "s3:GetObject*",
			"s3:ListBucket"
         ],
"Resource":"arn:aws:s3:::<my-logs-bucket>/AWSLogs/<AccountNumber>/*",
         "Condition":{
            "StringNotEquals":{
               "aws:SourceVpc":"<vpc-111bbb22>"
            },
            "BoolIfExists":{
               "aws:PrincipalIsAWSService":"false"
            }
         }
      }
   ]
}

The first two Allow statements in the preceding bucket policy are part of the standard cross account bucket policy configuration for CloudTrail. The last statement—expected-network+service-principal—uses a combination of aws:SourceVpc and the newly launched aws:PrincipalIsAWSService conditions to deny access unless the call originates from your VPC network, or is made by an AWS service principal, such as CloudTrail.

Data perimeter policy for common data access patterns

Now that you have reviewed the common data access patterns and various IAM condition keys, including the new aws:PrincipalIsAWSService, let’s look at a data perimeter policy. This sample policy can be appended to your other buckets or other resource-based policies. Replace each <placeholder> with your values.

Note: appending policies to existing resources may cause an unintended disruption to your application. Consider testing your policies in lower environments before applying them to production resources.

{
   "Version":"2012-10-17",
   "Id":"S3BucketPolicyId1",
   "Statement":[
      {
         "Sid":"network-data-perimeter",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:ListBucket",
            "s3:PutObject*",
            "s3:GetObject*"
         ],
         "Resource":[
            "arn:aws:s3:::<my-data-bucket>",
            "arn:aws:s3:::<my-data-bucket>/*"
         ],
         "Condition":{
            "NotIpAddress":{
               "aws:SourceIp":[
                  "<203.0.113.0/24>"
               ]
            },
            "StringNotEquals":{
               "aws:SourceVpc":[
                  "<vpc-111bbb22>"
               ],
             "aws:PrincipalArn":"arn:aws:iam::<AccountNumber>:role/<AmazonS3ReplicationRole>"
            },
            "Bool":{
               "aws:PrincipalIsAWSService":"false",
               "aws:ViaAWSService":"false"
            }
         }
      },
      {
         "Sid":"identity-data-perimeter",
         "Effect":"Deny",
         "Principal":"*",
         "Action":[
            "s3:ListBucket",
            "s3:PutObject*",
            "s3:GetObject*"
         ],
         "Resource":[
            "arn:aws:s3:::<my-data-bucket>",
            "arn:aws:s3:::<my-data-bucket>/*"
         ],
         "Condition":{
            "StringNotEquals":{
               "aws:PrincipalOrgID":"<o-yyyyyyyyyy>"
            },
            "Bool":{
               "aws:PrincipalIsAWSService":"false"
            }
         }
      }
   ]
}

The preceding policy consists of two statements. The first statement—network-data-perimeter—sets the expected network data perimeter. Let’s examine all of the condition elements in this statement:

Condition key Usage Example data access pattern
aws:SourceIp Use to restrict access to public IP ranges of your expected network when the request doesn’t originate over a VPC endpoint. Console access from an on-premises corporate network as discussed in data access pattern #1 and #2a.
aws:SourceVpc Use to restrict access to specific VPC IDs of your expected network if the request originates over a VPC endpoint. An application running on Amazon Elastic Compute Cloud (Amazon EC2) instance using an instance profile, a Lambda function deployed within a VPC, or an AWS Glue crawler configured with VPC network connection as discussed previously in data access pattern #3a.
aws:PrincipalArn Allows you to exclude a principal, such as a service role for an AWS service when the request doesn’t originate from your network. Amazon S3 Replication in data access pattern #3b.
aws:PrincipalIsAWSService Provides a straightforward way to allow access to an AWS service when the service uses its own service principal to access your bucket from its own network. Cannot be used when the AWS service makes a request on behalf of the IAM principal (such as in data access pattern #2, in which case you have to use aws:CalledVia instead). CloudTrail in data access pattern #4.
aws:ViaAWSService

This condition key is similar to aws:CalledVia and aws:CalledViaFirst, but instead of being limited to a specific AWS service (i.e. Athena), it can be used to allow or deny access to any AWS service (hence it’s either set to true or false) that makes a request on behalf of the IAM principal to access your resources as discussed in data access pattern #2b.

You typically wouldn’t use aws:CalledVia and aws:ViaAWSService in the same bucket policy. Instead, use aws:CalledVia for policies scoped to a specific AWS service and aws:ViaAWSService when you want to allow or deny an AWS service that makes a request on behalf of the IAM principal.

Amazon Athena in data access pattern 2b.

The second statement—identity-data-perimeter—in the preceding policy sets the trusted identity data perimeter. Let’s examine the two conditions in this statement:

Condition key Usage Data access pattern
aws:PrincipalOrgID Restricts access to trusted principals that belong to your AWS Organizations. See the blog post An easier way to control access to AWS resources by using the AWS organization of IAM principals for additional use cases for this powerful condition key. Used in resource based policies such as bucket policies and VPC endpoint policies.
aws:PrincipalIsAWSService Similar to the first statement in the preceding policy, you can use this condition key to allow AWS service to access your bucket from its network using its own service principal. CloudTrail in data access pattern #4.

Next steps

The newly launched aws:PrincipalIsAWSService condition key simplifies resource-based policies by providing a straightforward way to limit access to trusted identities and expected networks while at the same time granting access to AWS services that use their own service principal from outside of your network locations. You can also use this condition key as part of a broad data perimeter strategy across the common data access patterns we discussed in this blog post. If you have any questions, comments, or concerns, contact AWS Support or start a new thread on the AWS Identity and Access Management forum.

Thanks for reading about this new feature. If you have feedback about this post, submit comments in the Comments section below.

Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.

Author

Ilya Epshteyn

Ilya is a Senior Manager of Identity Solutions in AWS Identity. He helps customers to innovate on AWS by building highly secure, available, and scalable architectures. He enjoys spending time outdoors and building Lego creations with his kids.

Author

Harsha Sharma

Harsha is a Solutions Architect with AWS in New York. He joined AWS in 2016, and he works with Global Financial Services customers to design and develop architectures on AWS, supporting their journey on the AWS Cloud.