Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
sbkok committed Jul 24, 2023
2 parents 134b811 + 0e6d022 commit 6d954db
Show file tree
Hide file tree
Showing 26 changed files with 1,752 additions and 410 deletions.
7 changes: 7 additions & 0 deletions docs/admin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ ensures the existence of AWS Accounts defined in `.yml` files within the
or alternatively in the bootstrap repository you can find it in the
`adf-accounts` directory.

**Note on Provisioning AWS Accounts via AWS Control Tower**
ADF is fully compatible with [AWS Control Tower](https://aws.amazon.com/de/controltower/).
If you deployed ADF and AWS Control Tower in your AWS Organization and if you
opted for vending AWS Accounts via AWS Control Tower, you can ignore the ADF
Account Provisioning feature. Any AWS Account vended via AWS Control Tower will
go through the regular ADF bootstrap process described below.

### Bootstrapping Accounts

#### Bootstrapping Overview
Expand Down
101 changes: 76 additions & 25 deletions docs/installation-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,28 @@
- [git](https://git-scm.com/)
- [AWS CodeCommit Setup](https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-https-unixes.html)
- [AWS CloudTrail configured](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html)
in the `us-east-1` region within the AWS Organizations Management AWS Account.
in the `us-east-1` region within the AWS Organizations Management AWS Account.

## ADF-Compability with AWS Control Tower

ADF is fully compatible with [AWS Control Tower](https://aws.amazon.com/de/controltower/).
ADF augments AWS Control Tower. A common operations model is defined as follows:

- AWS Control Tower is responsible for AWS Account creation and OU mapping.
- ADF is responsible for deploying applications as defined in the ADF
deployment maps.

In the following, we assume that you install ADF without AWS Control Tower.
However, if a specific installation step requires a "AWS Control Tower-specific
action, we call those out explicitly.

It is okay to install ADF and AWS Control Tower in different regions. Example:

- Install AWS Control Tower in eu-central-1.
- Install ADF in us-east-1.

**If you want to use ADF and AWS Control Tower, we recommend that you setup
AWS Control Tower prior to installing ADF.**

## Installation Instructions

Expand All @@ -18,10 +39,10 @@ in the `us-east-1` region within the AWS Organizations Management AWS Account.
for AWS Organizations can only be acted upon in the US East (N. Virginia)
Region.

2. In the AWS Console from your management account within `us-east-1`, head over
to the Serverless Application Repository *(SAR)*. From there, search for
`aws-deployment-framework` *(or "adf")* (ensure the checkbox *"Show apps that
create custom IAM roles or resource policies"* is checked).
2. In the AWS Console from your management account within `us-east-1`, head
over to the Serverless Application Repository *(SAR)*. From there, search
for `aws-deployment-framework` *(or "adf")* (ensure the checkbox
*"Show apps that create custom IAM roles or resource policies"* is checked).

If you are deploying ADF for the first time, fill in the required parameters
for your specific use-case. For example, if you have no AWS Organization
Expand All @@ -30,9 +51,18 @@ in the `us-east-1` region within the AWS Organizations Management AWS Account.
deployment OU, along with an AWS Account that will be used to house
deployment pipelines throughout your Organization.

If you already have an AWS Account you want to use as your deployment account
you can specify its Account ID in the parameter `DeploymentAccountId`
and leave the`DeploymentAccountName` and `DeploymentAccountEmail` empty.
If you already have an AWS Account you want to use as your deployment
account you can specify its Account ID in the parameter
`DeploymentAccountId` and leave the `DeploymentAccountName` plus
`DeploymentAccountEmail` empty.

**AWS Control Tower-specific Note:**
If you use AWS Control Tower, we recommend to create the deployment AWS
Account via the account vending feature of AWS Control Tower.

It is **MANDATORY**, that your designated deployment AWS Account resides in
the OU `deployment` (case-sensitive!). This can't be changed currently.
Otherwise, the ADF deployment will fail!

Next, specify the `DeploymentAccountMainRegion` parameter as the region that
will host your deployment pipelines and would be considered your main AWS
Expand All @@ -44,40 +74,61 @@ in the `us-east-1` region within the AWS Organizations Management AWS Account.
Also specify a main notification endpoint *(email)* to receive updates
about the bootstrap process.

**AWS Control Tower-specific Note:**
If you use AWS Control Tower, in the `CrossAccountAccessRoleName` section of
the parameters enter the string `AWSControlTowerExecution`.
Alternatively, leave empty for a default ADF setup.

When you have entered all required information press **'Deploy'**.

3. As the stack `serverlessrepo-aws-deployment-framework` completes you can now
open AWS CodePipeline from within the management account in `us-east-1` and
see that there is an initial pipeline execution that has been started.

When ADF is deployed for the first time, it will make the initial commit with
the skeleton structure of the `aws-deployment-framework-bootstrap` CodeCommit
repository.
When ADF is deployed for the first time, it will make the initial commit
with the skeleton structure of the `aws-deployment-framework-bootstrap`
CodeCommit repository.

From that initial commit, you can clone the repository to your local
environment and make the changes required to define your desired base stacks
via AWS CloudFormation Templates, Service Control Policies or Tagging
Policies.

4. As part of the AWS CodePipeline Execution from the previous step, the account
provisioner component will run *(in CodeBuild)*.
4. As part of the AWS CodePipeline Execution from the previous step, the
account provisioner component will run *(in CodeBuild)*.

OPTION 4.1: ONLY applies when requesting the creation of a NEW deployment
account AND when using ADF for vending AWS Accounts.

- If you let ADF create a new Deployment account for you
*(by not giving a pre-existing account id when deploying from SAR)*,
then ADF will handle creating and moving this account automatically into
the deployment OU.

OPTION 4.2: ONLY applies when reusing an pre-created deployment account
AND when using ADF for vending AWS Accounts

- If you are using a pre-existing deployment account, you will need to
move the account into the deployment OU from within the Organization
console, or add your deployment account into a `.yml` file within the
`adf-accounts` folder *(see docs)*.

OPTION 4.3: ONLY applies when reusing a pre-existing deployment account
AND when using AWS Control Tower for vending AWS Accounts

If you let ADF create a new Deployment account for you *(by not giving a
pre-existing account id when deploying from SAR)*, then ADF will handle
creating and moving this account automatically into the deployment OU.
- Ensure that the AWS Control Tower-created deployment AWS Account
resides in the OU `deployment` (case-sensitive!).

If you are using a pre-existing deployment account, you will need to move the
account into the deployment OU from within the Organization console, or
add your deployment account into a `.yml` file within the `adf-accounts`
folder *(see docs)*. This action will trigger [AWS Step
Functions](https://aws.amazon.com/step-functions/) to run and start the
bootstrap process for the deployment account. You can view the progress of
this in the AWS Step Functions console from the management account in the
Regardless of the option taken above, after AWS Account creation, you should
see an [AWS Step Functions](https://aws.amazon.com/step-functions/) run
that started the bootstrap process for the deployment account. You can view
the progress of this in the management account in the AWS Step Functions
console for the step function `AccountBootstrappingStateMachine-` in the
`us-east-1` region.

5. Once the Step Function has completed, switch roles over to the newly
bootstrapped deployment account in the region you defined as your main region
from step 2.
bootstrapped deployment account in the region you defined as your main
region from step 2.

An AWS CodeCommit repository will have been created and will contain the
initial skeleton structure committed which serves as a starting point for
Expand Down
10 changes: 8 additions & 2 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,16 @@ targets:
# stages, each stage containing up to X accounts
size: 30
exclude:
# (Optional) List of accounts to exclude from this target.
# Currently only supports account Ids
# (Optional) List of accounts to exclude from this path.
- 9999999999
properties: ...
- path: /my_ou/production/**/* # This would target all accounts in the OU path
regions: [eu-central-1, us-west-1]
name: production_step
exclude: # (Optional) List of OU Paths and Account Ids
- /my-ou/production/alpha # excludes any accounts and OUs in this path.
- 11111111111 # Excludes this account, regardless of OU path.
properties: ...
```

CodePipeline has a limit of 50 actions per stage.
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ isort==5.12.0
mock==5.1.0
pylint==2.17.4
pytest~=7.4.0
pytest-cov==3.0.0
tox==3.28.0
yamllint==1.32.0
1 change: 1 addition & 0 deletions samples/sample-terraform/buildspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ phases:
- aws s3 cp s3://$S3_BUCKET_NAME/adf-build/ adf-build/ --recursive --quiet
- export PATH=$PATH:$(pwd)
- bash adf-build/helpers/terraform/install_terraform.sh
- pip install --upgrade pip
- pip install -r adf-build/requirements.txt -q
build:
commands:
Expand Down
103 changes: 103 additions & 0 deletions src/lambda_codebase/account_processing/configure_account_regions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

"""
Takes regions that the account is not-opted into and opts into them.
"""
from ast import literal_eval


import boto3
from aws_xray_sdk.core import patch_all
from logger import configure_logger

patch_all()
LOGGER = configure_logger(__name__)


def get_regions_from_ssm(ssm_client):
regions = ssm_client.get_parameter(Name="target_regions")["Parameter"].get("Value")
regions = literal_eval(regions)
return regions


def get_region_status(account_client, **list_region_args):
region_status_response = account_client.list_regions(**list_region_args)
region_status = {
region.get("RegionName"): region.get("RegionOptStatus")
for region in region_status_response.get("Regions")
}
# Currently no built in paginator for list_regions...
# So we have to do this manually.
next_token = region_status_response.get("NextToken")
if next_token:
while next_token:
list_region_args["NextToken"] = next_token
region_status_response = account_client.list_regions(**list_region_args)
next_token = region_status_response.get("NextToken")
region_status = region_status | {
region.get("RegionName"): region.get("RegionOptStatus")
for region in region_status_response.get("Regions")
}
return region_status


def enable_regions_for_account(
account_client, account_id, desired_regions, org_root_account_id
):
list_region_args = {}
enable_region_args = {}
target_is_different_account = org_root_account_id != account_id
if target_is_different_account:
list_region_args["AccountId"] = account_id
enable_region_args["AccountId"] = account_id

region_status = get_region_status(account_client, **list_region_args)

regions_enabled = {}
for region in desired_regions:
regions_enabled[region] = False
desired_region_status = region_status.get(region.lower())
if not desired_region_status:
LOGGER.warning("Unable to obtain status of %s, not enabling")
if desired_region_status == "DISABLED":
LOGGER.info("Enabling Region %s because it is currently Disabled", region)
enable_region_args["RegionName"] = region.lower()
account_client.enable_region(**enable_region_args)
else:
LOGGER.info(
"Not enabling Region: %s because it is: %s",
region,
desired_region_status,
)
if desired_region_status in ["ENABLED_BY_DEFAULT", "ENABLED"]:
regions_enabled[region] = True
LOGGER.info(regions_enabled)
return all(regions_enabled.values())


def lambda_handler(event, _):
desired_regions = []
if event.get("regions"):
LOGGER.info(
"Account Level Regions is not currently supported."
"Ignoring these values for now and using SSM only"
)
desired_regions.extend(get_regions_from_ssm(boto3.client("ssm")))
org_root_account_id = boto3.client("sts").get_caller_identity().get("Account")
target_account_id = event.get("account_id")
LOGGER.info(
"Target Account Id: %s - This is running in %s. These are the same: %s",
target_account_id,
org_root_account_id,
target_account_id == org_root_account_id,
)
all_regions_enabled = enable_regions_for_account(
boto3.client("account"),
target_account_id,
desired_regions,
org_root_account_id,
)
event["all_regions_enabled"] = all_regions_enabled

return event
23 changes: 15 additions & 8 deletions src/lambda_codebase/account_processing/configure_account_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,44 @@
Will not delete tags that aren't
in the config file.
"""

from organizations import Organizations

import boto3
from aws_xray_sdk.core import patch_all
from logger import configure_logger

patch_all()
LOGGER = configure_logger(__name__)
ORG_CLIENT = boto3.client("organizations")


def create_account_tags(account_id, tags, org_session: Organizations):
def create_account_tags(account_id, tags, client):
LOGGER.info(
"Ensuring Account: %s has tags: %s",
account_id,
tags,
)
org_session.create_account_tags(account_id, tags)
formatted_tags = [
{"Key": str(key), "Value": str(value)}
for tag in tags
for key, value in tag.items()
]
LOGGER.debug(
"Ensuring Account: %s has tags (formatted): %s",
account_id,
formatted_tags,
)
client.tag_resource(ResourceId=account_id, Tags=formatted_tags)


def lambda_handler(event, _):
if event.get("tags"):
organizations = Organizations(boto3)
create_account_tags(
event.get("account_id"),
event.get("tags"),
organizations,
ORG_CLIENT,
)
else:
LOGGER.info(
"Account: %s does not need tags configured",
event.get('account_full_name'),
event.get("account_full_name"),
)
return event

0 comments on commit 6d954db

Please sign in to comment.