Skip to content

Commit 0c8f73e

Browse files
authored
[aws/bootstrap] Implement Bootstrap User Account (#66)
* Implement comprehensive bootstrap user and role provisioning module * Use wildcard target * Apply suggestions from code review Co-Authored-By: osterman <[email protected]>
1 parent 45770c7 commit 0c8f73e

File tree

11 files changed

+232
-30
lines changed

11 files changed

+232
-30
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ indent_size = 4
2222
[*.sh]
2323
indent_style = tab
2424
indent_size = 4
25+
26+
[*.{tf,tfvars,tpl}]
27+
indent_size = 2
28+
indent_style = space

aws/bootstrap/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.aws
2+
.envrc

aws/bootstrap/Makefile

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
AWS_DEFAULT_REGION := us-east-1
2-
IAM_ROLE ?= bootstrap
3-
IAM_POLICY_ARN ?= arn:aws:iam::aws:policy/AdministratorAccess
1+
init:
2+
init-terraform
43

5-
export AWS_ROOT_ACCOUNT_ID=$(shell aws sts get-caller-identity --output text --query 'Account')
4+
%:
5+
terraform $@
66

7-
export ASSUME_ROLE_POLICY=$(shell envsubst < assume-role.json > /tmp/assume-role.json; echo file:///tmp/assume-role.json)
8-
9-
## Provision a temporary IAM role for bootstrapping
10-
create/iam-role:
11-
@aws iam create-role --role-name "$(IAM_ROLE)" --assume-role-policy-document "$(ASSUME_ROLE_POLICY)" >/dev/null
12-
@aws iam attach-role-policy --role-name "$(IAM_ROLE)" --policy-arn $(IAM_POLICY_ARN) >/dev/null
13-
@echo "The '$(IAM_ROLE) role has been created. Use '$(IAM_ROLE)' anywhere an assumed role is required."
14-
15-
## Destroy the temporary IAM role for bootstrapping
16-
delete/iam-role:
17-
@aws iam detach-role-policy --role-name "$(IAM_ROLE)" --policy-arn $(IAM_POLICY_ARN) >/dev/null
18-
@aws iam delete-role --role-name "$(IAM_ROLE)" > /dev/null
19-
@echo "The '$(IAM_ROLE)' role has been deleted"
20-
21-
list/iam-role:
22-
@aws iam list-roles | jq -r '.Roles | .[] | .RoleName + " " + .Arn' | xargs -n 2 printf '%-40s %s\n'
7+
clean:
8+
rm -rf .terraform

aws/bootstrap/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# bootstrap
2+
3+
This module provisions an AWS user along with a bootstrap role suitable for bootstrapping an AWS multi-account architecture as found in our [reference architectures](https://github.com/cloudposse/reference-architecutres).
4+
5+
These user and role are intended to be used as a **temporary fixture** and should be deprovisioned after all accounts have been provisioned in order to maintain a secure environment.
6+
7+
__WARNING:__ This module grants `AdministrativeAccess` in the current account along with the `OrganizationAccountAccessRole` to all `accounts_enabled` **without MFA**. We repeat, this module should *only* be used during the bootstrapping phase when provisioning your infrastructure for the first time.

aws/bootstrap/assume-role.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

aws/bootstrap/config.tpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Temporary configuration for AWS bootstrapping
2+
[profile ${profile_name}]
3+
region = ${region}
4+
role_arn = ${role_arn}
5+
source_profile = ${source_profile}

aws/bootstrap/credentials.tpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Temporary credentials for AWS bootstrapping
2+
3+
[${source_profile_name}]
4+
aws_access_key_id = ${aws_access_key_id}
5+
aws_secret_access_key = ${aws_secret_access_key}

aws/bootstrap/env.tpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#export AWS_ACCESS_KEY_ID=${aws_access_key_id}
2+
#export AWS_SECRET_ACCESS_KEY=${aws_secret_access_key}
3+
export AWS_ASSUME_ROLE_ARN=${aws_assume_role_arn}
4+
export AWS_CONFIG_FILE=${aws_config_file}
5+
export AWS_DATA_PATH=${aws_data_path}

aws/bootstrap/main.tf

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
terraform {
2+
required_version = ">= 0.11.2"
3+
4+
backend "s3" {}
5+
}
6+
7+
provider "aws" {
8+
assume_role {
9+
role_arn = "${var.aws_assume_role_arn}"
10+
}
11+
}
12+
13+
# User account that will be used for provisioning
14+
module "user" {
15+
source = "git::https://github.com/cloudposse/terraform-aws-iam-system-user.git?ref=tags/0.3.2"
16+
17+
namespace = "${var.namespace}"
18+
stage = "${var.stage}"
19+
name = "bootstrap"
20+
}
21+
22+
# Allow the user to assume role
23+
data "aws_iam_policy_document" "assume_role" {
24+
statement {
25+
effect = "Allow"
26+
27+
actions = ["sts:AssumeRole"]
28+
29+
principals {
30+
type = "AWS"
31+
identifiers = ["${module.user.user_arn}"]
32+
}
33+
}
34+
}
35+
36+
# Fetch the OrganizationAccountAccessRole ARNs from SSM
37+
module "organization_account_access_role_arns" {
38+
source = "git::https://github.com/cloudposse/terraform-aws-ssm-parameter-store?ref=tags/0.1.5"
39+
parameter_read = "${formatlist("/${var.namespace}/%s/organization_account_access_role", var.accounts_enabled)}"
40+
}
41+
42+
# IAM role for bootstrapping; allow user to assume it
43+
resource "aws_iam_role" "bootstrap" {
44+
name = "${module.user.user_name}"
45+
assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
46+
}
47+
48+
# Grant Administrator Access to the current "root" account to the role
49+
resource "aws_iam_role_policy_attachment" "administrator_access" {
50+
role = "${aws_iam_role.bootstrap.name}"
51+
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
52+
}
53+
54+
# Generate a policy for assuming role into all child accounts
55+
data "aws_iam_policy_document" "organization_account_access_role" {
56+
statement {
57+
actions = [
58+
"sts:AssumeRole",
59+
]
60+
61+
effect = "Allow"
62+
resources = ["${module.organization_account_access_role_arns.values}"]
63+
}
64+
}
65+
66+
# Create an IAM policy from the generated document
67+
resource "aws_iam_policy" "organization_account_access_role" {
68+
name = "${aws_iam_role.bootstrap.name}"
69+
policy = "${data.aws_iam_policy_document.organization_account_access_role.json}"
70+
}
71+
72+
# Assign the policy to the user
73+
resource "aws_iam_user_policy_attachment" "organization_account_access_role" {
74+
user = "${aws_iam_role.bootstrap.name}"
75+
policy_arn = "${aws_iam_policy.organization_account_access_role.arn}"
76+
}
77+
78+
# Render the env file with IAM credentials
79+
data "template_file" "env" {
80+
template = "${file("${path.module}/env.tpl")}"
81+
82+
vars {
83+
aws_access_key_id = "${module.user.access_key_id}"
84+
aws_secret_access_key = "${module.user.secret_access_key}"
85+
aws_assume_role_arn = "${aws_iam_role.bootstrap.arn}"
86+
aws_data_path = "${dirname(local_file.config_file.filename)}"
87+
aws_config_file = "${local_file.config_file.filename}"
88+
}
89+
}
90+
91+
# Write the env file to disk
92+
resource "local_file" "env_file" {
93+
content = "${data.template_file.env.rendered}"
94+
filename = "${var.output_path}/${var.env_file}"
95+
}
96+
97+
# Render the credentials file with IAM credentials
98+
data "template_file" "credentials" {
99+
template = "${file("${path.module}/credentials.tpl")}"
100+
101+
vars {
102+
source_profile_name = "${var.namespace}"
103+
aws_access_key_id = "${module.user.access_key_id}"
104+
aws_secret_access_key = "${module.user.secret_access_key}"
105+
aws_assume_role_arn = "${aws_iam_role.bootstrap.arn}"
106+
}
107+
}
108+
109+
# Write the credentials file to disk
110+
resource "local_file" "credentials_file" {
111+
content = "${data.template_file.credentials.rendered}"
112+
filename = "${var.output_path}/${var.credentials_file}"
113+
}
114+
115+
# Render the config file with IAM credentials
116+
data "template_file" "config_root" {
117+
template = "${file("${path.module}/config.tpl")}"
118+
119+
vars {
120+
profile_name = "${var.namespace}-${var.stage}-admin"
121+
source_profile = "${var.namespace}"
122+
region = "${var.aws_region}"
123+
role_arn = "${aws_iam_role.bootstrap.arn}"
124+
}
125+
}
126+
127+
# Render the config file with IAM credentials
128+
data "template_file" "config" {
129+
count = "${length(module.organization_account_access_role_arns.values)}"
130+
template = "${file("${path.module}/config.tpl")}"
131+
132+
vars {
133+
profile_name = "${var.namespace}-${var.accounts_enabled[count.index]}-admin"
134+
source_profile = "${var.namespace}"
135+
region = "${var.aws_region}"
136+
role_arn = "${module.organization_account_access_role_arns.values[count.index]}"
137+
}
138+
}
139+
140+
# Write the config file to disk
141+
resource "local_file" "config_file" {
142+
content = "${join("\n\n",
143+
concat(list("[profile ${var.namespace}]"),
144+
list(data.template_file.config_root.rendered), data.template_file.config.*.rendered))}"
145+
146+
filename = "${var.output_path}/${var.config_file}"
147+
}

aws/bootstrap/outputs.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "env_file" {
2+
description = "Env file with IAM bootstrap credentials"
3+
value = "${var.env_file}"
4+
}

0 commit comments

Comments
 (0)