Skip to content

Commit

Permalink
Release: 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
AWS committed Jan 27, 2022
1 parent 0a53b53 commit 2906171
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 115 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.2
1.2.0
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
from boto3.session import Session

if TYPE_CHECKING:
from mypy_boto3_iam import IAMClient
from mypy_boto3_iam import IAMClient, IAMServiceResource
from mypy_boto3_iam.type_defs import CreateRoleResponseTypeDef
else:
IAMClient = object
CreateRoleResponseTypeDef = object
IAMServiceResource = object


logger = utils.get_logger()
Expand Down Expand Up @@ -77,19 +78,33 @@ def create_aft_execution_role(
)
exec_iam_client = ct_execution_session.client("iam")

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

try:
role = exec_iam_client.get_role(RoleName=role_name.split("/")[-1])
logger.info("Role Exists. Exiting")
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",
)
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 create_role_in_account(
def update_aft_role_trust_policy(
session: Session, ct_execution_session: Session, role_name: str
) -> str:
logger.info("Function Start - create_role_in_account")
) -> 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__), "iam/trust-policies/aftmanagement.tpl"
)
Expand All @@ -99,29 +114,47 @@ def create_role_in_account(
with open(trust_policy_template) as trust_policy_file:
template = trust_policy_file.read()
template = template.replace("{AftManagementAccount}", aft_management_account)
assume_role_policy_document = json.loads(template)
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=json.dumps(assume_role_policy_document),
AssumeRolePolicyDocument=assume_role_policy_document,
Description="AFT Execution Role",
MaxSessionDuration=3600,
Tags=[
{"Key": "managed_by", "Value": "AFT"},
],
)
role = response["Role"]["Arn"]
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",
)
return role_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")
exec_client.attach_role_policy(
RoleName=role_name.split("/")[-1],
PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess",
role.attach_policy(
PolicyArn=policy_arn,
)
logger.info("Returning role")
logger.info(role)
return role
return None


def lambda_handler(event: Dict[str, Any], context: Union[Dict[str, Any], None]) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import aft_common.aft_utils as utils
import boto3
from aft_common.account import AftAccountInfo
from aft_common.types import AftAccountInfo
from boto3.session import Session

logger = utils.get_logger()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"Version": "2012-10-17",
"Statement": [{
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
Expand Down Expand Up @@ -37,27 +38,36 @@
"arn:aws:ssm:${data_aws_region_aft-management_name}:${data_aws_caller_identity_aft-management_account_id}:parameter/aft/*"
]
},
{
"Effect" : "Allow",
"Action" : [
"sns:Publish"
],
"Resource" : [
"${aws_sns_topic_aft_notifications_arn}",
"${aws_sns_topic_aft_failure_notifications_arn}"
]
},
{
"Effect" : "Allow",
"Action" : [
"kms:GenerateDataKey",
"kms:Encrypt",
"kms:Decrypt"
],
"Resource" : [
"${aws_kms_key_aft_arn}",
"arn:aws:kms:${data_aws_region_aft-management_name}:${data_aws_caller_identity_aft-management_account_id}:alias/aws/sns"
]
}
{
"Effect" : "Allow",
"Action" : [
"sns:Publish"
],
"Resource" : [
"${aws_sns_topic_aft_notifications_arn}",
"${aws_sns_topic_aft_failure_notifications_arn}"
]
},
{
"Effect" : "Allow",
"Action" : [
"kms:GenerateDataKey",
"kms:Encrypt",
"kms:Decrypt"
],
"Resource" : [
"${aws_kms_key_aft_arn}",
"arn:aws:kms:${data_aws_region_aft-management_name}:${data_aws_caller_identity_aft-management_account_id}:alias/aws/sns"
]
},
{
"Effect" : "Allow",
"Action" : [
"sts:AssumeRole"
],
"Resource" : [
"arn:aws:iam::${data_aws_caller_identity_aft-management_account_id}:role/AWSAFTAdmin"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

import aft_common.aft_utils as utils
import boto3
from aft_common.account import Account
from boto3.session import Session

logger = utils.get_logger()


def new_account_request(record: Dict[str, Any]) -> bool:
if record["eventName"] == "INSERT":
return True
return False


def modify_account_request(record: Dict[str, Any]) -> bool:
if record["eventName"] == "MODIFY":
return True
return False
ct_management_session = utils.get_ct_management_session(aft_mgmt_session=Session())
account_name = utils.unmarshal_ddb_item(record["dynamodb"]["NewImage"])[
"control_tower_parameters"
]["AccountName"]
provisioned_product = Account(
ct_management_session=ct_management_session, account_name=account_name
).provisioned_product
return provisioned_product is None


def delete_account_request(record: Dict[str, Any]) -> bool:
Expand Down Expand Up @@ -74,52 +75,51 @@ def lambda_handler(event: Dict[str, Any], context: Union[Dict[str, Any], None])
try:
logger.info("Lambda_handler Event")
logger.info(event)

session = boto3.session.Session()

# validate event
if "Records" in event:
event_record = event["Records"][0]
if "eventSource" in event_record:
if event_record["eventSource"] == "aws:dynamodb":
logger.info("DynamoDB Event Record Received")
if new_account_request(event_record):
logger.info("New Account Request Received")
sqs_queue = utils.get_ssm_parameter_value(
session, utils.SSM_PARAM_ACCOUNT_REQUEST_QUEUE
)
sqs_queue = utils.build_sqs_url(session, sqs_queue)
message = build_sqs_message(event_record)
utils.send_sqs_message(session, sqs_queue, message)
elif modify_account_request(event_record):
if control_tower_param_changed(event_record):
logger.info(
"Control Tower Parameter Update Request Received"
)
sqs_queue = utils.get_ssm_parameter_value(
session, utils.SSM_PARAM_ACCOUNT_REQUEST_QUEUE
)
sqs_queue = utils.build_sqs_url(session, sqs_queue)
message = build_sqs_message(event_record)
utils.send_sqs_message(session, sqs_queue, message)
else:
logger.info(
"NON-Control Tower Parameter Update Request Received"
)
payload = build_aft_account_provisioning_framework_event(
event_record
)
lambda_name = utils.get_ssm_parameter_value(
session,
utils.SSM_PARAM_AFT_ACCOUNT_PROVISIONING_FRAMEWORK_LAMBDA,
)
utils.invoke_lambda(
session, lambda_name, json.dumps(payload).encode()
)
elif delete_account_request(event_record):
logger.info("Delete account request Received")
else:
logger.info("Non Service Catalog Request Received")
if "Records" not in event:
return None
event_record = event["Records"][0]
if "eventSource" not in event_record:
return None
if event_record["eventSource"] != "aws:dynamodb":
return None

logger.info("DynamoDB Event Record Received")
if delete_account_request(event_record):
# Terraform handles removing the request record from DynamoDB
# AWS does not support automated deletion of accounts
logger.info("Delete account request received")
return None

new_account = new_account_request(event_record)
if new_account:
logger.info("New account request received")
sqs_queue = utils.get_ssm_parameter_value(
session, utils.SSM_PARAM_ACCOUNT_REQUEST_QUEUE
)
sqs_queue = utils.build_sqs_url(session, sqs_queue)
message = build_sqs_message(event_record)
utils.send_sqs_message(session, sqs_queue, message)
else:
logger.info("Modify account request received")
if control_tower_param_changed(event_record):
logger.info("Control Tower Parameter Update Request Received")
sqs_queue = utils.get_ssm_parameter_value(
session, utils.SSM_PARAM_ACCOUNT_REQUEST_QUEUE
)
sqs_queue = utils.build_sqs_url(session, sqs_queue)
message = build_sqs_message(event_record)
utils.send_sqs_message(session, sqs_queue, message)
else:
logger.info("NON-Control Tower Parameter Update Request Received")
payload = build_aft_account_provisioning_framework_event(event_record)
lambda_name = utils.get_ssm_parameter_value(
session,
utils.SSM_PARAM_AFT_ACCOUNT_PROVISIONING_FRAMEWORK_LAMBDA,
)
utils.invoke_lambda(session, lambda_name, json.dumps(payload).encode())

except Exception as e:
message = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def modify_existing_account(
"AccountEmail",
],
)

if (
product_outputs_response["Outputs"][0]["OutputValue"]
== request["control_tower_parameters"]["AccountEmail"]
Expand Down
52 changes: 37 additions & 15 deletions sources/aft-lambda-layer/aft_common/account.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
from typing import Literal, TypedDict


class AftAccountInfo(TypedDict):
id: str
email: str
name: str
joined_method: str
joined_date: str
status: str
parent_id: str
parent_type: str
org_name: str
type: Literal["account"]
vendor: Literal["aws"]
from typing import TYPE_CHECKING, List, Optional

from aft_common.aft_utils import get_logger
from boto3.session import Session

if TYPE_CHECKING:
from mypy_boto3_servicecatalog import ServiceCatalogClient
from mypy_boto3_servicecatalog.type_defs import (
DescribeProvisionedProductOutputTypeDef,
ProvisionedProductDetailTypeDef,
)
else:
ServiceCatalogClient = object
DescribeProvisionedProductOutputTypeDef = object
ProvisionedProductDetailTypeDef = object

logger = get_logger()


class Account:
def __init__(self, ct_management_session: Session, account_name: str) -> None:
self.ct_management_session = ct_management_session
self.account_name = account_name

@property
def provisioned_product(self) -> Optional[ProvisionedProductDetailTypeDef]:
client: ServiceCatalogClient = self.ct_management_session.client(
"servicecatalog"
)
try:
response: DescribeProvisionedProductOutputTypeDef = (
client.describe_provisioned_product(Name=self.account_name)
)
return response["ProvisionedProductDetail"]
except client.exceptions.ResourceNotFoundException:
logger.debug(f"Account with name {self.account_name} does not exists")
return None
Loading

0 comments on commit 2906171

Please sign in to comment.