AWS Security Blog

How to define least-privileged permissions for actions called by AWS services

August 31, 2021: AWS KMS is replacing the term customer master key (CMK) with AWS KMS key and KMS key. The concept has not changed. To prevent breaking changes, AWS KMS is keeping some variations of this term. More info.

February 21, 2020: We fixed a missing comma in a policy example.

March 3, 2020: We added some clarifying language to the “Step 2: Define permissions on the S3 bucket” section.


When you perform certain actions in AWS, the service you called sometimes takes additional actions in other AWS services on your behalf. AWS Identity and Access Management (IAM) now includes condition keys to make it easier to grant only the minimum level of access necessary for IAM principals (users and roles) and AWS services to take those actions. Using the aws:CalledVia condition key, you can create distinct access rules for the actions performed by your IAM principals, and for the subsequent actions taken by AWS services on your behalf.

For example, if you use AWS CloudFormation to launch an Amazon Elastic Compute Cloud (EC2) instance, the CloudFormation service independently uses your credentials to launch the instance in EC2. Your principals need permissions from both services. Based on the principle of granting least privileged permissions, you might want to prevent your principals from taking each of those actions independently. Using the new condition, you can grant your IAM principals the ability to launch EC2 instances only by using CloudFormation, without granting them direct access to EC2.

You can also use the aws:CalledVia condition key to define rules for the initial call made to AWS by your principals, without impacting the additional calls the service makes. For example, you can require that all initial calls to AWS services come from inside your Virtual Private Cloud (VPC) or your private IP subnet, but you will not impose the same rule for downstream requests to other services, since those requests come from AWS rather than your private network.

In this post, I explain the aws:CalledVia condition key and outline the context it provides during authorization. Then, I walk through a detailed use case with examples that show you how to secure access to a database managed in Amazon Athena behind a VPC. In the examples, I’ll cover how to grant access to execute queries in Athena without granting direct access to dependent services such as Amazon Simple Storage Service (S3). I will also explain how you can use the aws:CalledVia condition key to prevent access to your databases from outside your private networks.

How to use the aws:CalledVia condition key

The aws:CalledVia condition key contains an ordered list of each service principal that triggers the AWS action in another service using your credentials. It is a global condition key, meaning you can use it in combination with any AWS service action. For example, say that you use CloudFormation to read and write from an Amazon DynamoDB table that uses encryption supplied by the AWS Key Management Service (AWS KMS). When you call CloudFormation, it calls Amazon DynamoDB to read from the table, then Amazon DynamoDB calls AWS KMS to decrypt the data. Each call gets its own authorization check, and the aws:CalledVia key keeps track of who called whom.

  • For the call to CloudFormation, aws:CalledVia will be empty.
  • For the call from CloudFormation to Amazon DynamoDB, aws:CalledVia will contain [ “cloudformation.amazonaws.com” ]
  • For the call from Amazon DynamoDB to AWS KMS, aws:CalledVia will contain [ “cloudformation.amazonaws.com” , “dynamodb.amazonaws.com” ]

    Important: The aws:CalledVia context is only available when AWS reuses your credentials after you make a request. For example, if you provided a service role to CloudFormation instead of triggering the action directly, the aws:CalledVia context would be empty for a call from CloudFormation to Amazon DynamoDB.

To identify the service principal that aws:CalledVia returns for each AWS service, see to the CalledVia Services table in the documentation. AWS will update this list as more services add support for this key.

You can use condition operators, such as StringLike and StringEquals, in your policies to check the contents of the key during authorization. Because aws:CalledVia is a multivalued condition key, you also need to use the ForAnyValue or ForAllValues set operators along with your comparison operators to check the content of the key. For more information, see Creating a Condition with Multiple Keys or Values in the IAM documentation.

Here’s an example policy that follows this use case.


