Skip to content

Commit 5450066

Browse files
committed
Added Enable Snapshot Block Public Access
1 parent 3a5a9c1 commit 5450066

File tree

12 files changed

+116
-59
lines changed

12 files changed

+116
-59
lines changed

dependencies/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
aws-lambda-powertools[tracer,validation,aws-sdk]==2.22.0
1+
aws-lambda-powertools[tracer,validation,aws-sdk]==2.26.1

requirements-dev.txt

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
black==23.7.0
2-
wheel==0.41.1
3-
pre-commit==3.3.3
4-
mypy-boto3-ec2==1.28.19.post1
5-
mypy-boto3-ecs==1.28.20
6-
mypy-boto3-iam==1.28.16
7-
mypy-boto3-identitystore==1.28.16
8-
mypy-boto3-organizations==1.28.16
9-
mypy-boto3-sts==1.28.16
10-
mypy-boto3-servicecatalog==1.28.22
11-
mypy-boto3-sso-admin==1.28.16
1+
black==23.11.0
2+
wheel==0.41.3
3+
pre-commit==3.5.0
4+
mypy-boto3-ec2==1.29.0
5+
mypy-boto3-ecs==1.29.0
6+
mypy-boto3-iam==1.29.0
7+
mypy-boto3-identitystore==1.29.0
8+
mypy-boto3-organizations==1.29.0
9+
mypy-boto3-sts==1.29.0
10+
mypy-boto3-servicecatalog==1.29.0
11+
mypy-boto3-sso-admin==1.29.0

src/regional/account_setup/lambda_handler.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@
3737
@tracer.capture_lambda_handler
3838
@logger.inject_lambda_context(log_event=True)
3939
def handler(event: Dict[str, Any], context: LambdaContext) -> None:
40-
account_id = event["account_id"]
41-
region_name = event["region"]
40+
account_id = event["AccountId"]
41+
region_name = event["Region"]
42+
execution_role_arn = event["ExecutionRoleArn"]
4243

4344
logger.append_keys(account_id=account_id, region=region_name)
4445
tracer.put_annotation("AccountId", account_id)
4546
tracer.put_annotation("Region", region_name)
4647

4748
session = boto3.Session()
4849

49-
assumed_session = STS(session).assume_role(account_id)
50+
assumed_session = STS(session).assume_role(execution_role_arn)
5051

5152
ec2 = EC2(assumed_session, region_name)
5253
default_vpc_id = ec2.get_default_vpc_id()
@@ -56,6 +57,11 @@ def handler(event: Dict[str, Any], context: LambdaContext) -> None:
5657
else:
5758
logger.debug(f"No default VPC found in {region_name} in {account_id}")
5859

60+
# TODO 11/15: move this into the state machine once the aws-sdk integration has been updated
61+
logger.info(f"Enabling snapshot block public access in {region_name} in {account_id}")
62+
ec2.enable_snapshot_block_public_access()
63+
5964
logger.info(f"Setting default ECS settings in {region_name} in {account_id}")
6065
ecs = ECS(assumed_session, region_name)
6166
ecs.put_account_setting_default()
67+

src/regional/account_setup/resources/ec2.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from aws_lambda_powertools import Logger
2525
import boto3
26+
import botocore
2627

2728
if TYPE_CHECKING:
2829
from mypy_boto3_ec2 import EC2Client, EC2ServiceResource
@@ -39,11 +40,20 @@ def __init__(self, session: boto3.Session, region: str) -> None:
3940
self.region_name = region
4041

4142
def get_default_vpc_id(self) -> Optional[str]:
42-
params = {"Filters": [{"Name": "isDefault", "Values": ["true"]}]}
43+
params = {
44+
"Filters": [
45+
{
46+
"Name": "isDefault",
47+
"Values": [
48+
"true",
49+
],
50+
}
51+
]
52+
}
4353

4454
response = self.client.describe_vpcs(**params)
4555
for vpc in response.get("Vpcs", []):
46-
if vpc["IsDefault"]:
56+
if vpc.get("IsDefault", False):
4757
return vpc["VpcId"]
4858

