Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
00fa111
Feat: Adf Multi-Org support
alexevansigg Oct 18, 2023
51ce241
fix: Broken links
alexevansigg Oct 18, 2023
2ad95db
fix: Updated link
alexevansigg Oct 18, 2023
fd4f658
docs: update wording
alexevansigg Oct 18, 2023
0c35eec
fix: codespell fixes
alexevansigg Oct 18, 2023
182d02a
fix: Added propagation of org/stage parameter
alexevansigg Oct 18, 2023
677cddb
fix path part missing for os.path.exists
alexevansigg Oct 18, 2023
7c5f647
feat: extended Multi-Org Docs
falkena1 Oct 31, 2023
1e88aa7
feat: extended Multi-Org Docs
falkena1 Oct 31, 2023
3fcd722
Merge branch 'awslabs:master' into feat/multi-org-support
AndyEfaa Oct 31, 2023
dfb540c
feat: extended Multi-Org Docs - fix typo
falkena1 Oct 31, 2023
24e6ec9
Update README.md
AndyEfaa Nov 11, 2023
ae42efb
Update docs/admin-guide.md
AndyEfaa Nov 11, 2023
5ee01ba
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
058b393
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
e0ca137
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
851bcd3
Update src/lambda_codebase/initial_commit/bootstrap_repository/exampl…
AndyEfaa Nov 11, 2023
d9677a1
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bo…
AndyEfaa Nov 11, 2023
0512cd5
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bo…
AndyEfaa Nov 11, 2023
caa4afb
Update docs/admin-guide.md
AndyEfaa Nov 11, 2023
2569f21
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
0e382e2
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
cf42060
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
3d25aeb
Update docs/multi-organization-guide.md
AndyEfaa Nov 11, 2023
815bc19
Apply suggestions from code review
AndyEfaa Nov 11, 2023
09a8d89
fix: make doc changes based on review
falkena1 Nov 11, 2023
aabc72f
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
c7d39f9
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
c50db6c
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
aa68074
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
8a25e90
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
47893ab
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
3b2e789
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bu…
AndyEfaa Jan 20, 2024
c593135
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
94bd041
Update docs/multi-organization-guide.md
AndyEfaa Jan 20, 2024
6657729
fix: linter issues
falkena1 Jan 20, 2024
e58d35e
fix: linter issues
falkena1 Jan 20, 2024
24c0b06
Merge branch 'awslabs:master' into feat/multi-org-support
AndyEfaa Jan 20, 2024
0650291
fix: linter issues
falkena1 Jan 20, 2024
41b75bf
fix: linter issues
falkena1 Jan 20, 2024
5c5cccf
Merge branch 'master' into feat/multi-org-support
AndyEfaa Jan 20, 2024
3986e16
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
6f16517
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
d7c640c
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bo…
AndyEfaa Jan 22, 2024
13bd9b0
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bu…
AndyEfaa Jan 22, 2024
10161ac
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
4011722
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
97493fc
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
53e8b5d
Update src/lambda_codebase/initial_commit/bootstrap_repository/adf-bo…
AndyEfaa Jan 22, 2024
f1a5029
Apply suggestions from code review
AndyEfaa Jan 22, 2024
d59b764
Merge branch 'master' into feat/multi-org-support
AndyEfaa Jan 22, 2024
90172b2
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
71fcfe4
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
0933119
Update docs/multi-organization-guide.md
AndyEfaa Jan 22, 2024
2911436
Apply suggestions from code review
AndyEfaa Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ within the AWS Console.
- Refer to the [User Guide](docs/user-guide.md) for using ADF once it is setup.
- Refer to the [Samples Guide](docs/samples-guide.md) for a detailed walk
through of the provided samples.
- Refer to the [Multi-Orgaization ADF Setup](docs/multi-organization-guide.md) for using ADF in an enterprise-grade setup.
7 changes: 6 additions & 1 deletion docs/admin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ config:
scm: # Source Control Management
auto-create-repositories: enabled # Optional
default-scm-branch: main # Optional
org:
stage: dev # Optional
```

In the above example the properties are categorized into `roles`, `regions`,
Expand Down Expand Up @@ -232,10 +234,13 @@ Config has five components in `main-notification-endpoint`, `scp`, `scm`,
and prod AWS Organization with its own ADF instance per AWS organization.
This approach allows for well-tested and stable prod AWS Organization
deployments. If set, a matching SSM parameter `/adf/org/stage` gets
created that you can reference in your buildspec files to allow for
created in deployment aswell as all target accounts.
You can reference it within your buildspec files to allow for
org-specific deployments; without hardcoding the AWS Organization stage in
your buildspec. If this variable is not set, the SSM parameter
`/adf/org/stage` defaults to "none".
More information about Multi-AWS Organization ADF setup can be found in the
[Multi-Organization Guide](multi-org-guide.md)

## Accounts

Expand Down
Binary file added docs/images/aws-multi-org-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/aws-multi-org-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
182 changes: 182 additions & 0 deletions docs/multi-organization-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Multi-Organization ADF Setup Guide

This document describes how ADF can be run and managed in a multi AWS organization setup.

- [Intended Audience](#intended-audience)
- [Definition of a Multi-Organization ADF Setup](#definition-of-a-multi-organization-adf-setup)
- [Common Use Case for a Multi-Organization ADF Setup - A Multi-Stage Landing Zone](#common-use-case-for-a-multi-organization-adf-setup---a-multi-stage-landing-zone)
- [Propagating Code Changes Between ADF Installations](#propagating-code-changes-between-adf-installations)
- [Customizing ADF Config Per AWS Organization](#customizing-adfconfig.yml-per-aws-organization)
- [Best Practices for Multi-Organization ADF setups](#best-practices-for-multi-organization-adf-setups)
- [1. Create a dedicated adfconfig.yml Per AWS Organization](#1-create-a-dedicated-adfconfigyml-per-aws-organization)
- [2. Design Multi-Organization ADF Deployment Maps](#2-design-multi-organization-adf-deployment-maps)
- [3. Make the AWS Organization Stage Context Available in Codepipeline Build jobs](#3-make-the-aws-organization-stage-context-available-in-codepipeline-build-jobs)
- [4. Customize the Base IAM Roles Per Organization](#4-customize-the-base-iam-roles-per-organization)

## Intended Audience
This guide is intended for users that run a large scale AWS Organization with complex ADF application deployments and large numbers of ADF deployment pipelines.
Enterprises usually best meet the criteria for a multi AWS organization setup. We therefore refer to "Enterprises" as the target audience in the section below.
However, the approach described here should be applied to smaller organizations as well; assuming that sufficient engineering staff is available to support a multi AWS organization setup.

## Definition of a Multi-Organization ADF Setup
A multi-organization AWS-Deployment-Framework (ADF) setup describes a scenario where an
enterprise (or any user) maintains more than one AWS Organizations and each with it's own
dedicated ADF installation.

The following diagram shows such a setup in the most generic level:

![Multi Org Intro](images/aws-multi-org-1.png)

## Common Use Case for a Multi-Organization ADF Setup - A Multi-Stage Landing Zone
The most common use case for a multi-organization ADF setup is a multi-stage (and multi-organization) [landing zone](https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-aws-environment/understanding-landing-zones.html). Such a setup enables stable landing zone feature development that is otherwise not possible in a single AWS Organization.

Let's assume that "Enterprise A" has a dedicated "prod" AWS Organization. This "prod" AWS Organization is used by it's end users to run all their workloads. In a single AWS Organization setup, the "prod" AWS Organization is be the only AWS Organization that exists. As part of the multi-organization ADF setup, we now add a separate "dev" and "int" AWS Organization. The following diagram shows such an architecture:
![Multi Org Intro](images/aws-multi-org-2.png)

The development flow is as follows:
1. Development work for any landing zone feature always starts in the "dev" AWS Organization. The ADF repository `aws-deployment-framework-bootstrap` and `aws-deployment-framework-pipelines` are also considered a landing zone feature. The "dev" AWS Organization is exclusivly reserved for the landing zone development team. No end-user has access to the "dev" AWS Organization.
2. Once the code under development is stable and underwent successful unit and basic integration tests, it is moved from the "dev" AWS Organization to the "int" AWS Organization. The process of propagating code form one AWS Organization to another is described here [Propagating Code Changes Between ADF Installations](#Propagating-Code-Changes-Between-ADF-Installations).
3. The "int" AWS Organization is used for final integration testing and verification. The "dev" AWS Organization is exclusivly reserved for the landing zone development team. No end-user has access to the dev org.
4. Once all tests passed successfully, the code is moved from the "int" AWS Organization to the "prod" AWS Organization.
5. Assuming that the propagation and the deploymemt in the "prod" AWS Organization was successful, the code is now fully deployed in the "prod" AWS Organization and is available to the end-users.

The benefits of such a setup is that an Enteprise can apply the same common `Software Development Lifecycle` to typical "one-off" landing zone services that are hard to test in a single-orgaization setup. It enables an Enterprises 'Cloud Center of Excellence' (landing zone team) a
controlled process to develop, test and validate changes to wide reaching mission-critical
services, including but not limited to:
- Service Control Policies changes.
- Identity Center and IAM based Access Management Configuration changes.
- Deployment Framework changes.
- AWS Organization changes; including OU structure.
- Control Tower and Account Provisioning configurations changes.
- Centralized security service configuration changes.
- Centralized cost management configuration changes.
- Centralized networking changes.

The following sections are written in the context of this "".

## Propagating Code Changes Between ADF Installations
With multiple ADF configurations across multiple AWS organization there comes a new challenge to maintain repositories and its configurations across multiple environments.
This applies to the following repositories:
- aws-deployment-framework-bootstrap
- aws-deployment-framework-pipelines
- any other landing zone repository

As the requirements of the bootstrapped resources and adf configuration evolve they need to be updated over time, and these changes ideally propagated from one Installation to the next in a coordinated, controlled fashion.

With an `Environment Branching` approach it's possible to build a 'hands-off'
automated mechanism to Promote from a "dev" AWS Organization installation to a "prod" AWS Organization installation.
This means that for example merging code from the "dev" branch of a repository to the "int" branch of a repository, will trigger the deployment process in the "int" AWS Organization.

Implementing such an approach is out of scope of this guide and it heavily depends on the specific source code & CICD tool in use.


## Best Practices for Multi-Organization ADF setups
If you want to run ADF in a multi-organization setup, there are various best practices that should be followed.
When following these recommendations, the content of the repository `aws-deployment-framework-bootstrap` and `aws-deployment-framework-pipelines` should be stage agnostic.
This means that you can copy and paste the content of any of those two repositories into any AWS Organization stage ("dev", "int", "prod") and ADF will behave exactly

### 1. Create a dedicated adfconfig.yml Per AWS Organization
One challenge with synchronising the aws-deployment-framework-bootstrap repository
across AWS Organizations is that the contents of the `adfconfig.yml` configuration
file is typically tailored to the ADF installation. The can be solved by adding a
custom adfconfig file for the given organization.

Adding a configuration file with the name pattern `adfconfig.{organization id}.yml`
in the root of the `aws-deployment-framework-bootstrap` repository will take
precedence over the default `adfconfig.yml` settings file for that organization.

For each AWS organization used with the ADF Framework setup an additional adfconfig
file can be defined.


### 2. Design Multi-Organization ADF Deployment Maps
The Deployment Maps for ADF exist in the codecommit repository
`aws-deployment-framwork-pipelines` within the deployment
account. Some additional Multi-Org challenges exist when defining targets for Deployments. As a high-level goal, a deployment map should be setup in such a way, that it can be copied over from one ADF instance to another without breaking / requiring any change.

The following considerations should be observed when creating Deployment Maps for an multi-organization ADF setup:
1. Create Organization-agnostic deployment maps
- As a best-practice, deployment maps should be free of any hardocded AWS Account Ids for Deployment Map Targets, unless the deployment is destined for a single AWS Organization.
- Instead, target AWS accounts via `Account Names`, `Account Tags` or `OU Paths`
which ADF will then use to dynamically generate the respective Account IDs
Target List when updating the Pipelines.
2. Consider AWS service limits for AWS CodePipeline
- In a large enteprise setup the number of targets in a "prod" AWS Organizations AWS CodePipeline
stage may be much greater than in predecessing stages of the "int" and "prod" AWS Organizations.
- Review the Codepipeline action limitations.
- ADF distributes targets across AWS CodePipeline stages within a AWS CodePipeline on a best efforts basis, however
deployments may need to be distributed across multiple AWS CodePipeline when upper limits are reached.
- The current limits are ([AWS CodePipeline Limits](https://docs.aws.amazon.com/codepipeline/latest/userguide/limits.html))
- 1000 AWS CodePipeline per AWS Account per region
- 500 Actions per AWS CodePipeline
- 50 Actions per AWS CodePipeline Stage
- This implies that a single ADF pipeline can target 500 AWS accounts max. At this point latest, the ADF pipeline needs to be manually rebalanced into smaller individual ADF pipelines.
3. Allow for empty deployment map targets
- With the adfconfig setting `allow-empty-target` ([ADF Admin Guide](admin-guide.md)), ADF can be instructed to ignore any target that is not resolvable or empty (because no AWS Accounts exists in it). It is suggested to set this setting to `True`. Eventhough the OU structure and general setup across the different AWS Organization stages is usually identical, the number of created AWS Accounts might not. With this setting to `True`, temporary empty OUs are just ignored and do not lead to an error.

4. The source branch for the application code may be different per AWS Organization
- The above described custom `adfconfig` configuration allows a different default
branch to be specified in the path `config.scm.default-scm-branch` per AWS Organization

### 3. Make the AWS Organization Stage Context Available in Codepipeline Build jobs
ADF applications often contain environment / AWS Organization stage specific configuration files.
In order to allow AWS Codebuild to select the proper configuration context for an application, the environment / AWS Organization stage context needs to be made available.
A simple pattern to solve this problem is the introduction of the SSM parameter `adf/org/stage` in the buildspec file of the application.
The following snippet shows the header of such a `codebuild.yaml` file.
```
env:
parameter-store:
ADF_ORG_STAGE: "/adf/org/stage"
[...]
```
This environment variable can then be used to drive decision/deployment logic
within any of the subsequent build commands/actions.
Some scenarios which could require Org specific context:
- Deriving the default log level based on the org stage for
a specific CDK application
- Appending the Stage name to AWS resource names having a requirement to be
both deterministic as well as globally unique
(whilst being deployed into multiple organizatinos)
- Selection a config file from a config folder with the following files:
- `config-dev.yaml`
- `config-int.yaml`
- `config-prod.yaml`

### 4. Customize the Base IAM Roles Per Organization
ADF Supports Bootstrapping Baseline Cloudformation Stacks to all accounts
when they first join an AWS Organization and centrally governing the subsequent
Lifecycle of those Stacks. [See Here](admin-guide.md#bootstrapping-accounts)

These Baseline Templates are typically used for Setting up Default IAM Roles and
Policies necessary for the foundations of an ADF Based Enteprise Landing Zone.

In guidance with AWS Security Guidelines and `Least Privilege Access Principles`,
it it recommended to reduce the scope of any IAM Policy to the minimum required
Actions, Principals and Resource Scope necessary.

To customize the scope of which resources or Principals are permitted within the
IAM Policies of the Baseline templates CFN Mapping fields can be utilized based
on the `Org Stage` SSM Parameter. As shown below:

```
Parameters:
OrgStage:
Type: "AWS::SSM::Parameter::Value<String>"
Description: Org Stage
Default: /adf/org/stage
# At the time this Stack is deployed, the FinOps Account ID SSM Parameter doesn't
# exist, so we derive it from mapping it to the org stage
Mappings:
# Usage:!FindInMap [OrgStageBasedPropertyMap, !Ref OrgStage, FinOpsAccountId]
OrgStageBasedPropertyMap:
dev:
FinOpsAccountId: 1234567891012 # Dev Org
int:
FinOpsAccountId: 1234567891013 # Int Org
prod:
FinOpsAccountId: 1234567891014 # Prod Org
```
In the above usage example you can see how the Cloudformation function FindInMap
`!FindInMap [OrgStageBasedPropertyMap, !Ref OrgStage, FinOpsAccountId]` can be
utilized to dynamically reference a custom 'AccountId' within the Template,
enabling the construction account specific granular Resource/Principal ARNS.
4 changes: 4 additions & 0 deletions src/lambda_codebase/account_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def configure_generic_account(sts, event, region, role):
bucket_name = parameter_store_deployment_account.fetch_parameter(
f'/cross_region/s3_regional_bucket/{region}',
)
org_stage = parameter_store_deployment_account.fetch_parameter(
'/adf/org/stage'
)
except (ClientError, ParameterNotFoundError):
raise GenericAccountConfigureError(
f'Account {event["account_id"]} cannot yet be bootstrapped '
Expand All @@ -77,6 +80,7 @@ def configure_generic_account(sts, event, region, role):
'deployment_account_id',
event['deployment_account_id'],
)
parameter_store_target_account.put_parameter('/adf/org/stage', org_stage)


def configure_master_account_parameters(event):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: ADF CloudFormation Template (Global) for IAM in the Deployment Account

Parameters:
# OrgStage can be set in the respective adfconfig file using the
# path config.org.stage
OrgStage:
Type: "AWS::SSM::Parameter::Value<String>"
Description: A stage used to differentiate Multi-Org ADF environments
Default: /adf/org/stage

# Org StageCustom Mappings allows you to dynamically build different IAM
# Conditions / Principals ARN / Resource ARN per Organization applying Least
# Privilege Principals whilst retaining a Single Stack Definition for all
# environments.
# Usage: !FindInMap[OrgStageMap: !Ref OrgStage, ExampleCustomProperty]
Mappings:
OrgStageMap:
Dev:
ExampleCustomProperty: 1234
Int:
ExampleCustomProperty: 5678
Prod:
ExampleCustomProperty: 9102

Resources:
CloudFormationDeploymentPolicy:
# This is the policy that will be used to deploy CloudFormation resources from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ Parameters:
Type: "AWS::SSM::Parameter::Value<String>"
Description: Deployment Account ID
Default: deployment_account_id
# OrgStage can be set in the respective adfconfig file using the
# path config.org.stage
OrgStage:
Type: "AWS::SSM::Parameter::Value<String>"
Description: A stage used to differentiate Multi-Org ADF environments
Default: /adf/org/stage

# Org StageCustom Mappings allows you to dynamically build different IAM
# Conditions / Principals ARN / Resource ARN per Organization applying Least
# Privilege Principals whilst retaining a Single Stack Definition for all
# environments.
# usage !FindInMap[OrgStageMap: !Ref OrgStage, ExampleCustomProperty]
Mappings:
OrgStageMap:
Dev:
ExampleCustomProperty: 1234
Int:
ExampleCustomProperty: 5678
Prod:
ExampleCustomProperty: 9102

Resources:
CloudFormationDeploymentPolicy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,20 @@ def _validate(self):

def _load_config_file(self):
"""
Loads the adfconfig.yml file and executes _parse_config
Checks for an Org Specific adfconfig.yml (adfconfig.{ORG_ID}.yml)
and uses that if it exists. Otherwise it uses the default adfconfig.yml
and executes _parse_config
"""
with open(self.config_path, encoding="utf-8") as config:
self.config_contents = yaml.load(config, Loader=yaml.FullLoader)
self._parse_config()
org_config_path = self.config_path.replace(".yml", f".{self.organization_id}.yml")
if os.path.exists(org_config_path):
with open(org_config_path, encoding="utf-8") as org_config_file:
LOGGER.info("Using custom org config")
self.config_contents = yaml.load(org_config_file, Loader=yaml.FullLoader)
else:
LOGGER.info("Using default org config")
with open(self.config_path, encoding="utf-8") as config:
self.config_contents = yaml.load(config, Loader=yaml.FullLoader)
self._parse_config()

def _parse_config(self):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,15 @@ def worker_thread(
'bucket_name',
updated_kms_bucket_dict[region]['s3_regional_bucket'],
)

# Ensuring the stage parameter on the target account is up-to-date
parameter_store.put_parameter(
'/adf/org/stage',
config.config.get('org', {}).get(
'stage',
ADF_DEFAULT_ORG_STAGE,
)
)
cloudformation = CloudFormation(
region=region,
deployment_account_region=config.deployment_account_region,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ config:
default-scm-branch: master
# ^ The default branch is used when the pipeline does not specify a specific branch.
# If this parameter is not specified, it defaults to the "master" branch.

org:
stage: prod
# ^ This value will be set as an SSM Parameter named /adf/org/stage
# in both the deployment account and and all
# Target member accounts as part of the Bootstrap Statemachine.
# It is useful as a flag to drive Organization specific logic within
# IAM Role definitions/conditions aswell as Build spec behavior.

extensions:
terraform:
enabled: false # If true resources needed to run Terraform template will be deployed