{ 
   "Version":"2012-10-17",
   "Statement":[ 
      { 
         "Sid":"AllowCFNActions",
         "Effect":"Allow",
         "Action":[ 
            "cloudformation:CreateStack*"
         ],
         "Resource":"*"
      },
      { 
         "Sid":"AllowDDBActionsViaCFN",
         "Effect":"Allow",
         "Action":[ 
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem"
         ],
         "Resource":"arn:aws:dynamodb:region:111122223333:table",
         "Condition":{ 
            "ForAnyValue:StringEquals":{ 
               "aws:CalledVia":[ 
                  "cloudformation.amazonaws.com"
               ]
            }
         }
      },
      { 
         "Sid":"AllowKMSActionsViaDDB",
         "Effect":"Allow",
         "Action":[ 
            "kms:Encrypt",
            "kms:Decrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey",
            "kms:DescribeKey"
         ],
         "Resource":[
            "arn:aws:kms:region:111122223333:key/example"
         ],
         "Condition":{ 
            "ForAnyValue:StringEquals":{ 
               "aws:CalledVia":[ 
                  "dynamodb.amazonaws.com"
               ]
            }
         }
      }
   ]
}

I’ll walk you through each statement in this policy. In the first statement, AllowCFNActions, I grant access to use CloudFormation to create stacks and stack sets. The second statement, AllowDDBActionsViaCFN, grants access to read and write from a specific DynamoDB table, but only if CloudFormation took the action. Finally, the third statement AllowKMSActionsViaDDB allows AWS KMS encrypt and decrypt operations that are triggered through Amazon DynamoDB, using the AWS KMS key specified in the Resource element. Each condition statement uses the ForAnyValue:StringEquals combination of operators to check for the existence of the CloudFormation or DynamoDB service principals. Putting it all together, the policy allows an IAM principal to do the following:

  1. Create stacks and stack sets by using CloudFormation.
  2. Read and write to Amazon DynamoDB tables via CloudFormation.
  3. Encrypt and decrypt by using AWS KMS actions via Amazon DynamoDB.

Controlling access based on the first and last requesting services

Along with aws:CalledVia, AWS has introduced two companion keys to make it easy to retrieve the first and last services in the chain of requests. The aws:CalledViaFirst condition key returns the first service principal in the chain, and aws:CalledViaLast returns the last service principal in the chain. For example, if aws:CalledVia contained [ “cloudformation.amazonaws.com” , “dynamodb.amazonaws.com” ], then aws:CalledViaFirst would contain “cloudformation.amazonaws.com” and aws:CalledViaLast would contain “dynamodb.amazonaws.com”.

Still following the previous example, here’s a policy that uses aws:CalledViaFirst to allow access to Amazon DynamoDB and AWS KMS, as long as the entry point is CloudFormation. You can use this policy to ensure that Amazon DynamoDB tables in your organization are accessed according to the best practices you’ve defined in your CloudFormation templates.


{ 
   "Version":"2012-10-17",
   "Statement":[ 
      { 
         "Sid":"AllowCFNActions",
         "Effect":"Allow",
         "Action":[ 
            "cloudformation:CreateStack*"
         ],
         "Resource":"*"
      },
      { 
         "Sid":"AllowDDBAndKMSActionsViaCFN",
         "Effect":"Allow",
         "Action":[ 
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem",
            "kms:Encrypt",
            "kms:Decrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey",
            "kms:DescribeKey"
         ],
         "Resource":[
            "arn:aws:dynamodb:region:111122223333:table",
            "arn:aws:kms:region:111122223333:key/example"
         ],
         "Condition":{ 
            "StringEquals":{ 
               "aws:CalledViaFirst":[ 
                  "cloudformation.amazonaws.com"
               ]
            }
         }
      }
   ]
}

I’ll walk you through each statement in this policy. The first statement of the policy, AllowCFNActions, enables the principal to create stacks and stack sets through CloudFormation. The second statement, AllowDDBAndKMSActionsViaCFN, allows Amazon DynamoDB and AWS KMS actions for a specific KMS key and table, but only under the condition that the first request made by the principal was to the CloudFormation service.

Now that I’ve described the conditions and their usage, I’ll share detailed examples in the use case that follows.

Use case: Permissions to use Athena inside your Virtual Private Cloud

Amazon Athena is a service you can use to analyze data in Amazon S3 by using standard SQL. Athena is serverless, which makes it cost-effective for operating a data lake. In this example, I define the IAM permissions for a role that manages and executes Athena queries from behind a secure network perimeter.

