DEV Community

Cover image for Delete Default VPC in multiple AWS Accounts using SNS & Lambda
Peter Eskandar for AWS Community Builders

Posted on

Delete Default VPC in multiple AWS Accounts using SNS & Lambda

Problem Introduction :

When working on AWS to setup a large-scale Architecture based on your own Network "VPCs, Transit Gateways, Firewalls etc...", Usually you don't use the Default VPC created by AWS in every enabled region.

So, as a best practice the Default VPCs in the regions that you're using to deploy your application workload should be deleted.


Solution :

In our case, We've created an SNS Topic and a Lambda function in our Automation account.

We've added a subscription to trigger the Lambda function each time we publish something on the SNS Topic.

One of our Automation Pipelines will publish a message to the SNS Topic each time a new AWS Account is being created.

The Lambda function based on the information recieved from SNS "Usually the AccountID from where we want to delete the default VPCs" will assume a role in the destination account and then delete the Default VPCs.

Something similar to what is shown in the diagram below :

Delete Default VPC Architecture


Lambda Permissions :

In the Automation Account, the Lambda function should have the permission to assume a role in the destination account :

        {
            "Sid": "AllowAssumeRole",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::*:role/DeleteDefaultVPCRole"
        }
Enter fullscreen mode Exit fullscreen mode

Where DeleteDefaultVPCRole is the name of the IAM Role we are using in our Destination Account/s.

In the Destination Account , the DeleteDefaultVPCRole should have the following permissions, so that the Lambda can delete the Default VPC and all of its resources (IGW, Route Tables, Subnets and Security Groups) :

        {
            "Sid": "AllowDeleteDefaultVPCResources",
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteVpc",
                "ec2:DetachInternetGateway",
                "ec2:DeleteInternetGateway",
                "ec2:DeleteSubnet",
                "ec2:DeleteRouteTable"
            ],
            "Resource": "*"
        }
Enter fullscreen mode Exit fullscreen mode

Lambda Code :

The Lambda based on the following environment variable will operate in multiple regions and switch from one region to another in the destination account:

regions env variable

The Lambda will search for all the VPCs in different regions with the flag isDefault equals true.

Using the function getCrossAccountCredentials the Lambda will assume the DeleteDefaultVPCRole in the Destination account.

Below you can find the code in node.js "index.js & utils.js files"

index.js :


const aws = require('aws-sdk');
const utils = require('./utils')(aws);

exports.handler = async (event, context) => {

   try {
      // get accountId from the sns published message
      // the message should contain only the crossAccount accountId
      const snsEvent  = event['Records'][0]['Sns'];
      const accountId = snsEvent ? snsEvent['Message'] : null;
      // get list of all regions
      const regions = process.env.REGIONS.split(',');
      // get only the regions with default vpc <RegionName, VpcId>
      let regionVpcMap = accountId ? await deleteDefaultVPCByRegion(regions, accountId) : null;
      return regionVpcMap
   } catch (e) {
      console.log(e);
   }

};

// get Current default VPC and start deleting all dependencies
async function deleteDefaultVPCByRegion(regions, accountId) {
   let regionVpcMap = new Map();
   let vpcParams = { Filters: [{'Name' : 'isDefault', 'Values' : ['true']}]}
   for(const region of regions)
   {
      try {
         const token = await utils.getCrossAccountCredentials(`arn:aws:iam::${accountId}:role/DeleteDefaultVPCRole`)
         const regionalEC2 = new aws.EC2({ region: region, credentials: token });
         const result = await regionalEC2.describeVpcs(vpcParams).promise();
         console.log(result, region, regions);
         if(result['Vpcs'].length > 0) {
            regionVpcMap.set(region, result['Vpcs'][0]['VpcId'])
         /**
              - Action to follow : 
              1.) Detach & Delete the internet-gateway
              2.) Delete subnets
              3.) Delete route-tables if not main
              4.) Delete security-groups if not default
              5.) Delete the VPC 
         **/  

            await utils.deleteInternetGateway(regionalEC2, result['Vpcs'][0]['VpcId']);
            await utils.deleteSubnetsDefaultVPC(regionalEC2, result['Vpcs'][0]['VpcId']);
            await utils.deleteSGDefaultVPC(regionalEC2, result['Vpcs'][0]['VpcId']);
            await utils.deleteRouteTablesDefaultVPC(regionalEC2, result['Vpcs'][0]['VpcId']);
            await utils.deleteDefaultVPC(regionalEC2, result['Vpcs'][0]['VpcId']);
         } else {
            console.log('No Default VPCs found !');
         }
      } catch (e) {
         throw new Error(e);
      }
   }

   return regionVpcMap.size > 0 ? regionVpcMap : null;
}


