Skip to content

Commit

Permalink
Release: 1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
AWS committed May 23, 2022
1 parent 5ebfd2b commit 6005cfe
Show file tree
Hide file tree
Showing 26 changed files with 667 additions and 339 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.7
1.4.0
5 changes: 4 additions & 1 deletion modules/aft-iam-roles/admin-role/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ resource "aws_iam_role" "role" {
trusted_entity = var.trusted_entity
}
)
}

managed_policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"]
resource "aws_iam_role_policy_attachment" "administrator-access-attachment" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

output "arn" {
Expand Down
33 changes: 33 additions & 0 deletions modules/aft-iam-roles/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,36 @@ module "aft_exec_role" {
}
trusted_entity = aws_iam_role.aft_admin_role.arn
}


module "ct_management_service_role" {
source = "./service-role"
providers = {
aws = aws.ct_management
}
trusted_entity = aws_iam_role.aft_admin_role.arn
}

module "log_archive_service_role" {
source = "./service-role"
providers = {
aws = aws.log_archive
}
trusted_entity = aws_iam_role.aft_admin_role.arn
}

module "audit_service_role" {
source = "./service-role"
providers = {
aws = aws.audit
}
trusted_entity = aws_iam_role.aft_admin_role.arn
}

module "aft_service_role" {
source = "./service-role"
providers = {
aws = aws.aft_management
}
trusted_entity = aws_iam_role.aft_admin_role.arn
}
5 changes: 4 additions & 1 deletion modules/aft-iam-roles/iam/aft_admin_role_policy.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::*:role/AWSAFTExecution"
"Resource": [
"arn:aws:iam::*:role/AWSAFTExecution",
"arn:aws:iam::*:role/AWSAFTService"
]
}
]
}
36 changes: 36 additions & 0 deletions modules/aft-iam-roles/service-role/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright Amazon.com, Inc. or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
variable "trusted_entity_type" {
default = "AWS"
}

variable "role_name" {
default = "AWSAFTService"
}

variable "trusted_entity" {

}

resource "aws_iam_role" "role" {
name = var.role_name

# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
assume_role_policy = templatefile("${path.module}/trust_policy.tpl",
{
trusted_entity_type = var.trusted_entity_type
trusted_entity = var.trusted_entity
}
)
}

resource "aws_iam_role_policy_attachment" "administrator-access-attachment" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

output "arn" {
value = aws_iam_role.role.arn
}
12 changes: 12 additions & 0 deletions modules/aft-iam-roles/service-role/trust_policy.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"${trusted_entity_type}": ["${trusted_entity}"]
},
"Action": "sts:AssumeRole"
}
]
}
207 changes: 94 additions & 113 deletions sources/aft-lambda-layer/aft_common/account_provisioning_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

import aft_common.aft_utils as utils
import jsonschema
from aft_common.auth import AuthClient
from aft_common.types import AftAccountInfo
from boto3.session import Session

if TYPE_CHECKING:
from mypy_boto3_iam import IAMClient, IAMServiceResource
from mypy_boto3_iam.type_defs import CreateRoleResponseTypeDef
from mypy_boto3_iam import IAMClient
from mypy_boto3_organizations.type_defs import TagTypeDef
else:
IAMClient = object
Expand All @@ -25,122 +25,108 @@
logger = utils.get_logger()


def get_ct_execution_session(
aft_management_session: Session, ct_management_session: Session, account_id: str
) -> Session:
session_name = utils.get_ssm_parameter_value(
aft_management_session, utils.SSM_PARAM_AFT_SESSION_NAME
)
admin_credentials = utils.get_assume_role_credentials(
ct_management_session,
utils.build_role_arn(
ct_management_session, "AWSControlTowerExecution", account_id
),
session_name,
)
AFT_EXEC_ROLE = "AWSAFTExecution"

return utils.get_boto_session(admin_credentials)
SSM_PARAMETER_PATH = "/aft/account-request/custom-fields/"