In this use case, my business metrics team needs an IAM role to execute queries against my data lake that is stored in Amazon S3. They’ll use the IAM role to manage the BizMetrics workgroup in Athena, which is responsible for managing exploratory queries and generating regular reports for key performance indicators within my company. Because my business metrics are internal to my organization, I want to ensure the data is only accessible inside my Amazon Virtual Private Cloud (VPC). VPCs let you create a logically-isolated section of the AWS Cloud and connect it to your private network. For more information about VPCs, see What is Amazon VPC? in the VPC documentation.

When an IAM role makes a call to Athena to execute a query inside a VPC, Athena makes subsequent calls to Amazon S3 and other services to complete the task. You can see the interaction on the following diagram.

Figure 1: IAM role makes a call to Athena to execute a query inside a VPC

Figure 1: IAM role makes a call to Athena to execute a query inside a VPC

The calls from the IAM role to Athena, and from Athena to Amazon S3, use the same role credentials. This means that the principal needs permissions for both Athena and Amazon S3 actions to accomplish the query. You can also see that the IAM role calls Athena through the VPC endpoint, rather than the public AWS endpoint. VPC endpoints allow you to communicate with AWS from your private network without a connection to the public internet. For more information about VPC endpoints, see Interface VPC Endpoints (AWS PrivateLink) in the VPC documentation.

The subsequent calls between Athena and Amazon S3 don’t use the VPC endpoint. If I want to require that each call to Athena must use the VPC endpoint, I cannot apply the same restriction to Athena’s calls to Amazon S3. I will need to use aws:CalledVia to define distinct permissions for the initial call to Athena, and the call to Amazon S3 from Athena. I’ll apply these permissions to the IAM role I create, as well as the Amazon S3 bucket that contains the data.

Step 1: Define permissions for the IAM Role

