Skip to content

Instantly share code, notes, and snippets.

@atheiman
Last active October 18, 2024 18:03

Revisions

  1. atheiman revised this gist Mar 10, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions template.yml
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@ Resources:
    Properties:
    RoleName: !Ref RoleName
    Path: '/'
    Description: Used by organization automation to delete Default VPC
    Description: Used by organization automation to delete Default VPCs
    AssumeRolePolicyDocument:
    Version: '2012-10-17'
    Statement:
    @@ -94,7 +94,7 @@ Resources:
    Properties:
    RoleName: !Ref CrossAcctRoleName
    Path: '/'
    Description: Used by organization automation to delete Default VPC
    Description: Used by organization automation to delete Default VPCs
    AssumeRolePolicyDocument:
    Version: '2012-10-17'
    Statement:
  2. atheiman revised this gist Mar 10, 2022. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions template.yml
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,8 @@
    Description: Scheduled Lambda function to delete Default VPC from all accounts in the organization
    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:
    @@ -122,7 +126,7 @@ Resources:
    DeleteAllOrganizationDefaultVpcsFunction:
    Type: AWS::Lambda::Function
    Properties:
    Description: 'Deletes all Default VPCs across the AWS organization'
    Description: Deletes all Default VPCs across the AWS organization
    Role: !Sub '${DeleteAllOrganizationDefaultVpcsFunctionRole.Arn}'
    Handler: index.handler
    Timeout: 300
  3. atheiman created this gist Mar 10, 2022.
    315 changes: 315 additions & 0 deletions template.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,315 @@
    Description: Scheduled Lambda function to delete Default VPC from all accounts in the organization

    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 VPC
    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 VPC
    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}'