Enter fullscreen mode Exit fullscreen mode

utils.js :

module.exports = (AWS) => {
   // get crossAccount temp Credentials
   async function getCrossAccountCredentials (roleArn)  {
      const sts = new AWS.STS();
      const timestamp = (new Date()).getTime();
      const params = {
        RoleArn: roleArn,
        RoleSessionName: `VPC-Deleter-${timestamp}`,
      };
      const assumeRoleResult = await sts.assumeRole(params).promise();
      return {
        accessKeyId: assumeRoleResult.Credentials.AccessKeyId,
        secretAccessKey: assumeRoleResult.Credentials.SecretAccessKey,
        sessionToken: assumeRoleResult.Credentials.SessionToken,
      }
    }

   // delete  Internet Gateway for attached to the default VPC
   async function deleteInternetGateway(ec2, vpcId) {
       const params = {Filters: [ { Name: "attachment.vpc-id", Values: [vpcId]}]};
       try {
         const result =  await ec2.describeInternetGateways(params).promise();
         for(const igw of result['InternetGateways']){
             // detach IGW from VPC
             await ec2.detachInternetGateway({ InternetGatewayId: igw['InternetGatewayId'],  VpcId: vpcId}).promise();
             // delete IGW
             await ec2.deleteInternetGateway({ InternetGatewayId: igw['InternetGatewayId']}).promise();
         }
       } catch (e) {
          throw new Error(`Unable to detach IGW for VPC - ${vpcId} - reason :  ${e}`)
       }
      }


      // delete all subnets related to the default VPC
      async function deleteSubnetsDefaultVPC(ec2, vpcId) {
      const params = {Filters: [ { Name: "vpc-id", Values: [vpcId]}]};
       try {
         const subnetsResult =  await ec2.describeSubnets(params).promise();
         for(const subnet of subnetsResult['Subnets']){
           // delete each subnet attached to the current default VPC   
           await ec2.deleteSubnet({SubnetId: subnet['SubnetId']}).promise();
         }
       } catch (e) {
          throw new Error(`Unable to delete subnets for VPC - ${vpcId} - reason :  ${e}`);
       }
      }


      // delete all security groups related to the default VPC
      async function deleteSGDefaultVPC(ec2, vpcId) {
      const params = {Filters: [ { Name: "vpc-id", Values: [vpcId]}]};
       try {
          const sgResult = await ec2.describeSecurityGroups(params).promise();
          for(const sg of sgResult['SecurityGroups']){
             if(sg['GroupName'] !== 'default')
               await ec2.deleteSecurityGroup({GroupId: sg['GroupId']}).promise();
         }
       } catch (e) {
          throw new Error(`Unable to delete Security Groups for VPC - ${vpcId} - reason :  ${e}`);
       }
      }


      // delete all route tables related to the default VPC
      async function deleteRouteTablesDefaultVPC(ec2, vpcId) {
      const params = {Filters: [ { Name: "vpc-id", Values: [vpcId]}]};
       try {
          const rtbResult = await ec2.describeRouteTables(params).promise();
          for(const rtb of rtbResult['RouteTables']){
             if(!rtb['Associations'].length ) {
                 await ec2.deleteRouteTable({RouteTableId: rtb['RouteTableId'] }).promise();
             }
         }
       } catch (e) {
          throw new Error(`Unable to delete Route Tables for VPC - ${vpcId} - reason :  ${e}`);
       }
      }

      // delete default VPC
      async function deleteDefaultVPC(ec2, vpcId) {
       try {
          await ec2.deleteVpc({VpcId: vpcId}).promise();
          console.log(`Default VPC ${vpcId} has been deleted`);
       } catch (e) {
          throw new Error(`Unable to delete Default VPC - ${vpcId} - reason :  ${e}`);
       }
      }

      return {
        getCrossAccountCredentials, 
        deleteInternetGateway,
        deleteSubnetsDefaultVPC,
        deleteSGDefaultVPC,
        deleteRouteTablesDefaultVPC,
        deleteDefaultVPC
      }

   }

Enter fullscreen mode Exit fullscreen mode

The End

Hope this article hepls you to organize your AWS Accounts in a better way

If you end up here, I want to say, thank you for reading this article! 🙌

Peter

Oldest comments (0)