def create_aft_execution_role(
account_info: Dict[str, Any], session: Session, ct_management_session: Session
) -> str:
logger.info("Function Start - create_aft_execution_role")
role_name = utils.get_ssm_parameter_value(session, utils.SSM_PARAM_AFT_EXEC_ROLE)
ct_execution_session = get_ct_execution_session(
session, ct_management_session, account_info["id"]
class ProvisionRoles:
ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN = (
"arn:aws:iam::aws:policy/AdministratorAccess"
)
exec_iam_client = ct_execution_session.client("iam")

role_name = role_name.split("/")[-1]

try:
role = exec_iam_client.get_role(RoleName=role_name)
logger.info("Role Exists. Updating...")
update_aft_role_trust_policy(session, ct_execution_session, role_name)
set_role_policy(
ct_execution_session=ct_execution_session,
role_name=role_name,
policy_arn="arn:aws:iam::aws:policy/AdministratorAccess",
SERVICE_ROLE_NAME = "AWSAFTService"

def __init__(self, auth: AuthClient, account_id: str) -> None:
self.auth = auth
self.target_account_id = account_id

def generate_aft_trust_policy(self) -> str:
return json.dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": f"arn:aws:iam::{self.auth.aft_management_account_id}:assumed-role/AWSAFTAdmin/AWSAFT-Session"
},
"Action": "sts:AssumeRole",
}
],
}
)
return role["Role"]["Arn"]
except exec_iam_client.exceptions.NoSuchEntityException:
logger.info("Role not found in account. Creating...")
return create_role_in_account(session, ct_execution_session, role_name)


def update_aft_role_trust_policy(
session: Session, ct_execution_session: Session, role_name: str
) -> None:
assume_role_policy_document = get_aft_trust_policy_document(session)
iam_resource: IAMServiceResource = ct_execution_session.resource("iam")
role = iam_resource.Role(name=role_name)
role.AssumeRolePolicy().update(PolicyDocument=assume_role_policy_document)


def get_aft_trust_policy_document(session: Session) -> str:
trust_policy_template = os.path.join(
os.path.dirname(__file__), "templates/aftmanagement.tpl"
)
aft_management_account = utils.get_ssm_parameter_value(
session, utils.SSM_PARAM_ACCOUNT_AFT_MANAGEMENT_ACCOUNT_ID
)
with open(trust_policy_template) as trust_policy_file:
template = trust_policy_file.read()
template = template.replace("{AftManagementAccount}", aft_management_account)
return template


def create_role_in_account(
session: Session,
ct_execution_session: Session,
role_name: str,
) -> str:
logger.info("Function Start - create_role_in_account")
assume_role_policy_document = get_aft_trust_policy_document(session=session)
exec_client: IAMClient = ct_execution_session.client("iam")
logger.info("Creating Role")
response: CreateRoleResponseTypeDef = exec_client.create_role(
RoleName=role_name.split("/")[-1],
AssumeRolePolicyDocument=assume_role_policy_document,
Description="AFT Execution Role",
MaxSessionDuration=3600,
Tags=[
{"Key": "managed_by", "Value": "AFT"},
],
)
role_arn = response["Role"]["Arn"]
logger.info(response)
set_role_policy(
ct_execution_session=ct_execution_session,
role_name=role_name,
policy_arn="arn:aws:iam::aws:policy/AdministratorAccess",
)

# Adding sleep to account for IAM Role creation eventual consistency
eventual_consistency_sleep = 60
logger.info(f"Sleeping for {eventual_consistency_sleep}s to ensure Role exists")
time.sleep(eventual_consistency_sleep)

return role_arn

def _deploy_role_in_target_account(
self, role_name: str, trust_policy: str, policy_arn: str
) -> None:
"""
Since we're creating the AFT roles in the account, we must assume
AWSControlTowerExecution as the target role. Since this role only
trusts federation from the CT Management account, we pass a hub session
that has already been federated into the CT Management account
"""
ct_mgmt_acc_id = (
self.auth.get_ct_management_session()
.client("sts")
.get_caller_identity()["Account"]
)
if self.target_account_id == ct_mgmt_acc_id:
target_account_session = self.auth.get_ct_management_session()
else:
target_account_session = self.auth.get_target_account_session(
account_id=self.target_account_id,
hub_session=self.auth.get_ct_management_session(),
role_name=AuthClient.CONTROL_TOWER_EXECUTION_ROLE_NAME,
)
client: IAMClient = target_account_session.client("iam")
try:
client.get_role(RoleName=role_name)
client.update_assume_role_policy(
RoleName=role_name, PolicyDocument=trust_policy
)

