[小ネタ]Lambda-backed custom resourcesで全リージョンのデフォルトVPCを削除する

新しいAWSアカウントを作成するタイミングで全リージョンのデフォルトVPCを削除したい方向け
2022.02.23

こんにちは、おんづか(@onzuka_muscle)です!

こちらのブログを参考にアカウントが特定OUに所属したタイミングでLambdaを実行し、デフォルトVPCを削除してみました。

注意点

  • 作成したばかりのAWSアカウントを想定しています
    • そのためデフォルトVPCに紐づいたリソースをユーザーが作成していると本Lambdaは失敗します
  • Lambdaが実行された後もLambda , IAMロール , CloudWatch Logsといったリソースは残ります
    • 必要に応じて削除してください
  • あくまでサンプルコードです
    • エラーハンドリング・ロギングについてはそこそこなので必要に応じてカスタマイズください

テンプレート

AWSTemplateFormatVersion: "2010-09-09"
# ------------------------------------------------------------#
#  デフォルト VPC を削除する Lambda-backed custom resources
# ------------------------------------------------------------#
Parameters:
  RoleName:
    Type: String
  FunctionName:
    Type: String

Resources:
  # ------------------------------------------------------------#
  #  Lambda-backed custom resources
  # ------------------------------------------------------------#
  DeleteDefaultVPC:
    Type: Custom::DeleteDefaultVPC
    Properties:
      ServiceToken: !GetAtt "LambdaFunction.Arn"

  # ------------------------------------------------------------#
  #  Lambda 関数
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Ref FunctionName
      Description: Lambda for delete default VPC
      Role: !GetAtt "LambdaExecutionRole.Arn"
      Runtime: "python3.8"
      Handler: index.lambda_handler
      Timeout: 120
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse
          from logging import getLogger, INFO

          logger = getLogger()
          logger.setLevel(INFO)


          def delete_default_vpc(event, context):
              region_list = []
              ec2_client = boto3.client('ec2')
              regions = ec2_client.describe_regions()

              for r in range(0, len(regions['Regions'])):
                  region_name = regions['Regions'][r]['RegionName']
                  region_list.append(region_name)

              for region in region_list:
                  try:
                      client = boto3.client('ec2', region_name=region)
                      ec2 = boto3.resource('ec2', region_name=region)
                      vpcs = get_default_vpcs(client)
                  except boto3.exceptions.Boto3Error as e:
                      logger.info(e)
                      exit(1)
                  else:
                      for vpc in vpcs:
                          logger.info("\n" + "\n" + "REGION:" +
                                      region + "\n" + "VPC Id:" + vpc)
                          del_igw(ec2, vpc)
                          del_sub(ec2, vpc)
                          del_vpc(ec2, vpc)


          def lambda_handler(event, context):
              if event['RequestType'] == 'Create':
                  # Create時に実行したい処理 -->  デフォルト VPC を削除する
                  delete_default_vpc(event, context)
                  cfnresponse.send(event, context, cfnresponse.SUCCESS,
                                  {'Response': 'Success'})
              if event['RequestType'] == 'Delete':
                  # Delete時に実行したい処理 --> 無し
                  cfnresponse.send(event, context, cfnresponse.SUCCESS,
                                  {'Response': 'Success'})
              if event['RequestType'] == 'Update':
                  # Update時に実行したい処理 --> 無し
                  cfnresponse.send(event, context, cfnresponse.SUCCESS,
                                  {'Response': 'Success'})


          def get_default_vpcs(client):
              vpc_list = []
              vpcs = client.describe_vpcs(
                  Filters=[
                      {
                          'Name': 'isDefault',
                          'Values': [
                              'true',
                          ],
                      },
                  ]
              )
              vpcs_str = json.dumps(vpcs)
              resp = json.loads(vpcs_str)
              data = json.dumps(resp['Vpcs'])
              vpcs = json.loads(data)

              for vpc in vpcs:
                  vpc_list.append(vpc['VpcId'])

              return vpc_list


          def del_igw(ec2, vpcid):
              vpc_resource = ec2.Vpc(vpcid)
              igws = vpc_resource.internet_gateways.all()
              if igws:
                  for igw in igws:
                      try:
                          igw.detach_from_vpc(
                              VpcId=vpcid
                          )
                          igw.delete()
                      except boto3.exceptions.Boto3Error as e:
                          logger.info(e)


          def del_sub(ec2, vpcid):
              vpc_resource = ec2.Vpc(vpcid)
              subnets = vpc_resource.subnets.all()
              default_subnets = [ec2.Subnet(subnet.id)
                                for subnet in subnets if subnet.default_for_az]

              if default_subnets:
                  try:
                      for sub in default_subnets:
                          sub.delete()
                  except boto3.exceptions.Boto3Error as e:
                      logger.info(e)


          def del_vpc(ec2, vpcid):
              vpc_resource = ec2.Vpc(vpcid)
              try:
                  vpc_resource.delete()
              except boto3.exceptions.Boto3Error as e:
                  logger.info(e)
                  logger.info("Please remove dependencies and delete VPC manually.")

  # ------------------------------------------------------------#
  #  Lambda 関数用の IAM ロール
  # ------------------------------------------------------------#
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref RoleName
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: DeleteDefaultVPCPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ec2:DescribeRegions"
                  - "ec2:DescribeInternetGateways"
                  - "ec2:DetachInternetGateway"
                  - "ec2:DeleteInternetGateway"
                  - "ec2:DescribeSubnets"
                  - "ec2:DeleteSubnet"
                  - "ec2:DescribeVpcs"
                  - "ec2:DeleteVpc"
                Resource: "*"

動作確認

EC2 Global Viewより確認しました。

Before

After

綺麗さっぱりになりました!

おわり

サンプルコードになりますが需要あるかなと思いブログにしました。

こちらのブログのようにCloudFormation StackSetsと組み合わせることで新しく作成したアカウントのデフォルトVPC削除を効率化できます。

誰かのお役に立てば嬉しいです。