Skip to content

Commit

Permalink
Merge pull request #575 from remind101/cross-account-example
Browse files Browse the repository at this point in the history
Example cross-account configuration/structure
  • Loading branch information
ejholmes committed Mar 28, 2018
2 parents 042c6ec + 640ebac commit 9b138c1
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 0 deletions.
19 changes: 19 additions & 0 deletions examples/cross-account/.aws/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# The master account is like the root of our AWS account tree. It's the
# entrypoint for all other profiles to sts.AssumeRole from.
[profile master]
region = us-east-1
role_arn = arn:aws:iam::<master account id>:role/Stacker
role_session_name = stacker
credential_source = Environment

[profile prod]
region = us-east-1
role_arn = arn:aws:iam::<prod account id>:role/Stacker
role_session_name = stacker
source_profile = master

[profile stage]
region = us-east-1
role_arn = arn:aws:iam::<stage account id>:role/Stacker
role_session_name = stacker
source_profile = master
96 changes: 96 additions & 0 deletions examples/cross-account/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
This is a secure example setup to support cross-account provisioning of stacks with stacker. It:

1. Sets up an appropriate [AWS Config File](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html) in [.aws/config] for stacker to use, with profiles for a "master", "prod" and "stage" AWS account.
2. Configures a stacker bucket in the "master" account, with permissions that allows CloudFormation in "sub" accounts to fetch templates.

## Setup

### Create IAM roles

First things first, we need to create some IAM roles that stacker can assume to make changes in each AWS account. This is generally a manual step after you've created a new AWS account.

In each account, create a new stack using the [stacker-role.yaml](./templates/stacker-role.yaml) CloudFormation template. This will create an IAM role called `Stacker` in the target account, with a trust policy that will allow the `Stacker` role in the master account to `sts:AssumeRole` it.

Once the roles have been created, update the `role_arn`'s in [.aws/config] to match the ones that were just created.

```console
$ aws cloudformation describe-stacks \
--profile <profile> \
--stack-name <stack name> \
--query 'Stacks[0].Outputs' --output text
StackerRole arn:aws:iam::<account id>:role/Stacker
```

### GetSessionToken

In order for stacker to be able to call `sts:AssumeRole` with the roles we've specified in [.aws/config], we'll need to pass it credentials via environment variables (see [`credential_source = Environment`](./.aws/config)) with appropriate permissions. Generally, the best way to do this is to obtain temporary credentials via the `sts:GetSessionToken` API, while passing an MFA OTP.

Assuming you have an IAM user in your master account, you can get temporary credentials using the AWS CLI:

```console
$ aws sts get-session-token \
--serial-number arn:aws:iam::<master account id>:mfa/<iam username> \
--token-code <mfa otp>
```

At Remind, we like to use [aws-vault], which allows us to simplify this to:

```console
$ aws-vault exec default -- env
AWS_VAULT=default
AWS_DEFAULT_REGION=us-east-1
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=ASIAJ...ICSXSQ
AWS_SECRET_ACCESS_KEY=4oFx...LSNjpFq
AWS_SESSION_TOKEN=FQoDYXdzED...V6Wrdko2KjW1QU=
AWS_SECURITY_TOKEN=FQoDYXdzED...V6Wrdko2KjW1QU=
```

For the rest of this guide, I'll use `aws-vault` for simplicity.

**NOTE**: You'll need to ensure that this IAM user has access to call `sts:AssumeRole` on the `Stacker` IAM role in the "master" account.

### Bootstrap Stacker Bucket

After we have some IAM roles that stacker can assume, and some temporary credentials, we'll want to create a stacker bucket in the master account, and allow the Stacker roles in sub-accounts access to fetch templates from it.

To do that, first, change the "Roles" variable in [stacker.yaml], then:

```console
$ aws-vault exec default # GetSessionToken + MFA
$ AWS_CONFIG_FILE=.aws/config stacker build --profile master --stacks stacker-bucket stacker.yaml
```