4959
logger.debug(f"No default VPC found in {self.region_name}", region=self.region_name)
@@ -94,3 +104,9 @@ def delete_vpc(self, vpc_id: str) -> None:
94104
logger.info(
95105
f"VPC {vpc_id} and associated resources has been deleted in {self.region_name}.", region=self.region_name
96106
)
107+
108+
def enable_snapshot_block_public_access(self) -> None:
109+
try:
110+
self.client.enable_snapshot_block_public_access(State="block-all-sharing")
111+
except botocore.exceptions.ClientError:
112+
logger.exception(f"Unable to enable snapshot block public access in {self.region}")

src/regional/account_setup/resources/sts.py

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

22-
import os
2322
from typing import TYPE_CHECKING
2423

2524
from aws_lambda_powertools import Logger
@@ -29,8 +28,6 @@
2928
from mypy_boto3_sts import STSClient
3029

3130
logger = Logger(child=True)
32-
EXECUTION_ROLE_NAME = os.environ["EXECUTION_ROLE_NAME"]
33-
AWS_PARTITION = os.environ["AWS_PARTITION"]
3431

3532
__all__ = ["STS"]
3633

@@ -39,14 +36,12 @@ class STS:
3936
def __init__(self, session: boto3.Session) -> None:
4037
self.client: STSClient = session.client("sts")
4138

42-
def assume_role(self, account_id: str, role_session_name: str = "AccountSetup") -> boto3.Session:
39+
def assume_role(self, role_arn: str, role_session_name: str = "AccountSetup") -> boto3.Session:
4340
"""
4441
Assume the AWSControlTowerExecution role in an account
4542
"""
4643