except client.exceptions.NoSuchEntityException:
logger.info(f"Creating {role_name} in {self.target_account_id}")
client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=trust_policy,
Description="Role for use with Account Factory for Terraform",
MaxSessionDuration=3600,
Tags=[{"Key": "managed_by", "Value": "AFT"}],
)
eventual_consistency_sleep = 60
logger.info(
f"Sleeping for {eventual_consistency_sleep}s to ensure Role exists"
)
time.sleep(eventual_consistency_sleep)

client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)

def deploy_aws_aft_execution_role(self) -> None:
aft_execution_role_name = utils.get_ssm_parameter_value(
session=self.auth.get_aft_management_session(),
param=AuthClient.SSM_PARAM_AFT_EXEC_ROLE_NAME,
)
# Account for paths
aft_execution_role_name = aft_execution_role_name.split("/")[-1]
trust_policy = self.generate_aft_trust_policy()
self._deploy_role_in_target_account(
role_name=aft_execution_role_name,
trust_policy=trust_policy,
policy_arn=ProvisionRoles.ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN,
)

def set_role_policy(
ct_execution_session: Session, role_name: str, policy_arn: str
) -> None:
iam_resource: IAMServiceResource = ct_execution_session.resource("iam")
role = iam_resource.Role(name=role_name)
for policy in role.attached_policies.all():
role.detach_policy(PolicyArn=policy.arn)
logger.info("Attaching Role Policy")
role.attach_policy(
PolicyArn=policy_arn,
)
return None
def deploy_aws_aft_service_role(self) -> None:
trust_policy = self.generate_aft_trust_policy()
self._deploy_role_in_target_account(
role_name=ProvisionRoles.SERVICE_ROLE_NAME,
trust_policy=trust_policy,
policy_arn=ProvisionRoles.ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN,
)


def get_account_info(
payload: Dict[str, Any], session: Session, ct_management_session: Session
payload: Dict[str, Any], ct_management_session: Session
) -> AftAccountInfo:
logger.info("Function Start - get_account_info")

Expand Down Expand Up @@ -212,11 +198,6 @@ def persist_metadata(
return response


AFT_EXEC_ROLE = "AWSAFTExecution"

SSM_PARAMETER_PATH = "/aft/account-request/custom-fields/"


def get_ssm_parameters_names_by_path(session: Session, path: str) -> List[str]:

client = session.client("ssm")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)

from aft_common import aft_utils as utils
from aft_common.auth import AuthClient
from boto3.session import Session

if TYPE_CHECKING:
Expand Down Expand Up @@ -85,7 +86,8 @@ def get_healthy_ct_product_batch(

def provisioned_product_exists(record: Dict[str, Any]) -> bool:
# Go get all my accounts from SC (Not all PPs)
ct_management_session = utils.get_ct_management_session(aft_mgmt_session=Session())
auth = AuthClient()
ct_management_session = auth.get_ct_management_session()
account_email = utils.unmarshal_ddb_item(record["dynamodb"]["NewImage"])[
"control_tower_parameters"
]["AccountEmail"]
Expand Down Expand Up @@ -333,7 +335,9 @@ def update_existing_account(

# check to see if the product still exists and is still active
if utils.ct_provisioning_artifact_is_active(
session, ct_management_session, target_product["ProvisioningArtifactId"]
session=session,
ct_management_session=ct_management_session,
artifact_id=target_product["ProvisioningArtifactId"],
):
target_provisioning_artifact_id = target_product["ProvisioningArtifactId"]
else:
Expand Down
Loading

0 comments on commit 6005cfe

Please sign in to comment.