Once the bucket has been created, replace `stacker_bucket` with the name of the bucket in [stacker.yaml].

```console
$ aws cloudformation describe-stacks \
--profile master \
--stack-name stacker-bucket \
--query 'Stacks[0].Outputs' --output text
BucketId stacker-bucket-1234
```

### Provision stacks

Now that everything is setup, you can add new stacks to your config file, and target them to a specific AWS account using the `profile` option. For example, if I wanted to create a new VPC in both the "production" and "staging" accounts:

```yaml
stacks:
- name: prod/vpc
stack_name: vpc
class_path: stacker_blueprints.vpc.VPC
profile: prod # target this to the production account
- name: stage/vpc
stack_name: vpc
class_path: stacker_blueprints.vpc.VPC
profile: stage # target this to the staging account
```

```console
$ AWS_CONFIG_FILE=.aws/config stacker build --profile master stacker.yaml
```

[.aws/config]: ./.aws/config
[stacker.yaml]: ./stacker.yaml
[aws-vault]: https://github.com/99designs/aws-vault
20 changes: 20 additions & 0 deletions examples/cross-account/stacker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
namespace: ''

# We'll set this to an empty string until we've provisioned the
# "stacker-bucket" stack below.
stacker_bucket: ''

stacks:
# This stack will provision an S3 bucket for stacker to use to upload
# templates. This will also configure the bucket with a bucket policy
# allowing CloudFormation in other accounts to fetch templates from it.
- name: stacker-bucket
# We're going to "target" this stack in our "master" account.
profile: master
template_path: templates/stacker-bucket.yaml
variables:
Roles:
# Change these to the correct AWS account IDs
- arn:aws:iam::<prod account id>:role/Stacker
- arn:aws:iam::<stage account id>:role/Stacker
37 changes: 37 additions & 0 deletions examples/cross-account/templates/stacker-bucket.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
AWSTemplateFormatVersion: "2010-09-09"
Description: A bucket for stacker to store CloudFormation templates
Parameters:
Roles:
Type: CommaDelimitedList
Description: A list of IAM roles that will be given read access on the bucket.

Resources:
StackerBucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256

BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: StackerBucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Principal:
AWS:
Ref: Roles
Resource:
- Fn::Sub: arn:aws:s3:::${StackerBucket}/*

Outputs:
BucketId:
Value:
Ref: StackerBucket
73 changes: 73 additions & 0 deletions examples/cross-account/templates/stacker-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
AWSTemplateFormatVersion: "2010-09-09"
Description: A role that stacker can assume
Parameters:
MasterAccountId:
Type: String
Description: The 12-digit ID for the master account
MinLength: 12
MaxLength: 12
AllowedPattern: "[0-9]+"
ConstraintDescription: Must contain a 12 digit account ID
RoleName:
Type: String
Description: The name of the stacker role.
Default: Stacker


Conditions:
# Check if we're creating this role in the master account.
InMasterAccount:
Fn::Equals:
- { Ref: "AWS::AccountId" }
- { Ref: "MasterAccountId" }

Resources:
StackerRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Ref: RoleName
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
Fn::If:
- InMasterAccount
- Effect: Allow
Principal:
AWS:
Fn::Sub: "arn:aws:iam::${MasterAccountId}:root"
Action: sts:AssumeRole
Condition:
'Null':
aws:MultiFactorAuthAge: false
- Effect: Allow
Principal:
AWS:
Fn::Sub: "arn:aws:iam::${MasterAccountId}:role/${RoleName}"
Action: sts:AssumeRole
Condition:
'Null':
aws:MultiFactorAuthAge: false

# Generally, Stacker will need fairly wide open permissions, since it will be
# managing all resources in an account.
StackerPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: Stacker
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: ["*"]
Resource: "*"
Roles:
- Ref: StackerRole

Outputs:
StackerRole:
Value:
Fn::GetAtt:
- StackerRole
- Arn

0 comments on commit 9b138c1

Please sign in to comment.