47-
role_arn = f"arn:{AWS_PARTITION}:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
48-
49-
logger.info(f"Assuming role {EXECUTION_ROLE_NAME} in {account_id}")
44+
logger.info(f"Assuming role {role_arn}")
5045
response = self.client.assume_role(
5146
RoleArn=role_arn,
5247
RoleSessionName=role_session_name,

src/regional/account_setup/schemas.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
"$schema": "http://json-schema.org/draft-07/schema",
2424
"type": "object",
2525
"properties": {
26-
"account_id": {
26+
"AccountId": {
2727
"type": "string",
2828
},
29-
"region": {
29+
"Region": {
30+
"type": "string",
31+
},
32+
"ExecutionRoleArn": {
3033
"type": "string",
3134
},
3235
},
33-
"required": ["account_id", "region"],
36+
"required": ["AccountId", "Region", "ExecutionRoleArn"],
3437
}

src/service_catalog_portfolio/account_setup/lambda_handler.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
"""
2121

2222
import os
23-
from typing import Dict, Any, List, Optional
23+
from typing import Dict, Any, List
2424

2525
from aws_lambda_powertools import Logger, Tracer
2626
from aws_lambda_powertools.utilities.typing import LambdaContext
27+
from aws_lambda_powertools.utilities.validation import validator
2728

28-
from .resources import IAM, ServiceCatalog, STS
29+
from account_setup.resources import IAM, ServiceCatalog, STS
30+
from account_setup.schemas import INPUT
2931

3032
tracer = Tracer()
3133
logger = Logger()
@@ -35,22 +37,19 @@ def get_env_list(key: str) -> List[str]:
3537
"""
3638
Return an optional environment variable as a list
3739
"""
38-
value = os.environ.get(key, "").split(",")
40+
value = os.getenv(key, "").split(",")
3941
return list(filter(None, value))
4042

4143

4244
PORTFOLIO_IDS = get_env_list("PORTFOLIO_IDS")
4345
PERMISSION_SET_NAMES = get_env_list("PERMISSION_SET_NAMES")
4446

4547

48+
@validator(inbound_schema=INPUT)
4649
@tracer.capture_lambda_handler
4750
@logger.inject_lambda_context(log_event=True)
4851
def handler(event: Dict[str, Any], context: LambdaContext) -> None:
49-
account_id: Optional[str] = event.get("account", {}).get("accountId")
50-
if not account_id:
51-
raise Exception("Account ID not found in event")
52-
53-
session = STS().assume_role(account_id, "service_catalog_portfolio")
52+
session = STS().assume_role(event["ExecutionRoleArn"], "service_catalog_portfolio")
5453

5554
iam = IAM(session)
5655

src/service_catalog_portfolio/account_setup/resources/iam.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def get_sso_roles(self) -> Dict[str, str]:
5151
page_iterator = paginator.paginate(PaginationConfig={"PageSize": 1000})
5252
for page in page_iterator:
5353
for role in page.get("Roles", []):
54-
if role["RoleName"].startswith(AWS_SSO_ROLE_PREFIX):
54+
if role.get("RoleName", "").startswith(AWS_SSO_ROLE_PREFIX):
5555
# AWSReservedSSO_AWSAdministratorAccess_a1ff75f56dfb0e2f -> AWSAdministratorAccess
5656
permission_set_name = role["RoleName"].rsplit("_", 1)[0].replace(AWS_SSO_ROLE_PREFIX, "")
5757
roles[permission_set_name] = role["Arn"]

src/service_catalog_portfolio/account_setup/resources/sts.py

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

22-
import os
2322
from typing import TYPE_CHECKING, Optional
2423

24+
from aws_lambda_powertools import Logger
2525
import boto3
2626

2727
if TYPE_CHECKING:
2828
from mypy_boto3_sts import STSClient
2929

30-
EXECUTION_ROLE_NAME = os.environ["EXECUTION_ROLE_NAME"]
31-
AWS_PARTITION = os.environ["AWS_PARTITION"]
30+
logger = Logger(child=True)
3231

3332
__all__ = ["STS"]
3433

@@ -39,12 +38,11 @@ def __init__(self, session: Optional[boto3.Session] = None) -> None:
3938
session = boto3._get_default_session()
4039
self.client: STSClient = session.client("sts")
4140

42-
def assume_role(self, account_id: str, role_session_name: str) -> boto3.Session:
41+
def assume_role(self, role_arn: str, role_session_name: str) -> boto3.Session:
4342
"""
4443
Assume a role and return a new boto3 session
4544
"""
46-
role_arn = f"arn:{AWS_PARTITION}:iam::{account_id}:role/{EXECUTION_ROLE_NAME}"
47-
45+
logger.info(f"Assuming role {role_arn}")
4846
response = self.client.assume_role(
4947
RoleArn=role_arn,
5048
RoleSessionName=role_session_name,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
* SPDX-License-Identifier: MIT-0
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
9+
* software and associated documentation files (the "Software"), to deal in the Software
10+
* without restriction, including without limitation the rights to use, copy, modify,
11+
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
12+
* permit persons to whom the Software is furnished to do so.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
"""
21+
22+
INPUT = {
23+
"$schema": "http://json-schema.org/draft-07/schema",
24+
"type": "object",
25+
"properties": {
26+
"ExecutionRoleArn": {
27+
"type": "string",
28+
},
29+
},
30+
"required": ["ExecutionRoleArn"],
31+
}

src/sso_assignment/account_setup/lambda_handler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def handler(event: Dict[str, Any], context: LambdaContext) -> None:
9595

9696
# Below handles organizational groups
9797

98-
account_id: Optional[str] = event.get("account", {}).get("accountId")
98+
account_id: Optional[str] = event.get("AccountId")
9999
if not account_id:
100100
raise Exception("Account ID not found in event")
101101

template.yml

+25-16
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ Globals:
3838
Environment:
3939
Variables:
4040
POWERTOOLS_METRICS_NAMESPACE: AccountSetup
41-
EXECUTION_ROLE_NAME: !Ref ExecutionRoleName
4241
LOG_LEVEL: INFO
43-
AWS_PARTITION: !Ref "AWS::Partition"
4442
Handler: lambda_handler.handler
4543
Layers:
4644
- !Ref DependencyLayer
@@ -348,13 +346,20 @@ Resources:
348346
Type: "AWS::Serverless::StateMachine"
349347
Properties:
350348
Definition:
351-
StartAt: UpdatePasswordPolicy
349+
StartAt: BuildParameters
352350
States:
351+
BuildParameters:
352+
Type: Pass
353+
InputPath: "$.account"
354+
Parameters:
355+
"AccountId.$": "$.accountId"
356+
"ExecutionRoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.accountId)"
357+
Next: UpdatePasswordPolicy
353358
UpdatePasswordPolicy:
354359
Type: Task
355360
Resource: "arn:aws:states:::aws-sdk:iam:updateAccountPasswordPolicy"
356361
Credentials:
357-
"RoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.account.accountId)"
362+
"RoleArn.$": "$.ExecutionRoleArn"
358363
Parameters:
359364
MinimumPasswordLength: 14 # https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.9
360365
RequireSymbols: true # https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.7
@@ -371,14 +376,14 @@ Resources:
371376
Type: Task
372377
Resource: "arn:aws:states:::aws-sdk:s3control:putPublicAccessBlock"
373378
Credentials:
374-
"RoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.account.accountId)"
379+
"RoleArn.$": "$.ExecutionRoleArn"
375380
Parameters:
376381
PublicAccessBlockConfiguration:
377382
BlockPublicAcls: true
378383
IgnorePublicAcls: true
379384
BlockPublicPolicy: true
380385
RestrictPublicBuckets: true
381-
"AccountId.$": $.account.accountId
386+
"AccountId.$": "$.AccountId"
382387
ResultPath: null # discard result and keep original input
383388
Next: Route53PolicyDocument
384389
Route53PolicyDocument:
@@ -413,10 +418,10 @@ Resources:
413418
Type: Task
414419
Resource: "arn:aws:states:::aws-sdk:cloudwatchlogs:putResourcePolicy"
415420
Credentials:
416-
"RoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.account.accountId)"
421+
"RoleArn.$": "$.ExecutionRoleArn"
417422
Parameters:
418423
PolicyName: AWSServiceRoleForRoute53
419-
"PolicyDocument.$": States.Format($.Policy.PolicyDocument, $.account.accountId, $.account.accountId)
424+
"PolicyDocument.$": States.Format($.Policy.PolicyDocument, $.AccountId, $.AccountId)
420425
ResultPath: null # discard result and keep original input
421426
Next: DescribeRegions
422427
DescribeRegions:
@@ -436,35 +441,39 @@ Resources:
436441
Type: Map
437442
ItemsPath: "$.Regions.RegionNames"
438443
MaxConcurrency: 0
439-
Parameters:
440-
"account_id.$": "$.account.accountId"
441-
"region.$": "$$.Map.Item.Value"
442-
Iterator:
444+
ItemSelector:
445+
"AccountId.$": "$.AccountId"
446+
"Region.$": "$$.Map.Item.Value"
447+
"ExecutionRoleArn.$": "$.ExecutionRoleArn"
448+
ItemProcessor:
443449
StartAt: EbsEncryptionByDefault
444450
States:
445451
EbsEncryptionByDefault:
446452
Type: Task
447453
Resource: "arn:aws:states:::aws-sdk:ec2:enableEbsEncryptionByDefault"
448454
Credentials:
449-
"RoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.account_id)"
455+
"RoleArn.$": "$.ExecutionRoleArn"
450456
Parameters: {}
451457
ResultPath: null # discard result and keep original input
452458
Next: DisableSsmPublicSharing
453459
DisableSsmPublicSharing:
454460
Type: Task
455461
Resource: "arn:aws:states:::aws-sdk:ssm:updateServiceSetting"
456462
Credentials:
457-
"RoleArn.$": "States.Format('arn:aws:iam::{}:role/${ExecutionRoleName}', $.account_id)"
463+
"RoleArn.$": "$.ExecutionRoleArn"
458464
Parameters:
459-
"SettingId.$": "States.Format('arn:aws:ssm:{}:{}:servicesetting/ssm/documents/console/public-sharing-permission', $.region, $.account_id)"
465+
"SettingId.$": "States.Format('arn:aws:ssm:{}:{}:servicesetting/ssm/documents/console/public-sharing-permission', $.Region, $.AccountId)"
460466
SettingValue: Disable
461467
Catch:
462468
- ErrorEquals:
463469
- States.ALL
464470
ResultPath: null # discard result and keep original input
465-
Next: Regional
471+
Next: IgnoreError
466472
ResultPath: null # discard result and keep original input
467473
Next: Regional
474+
IgnoreError:
475+
Type: Pass
476+
Next: Regional
468477
Regional:
469478
Type: Task
470479
Resource: !GetAtt RegionalFunction.Arn

0 commit comments

Comments
 (0)