Skip to content

Commit

Permalink
Merge pull request #9 from mhlias/secondary_cross_acount
Browse files Browse the repository at this point in the history
Add support for secondary account per primary account for provisionin…
  • Loading branch information
mhlias authored Aug 1, 2017
2 parents f015b9f + 3ece08f commit cddc827
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 1 deletion.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,55 @@ parallelism: 10

*1 More information on how to setup AWS assume roles can be found here: [tutorial] (http://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html) [To create a role for cross-account access (AWS CLI)] (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html#roles-creatingrole-user-cli)

`Cross-account resources`:

From v0.9.0 there is support for a secondary account per primary account to allow to provision AWS resources across different AWS accounts. Can only be used with STS enabled. A requirement is the AWS shared credentials profile that is used on the primary account to be able to assume the role specified on the secondary account. `project.yaml` configuration example follows:


```
project: name_of_your_project
region: eu-west-1
profiles:
project-dev: aws_shared_credentials_profile1
project-prd: aws_shared_credentials_profile2
roam-roles:
project-dev: roam-role-dev
project-prd: roam-role-prd
use-sts: true
encrypt-s3-state: true
accounts-mapping:
project-dev: 100000000001
project-prd: 100000000002
secondary_accounts:
project_dev:
id: 20001
role: secondary-assumed-dev-role
region: eu-west-2
parallelism: 10
```

All of `id`, `role` and `region` are required for each secondary account.
Then at runtime tholos will export the following environment variables that Terraform can pick up:

- TF_VAR_secondary_access_key_id
- TF_VAR_secondary_secret_access_key
- TF_VAR_secondary_security_token
- TF_VAR_secondary_session_token
- TF_VAR_secondary_region

From there in your Terraform code you can have a provider entry for the secondary account like:

```
provider "aws" {
alias = "secondary"
region = "${var.secondary_region}"
access_key = "${var.secondary_access_key_id}"
secret_key = "${var.secondary_secret_access_key}"
token = "${var.secondary_session_token}"
}
```

`Terrafile`:

Expand Down
35 changes: 35 additions & 0 deletions aws_helper/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ type Config struct {
Use_sts bool
Mfa_device_id string
Mfa_token string
Secondary *Account
}

type Account struct {
Account_id string `yaml:"id"`
Role string `yaml:"role"`
Region string `yaml:"region"`
}

func (c *Config) Connect() interface{} {
Expand All @@ -41,6 +48,12 @@ func (c *Config) Connect() interface{} {
os.Unsetenv("AWS_SESSION_TOKEN")
os.Unsetenv("AWS_DEFAULT_REGION")

os.Unsetenv("TF_VAR_secondary_access_key_id")
os.Unsetenv("TF_VAR_secondary_secret_access_key")
os.Unsetenv("TF_VAR_secondary_security_token")
os.Unsetenv("TF_VAR_secondary_session_token")
os.Unsetenv("TF_VAR_secondary_region")

screds := &credentials.SharedCredentialsProvider{Profile: c.Profile}

log.Printf("[INFO] Using aws shared credentials profile: %s\n", c.Profile)
Expand Down Expand Up @@ -92,6 +105,28 @@ func (c *Config) Connect() interface{} {
os.Setenv("AWS_SESSION_TOKEN", *sts_resp.Credentials.SessionToken)
os.Setenv("AWS_DEFAULT_REGION", c.Region)

if len(c.Secondary.Account_id) > 0 && len(c.Secondary.Role) > 0 && len(c.Secondary.Region) > 0 {

params2 := &sts.AssumeRoleInput{
RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", c.Secondary.Account_id, c.Secondary.Role)),
RoleSessionName: aws.String(fmt.Sprintf("%s-%s", c.Account_id, c.Role)),
DurationSeconds: aws.Int64(3600),
}

stssec_resp, stssec_err := client.stsconn.AssumeRole(params2)

if stssec_err != nil {
log.Fatalf("Unable to assume role: %v", stssec_err.Error())
}

os.Setenv("TF_VAR_secondary_access_key_id", *stssec_resp.Credentials.AccessKeyId)
os.Setenv("TF_VAR_secondary_secret_access_key", *stssec_resp.Credentials.SecretAccessKey)
os.Setenv("TF_VAR_secondary_security_token", *stssec_resp.Credentials.SessionToken)
os.Setenv("TF_VAR_secondary_session_token", *stssec_resp.Credentials.SessionToken)
os.Setenv("TF_VAR_secondary_region", c.Secondary.Region)

}

return c.assumeConnect(sts_resp)

} else {
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type conf struct {
Parallelism int16 `yaml:"parallelism"`
environment string
account string
Secondary map[string]*aws_helper.Account `yaml:"secondary_accounts"`
}

type multiflag []string
Expand Down Expand Up @@ -175,6 +176,7 @@ func main() {
Use_sts: project_config.Use_sts,
Mfa_device_id: mfa_device_id,
Mfa_token: mfa_token,
Secondary: project_config.Secondary[project_config.account],
}

client = awsconf.Connect()
Expand Down
10 changes: 9 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ func TestProjectConfig(t *testing.T) {

project_config := load_config(fmt.Sprintf("%s/project.yaml", fixtures_dir))

if project_config.Parallelism != 4 || project_config.Project != "test" || project_config.Region != "eu-west-1" || !project_config.Use_sts || !project_config.Encrypt_s3_state || len(project_config.Roam_roles[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 || len(project_config.Accounts_mapping[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 || len(project_config.Accounts_mapping[fmt.Sprintf("%s-prd", project_config.Project)]) <= 0 {
if project_config.Parallelism != 4 || project_config.Project != "test" ||
project_config.Region != "eu-west-1" || !project_config.Use_sts ||
!project_config.Encrypt_s3_state || len(project_config.Roam_roles[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 ||
len(project_config.Accounts_mapping[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 ||
len(project_config.Accounts_mapping[fmt.Sprintf("%s-prd", project_config.Project)]) <= 0 ||
len(project_config.Secondary) != 1 || project_config.Secondary[fmt.Sprintf("%s-dev", project_config.Project)].Account_id != "2001" ||
project_config.Secondary[fmt.Sprintf("%s-dev", project_config.Project)].Role != "secondary-role-dev" ||
project_config.Secondary[fmt.Sprintf("%s-dev", project_config.Project)].Region != "eu-west-2" ||
len(project_config.Profiles) != 2 || project_config.Profiles[fmt.Sprintf("%s-dev", project_config.Project)] != "nproot" {
t.Fatal("Project configuration parameters in fixtures don't match expected values when parsed.")
}

Expand Down
8 changes: 8 additions & 0 deletions test-fixtures/project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

project: test
region: eu-west-1
profiles:
test-dev: nproot
test-prd: root
roam-roles:
test-dev: roam-role-dev
test-prd: roam-role-prd
Expand All @@ -10,4 +13,9 @@ encrypt-s3-state: true
accounts-mapping:
test-dev: 1001
test-prd: 1002
secondary_accounts:
test-dev:
id: 2001
role: secondary-role-dev
region: eu-west-2
parallelism: 4

0 comments on commit cddc827

Please sign in to comment.