Credential evaluation

One of my favorite interview questions when it comes to AWS knowledge is about IAM. The question is simple:

You have an EC2 instance with Instance profile, which has associated IAM Role. This Role policies allow S3 FullAccess. On that EC2 instance, you have a Docker container running, with hardcoded IAM keys of a user who has an inline policy allowing S3 ReadOnly access. Which privileges this app have?

You see, there are still people out there who do not know how IAM credentials are evaluated, yet it is really simple to understand and remember.

When the application is invoking AWS API using AWS SDK, it evaluates credentials in the following workflow:

Credentials hardcoded in the app (e.g. assigned in the client like boto3.client("ec2", aws_access_key="foo", aws_secret_access_key="bar", aws_session_token="foobar") ).

If those credentials are not provided SDK will check environment variables.

If environment variables are not provided SDK will check ~/.aws/credentials file.

If that file is not present, SDK will call http://169.254.169.254 (Instance meta-data) for credentials.

If IAM section of meta-data is empty — you get “no credentials” exception.

So a proper answer to the question above is “S3 ReadOnly”. But I usually expect a candidate to explain why it is like that.

As I said this is fairly simple but really important to know. Let’s move on to the next part.

Principal

From the previous chapter, you already know that Principal is an entity which is allowed to perform API calls, stated in the Policy. Let’s look at the example policy again.

Specifying an AWS Account in Principal section allows to not only give access to calls within that account but also enable Cross-Account Roles.

For example, if you have a Production AWS account with ID 1234 and Development AWS account with ID 5678, you can enable access to Role from Dev to Prod by adding Development Account ID in the Principal.

Another Principal I’d like to focus on is the AWS Service Principal. You add “trusted” AWS Services to principals by specifying their Full-Service Name full_service_name.amazonaws.com . Let’s say we need to give Lambda Function access to the Role to work with S3 (Get and Put Objects for example). Then our Assume Role Policy Document will look like this:

Service is the only Principal which does not allow wildcards. So you can not do "Service": "*.amazonaws.com" if you ever wish to do so.

Another thing to know is that the Service entity is limited within the account. That means that only services in that exact account can assume the role and you can not allow service in account A to assume the role in account B.

Last but not least — a Principal called “Everyone”. There are 2 types of “everyone”

"AWS": "*"

"*"

The difference between these two is the limit. If you add “AWS” at the beginning that will allow public access to a policy from all AWS “containers” and other accounts.

While the wildcard without “AWS” in front allows anonymous access. While you might think it is crazy, think of the case of Static Website Hosting on S3. In order to give access to S3 contents (HTML, CSS, JS, fonts, pictures, etc) you need to allow ReadOnly Allow action for Bucket objects. And since S3 Bucket Policy is also IAM policy (but associated with the bucket), you do the same actions as with regular IAM Policies:

Conditions

Now, this is where things get serious. Conditional policy elements specify conditions (sic!) in which circumstances API call can be made. Conditions are the parts of Statement and you can have multiple Conditions in one Statement or in multiple Statements. There are tons of use cases for them, but I will cover only the most popular of them, while I encourage you to go through the list of all Conditional operators.

Conditions have a key and a value. The key is a variable which is replaced by another value during the policy evaluation. For example this condition

"Condition" : { "StringEquals" : { "aws:username" : "johndoe" }}

contains:

Which patterns must match (String in this case)

A “key” — IAM user name

A “value” — “johndoe”

Which means this condition will check if the user who’s making a call is “johndoe”.

Now, we can have conditions with multiple values to the key (they act as OR) and with multiple keys (which is AND).

Condition evaluation (from AWS documentation)

For example, we want to check that only specific users will assume AdminRole only from specific IP address and only if logged in with MFA device.

Note that both conditions must match the value, otherwise users won’t be able to assume the role.

Another example is the case I face quite often. You remember that one of the Principal entities is AWS Service. When you specify AWS Service Full Name, such as lambda.amazonaws.com , that means that all Lambda functions can assume the role. Now, I don’t want that. I am paranoid (I used to work with banks and trading companies, hope you understand my childhood traumas) and I want specificfunctions to work with only specificroles.

Last but not least: I promised to give an example of limiting access to an S3 Bucket only during a specific time range. The thing here is that Date Conditions need to have both date and time. That is quite frustrating that ISO 8601 is not satisfied completely (If you found a way to use only time, please share with me). So the date condition looks like the following:

This will limit access between 9 a.m. and 5 p.m., but only for a specific date. The only workaround I found is to deploy a Lambda function, which runs every day at midnight and replaces the date with current day, but that’s a dangerous hack (what if Lambda fails to run?)

Again, there are plenty of things you can do with conditions, so dig into the docs and see if there are some solutions for your use cases.

Policy variables

Now the final part of this chapter is about Policy Variables.

IAM Policy Variables allow you to render policies on the fly without duplicating the same entries over and over again.

Let’s say we have an S3 Bucket which acts as a home directory for multiple users.

home_dir_bucket/ \ |_ David/ |_ John/ |_ Ivan/ |_ Tom/ # and so on

What we want to do is to limit the uploading/downloading of objects for users only from and to their home dir. If we go with “classic” IAM policies each user will have a policy like this:

We’d have to make multiple policies, each for a user, which is a perfect example of unnecessary duplication. Instead, we can create a policy with variable and associate with every user or a single group: