Last active
October 18, 2024 18:03
-
-
Save atheiman/e619c25e68163fb10c48df8090cadb5c to your computer and use it in GitHub Desktop.
aws-delete-all-organization-default-vpcs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Description: >- | |
Scheduled Lambda function to delete Default VPC from all accounts in the organization. Deploys an IAM role via | |
CloudFormation stackset to the organization root and schedules a Lambda function to assume the IAM roles in each | |
account and attempt to delete the Default VPCs in every Region. If any operation fails, or if a Default VPC is not | |
empty, or if a Default VPC has a peering connection, the function invocation fails. | |
Parameters: | |
OrgRootId: | |
Type: String | |
AllowedPattern: '^r-[a-zA-Z0-9]+$' | |
Description: >- | |
Organization root ID. Example: "r-abcd" | |
CrossAcctRoleName: | |
Type: String | |
Default: DeleteDefaultVpc | |
Description: Name of IAM role to be created in all accounts in the organization with permission to delete VPCs. | |
ScheduleExpression: | |
Type: String | |
Default: 'cron(0 18 * * ? *)' | |
Description: >- | |
Frequency to invoke the Lambda function to attempt to delete all Default VPCs in the organization. Default cron | |
expression invokes the function once daily at 6:00pm UTC. See | |
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html | |
Resources: | |
StackSet: | |
Type: AWS::CloudFormation::StackSet | |
Properties: | |
StackSetName: !Ref AWS::StackName | |
AutoDeployment: | |
Enabled: True | |
RetainStacksOnAccountRemoval: False | |
Capabilities: [CAPABILITY_NAMED_IAM] | |
OperationPreferences: | |
RegionConcurrencyType: PARALLEL | |
FailureTolerancePercentage: 25 | |
MaxConcurrentPercentage: 100 | |
PermissionModel: SERVICE_MANAGED | |
StackInstancesGroup: | |
- DeploymentTargets: | |
OrganizationalUnitIds: [!Ref OrgRootId] | |
Regions: [!Ref AWS::Region] | |
Tags: | |
- Key: CfnStackId | |
Value: !Ref AWS::StackId | |
Parameters: | |
- ParameterKey: AssumeRolePrincipal | |
ParameterValue: !Sub '${DeleteAllOrganizationDefaultVpcsFunctionRole.Arn}' | |
- ParameterKey: RoleName | |
ParameterValue: !Ref CrossAcctRoleName | |
TemplateBody: | | |
Parameters: | |
AssumeRolePrincipal: | |
Type: String | |
RoleName: | |
Type: String | |
Resources: | |
CrossAcctRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Ref RoleName | |
Path: '/' | |
Description: Used by organization automation to delete Default VPCs | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
AWS: !Sub '${AssumeRolePrincipal}' | |
Action: sts:AssumeRole | |
Tags: | |
- Key: CfnStackId | |
Value: !Ref AWS::StackId | |
ManagedPolicyArns: | |
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess' | |
Policies: | |
- PolicyName: Inline | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
# Be sure to keep these permissions in sync between stackset role and org mgmt acct role | |
- Effect: Allow | |
Action: | |
- ec2:DescribeRegions | |
- ec2:DetachInternetGateway | |
- ec2:DeleteInternetGateway | |
- ec2:DeleteSubnet | |
- ec2:DeleteSecurityGroup | |
- ec2:DeleteVpc | |
Resource: '*' | |
CrossAcctRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Ref CrossAcctRoleName | |
Path: '/' | |
Description: Used by organization automation to delete Default VPCs | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
AWS: !Sub '${DeleteAllOrganizationDefaultVpcsFunctionRole.Arn}' | |
Action: sts:AssumeRole | |
Tags: | |
- Key: CfnStackId | |
Value: !Ref AWS::StackId | |
ManagedPolicyArns: | |
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess' | |
Policies: | |
- PolicyName: Inline | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
# Be sure to keep these permissions in sync between stackset role and org mgmt acct role | |
- Effect: Allow | |
Action: | |
- ec2:DescribeRegions | |
- ec2:DetachInternetGateway | |
- ec2:DeleteInternetGateway | |
- ec2:DeleteSubnet | |
- ec2:DeleteSecurityGroup | |
- ec2:DeleteVpc | |
Resource: '*' | |
DeleteAllOrganizationDefaultVpcsFunction: | |
Type: AWS::Lambda::Function | |
Properties: | |
Description: Deletes all Default VPCs across the AWS organization | |
Role: !Sub '${DeleteAllOrganizationDefaultVpcsFunctionRole.Arn}' | |
Handler: index.handler | |
Timeout: 300 | |
Runtime: python3.8 | |
Tags: | |
- Key: CfnStackId | |
Value: !Ref AWS::StackId | |
Environment: | |
Variables: | |
ASSUME_ROLE_ARN_FMT_STR: !Sub 'arn:${AWS::Partition}:iam::{acct_id}:role/${CrossAcctRoleName}' | |
Code: | |
ZipFile: | | |
import boto3 | |
import botocore | |
import json | |
import os | |
import traceback | |
import cfnresponse | |
orgs = boto3.client("organizations", region_name=os.environ["AWS_REGION"]) | |
sts = boto3.client("sts", region_name=os.environ["AWS_REGION"]) | |
local_ec2 = boto3.client("ec2", region_name=os.environ["AWS_REGION"]) | |
def handler(event, context): | |
print(json.dumps(event, default=str)) | |
# Capture errors and do not raise until finished configuring all monitoring | |
errors = [] | |
regions = local_ec2.describe_regions()["Regions"] | |
accts = [] | |
for pg in orgs.get_paginator('list_accounts').paginate(): | |
accts += pg['Accounts'] | |
for acct in accts: | |
print(f"Info - Deleting Default VPCs in account '{acct['Id']}'") | |
role_arn = os.environ["ASSUME_ROLE_ARN_FMT_STR"].format(acct_id=acct['Id']) | |
creds = sts.assume_role(RoleArn=role_arn, RoleSessionName=os.environ["AWS_LAMBDA_FUNCTION_NAME"][:63])[ | |
"Credentials" | |
] | |
boto3_client_args = { | |
"aws_access_key_id": creds["AccessKeyId"], | |
"aws_secret_access_key": creds["SecretAccessKey"], | |
"aws_session_token": creds["SessionToken"], | |
} | |
for region in regions: | |
regional_ec2_client = boto3.client("ec2", region_name=region["RegionName"], **boto3_client_args) | |
print(f"Info - Deleting Default VPC in account '{acct['Id']}', Region '{region['RegionName']}'") | |
try: | |
delete_default_vpc(regional_ec2_client) | |
except Exception as e: | |
print( | |
f"Error - Failed to delete Default VPC in account '{acct['Id']}', Region '{region['RegionName']}' - {repr(e)} - " | |
f"{traceback.format_exc()}" | |
) | |
errors.append(e) | |
if errors: | |
raise Exception("Errors encountered:", errors) | |
def delete_default_vpc(ec2): | |
try: | |
default_vpc = ec2.describe_vpcs(Filters=[{"Name": "is-default", "Values": ["true"]}])["Vpcs"][0] | |
except IndexError: | |
print(f"Info - Default VPC not found") | |
return | |
skip_delete = False | |
net_ifs = ec2.describe_network_interfaces( | |
Filters=[{"Name": "vpc-id", "Values": [default_vpc["VpcId"]]}] | |
)["NetworkInterfaces"] | |
if net_ifs: | |
skip_delete = True | |
print( | |
f"Warning - Found network interfaces in Default VPC '{default_vpc['VpcId']}'. " | |
"Delete will likely fail, see network interfaces below:" | |
) | |
print(json.dumps(net_ifs, default=str)) | |
pcxs = ec2.describe_vpc_peering_connections( | |
Filters=[ | |
{"Name": "requester-vpc-info.vpc-id", "Values": [default_vpc["VpcId"]]}, | |
{"Name": "status-code", "Values": ["pending-acceptance", "failed", "expired", "provisioning", "active", "deleting"]}, | |
] | |
)["VpcPeeringConnections"] | |
pcxs += ec2.describe_vpc_peering_connections( | |
Filters=[ | |
{"Name": "accepter-vpc-info.vpc-id", "Values": [default_vpc["VpcId"]]}, | |
{"Name": "status-code", "Values": ["pending-acceptance", "failed", "expired", "provisioning", "active", "deleting"]}, | |
] | |
)["VpcPeeringConnections"] | |
if pcxs: | |
skip_delete = True | |
print( | |
f"Warning - Found peering connections in Default VPC '{default_vpc['VpcId']}'. " | |
"Delete will likely fail, see peering connections below:" | |
) | |
print(json.dumps(pcxs, default=str)) | |
if skip_delete: | |
raise Exception( | |
f"Found dependencies in Default VPC '{default_vpc['VpcId']}', see above. " | |
"Not attempting to delete." | |
) | |
igws = ec2.describe_internet_gateways( | |
Filters=[{"Name": "attachment.vpc-id", "Values": [default_vpc["VpcId"]]}] | |
)["InternetGateways"] | |
for igw in igws: | |
print(f"Info - Detaching internet gateway '{igw['InternetGatewayId']}' from Default VPC") | |
ec2.detach_internet_gateway(InternetGatewayId=igw["InternetGatewayId"], VpcId=default_vpc["VpcId"]) | |
print(f"Info - Deleting internet gateway '{igw['InternetGatewayId']}'") | |
ec2.delete_internet_gateway(InternetGatewayId=igw["InternetGatewayId"]) | |
subnets = ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [default_vpc["VpcId"]]}])["Subnets"] | |
for subnet in subnets: | |
print(f"Info - Deleting Default VPC subnet '{subnet['SubnetId']}'") | |
ec2.delete_subnet(SubnetId=subnet["SubnetId"]) | |
security_groups = ec2.describe_security_groups( | |
Filters=[{"Name": "vpc-id", "Values": [default_vpc["VpcId"]]}] | |
)["SecurityGroups"] | |
for security_group in security_groups: | |
if security_group["GroupName"] == "default": | |
continue | |
print( | |
f"Info - Deleting Default VPC security group '{security_group['GroupId']}' " | |
f"('{security_group['GroupName']}')" | |
) | |
ec2.delete_security_group(GroupId=security_group["GroupId"]) | |
print(f"Info - Deleting Default VPC '{default_vpc['VpcId']}'") | |
ec2.delete_vpc(VpcId=default_vpc["VpcId"]) | |
print(f"Info - Deleted Default VPC '{default_vpc['VpcId']}'") | |
DeleteAllOrganizationDefaultVpcsFunctionLogGroup: | |
Type: AWS::Logs::LogGroup | |
DeletionPolicy: Retain | |
UpdateReplacePolicy: Retain | |
Properties: | |
LogGroupName: !Sub /aws/lambda/${DeleteAllOrganizationDefaultVpcsFunction} | |
RetentionInDays: 90 | |
DeleteAllOrganizationDefaultVpcsFunctionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: lambda.amazonaws.com | |
Action: sts:AssumeRole | |
Tags: | |
- Key: CfnStackId | |
Value: !Ref AWS::StackId | |
ManagedPolicyArns: | |
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' | |
Policies: | |
- PolicyName: Inline | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- organizations:ListAccounts | |
- ec2:DescribeRegions | |
- sts:AssumeRole | |
Resource: '*' | |
DeleteAllOrganizationDefaultVpcsFunctionEventsRule: | |
Type: AWS::Events::Rule | |
Properties: | |
State: ENABLED | |
ScheduleExpression: !Ref ScheduleExpression | |
Targets: | |
- Id: DeleteAllOrganizationDefaultVpcsFunction | |
Arn: !Sub '${DeleteAllOrganizationDefaultVpcsFunction.Arn}' | |
DeleteAllOrganizationDefaultVpcsFunctionPermissionEventsRule: | |
Type: AWS::Lambda::Permission | |
Properties: | |
FunctionName: !Ref DeleteAllOrganizationDefaultVpcsFunction | |
Action: lambda:InvokeFunction | |
Principal: events.amazonaws.com | |
SourceArn: !Sub '${DeleteAllOrganizationDefaultVpcsFunctionEventsRule.Arn}' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment