In a previous post on in the AWS 101 series, I covered some details on Identity and Access Management and touched on IAM policies. In this blog, I will be expanding on the concept of policies and how they are used to authorize identities, whether human users or applications, to perform actions in an AWS account.
This post is part of a 101 series on AWS. If interested, checkout the other posts in this series:
Understanding how authentication happens in AWS is not critical but is interesting to know and offers some insight when attempting to write good policies. Whether you are using the console, CLI or SDK, the authentication is handled by AWS.
In the above diagram, we have a principal or identity attempting to make a call to the S3 service. The attempted action is a listing on an object in a bucket using the ListObjects API call. Like everything else in AWS, this call will be authenticated and authorized. It’s a role, so it will have short term credentials. If we look at the output generated by the debug flag on the CLI command, we can see the role that is providing the credentials for the call.
This role happens to be called S3ReadOnlyForEC2. Next, you can see the bucket endpoint. In this case, the bucket is in af-south-1. Then further down, you will notice the Credential header. The first part of this header is the Access Key ID. This is passed in the clear because there is nothing secret about it. The secret part is the secret access key. The Access key ID is an identifier for the caller of this API call and the principal they claim to be. They have not proven it yet, but it’s who they claim to be.
Lastly, is the Signature. The signature is an HMAC signature and is produced client side with the secret access key that belongs to the session since the identity is a role. The crucial parts of the API call were signed and if this signature is validated by AWS, it means that the call was definitely made by the principal and the contents of the call has not been meddled with. Essentially proving the principal’s identity.
Proving the identities validity is only the first part of the process. The next step is whether the identity actually has permissions to make this call. This is where Policies come into play.
Authorization with Policies
Unlike authentication, which is handled mostly by AWS, it’s important to understand how policy authorization works. Authorization is understood to be extremely literal.
The above basic graphic indicates how policies decide the authorization status of a principal. In this example it can be assumed the principal has successfully authenticated. Policies will now decide whether the desired action to the S3 service can be fulfilled. There will be a number of policies associated to the request. Some will be associated with the principle making the call, some possibly with the resource or service and some with a number of other things.
These policies will contain several statements. There could be statements that relate to services other than the one in question and these statements can be considered irrelevant. The policies will be evaluated, and determinations will be made as to which are relevant and which are not. Once the relevant statements have been determined, each one will either allow or deny.
The overall rule is simple: a policy must allow, and no policy must deny. If no policy claims the request, it’s denied. In AWS, one needs permission to execute any action, otherwise you will not be able to. In the above example, the policy on the left is relevant and is allowing the request. The policy on the right is also relevant but it is denying the request. Hence, the overall judgement on authorization will be deny.
Reading and Writing IAM Policy
Understanding the literal nature of IAM policies is best shown by example. The example we will be looking at is one that represents an application running on EC2 and connecting to DynamoDB. The EC2 instance has a role associated to it. We want to give the role permission to read and write to DynamoDB. Let see the different options available to us and which option is the most effective in regard to the principle of least privilege.
We could write a policy like this:
This policy will definitely work by allowing all actions. But this can be considered a bad policy for the requirements of the application. This policy is akin to full administrative permissions and provides way too many actions that this application will probably ever need and introduces risk into the equation. We could improve the policy by doing this:
This policy is a slight improvement, since it limits the actions to the dynamodb service. But it allows all actions to the service and to all resources related to the service. The policy is still unacceptable since it still allows too much permission for the purpose of the application. Let’s keep improving the policy. What if we tried this?
This is a vast improvement because it states specific actions. Like all the services on AWS, dynamodb has many API’s associated with it. There are API’s that can delete tables, provision tables etc. However, we know that this application only needs to read and write to the tables. So those are the only API actions that should be allowed. What about the resource field of the policy? It’s not ideal. How can we do better?
To write an optimal policy, one should become familiar with the looking up the service details on the IAM policy reference page.
On the page you will see a link called "Actions, Resources, and Condition Keys for AWS Services". This page will show a list of each and every AWS service. You can then select the service you are attempting to write a policy for. In our case we will choose Amazon DynamoDB. You will be presented with a structured table to show you how to write the policy.
One of our actions were GetItems, so below is the row that describes this action.
You will notice that the resource type that applies is a table. This implies that with this action, one can authorize specific tables. How do we apply this to the resource field in the policy?
Notice the string that now appears in the resource field. This is called an Amazon Resource Name (ARN). Each resource on AWS has an ARN and they are all formatted in the same fashion. After "arn:aws", the ARN takes the format of: the name of the service, the region, the aws account number and then the service specific part. This uniquely identifies the resource across all of AWS. How does one get the ARN for this specific resource? Once again, let’s look at the documentation. If one clicks on "table*" in the Resource Types column in the above table your will be presented with a page that lists the "Resource Types Defined by Amazon DynamoDB"
This will show you the format that the ARN needs to take to comply with the resource that the policy requires. The full policy will now look like this:
The policy now has specific actions of reading and writing and a specific resource of a dynamo table. It follows the principle of least privilege and will match the request with the exact requirements and can be considered the optimum policy for the applications requirements. It should be noted that policies can be more complex than this, with additional attributes. For this example we've chosen a simple policy for the purpose of illustration. The reader is encouraged to further explore policies.
Authorization practices in AWS should always follow the principle of least privilege. IAM policies are designed in such a way to achieve just this. If configured correctly, one can rest assured that the manner in which services are accessed by principals, whether human or machine, are always done in the most secure manner possible. As always, any feedback is encouraged.