For the purposes of this use case, my organization’s data lake has one database, revenuedata, in the bucket called examplecorp-business-data. When you follow along with these examples, you should replace the bucket and database names in the policies with your own resources.

  1. First, I create the IAM role BizMetricsQuery. I can create the role in the IAM console without any permission policies attached. If you haven’t created a role before, see Creating IAM Roles in the IAM documentation.
  2. Next, I create a managed policy that defines permissions for the BizMetricsQuery role. For more information about creating a managed policy or attaching it to the BizMetricsQuery role, see Managing IAM Policies in the IAM documentation.The following policy grants the permissions I need:
    
    	{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowAthenaReadActions",
                "Effect": "Allow",
                "Action": [
                    "athena:ListWorkGroups",
                    "athena:GetExecutionEngine",
                    "athena:GetExecutionEngines",
                    "athena:GetNamespace",
                    "athena:GetCatalogs",
                    "athena:GetNamespaces",
                    "athena:GetTables",
                    "athena:GetTable"
                ],
                "Resource": "*",
                "Condition":{ 
                   "StringEquals":{ 
                      "aws:SourceVpce":[ 
                         "vpce-0e880cb0a9EXAMPLE"
                      ]
                   }
                }
            },
            {
                "Sid": "AllowAthenaWorkgroupActions",
                "Effect": "Allow",
                "Action": [
                    "athena:StartQueryExecution",
                    "athena:GetQueryResults",
                    "athena:DeleteNamedQuery",
                    "athena:GetNamedQuery",
                    "athena:ListQueryExecutions",
                    "athena:StopQueryExecution",
                    "athena:GetQueryResultsStream",
                    "athena:ListNamedQueries",
                    "athena:CreateNamedQuery",
                    "athena:GetQueryExecution",
                    "athena:BatchGetNamedQuery",
                    "athena:BatchGetQueryExecution",
                    "athena:GetWorkGroup"
                ],
                "Resource": [
                    "arn:aws:athena:us-east-1:111122223333:workgroup/BizMetrics"
                ],
                "Condition":{ 
                   "StringEquals":{ 
                      "aws:SourceVpce":[ 
                         "vpce-0e880cb0a9EXAMPLE"
                      ]
                   }
                }
            },
            {
                "Sid": "AllowGlueActionsViaVPCE",
                "Effect": "Allow",
                "Action": [
                    "glue:GetDatabase",
                    "glue:GetDatabases",
                    "glue:CreateDatabase",
                    "glue:GetTables",
                    "glue:GetTable"
                ],
                "Resource": [
                    "arn:aws:glue:us-east-1:111122223333:catalog",
                    "arn:aws:glue:us-east-1:111122223333:database/default",
                    "arn:aws:glue:us-east-1:111122223333:database/revenuedata",
                    "arn:aws:glue:us-east-1:111122223333:table/revenuedata/*"
                ],
                "Condition":{ 
                   "StringEquals":{ 
                      "aws:SourceVpce":[ 
                         "vpce-0e880cb0a9EXAMPLE"
                      ]
                   }
                }
            },
            {
                "Sid": "AllowGlueActionsViaAthena",
                "Effect": "Allow",
                "Action": [
                    "glue:GetDatabase",
                    "glue:GetDatabases",
                    "glue:CreateDatabase",
                    "glue:GetTables",
                    "glue:GetTable"
                ],
                "Resource": [
                    "arn:aws:glue:us-east-1:111122223333:catalog",
                    "arn:aws:glue:us-east-1:111122223333:database/default",
                    "arn:aws:glue:us-east-1:111122223333:database/revenuedata",
                    "arn:aws:glue:us-east-1:111122223333:table/revenuedata/*"
                ],
                "Condition":{ 
                   "ForAnyValue:StringEquals":{ 
                      "aws:CalledVia":[ 
                         "athena.amazonaws.com"
                      ]
                   }
                }
            },
    
            {
                "Sid": "AllowS3ActionsViaAthena",
                "Effect": "Allow",
                "Action": [
                    "s3:GetBucketLocation",
                    "s3:GetObject",
                    "s3:ListBucket",
                    "s3:ListBucketMultipartUploads",
                    "s3:ListMultipartUploadParts",
                    "s3:AbortMultipartUpload",
                    "s3:CreateBucket",
                    "s3:PutObject"
                ],
                "Resource": [
                    "arn:aws:s3:::examplecorp-business-data/*",
                    "arn:aws:s3:::athena-examples-*"
                ],
                "Condition":{ 
                   "ForAnyValue:StringEquals":{ 
                      "aws:CalledVia":[ 
                         "athena.amazonaws.com"
                      ]
                   }
                }
            }
        ]
    }
    	

    I’ll walk you through each statement in this policy. First, the AllowAthenaReadActions and AllowAthenaWorkgroupActions statements allow the role to use Athena actions within the BizMetrics workgroup. The role calls Athena directly from inside the VPC, so there is a condition on these statements that requires that the calls must come from my VPC endpoint.

    Next, the AllowGlueActionsViaVPCE and AllowGlueActionsViaAthena statements allow the role to access the AWS Glue Data Catalog and view the tables within my data lake. AWS Glue makes it easy to catalog your data and make it searchable, queryable, and available for ETL operations. To view the database and tables in the Athena console, I need access to these AWS Glue actions. The role calls AWS Glue directly, and allows Athena to call AWS Glue, so the policy has two statements that allow both paths of communication respectively. For more information about required permissions for Athena and AWS Glue, see Fine-Grained Access to Databases and Tables in the AWS Glue Data Catalog in the Amazon Athena documentation.

    Finally, the AllowS3ActionsViaAthena statement enables Athena to call Amazon S3 on my behalf. I use the aws:CalledVia condition to require that these S3 actions are only available through Athena. In the resource section of the policy, I specify that the access is limited to the examplecorp-business-data bucket, as well as the Athena examples repository, which is required for using Athena with the AWS console.

  3. Next, I attach the policy to the BizMetricsQuery role. With this policy attached, I ensure the role can use Athena to access data in the Amazon S3 bucket without granting permissions to access the bucket directly. I also ensure that the role can only access the data through the VPC endpoint located within my private network.

Step 2: Define permissions on the S3 bucket

I need to make sure the BizMetricsQuery role (which has no existing IAM permissions on S3) is granted access to the data in the examplecorp-business-data S3 bucket but only through Athena. To do this, I need to update the bucket policy that is attached to the bucket.

