Delete Default VPC in multiple AWS Accounts using SNS & Lambda
Tags: aws, community, vpc, lambda
Published: 2023-01-18 17:56:45

## **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn1uvzltzshacs3dsmvw.PNG)

---

## **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"
        }
```
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": "*"
        }
```

---

## **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdprp00332mcuy6djk0o.png)

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;
}


```


_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
      }
   
   }
      
```

---

## **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