AWS – Creating instance roles

Docker Compose Tutorial for Beginners

This recipe is reasonably short, but it contains a really important concept to anyone who is new to the AWS platform. Understanding and utilizing IAM roles for the EC2 will significantly reduce your exposure to lost credentials, and will probably help you sleep a little better at night, too. In a nutshell, instance roles help you to get AWS credentials off your servers and out of your code base(s).

Roles contain one or more policies. We’re going to create a role that has some AWS Managed Policies, as well as an Inline Policy. As the name would suggest, an AWS Managed Policy is a policy that is created and fully controlled by AWS. The Inline Policy is going to be created by us, and will be embedded in our role definition.

The AWS Managed Policies that we’ll use will allow read-only access to the S3 and EC2 APIs. The Inline Policy that we’ll create will allow writing access to CloudWatch Logs. We’ll talk through why you would or wouldn’t choose a Managed Policy later in this recipe.

How to do it…

Follow these steps in order to use CloudFormation to create an instance role:

  1. Create a new CloudFormation template file, and add the first Resource parameter. This is going to be our role that contains references to the Managed Policies, and also to our Inline Policy:
AWSTemplateFormatVersion: '2010-09-09' 
    Type: AWS::IAM::Role 
        Version: "2012-10-17" 
            Effect: Allow 
              - sts:AssumeRole 
  1. Complete the definition of the role by adding policies:
        - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess 
        - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess 
      Path: / 
          PolicyName: WriteToCloudWatchLogs 
            Version: "2012-10-17" 
                Effect: Allow 
                  - logs:CreateLogGroup 
                  - logs:CreateLogStream 
                  - logs:PutLogEvents 
                  - logs:DescribeLogStreams 
                Resource: "*"

  1. We now need to create an InstanceProfile resource. A profile encapsulates a single IAM role and, roughly speaking, that’s all it’s used for. A profile can contain only a single IAM role, so it’s not clear why AWS has built this extra layer of abstraction; presumably, they have plans to give profiles of other properties aside from roles:
    Type: AWS::IAM::InstanceProfile 
        - !Ref ExampleRole 
      Path: /
  1. For convenience, we’ll add some Outputs parameters, which will provide the profile name and ARN to us, after the stack has been created:
    Value: !Ref ExampleInstanceProfile 
    Value: !GetAtt ExampleInstanceProfile.Arn
  1. You can now create your instance role using CloudFormation via the web console:
      aws cloudformation create-stack \
        --stack-name example-instance-profile \
        --template-body file://08-creating-instance-roles.yaml \
        --capabilities CAPABILITY_IAM

This role can now be assigned to your EC2 instances. 

How it works…

How on earth does this solve the problem of hardcoded AWS API keys? Well, something really interesting happens when you assign a role to an EC2 instance. The metadata for that instance will return a set of short-lived API keys. You can retrieve these keys by sending an HTTP request to the metadata URL (this is a service that EC2 instances can use to fetch information about themselves):<role name>.

The output of a curl request to this URL will look something like this:

        "Code" : "Success", 
        "LastUpdated" : "2017-02-17T11:14:23Z", 
        "Type" : "AWS-HMAC", 
        "AccessKeyId" : "AAAAAAAAAAAAAAAAAAAA", 
        "SecretAccessKey" : "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", 
        "Token" : "token", 
        "Expiration" : "2017-02-17T12:14:23Z" 

If you take AccessKeyId and SecretAccessKey, which are returned in the response, you can use them to query the AWS API. The policies applied to the instance, based on the role assigned to it, will determine exactly what API actions the instance is able to perform using these keys.

The really fun part is that you don’t have to worry too much about handling these keys at all (although, it’s really useful to know how all this works under the hood). For example, the AWS CLI tools will automatically fetch these keys for you prior to running any CLI commands. The same goes for the AWS SDKs.

Take a scenario where your developers are building an application that needs to fetch files from S3. As long as they are using the AWS SDK to do this, and the application is running on an EC2 instance that has been assigned a role that contains a policy that allows files to be fetched from S3, then no credentials are required by the application whatsoever! The SDK will take care of the queries to the metadata service for you.

The AWS SDKs are available for almost every widely used language, so there’s no excuse for keeping hardcoded AWS credentials in config files or source code.

You will see your instances roles listed in the IAM console under the  Roles section. Clicking on the role will reveal further details, such as the policies that have been assigned to it.

There’s more…

Here are some more facts to keep in mind about IAM:

  • IAM is a global service. This means that the roles and policies that you create will be available in every region.
  • You’ll find all the available AWS Managed Policies in the AWS web console. There’s quite a few of them, so don’t be afraid to use the search bar.
  • There’s another kind of policy, called a Customer-Managed Policy. These are policies that are managed by you, and they will appear in the AWS console, amongst the AWS Managed Policies.
  • It is possible to attach an IAM role to an existing/running EC2 instance. This previously wasn’t the case, and the role could only be assigned at the time that the instance launched.
  • AWS automatically and periodically rotates the credentials that are returned by the metadata service.
  • It’s not always appropriate to use an AWS Managed Policy. For example, if a server needs to write to CloudWatch Logs, it may be tempting to assign it to the AWS Managed Policy that provides full access. If you do this, however, you’ll also be giving the server access to delete log groups and streams. This is almost certainly undesirable. You’ll want to inspect the policies before you apply them, and defer to an Inline or Customer-Managed Policy, where appropriate. The principle of least privilege applies here.

Comments are closed.