The following bucket policy grants the BizMetricsQuery role access to the data through the service athena.amazonaws.com.


{
	"Version": "2012-10-17",
	"Statement": [
    {
      "Sid": "AllowS3ActionsThroughAthena",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/BizMetricsQuery"
      },
      "Action": [
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:ListBucketMultipartUploads",
        "s3:ListMultipartUploadParts",
        "s3:AbortMultipartUpload",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::examplecorp-business-data/*",
        "arn:aws:s3:::examplecorp-business-data"
      ],
      "Condition": {
        "ForAnyValue:StringEquals": {
          "aws:CalledVia": [
            "athena.amazonaws.com"
          ]
        }
      }
    }
  ]
}

This policy is very similar to the statement on the role policy that grants permissions to Amazon S3. In this policy, I allow the role to take Amazon S3 operations, but only if the aws:CalledVia context includes the Athena service. In the Principal element of the policy, I include the BizMetricsQuery role’s ARN. This is an important step because without this requirement, anyone could interact with the data in the bucket as long as they make the call using Athena. I want to require that the only role that can reach the data is BizMetricsQuery, which is also subject to the VPC rules.

With this policy attached to the examplecorp-business-data bucket, I ensure that access to the databases is only available through the BizMetricsQuery role. Because the BizMetricsQuery role can only access the data through Athena, and all calls to Athena must use my VPC endpoint, this also ensures the Amazon S3 data is only accessible from within my VPC.

Step 3: Run the Athena queries

I’ll try a simple query in Athena from inside the VPC to make sure everything works as expected. I use the BizMetricsQuery role to view the service_revenue table in the revenuedata database. If you are following along with this example, you should replace the database and table in the query with your own resource names.

  1. In the Athena console, on the Query Editor page, enter the following query in a new tab:

    SELECT * FROM “revenuedata”.”service_revenue” limit 10;

    Then select Run query.

    Figure 2: Run an Athena test query

    Figure 2: Run an Athena test query

    When I run this test query, my role performs the StartQueryExecution action. For this request, the aws:CalledVia context is empty because the role takes the action directly. The role is allowed to use Athena actions, so it passes the first authorization check.

    To perform the query, Athena subsequently calls the Amazon S3 service. These requests have the aws:CalledVia context athena.amazonaws.com. The role has access to the Amazon S3 actions when aws:CalledVia is equal to this value, so it passes the second and final authorization check.

  2. When I try to access the data directly by using Amazon S3, I get an Access Denied error message.
    Figure 3: Error message attempting to access directly from Amazon S3

    Figure 3: Error message attempting to access directly from Amazon S3

    In this case, the aws:CalledVia context is empty because I made the call directly to the Amazon S3 service. The policy requires me to call Amazon S3 only through Athena, so this request is denied.

  3. When I call Athena by using the same query from outside my VPC, I get the following error message.

    Figure 4: Error message attempting to call Athena from outside my VPC

    Figure 4: Error message attempting to call Athena from outside my VPC

In the Athena case, the SourceVPCE context is not present because I didn’t make the call using the VPC endpoint. In Amazon S3, the policy requires that the calls can only happen from inside my private network, so I get an access denied message in that case, as well. With this policy and example, I’ve demonstrated that I can only call Athena from within the VPC, and shown that calling the dependent services directly is not possible.

Next steps

By using the aws:CalledVia condition key, you can now define specific permissions for when AWS services make calls to other services on your behalf. This makes it easier to ensure that your company’s guidelines are followed consistently. For more information about aws:CalledVia and other IAM conditions, see AWS Global Condition Context Keys in the IAM documentation. If you have any questions, comments, or concerns, please reach out to AWS Support or our Identity and Access Management Forums.

If you have feedback about this blog post, submit comments in the Comments section below.

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

Author

Michael Switzer

Mike is the product manager for the Identity and Access Management service at AWS. He enjoys working directly with customers to identify solutions to their challenges, and using data-driven decision making to drive his work. Outside of work, Mike is an avid cyclist and outdoorsperson. He holds a master’s degree in computational mathematics from the University of Washington.