Skip to content

Commit ec59028

Browse files
authored
New IdentityStore APIs (#7)
1 parent 21b0b24 commit ec59028

File tree

11 files changed

+109
-37
lines changed

11 files changed

+109
-37
lines changed

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: setup build deploy format create-signing-profile clean
22

33
setup:
4-
python3 -m venv .venv
4+
python3.9 -m venv .venv
55
.venv/bin/python3 -m pip install -U pip
66
.venv/bin/python3 -m pip install -r requirements-dev.txt
77
.venv/bin/python3 -m pip install -r dependencies/requirements.txt

Diff for: dependencies/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
aws-lambda-powertools==1.25.1
1+
aws-lambda-powertools==1.29.2

Diff for: requirements-dev.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
black==22.1.0
1+
black==22.8.0

Diff for: src/account/account_setup/resources/sts.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
logger = Logger(child=True)
2828
tracer = Tracer()
2929
EXECUTION_ROLE_NAME = os.environ["EXECUTION_ROLE_NAME"]
30+
AWS_PARTITION = os.environ["AWS_PARTITION"]
31+
3032

3133
__all__ = ["STS"]
3234

@@ -43,7 +45,7 @@ def assume_role(
4345
Assume the AWSControlTowerExecution role in an account
4446
"""
4547

46-
role_arn = f"arn:aws:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
48+
role_arn = f"arn:{AWS_PARTITION}:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
4749

4850
logger.info(f"Assuming role {EXECUTION_ROLE_NAME} in {account_id}")
4951
response = self.client.assume_role(

Diff for: src/regional/account_setup/resources/ec2.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, session: boto3.Session, region: str) -> None:
3636
self.region_name = region
3737

3838
def get_default_vpc_id(self) -> Optional[str]:
39-
params = {"Filters": [{"Name": "is-default", "Values": ["true"]}]}
39+
params = {"Filters": [{"Name": "isDefault", "Values": ["true"]}]}
4040

4141
response = self.client.describe_vpcs(**params)
4242
for vpc in response.get("Vpcs", []):
@@ -72,7 +72,19 @@ def delete_vpc(self, vpc_id: str) -> None:
7272
interface.delete()
7373
subnet.delete()
7474

75-
# Delete vpc
75+
# Network ACLs
76+
for nacl in vpc.network_acls.all():
77+
if not nacl.is_default:
78+
nacl.delete()
79+
80+
# DHCP Options
81+
if vpc.dhcp_options:
82+
vpc.associate_dhcp_options(
83+
DhcpOptionsId="default"
84+
) # associate no DHCP options
85+
vpc.dhcp_options.delete()
86+
87+
# Delete VPC
7688
self.client.delete_vpc(VpcId=vpc_id)
7789
logger.info(f"VPC {vpc_id} and associated resources has been deleted.")
7890

Diff for: src/regional/account_setup/resources/sts.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
logger = Logger(child=True)
2828
EXECUTION_ROLE_NAME = os.environ["EXECUTION_ROLE_NAME"]
29+
AWS_PARTITION = os.environ["AWS_PARTITION"]
2930

3031
__all__ = ["STS"]
3132

@@ -41,7 +42,7 @@ def assume_role(
4142
Assume the AWSControlTowerExecution role in an account
4243
"""
4344

44-
role_arn = f"arn:aws:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
45+
role_arn = f"arn:{AWS_PARTITION}:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
4546

4647
logger.info(f"Assuming role {EXECUTION_ROLE_NAME} in {account_id}")
4748
response = self.client.assume_role(

Diff for: src/service_catalog_portfolio/account_setup/resources/sts.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import boto3
2525

2626
EXECUTION_ROLE_NAME = os.environ["EXECUTION_ROLE_NAME"]
27+
AWS_PARTITION = os.environ["AWS_PARTITION"]
2728

2829
__all__ = ["STS"]
2930

@@ -38,7 +39,7 @@ def assume_role(self, account_id: str, role_session_name: str) -> boto3.Session:
3839
"""
3940
Assume a role and return a new boto3 session
4041
"""
41-
role_arn = f"arn:aws:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
42+
role_arn = f"arn:{AWS_PARTITION}:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
4243

4344
response = self.client.assume_role(
4445
RoleArn=role_arn,

Diff for: src/sso_assignment/account_setup/lambda_handler.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
import boto3
2727

2828
from .resources import Organizations, IdentityStore, SSO
29-
from .utils import parse_group, get_env_list
29+
from .utils import parse_group
30+
from .constants import GROUP_ORG_PREFIX
3031

3132
tracer = Tracer()
3233
logger = Logger()
3334

34-
ORGANIZATION_GROUPS = get_env_list("ORGANIZATION_GROUPS")
3535

36-
37-
@tracer.capture_method
36+
@tracer.capture_method(capture_response=False)
3837
def create_group_event(event: Dict[str, Any]) -> None:
3938
"""
4039
Assign the new group to an account and permission set
@@ -91,7 +90,7 @@ def create_group_event(event: Dict[str, Any]) -> None:
9190
logger.warn(f"Permission Set '{permission_set_name}' not found")
9291

9392

94-
@tracer.capture_lambda_handler
93+
@tracer.capture_lambda_handler(capture_response=False)
9594
@logger.inject_lambda_context(log_event=True)
9695
def handler(event: Dict[str, Any], context: LambdaContext) -> None:
9796

@@ -116,15 +115,12 @@ def handler(event: Dict[str, Any], context: LambdaContext) -> None:
116115
identity_store_id = instance["IdentityStoreId"]
117116

118117
identity_store = IdentityStore(session, identity_store_id)
119-
identity_store.clear_groups()
118+
organizational_groups = identity_store.get_groups_by_prefix(GROUP_ORG_PREFIX)
120119

121-
for group_name in ORGANIZATION_GROUPS:
122-
_, permission_set_name = parse_group(group_name)
120+
logger.info(f"Found organizational groups: {organizational_groups}")
123121

124-
group_id = identity_store.get_group_id(group_name)
125-
if not group_id:
126-
logger.error(f"Group '{group_name}' not found, skipping")
127-
continue
122+
for group_id, group_name in organizational_groups.items():
123+
_, permission_set_name = parse_group(group_name)
128124

129125
permission_set_arn = sso.get_permission_set_arn(
130126
instance_arn=instance_arn, name=permission_set_name

Diff for: src/sso_assignment/account_setup/resources/identity_store.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
"""
2121

22-
from functools import lru_cache
23-
from typing import Optional
22+
from typing import Dict
2423

2524
import boto3
2625

@@ -32,16 +31,22 @@ def __init__(self, session: boto3.Session, identity_store_id: str) -> None:
3231
self.client = session.client("identitystore")
3332
self._identity_store_id = identity_store_id
3433

35-
@lru_cache
36-
def get_group_id(self, name: str) -> Optional[str]:
37-
response = self.client.list_groups(
34+
def get_groups_by_prefix(self, prefix: str) -> Dict[str, str]:
35+
"""
36+
Return all of the groups that match a given prefix
37+
"""
38+
paginator = self.client.get_paginator("list_groups")
39+
page_iterator = paginator.paginate(
3840
IdentityStoreId=self._identity_store_id,
39-
Filters=[{"AttributePath": "DisplayName", "AttributeValue": name}],
41+
PaginationConfig={
42+
"PageSize": 100,
43+
},
4044
)
41-
for group in response.get("Groups", []):
42-
return group["GroupId"]
4345

44-
return None
46+
groups: Dict[str, str] = {}
47+
for page in page_iterator:
48+
for group in page.get("Groups", []):
49+
if group["DisplayName"].startswith(prefix):
50+
groups[group["GroupId"]] = group["DisplayName"]
4551

46-
def clear_groups(self) -> None:
47-
self.get_group_id.cache_clear()
52+
return groups

Diff for: src/sso_assignment/account_setup/resources/organizations.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929

3030
class Organizations:
3131
def __init__(self, session: boto3.Session) -> None:
32-
self.client = session.client("organizations")
32+
# @see https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/organizations.html
33+
self.client = session.client(
34+
"organizations",
35+
region_name="us-east-1",
36+
endpoint_url="https://organizations.us-east-1.amazonaws.com",
37+
)
3338

3439
@lru_cache
3540
def get_account_id(self, name: str) -> Optional[str]:

Diff for: template.yml

+55-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ Transform: "AWS::Serverless-2016-10-31"
77
Description: New account provisioning automation
88

99
Parameters:
10-
OrganizationGroups:
11-
Type: CommaDelimitedList
12-
Description: List of AWS SSO groups that should have access to all accounts
13-
Default: ""
1410
ExecutionRoleName:
1511
Type: String
1612
Description: Execution IAM role name
@@ -42,6 +38,7 @@ Globals:
4238
POWERTOOLS_METRICS_NAMESPACE: AccountSetup
4339
EXECUTION_ROLE_NAME: !Ref ExecutionRoleName
4440
LOG_LEVEL: INFO
41+
AWS_PARTITION: !Ref "AWS::Partition"
4542
Handler: lambda_handler.handler
4643
Layers:
4744
- !Ref DependencyLayer
@@ -80,6 +77,11 @@ Resources:
8077
Type: "AWS::Logs::LogGroup"
8178
UpdateReplacePolicy: Delete
8279
DeletionPolicy: Delete
80+
Metadata:
81+
cfn_nag:
82+
rules_to_suppress:
83+
- id: W84
84+
reason: "Ignoring KMS key"
8385
Properties:
8486
LogGroupName: !Sub "/aws/lambda/${AccountFunction}"
8587
RetentionInDays: 3
@@ -124,6 +126,13 @@ Resources:
124126

125127
AccountFunction:
126128
Type: "AWS::Serverless::Function"
129+
Metadata:
130+
cfn_nag:
131+
rules_to_suppress:
132+
- id: W58
133+
reason: "Ignoring CloudWatch Logs"
134+
- id: W89
135+
reason: "Ignoring VPC"
127136
Properties:
128137
CodeSigningConfigArn: !Ref CodeSigningConfig
129138
CodeUri: src/account
@@ -139,6 +148,11 @@ Resources:
139148
Type: "AWS::Logs::LogGroup"
140149
UpdateReplacePolicy: Delete
141150
DeletionPolicy: Delete
151+
Metadata:
152+
cfn_nag:
153+
rules_to_suppress:
154+
- id: W84
155+
reason: "Ignoring KMS key"
142156
Properties:
143157
LogGroupName: !Sub "/aws/lambda/${RegionalFunction}"
144158
RetentionInDays: 3
@@ -183,6 +197,13 @@ Resources:
183197

184198
RegionalFunction:
185199
Type: "AWS::Serverless::Function"
200+
Metadata:
201+
cfn_nag:
202+
rules_to_suppress:
203+
- id: W58
204+
reason: "Ignoring CloudWatch Logs"
205+
- id: W89
206+
reason: "Ignoring VPC"
186207
Properties:
187208
CodeSigningConfigArn: !Ref CodeSigningConfig
188209
CodeUri: src/regional
@@ -198,12 +219,22 @@ Resources:
198219
Type: "AWS::Logs::LogGroup"
199220
UpdateReplacePolicy: Delete
200221
DeletionPolicy: Delete
222+
Metadata:
223+
cfn_nag:
224+
rules_to_suppress:
225+
- id: W84
226+
reason: "Ignoring KMS key"
201227
Properties:
202228
LogGroupName: !Sub "/aws/lambda/${SSOAssignmentFunction}"
203229
RetentionInDays: 3
204230

205231
SSOAssignmentFunctionRole:
206232
Type: "AWS::IAM::Role"
233+
Metadata:
234+
cfn_nag:
235+
rules_to_suppress:
236+
- id: W11
237+
reason: "Ignoring wildcard resource"
207238
Properties:
208239
AssumeRolePolicyDocument:
209240
Version: "2012-10-17"
@@ -221,6 +252,7 @@ Resources:
221252
- Effect: Allow
222253
Action:
223254
- "organizations:ListAccounts"
255+
- "identitystore:GetGroupId"
224256
- "identitystore:ListGroups"
225257
- "sso:CreateAccountAssignment"
226258
- "sso:DescribePermissionSet"
@@ -257,14 +289,20 @@ Resources:
257289

258290
SSOAssignmentFunction:
259291
Type: "AWS::Serverless::Function"
292+
Metadata:
293+
cfn_nag:
294+
rules_to_suppress:
295+
- id: W58
296+
reason: "Ignoring CloudWatch Logs"
297+
- id: W89
298+
reason: "Ignoring VPC"
260299
Properties:
261300
CodeSigningConfigArn: !Ref CodeSigningConfig
262301
CodeUri: src/sso_assignment
263302
Description: DO NOT DELETE - AccountSetup - SSO Assignment
264303
Environment:
265304
Variables:
266305
POWERTOOLS_SERVICE_NAME: sso_assignment
267-
ORGANIZATION_GROUPS: !Join [",", !Ref OrganizationGroups]
268306
Events:
269307
CreateGroupEvent:
270308
Type: EventBridgeRule
@@ -287,6 +325,11 @@ Resources:
287325
Type: "AWS::Logs::LogGroup"
288326
UpdateReplacePolicy: Delete
289327
DeletionPolicy: Delete
328+
Metadata:
329+
cfn_nag:
330+
rules_to_suppress:
331+
- id: W84
332+
reason: "Ignoring KMS key"
290333
Properties:
291334
LogGroupName: !Sub "/aws/lambda/${ServiceCatalogPortfolioFunction}"
292335
RetentionInDays: 3
@@ -331,6 +374,13 @@ Resources:
331374

332375
ServiceCatalogPortfolioFunction:
333376
Type: "AWS::Serverless::Function"
377+
Metadata:
378+
cfn_nag:
379+
rules_to_suppress:
380+
- id: W58
381+
reason: "Ignoring CloudWatch Logs"
382+
- id: W89
383+
reason: "Ignoring VPC"
334384
Properties:
335385
CodeSigningConfigArn: !Ref CodeSigningConfig
336386
CodeUri: src/service_catalog_portfolio

0 commit comments

Comments
 (0)