tfctl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ // Settings:
2
+ :idprefix:
3
+ :idseparator: -
4
+ ifndef::env-github[:icons: font]
5
+ ifdef::env-github,env-browser[]
6
+ :toc: macro
7
+ :toclevels: 1
8
+ endif::[]
9
+ ifdef::env-github[]
10
+ :branch: master
11
+ :status:
12
+ :outfilesuffix: .adoc
13
+ :!toc-title:
14
+ :caution-caption: :fire:
15
+ :important-caption: :exclamation:
16
+ :note-caption: :paperclip:
17
+ :tip-caption: :bulb:
18
+ :warning-caption: :warning:
19
+ endif::[]
20
+
21
+ = Configuration
22
+
23
+ toc::[]
24
+
25
+ == Overview
26
+
27
+ Tfctl retrieves initial account configuration from AWS Organizations and merges
28
+ it with configuration specified in a yaml file.
29
+
30
+ The configuration is merged in the following order:
31
+
32
+ * AWS Organizations data is fetched and stored in an `accounts` array.
33
+ * `organization_root` settings are merged with all accounts.
34
+ * `organization_units` settings are merged with accounts matching the OU.
35
+ * `account_overrides` are merged with individual accounts matching the account name.
36
+
37
+ Parameters further down the hierarchy take precedence. For example:
38
+
39
+ [source, yaml]
40
+ ----
41
+ organization_root:
42
+ data:
43
+ example_param: 'will be overriden further down'
44
+
45
+ organization_units:
46
+ team:
47
+ data:
48
+ example_param: 'will win in team ou'
49
+ team/live:
50
+ data:
51
+ example_param: 'will win in team/live ou'
52
+ ----
53
+
54
+ One exception to this rule is the `profiles` parameter. Profiles are additive:
55
+
56
+ [source, yaml]
57
+ ----
58
+ organization_root:
59
+ profiles:
60
+ - profile-one
61
+ - profile-two
62
+
63
+ organization_units:
64
+ team:
65
+ profiles:
66
+ - profile-three
67
+ ----
68
+
69
+ This will result in all three profiles deployed to accounts in `team` OU.
70
+
71
+ TIP: You can display the fully merged configuration by running `tfctl -c
72
+ conf/CONFIG_FILE.yaml -s`. It's safe to run as it doesn't make any changes to
73
+ AWS resources. It's a good way to test your configuration.
74
+
75
+ == Defining arbitrary data
76
+
77
+ You can define arbitrary data under the `data:` parameter, both in the root of
78
+ the config and in the organization sections. It will be available in Terraform
79
+ profiles to use by your modules. You can use this to define things like VPC
80
+ subnet ranges, s3 bucket names and so on. `data:` in organization sections
81
+ will be merged with accounts following the usual merge order as described
82
+ above.
83
+
84
+ == Handling secrets
85
+
86
+ No secrets should be committed into Terraform or tfctl configuration. Use AWS
87
+ Secrets Manager instead and retrieve in Terraform profiles using
88
+ https://www.terraform.io/docs/providers/aws/d/secretsmanager_secret.html[secrets
89
+ manager data source]
@@ -0,0 +1,211 @@
1
+ // Settings:
2
+ :idprefix:
3
+ :idseparator: -
4
+ ifndef::env-github[:icons: font]
5
+ ifdef::env-github,env-browser[]
6
+ :toc: macro
7
+ :toclevels: 1
8
+ endif::[]
9
+ ifdef::env-github[]
10
+ :branch: master
11
+ :status:
12
+ :outfilesuffix: .adoc
13
+ :!toc-title:
14
+ :caution-caption: :fire:
15
+ :important-caption: :exclamation:
16
+ :note-caption: :paperclip:
17
+ :tip-caption: :bulb:
18
+ :warning-caption: :warning:
19
+ endif::[]
20
+
21
+ = Control Tower integration guide
22
+
23
+ This guide will help you integrate Terraform with AWS Control Tower using the
24
+ tfctl wrapper. This involves setting up resources for remote state tracking,
25
+ necessary IAM roles and a tfctl project.
26
+
27
+ toc::[]
28
+
29
+ == Overview
30
+
31
+ For state tracking we're going to create a dedicated `shared-services` account
32
+ under a `mgmt` organization unit. We'll use S3 for state storage and DynamoDB
33
+ for locking. `TerraformState` IAM role will be created for cross account
34
+ access to state resources from the primary account.
35
+
36
+ In the primary account we'll create a `TfctlOrgAccess` role. It gives tfctl
37
+ read only access to AWS Organizations which is used to discover accounts and
38
+ the organization unit structure.
39
+
40
+ We'll use CloudFormation stacks and stack-sets to bootstrap these resources.
41
+
42
+ For executing Terraform in spoke accounts we'll use the
43
+ `AWSControlTowerExecution` role which is automatically created by Control Tower
44
+ account factory and can be assumed from the primary account.
45
+
46
+ We're going to create a `live` and `test` organization units in Control Tower
47
+ and provision a couple of accounts for testing.
48
+
49
+ == Prerequisites
50
+
51
+ Before starting you'll need:
52
+
53
+ * Control Tower set up in your primary account.
54
+ * A user with `AdministratorAccess` privileges in primary account.
55
+ * AWS CLI tools installed on your machine.
56
+ * Terraform 0.12 or higher.
57
+
58
+ == Configure Control Tower
59
+
60
+ Create the following organization units in Control Tower:
61
+
62
+ * `mgmt`
63
+ * `live`
64
+ * `test`
65
+
66
+ Then provision accounts:
67
+
68
+ * In `mgmt` OU create an account called `mgmt-shared-services`
69
+ * In `live` create `live-example1`
70
+ * In `test` create `test-example1`
71
+
72
+ NOTE: Control Tower accounts need to be provisioned one at a time. It takes
73
+ approximately 20 mins to provision one.
74
+
75
+ == Install tfctl
76
+
77
+ ----
78
+ git clone git@github.com:scalefactory/tfctl.git
79
+ cd tfctl/ && sudo make install
80
+ ----
81
+
82
+ == Set up AWS resources
83
+
84
+ It's assumed you have configured AWS CLI access to the primary account.
85
+
86
+ We'll use CloudFormation templates in `examples/bootstrap/`.
87
+
88
+ First export configuration using environment variables making sure to change to
89
+ values to suit your set up:
90
+
91
+ ----
92
+ export PRIMARY_ACCOUNT_ID=11111111
93
+ export SHARED_SERVICES_ACCOUNT_ID=22222222
94
+ export STATE_BUCKET_NAME='example-terraform-state'
95
+ ----
96
+
97
+ Create the remote state resources stack set:
98
+
99
+ ----
100
+ cd examples/bootstrap/
101
+
102
+ aws cloudformation create-stack-set \
103
+ --stack-set-name TerraformState \
104
+ --template-body file://terraform-state.template \
105
+ --description "Resources for managing Terraform state" \
106
+ --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
107
+ --execution-role-name AWSControlTowerExecution \
108
+ --administration-role-arn arn:aws:iam::${PRIMARY_ACCOUNT_ID}:role/service-role/AWSControlTowerStackSetRole \
109
+ --parameters ParameterKey=PrimaryAccountId,ParameterValue=${PRIMARY_ACCOUNT_ID} \
110
+ ParameterKey=TerraformStateBucket,ParameterValue=${STATE_BUCKET_NAME}
111
+ ----
112
+
113
+ Create a stack set instance in you shared services account:
114
+
115
+ ----
116
+ aws cloudformation create-stack-instances \
117
+ --stack-set-name TerraformState \
118
+ --accounts ${SHARED_SERVICES_ACCOUNT_ID} \
119
+ --regions eu-west-1
120
+ ----
121
+
122
+ Check status:
123
+
124
+ ----
125
+ aws cloudformation describe-stack-instance \
126
+ --stack-set-name TerraformState \
127
+ --stack-instance-account ${SHARED_SERVICES_ACCOUNT_ID} \
128
+ --stack-instance-region eu-west-1
129
+ ----
130
+
131
+ NOTE: Initial status will be `OUTDATED`, it should change to `CURRENT` once deployed.
132
+
133
+ Deploy `TfctlOrgAccess` IAM role stack:
134
+
135
+ ----
136
+ aws cloudformation create-stack \
137
+ --stack-name TfctlOrgAccess \
138
+ --template-body file://tfctl-org-access.template \
139
+ --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
140
+ --parameters ParameterKey=PrimaryAccountId,ParameterValue=${PRIMARY_ACCOUNT_ID}
141
+ ----
142
+
143
+ Check status:
144
+
145
+ ----
146
+ aws cloudformation describe-stacks --stack-name TfctlOrgAccess
147
+ ----
148
+
149
+ NOTE: Successful status should read: `CREATE_COMPLETE`.
150
+
151
+ == Configure tfctl
152
+
153
+ Copy the example project directory `examples/control_tower` somewhere convenient
154
+ and edit `conf/example.yaml`.
155
+
156
+ You need to modify the following parameters:
157
+
158
+ * `tf_state_bucket` - set to `$STATE_BUCKET_NAME`
159
+ * `tf_state_role_arn` - set shared services account ID
160
+ * `tfctl_role_arn` - set primary account ID
161
+ * `primary_account` - set the primary account name. You can find it in AWS Organizations.
162
+
163
+ TIP: You should keep your project directory under version control.
164
+
165
+ == Deploy example tfctl profile
166
+
167
+ The example profile will create an S3 bucket in accounts under `test`, `live`
168
+ and `mgmt` OUs.
169
+
170
+ NOTE: Run tfctl commands from the root of you project directory.
171
+
172
+ First dump the configuration to verify everything works:
173
+
174
+ ----
175
+ tfctl -c conf/example.yaml -s
176
+ ----
177
+
178
+ This will not make any changes but will print out a yaml containing the final,
179
+ merged configuration data. It should contain a list of discovered accounts and
180
+ their configuration.
181
+
182
+ Initialise terraform for all discovered accounts:
183
+
184
+ ----
185
+ tfctl -c conf/example.yaml --all -- init
186
+ ----
187
+
188
+ Tfctl will run Terraform against all accounts in parallel.
189
+
190
+ Run plan:
191
+
192
+ ----
193
+ tfctl -c conf/example.yaml --all -- plan
194
+ ----
195
+
196
+ and apply:
197
+
198
+ ----
199
+ tfctl -c conf/example.yaml --all -- apply
200
+ ----
201
+
202
+ To destroy created resources run:
203
+
204
+ ----
205
+ tfctl -c conf/example.yaml --all -- destroy -auto-approve
206
+ ----
207
+
208
+ That's it! You can now execute terraform across your Control Tower estate.
209
+
210
+ TIP: Your project directory should be under version control excluding the
211
+ `.tfctl` directory which is automatically generated.
@@ -0,0 +1,191 @@
1
+ // Settings:
2
+ :idprefix:
3
+ :idseparator: -
4
+ ifndef::env-github[:icons: font]
5
+ ifdef::env-github,env-browser[]
6
+ :toc: macro
7
+ :toclevels: 1
8
+ endif::[]
9
+ ifdef::env-github[]
10
+ :branch: master
11
+ :status:
12
+ :outfilesuffix: .adoc
13
+ :!toc-title:
14
+ :caution-caption: :fire:
15
+ :important-caption: :exclamation:
16
+ :note-caption: :paperclip:
17
+ :tip-caption: :bulb:
18
+ :warning-caption: :warning:
19
+ endif::[]
20
+
21
+ = Creating and deploying a tfctl profile
22
+
23
+ This guide will show you how to create a tfctl profile, declare some resources
24
+ in it and deploy it to to a group of accounts in an organization unit.
25
+
26
+ toc::[]
27
+
28
+ == Create a new profile
29
+
30
+ In your tfctl project directory create a new profile:
31
+
32
+ ----
33
+ mkdir profiles/example-profile
34
+ ----
35
+
36
+ Withing the profile create `data.tf`:
37
+
38
+ .data.tf
39
+ [source, tf]
40
+ ----
41
+ data "aws_caller_identity" "current" {}
42
+ ----
43
+
44
+ This file contains Terraform
45
+ https://www.terraform.io/docs/configuration/data-sources.html[data source]
46
+ declarations. Data sources are a way of getting data not directly managed in
47
+ Terraform into Terraform. In this case we're using the
48
+ https://www.terraform.io/docs/providers/aws/d/caller_identity.html[aws_caller_identity]
49
+ . One of the outputs of this source is `account_id` which will
50
+ return the id of the account Terraform is currently running in.
51
+
52
+ Now create `variables.tf`:
53
+
54
+ .variables.tf
55
+ [source, tf]
56
+ ----
57
+ variable "config" {
58
+ description = "Configuration generated by tfctl in string encoded JSON"
59
+ type = string
60
+ }
61
+
62
+ # local variables
63
+ locals {
64
+ # Decode config JSON into a Terraform data structure
65
+ config = jsondecode(var.config)
66
+
67
+ # Get current account id from aws_caller_identity data source
68
+ current_account_id = "${data.aws_caller_identity.current.account_id}"
69
+
70
+ # Get tfctl configuration for the current account
71
+ current_account_conf = [ for account in local.config["accounts"]: account if account["id"] == local.current_account_id ][0]
72
+ }
73
+ ----
74
+
75
+ This file contains
76
+ https://www.terraform.io/docs/configuration/variables.html[input variables] for
77
+ the profile.
78
+
79
+ The `config` variable is special and must always be declared in a tfctl
80
+ profile. Tfctl configuration can be accessed using this variable. This It
81
+ includes an array of all discovered accounts as well their parameters from
82
+ tfctl config file.
83
+
84
+ TIP: You can run `tfctl -c conf/CONFIG_FILE.yaml -s` to show the config data in
85
+ yaml format. This exact data is available in the `config` variable in your
86
+ profile.
87
+
88
+ We also have few https://www.terraform.io/docs/configuration/locals.html[local
89
+ variables] in the `locals` block. We assign the current account id from the
90
+ data source we defined previously to `current_account_id`. This is mainly for
91
+ convenience to make the next statement easier to read. `current_account` loops
92
+ over the `config` data and returns configuration for an account which matches
93
+ the current account id (i.e. the current account configuration).
94
+
95
+ Now that we have our data inputs sorted we can start declaring actual AWS
96
+ resources to manage.
97
+
98
+ Create `main.tf`:
99
+
100
+ .main.tf
101
+ [source, tf]
102
+ ----
103
+ resource "aws_s3_bucket" "example" {
104
+ bucket = "tfctl-${local.current_account_conf["name"]}"
105
+ acl = "private"
106
+ }
107
+ ----
108
+
109
+ This will create an S3 bucket with a name containing the current account name
110
+ (which will vary depending on which account it's deployed to).
111
+
112
+ == Assign profile to accounts
113
+
114
+ Before you can deploy the new profile you need to tell `tfctl` which accounts
115
+ to deploy it to.
116
+
117
+ You have few options here:
118
+
119
+ * deploy to all accounts
120
+ * deploy to specific organization unit (OU)
121
+ * deploy to individual account
122
+
123
+
124
+ For the sake of this example we're going to deploy our bucket to all accounts
125
+ in `test` OU.
126
+
127
+ In tfctl config file add the profile to the `test` OU:
128
+
129
+ [source, yaml]
130
+ ----
131
+ organization_units:
132
+ test:
133
+ profiles:
134
+ - example-profile
135
+ ----
136
+
137
+
138
+ == Plan
139
+
140
+ To see what would happen when the change is applied run:
141
+
142
+ ----
143
+ tfctl -c conf/example.yaml -o test -- init
144
+ tfctl -c conf/example.yaml -o test -- plan
145
+ ----
146
+
147
+ This will run `terraform init` to initialise terraform and then `terraform
148
+ plan` across all accounts in the `test` OU in parallel. It will display a diff
149
+ of changes for each account.
150
+
151
+ .example terraform plan
152
+ ----
153
+ info: test-example: Terraform will perform the following actions:
154
+ info: test-example:
155
+ info: test-example: # module.example-profile.aws_s3_bucket.example will be created
156
+ info: test-example: + resource "aws_s3_bucket" "example" {
157
+ info: test-example: + acceleration_status = (known after apply)
158
+ info: test-example: + acl = "private"
159
+ info: test-example: + arn = (known after apply)
160
+ info: test-example: + bucket = "tfctl-test-example"
161
+ info: test-example: + bucket_domain_name = (known after apply)
162
+ info: test-example: + bucket_regional_domain_name = (known after apply)
163
+ info: test-example: + force_destroy = false
164
+ info: test-example: + hosted_zone_id = (known after apply)
165
+ info: test-example: + id = (known after apply)
166
+ info: test-example: + region = (known after apply)
167
+ info: test-example: + request_payer = (known after apply)
168
+ info: test-example: + website_domain = (known after apply)
169
+ info: test-example: + website_endpoint = (known after apply)
170
+ info: test-example:
171
+ info: test-example: + versioning {
172
+ info: test-example: + enabled = (known after apply)
173
+ info: test-example: + mfa_delete = (known after apply)
174
+ info: test-example: }
175
+ info: test-example: }
176
+ info: test-example:
177
+ info: test-example: Plan: 1 to add, 0 to change, 0 to destroy.
178
+ ----
179
+
180
+ If there are errors in your profile, terraform will fail and usually indicate
181
+ what went wrong.
182
+
183
+ tfctl will generate a plan file automatically and use it with `apply` in the
184
+ next step.
185
+
186
+ == Apply
187
+
188
+ Once you're happy with the plan, apply it.
189
+ ----
190
+ tfctl -c conf/example.yaml -o test -- apply
191
+ ----