Skip to content

Commit eed6959

Browse files
author
AWS
committed
Release: 1.4.2
1 parent 4ca135a commit eed6959

File tree

3 files changed

+119
-29
lines changed

3 files changed

+119
-29
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.4.1
1+
1.4.2

sources/aft-lambda-layer/aft_common/account_provisioning_framework.py

Lines changed: 117 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
import json
55
import os
66
import time
7+
from datetime import datetime, timedelta
78
from typing import TYPE_CHECKING, Any, Dict, List, Sequence
89

910
import aft_common.aft_utils as utils
1011
import jsonschema
1112
from aft_common.auth import AuthClient
1213
from aft_common.types import AftAccountInfo
1314
from boto3.session import Session
15+
from botocore.exceptions import ClientError
1416

1517
if TYPE_CHECKING:
16-
from mypy_boto3_iam import IAMClient
18+
from mypy_boto3_iam import IAMClient, IAMServiceResource
1719
from mypy_boto3_organizations.type_defs import TagTypeDef
1820
else:
1921
IAMClient = object
@@ -77,51 +79,140 @@ def _deploy_role_in_target_account(
7779
hub_session=ct_mgmt_session,
7880
role_name=AuthClient.CONTROL_TOWER_EXECUTION_ROLE_NAME,
7981
)
82+
self._put_role(
83+
target_account_session=target_account_session,
84+
role_name=role_name,
85+
trust_policy=trust_policy,
86+
)
87+
self._put_policy_on_role(
88+
target_account_session=target_account_session,
89+
role_name=role_name,
90+
policy_arn=policy_arn,
91+
)
92+
93+
def _put_role(
94+
self,
95+
target_account_session: Session,
96+
role_name: str,
97+
trust_policy: str,
98+
max_attempts: int = 20,
99+
delay: int = 5,
100+
) -> None:
80101
client: IAMClient = target_account_session.client("iam")
81-
try:
82-
client.get_role(RoleName=role_name)
102+
if self.role_exists(
103+
role_name=role_name, target_account_session=target_account_session
104+
):
83105
client.update_assume_role_policy(
84106
RoleName=role_name, PolicyDocument=trust_policy
85107
)
86-
87-
except client.exceptions.NoSuchEntityException:
88-
logger.info(f"Creating {role_name} in {self.target_account_id}")
108+
else:
89109
client.create_role(
90110
RoleName=role_name,
91111
AssumeRolePolicyDocument=trust_policy,
92112
Description="Role for use with Account Factory for Terraform",
93113
MaxSessionDuration=3600,
94114
Tags=[{"Key": "managed_by", "Value": "AFT"}],
95115
)
96-
eventual_consistency_sleep = 60
97-
logger.info(
98-
f"Sleeping for {eventual_consistency_sleep}s to ensure Role exists"
116+
waiter = client.get_waiter("role_exists")
117+
waiter.wait(
118+
RoleName=role_name,
119+
WaiterConfig={"Delay": delay, "MaxAttempts": max_attempts},
99120
)
100-
time.sleep(eventual_consistency_sleep)
101121

102-
client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
122+
@staticmethod
123+
def role_exists(role_name: str, target_account_session: Session) -> bool:
124+
client: IAMClient = target_account_session.client("iam")
125+
try:
126+
client.get_role(RoleName=role_name)
127+
return True
128+
129+
except ClientError as error:
130+
if error.response["Error"]["Code"] == "NoSuchEntity":
131+
return False
132+
raise
133+
134+
def _put_policy_on_role(
135+
self,
136+
target_account_session: Session,
137+
role_name: str,
138+
policy_arn: str,
139+
delay: int = 5,
140+
timeout_in_mins: int = 1,
141+
) -> None:
142+
if not self.role_policy_is_attached(
143+
role_name=role_name,
144+
policy_arn=policy_arn,
145+
target_account_session=target_account_session,
146+
):
147+
resource: IAMServiceResource = target_account_session.resource("iam")
148+
role = resource.Role(role_name)
149+
role.attach_policy(PolicyArn=policy_arn)
150+
timeout = datetime.utcnow() + timedelta(minutes=timeout_in_mins)
151+
while datetime.utcnow() < timeout:
152+
time.sleep(delay)
153+
if self.role_policy_is_attached(
154+
role_name=role_name,
155+
policy_arn=policy_arn,
156+
target_account_session=target_account_session,
157+
):
158+
return None
159+
return None
160+
161+
@staticmethod
162+
def role_policy_is_attached(
163+
role_name: str, policy_arn: str, target_account_session: Session
164+
) -> bool:
165+
resource: IAMServiceResource = target_account_session.resource("iam")
166+
role = resource.Role(role_name)
167+
policy_iterator = role.attached_policies.all()
168+
policy_arns = [policy.arn for policy in policy_iterator]
169+
logger.info(policy_arns)
170+
return policy_arn in policy_arns
171+
172+
def _ensure_role_can_be_assumed(
173+
self, role_name: str, timeout_in_mins: int = 1, delay: int = 5
174+
) -> None:
175+
timeout = datetime.utcnow() + timedelta(minutes=timeout_in_mins)
176+
while datetime.utcnow() < timeout:
177+
if self._can_assume_role(role_name=role_name):
178+
return None
179+
time.sleep(delay)
180+
raise TimeoutError(
181+
f"Could not assume role {role_name} within {timeout_in_mins} minutes"
182+
)
183+
184+
def _can_assume_role(self, role_name: str) -> bool:
185+
try:
186+
self.auth.get_target_account_session(
187+
account_id=self.target_account_id, role_name=role_name
188+
)
189+
return True
190+
except ClientError as error:
191+
if error.response["Error"]["Code"] == "AccessDenied":
192+
return False
193+
raise error
194+
195+
def deploy_aws_aft_roles(self) -> None:
196+
trust_policy = self.generate_aft_trust_policy()
103197

104-
def deploy_aws_aft_execution_role(self) -> None:
105198
aft_execution_role_name = utils.get_ssm_parameter_value(
106199
session=self.auth.get_aft_management_session(),
107200
param=AuthClient.SSM_PARAM_AFT_EXEC_ROLE_NAME,
108201
)
109-
# Account for paths
110202
aft_execution_role_name = aft_execution_role_name.split("/")[-1]
111-
trust_policy = self.generate_aft_trust_policy()
112-
self._deploy_role_in_target_account(
113-
role_name=aft_execution_role_name,
114-
trust_policy=trust_policy,
115-
policy_arn=ProvisionRoles.ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN,
116-
)
117203

118-
def deploy_aws_aft_service_role(self) -> None:
119-
trust_policy = self.generate_aft_trust_policy()
120-
self._deploy_role_in_target_account(
121-
role_name=ProvisionRoles.SERVICE_ROLE_NAME,
122-
trust_policy=trust_policy,
123-
policy_arn=ProvisionRoles.ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN,
124-
)
204+
aft_role_names = [ProvisionRoles.SERVICE_ROLE_NAME, aft_execution_role_name]
205+
for role_name in aft_role_names:
206+
self._deploy_role_in_target_account(
207+
role_name=role_name,
208+
trust_policy=trust_policy,
209+
policy_arn=ProvisionRoles.ADMINISTRATOR_ACCESS_MANAGED_POLICY_ARN,
210+
)
211+
logger.info(f"Deployed {role_name} role")
212+
213+
for role_name in aft_role_names:
214+
self._ensure_role_can_be_assumed(role_name=role_name)
215+
logger.info(f"Can assume {role_name} role")
125216

126217

127218
def get_account_info(

src/aft_lambda/aft_account_provisioning_framework/aft_account_provisioning_framework_create_role.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ def lambda_handler(event: Dict[str, Any], context: LambdaContext) -> None:
3232

3333
account_id = payload["account_info"]["account"]["id"]
3434
provisioning = ProvisionRoles(auth=auth, account_id=account_id)
35-
provisioning.deploy_aws_aft_execution_role()
36-
provisioning.deploy_aws_aft_service_role()
35+
provisioning.deploy_aws_aft_roles()
3736

3837
except Exception as error:
3938
notifications.send_lambda_failure_sns_message(

0 commit comments

Comments
 (0)