Skip to content

Commit 5863db6

Browse files
committed
add support for creating cfn stack to generate assumable role
1 parent 4bba286 commit 5863db6

File tree

10 files changed

+194
-24
lines changed

10 files changed

+194
-24
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
# .tfstate files
55
*.tfstate
66
*.tfstate.*
7+
8+
assumerole
9+
assumerole/*

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ repos:
4747
for DIR in $(printf "%s\n" "${DIRS[@]}" | sort -u)
4848
do
4949
cd $(dirname "$FILE")
50-
terraform providers lock -platform=windows_amd64 -platform=darwin_amd64 -platform=linux_amd64
50+
terraform providers lock -platform=linux_amd64
5151
cd ..
5252
done
5353
'

.terraform.lock.hcl

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ See [Use AssumeRole to Provision AWS Resources Across Accounts](https://learn.ha
3636
|------|---------|
3737
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.14 |
3838
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.0 |
39+
| <a name="requirement_local"></a> [local](#requirement\_local) | >= 2.0 |
40+
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 3.0 |
3941

4042
## Providers
4143

4244
| Name | Version |
4345
|------|---------|
4446
| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.28.0 |
47+
| <a name="provider_local"></a> [local](#provider\_local) | 2.2.3 |
48+
| <a name="provider_random"></a> [random](#provider\_random) | 3.4.1 |
4549

4650
## Modules
4751

@@ -55,28 +59,36 @@ No modules.
5559
| [aws_kms_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
5660
| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
5761
| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
58-
| [aws_s3_bucket_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
5962
| [aws_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
6063
| [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
64+
| [aws_s3_bucket_ownership_controls.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource |
6165
| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
6266
| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
63-
| [aws_s3_bucket_versioning.versioning_example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
67+
| [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
68+
| [local_file.assumerole_addrole](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
69+
| [local_sensitive_file.assumerole_tfassumerole](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource |
70+
| [random_password.external_id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
6471
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
65-
| [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source |
6672
| [aws_iam_policy_document.key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
73+
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
6774
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
6875

6976
## Inputs
7077

7178
| Name | Description | Type | Default | Required |
7279
|------|-------------|------|---------|:--------:|
73-
| <a name="input_bucket_name"></a> [bucket\_name](#input\_bucket\_name) | Name of bucket to create | `string` | n/a | yes |
80+
| <a name="input_assumerole_role_attach_policies"></a> [assumerole\_role\_attach\_policies](#input\_assumerole\_role\_attach\_policies) | Policy ARNs to attach to role (can be managed or custom but must exist) | `list(string)` | <pre>[<br> "arn:aws:iam::aws:policy/AdministratorAccess"<br>]</pre> | no |
81+
| <a name="input_assumerole_role_external_id"></a> [assumerole\_role\_external\_id](#input\_assumerole\_role\_external\_id) | External ID to attach to role (this is required, a random ID will be generated if not specified here) | `string` | `null` | no |
82+
| <a name="input_assumerole_role_name"></a> [assumerole\_role\_name](#input\_assumerole\_role\_name) | Name of role to create in assumerole template | `string` | `"Terraform"` | no |
83+
| <a name="input_assumerole_stack_name"></a> [assumerole\_stack\_name](#input\_assumerole\_stack\_name) | Name of CloudFormation stack | `string` | `"tf-assumerole"` | no |
84+
| <a name="input_bucket_name"></a> [bucket\_name](#input\_bucket\_name) | Name of bucket to hold tf state | `string` | n/a | yes |
85+
| <a name="input_create_assumerole_template"></a> [create\_assumerole\_template](#input\_create\_assumerole\_template) | If true, create a CloudFormation template that can be run against accounts to create an assumable role | `bool` | `false` | no |
86+
| <a name="input_dynamo_locktable_name"></a> [dynamo\_locktable\_name](#input\_dynamo\_locktable\_name) | Name of lock table for terraform | `string` | `"tf-locktable"` | no |
7487
| <a name="input_kms_alias_name"></a> [kms\_alias\_name](#input\_kms\_alias\_name) | Name of KMS Alias | `string` | `null` | no |
7588
| <a name="input_kms_key_id"></a> [kms\_key\_id](#input\_kms\_key\_id) | ARN for KMS key for all encryption operations (a key will be created if this is not provided) | `string` | `null` | no |
7689
| <a name="input_lifecycle_rules"></a> [lifecycle\_rules](#input\_lifecycle\_rules) | lifecycle rules to apply to the bucket (set to null to skip lifecycle rules) | <pre>list(object(<br> {<br> id = string<br> enabled = bool<br> prefix = string<br> expiration = number<br> noncurrent_version_expiration = number<br> }))</pre> | <pre>[<br> {<br> "enabled": true,<br> "expiration": 90,<br> "id": "tfstate-expire",<br> "noncurrent_version_expiration": 90,<br> "prefix": null<br> }<br>]</pre> | no |
7790
| <a name="input_logging_target_bucket"></a> [logging\_target\_bucket](#input\_logging\_target\_bucket) | The name of the bucket that will receive the log objects (logging will be disabled if null) | `string` | `null` | no |
7891
| <a name="input_logging_target_prefix"></a> [logging\_target\_prefix](#input\_logging\_target\_prefix) | A key prefix for log objects | `string` | `null` | no |
79-
| <a name="input_table"></a> [table](#input\_table) | Name of Dynamo Table to create | `string` | `"tf-locktable"` | no |
8092
| <a name="input_tags"></a> [tags](#input\_tags) | Mapping of any extra tags you want added to resources | `map(string)` | `{}` | no |
8193

8294
## Outputs

assumerole.tf

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
locals {
2+
external_id = coalesce(var.assumerole_role_external_id, random_password.external_id.result)
3+
}
4+
5+
resource "local_file" "assumerole_addrole" {
6+
count = var.create_assumerole_template ? 1 : 0
7+
8+
filename = "${path.module}/assumerole/addrole.sh"
9+
10+
content = templatefile("${path.module}/template/addrole.sh.tftpl", {
11+
stack_name = var.assumerole_stack_name
12+
})
13+
14+
}
15+
16+
resource "local_sensitive_file" "assumerole_tfassumerole" {
17+
count = var.create_assumerole_template ? 1 : 0
18+
19+
filename = "${path.module}/assumerole/tfassumerole.cfn.yml"
20+
21+
content = templatefile("${path.module}/template/tfassumerole.cfn.yml.tftpl", {
22+
external_id = local.external_id
23+
parent_account_id = local.account_id
24+
partition = local.partition
25+
policy_arns = var.assumerole_role_attach_policies
26+
role_name = var.assumerole_role_name
27+
})
28+
29+
}
30+
31+
# not used if an external id is specified
32+
resource "random_password" "external_id" {
33+
length = 16
34+
special = true
35+
override_special = "-_=+"
36+
}

main.tf

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
data "aws_caller_identity" "current" {}
2-
data "aws_region" "current" {}
1+
data "aws_caller_identity" "current" {
2+
}
3+
4+
data "aws_partition" "current" {
5+
}
6+
7+
data "aws_region" "current" {
8+
}
9+
310
locals {
411
account_id = data.aws_caller_identity.current.account_id
12+
partition = data.aws_partition.current.partition
513
region = data.aws_region.current.name
614

715
# Resolve resource names
816
bucket_name = try(var.bucket_name, "${local.account_id}-${local.region}-tfstate")
917
kms_key_id = try(aws_kms_key.this[0].arn, var.kms_key_id)
1018
}
1119

12-
data "aws_canonical_user_id" "current" {}
13-
1420
# tfsec is not yet smart enough to know new tf syntax for crypto/logging
1521
#tfsec:ignore:AWS017 #tfsec:ignore:AWS002
1622
resource "aws_s3_bucket" "this" {
@@ -21,13 +27,11 @@ resource "aws_s3_bucket" "this" {
2127
})
2228
}
2329

24-
resource "aws_s3_bucket_acl" "this" {
30+
resource "aws_s3_bucket_ownership_controls" "this" {
2531
bucket = aws_s3_bucket.this.id
2632

27-
access_control_policy {
28-
owner {
29-
id = data.aws_canonical_user_id.current.id
30-
}
33+
rule {
34+
object_ownership = "BucketOwnerEnforced"
3135
}
3236
}
3337

@@ -97,12 +101,12 @@ resource "aws_s3_bucket_public_access_block" "this" {
97101
}
98102

99103
resource "aws_dynamodb_table" "this" {
100-
name = var.table
104+
name = var.dynamo_locktable_name
101105
billing_mode = "PAY_PER_REQUEST"
102106
hash_key = "LockID"
103107

104108
tags = merge(var.tags, {
105-
"Name" = var.table
109+
"Name" = var.dynamo_locktable_name
106110
})
107111

108112
attribute {

template/addrole.sh.tftpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aws cloudformation create-stack --capabilities CAPABILITY_NAMED_IAM --template-body file://tfassumerole.cfn.yml --stack-name ${stack_name}

template/tfassumerole.cfn.yml.tftpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description:
3+
Creates a role that can be assumed by the designated account for terraform runs
4+
5+
Resources:
6+
FullAdminRole:
7+
Type: 'AWS::IAM::Role'
8+
Properties:
9+
RoleName: ${role_name}
10+
MaxSessionDuration: 7200
11+
AssumeRolePolicyDocument:
12+
Version: '2012-10-17'
13+
Statement:
14+
- Effect: Allow
15+
Principal:
16+
AWS: 'arn:${partition}:iam::${parent_account_id}:root'
17+
Action:
18+
- sts:AssumeRole
19+
Condition:
20+
StringEquals:
21+
sts:ExternalId: ${external_id}
22+
Path: '/'
23+
ManagedPolicyArns:
24+
%{ for policy in policy_arns ~}
25+
- ${policy}
26+
%{ endfor ~}

variables.tf

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
variable "bucket_name" {
2-
description = "Name of bucket to create"
2+
description = "Name of bucket to hold tf state"
3+
type = string
4+
}
5+
6+
variable "dynamo_locktable_name" {
7+
default = "tf-locktable"
8+
description = "Name of lock table for terraform"
39
type = string
410
}
511

@@ -48,14 +54,42 @@ variable "logging_target_prefix" {
4854
type = string
4955
}
5056

51-
variable "table" {
52-
default = "tf-locktable"
53-
description = "Name of Dynamo Table to create"
54-
type = string
55-
}
56-
5757
variable "tags" {
5858
default = {}
5959
description = "Mapping of any extra tags you want added to resources"
6060
type = map(string)
6161
}
62+
63+
########################################
64+
# Assume Role Template Vars
65+
########################################
66+
variable "create_assumerole_template" {
67+
default = false
68+
description = "If true, create a CloudFormation template that can be run against accounts to create an assumable role"
69+
type = bool
70+
}
71+
72+
73+
variable "assumerole_role_name" {
74+
default = "Terraform"
75+
description = "Name of role to create in assumerole template"
76+
type = string
77+
}
78+
79+
variable "assumerole_role_external_id" {
80+
default = null
81+
description = "External ID to attach to role (this is required, a random ID will be generated if not specified here)"
82+
type = string
83+
}
84+
85+
variable "assumerole_role_attach_policies" {
86+
default = ["arn:aws:iam::aws:policy/AdministratorAccess"]
87+
description = "Policy ARNs to attach to role (can be managed or custom but must exist)"
88+
type = list(string)
89+
}
90+
91+
variable "assumerole_stack_name" {
92+
default = "tf-assumerole"
93+
description = "Name of CloudFormation stack"
94+
type = string
95+
}

versions.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,15 @@ terraform {
77
source = "hashicorp/aws"
88
version = ">= 4.0"
99
}
10+
11+
local = {
12+
source = "hashicorp/local"
13+
version = ">= 2.0"
14+
}
15+
16+
random = {
17+
source = "hashicorp/random"
18+
version = ">= 3.0"
19+
}
1020
}
1121
}

0 commit comments

Comments
 (0)