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 :
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:
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
Top comments (0)