Bootstrapping Infrastructure as Code
How to codify your AWS configuration, from the very beginning
In the last article we looked at the bare minimum steps to create and secure a new AWS account. Everything so far was done via “click-ops” in the AWS web console.
Now that we’ve gotten so far as to create an IAM user for ourself for further administration, we’d like to start making our changes via Infrastructure as Code (IaC). Since it’s popularization with the tool Terraform, IaC has become an industry standard. The Stack Overflow blog has a great overview of IaC and its benefits.
One of the most common challenges of any mature organization is that they have implemented IaC… partially. When they were a young scrappy startup, either there wasn’t the skillset, or the foresight to define everything in code. Numerous, crucial resources were click-ops’d before there was even an inkling of using IaC. If you ask around, chances are that the details of how those resources were created is long lost knowledge, and everyone just collectively hopes they don’t need touched very much.
Let’s learn how to break that mold, and implement every last bit of our AWS configuration in code from the very beginning.
Enabling programmatic access
The first step is to enable programmatic access so that we can interact with AWS from something other than the web console.
To do so, navigate to AWS → IAM → <your IAM user> → Security Credentials → Create access key.
You will be given two keys, consider these extremely sensitive as they allow for doing anything your IAM user can. This will also be your only chance to copy the longer key, so don’t lose it!
Once you have installed the AWS CLI, you can store your new credentials by running aws configure
. I also highly recommend the tool AWS Vault for more secure handling of your credentials.
With your credentials configured, you should be able to run
aws sts get-caller-identity
and see a successful result that matches your IAM user.
Writing our first Infrastructure as Code
Now that we can interact with our AWS account programmatically, it’s time to write our first IaC resources! The first step will be to duplicate the IAM user that we created manually.
The following is written in Pulumi, an IaC tool that allows for writing in multiple programming languages. You may also choose to use Terraform. Both are great tools with tons of documentation to get started with them. We’ll just focus on what the actual resource code looks like here:
import * as aws from '@pulumi/aws'; | |
const timUser = new aws.iam.User('tim.myers', { | |
name: 'tim.myers', | |
}, { import: 'tim.myers'}); | |
new aws.iam.UserPolicyAttachment('tim.myers-admin', { | |
user: timUser.name, | |
policyArn: aws.iam.getPolicyOutput({ name: 'AdministratorAccess'}).arn, | |
}, { import: 'tim.myers/arn:aws:iam::aws:policy/AdministratorAccess' }); | |
new aws.iam.UserPolicyAttachment('tim.myers-password', { | |
user: timUser.name, | |
policyArn: aws.iam.getPolicyOutput({ name: 'IAMUserChangePassword'}).arn, | |
}, { import: 'tim.myers/arn:aws:iam::aws:policy/IAMUserChangePassword' }); |
We are creating three resources. The first is the IAM user itself, and the other two are IAM policy attachments. One for the AdministratorAccess
we selected when creating the user, and the other for IAMUserChangePassword
that was automatically added when we gave the user web console access.
All of these resources also specify import
settings because they are existing resources that we want to begin managing in our IaC as opposed to new resources to create.
After applying this IaC, our IAM user is now fully defined in code! Let’s make our first changes to it:
import * as aws from '@pulumi/aws'; | |
const defaultTags = { Creator: 'pulumi' }; | |
const timUser = new aws.iam.User('tim.myers', { | |
name: 'tim.myers', | |
tags: defaultTags, | |
}); | |
new aws.iam.UserPolicyAttachment('tim.myers-admin', { | |
user: timUser.name, | |
policyArn: aws.iam.getPolicyOutput({ name: 'AdministratorAccess'}).arn, | |
}); | |
new aws.iam.UserPolicyAttachment('tim.myers-password', { | |
user: timUser.name, | |
policyArn: aws.iam.getPolicyOutput({ name: 'IAMUserChangePassword'}).arn, | |
}); |
The first thing you may notice is that we’ve removed all the import
settings. After we applied the code and successfully imported the resources, they are no longer necessary and we can remove them to simplify things.
Next, we’ve added a tag to our IAM user to show the lineage of this resource is from pulumi. When we view the resource in the web console or elsewhere, it’s now easy to tell that this resource is indeed managed by IaC. We’ll apply this to all resources we define that support tagging.
Summary
Congrats! We have now bootstrapped IaC for our AWS account, and have imported the resources that we previously created manually in the web console to be managed in code. Everything that we create from here on out can be done by iterating upon this, and our AWS account will never have any resources with no history of where they came from!