Skip to content

Instantly share code, notes, and snippets.

@wparad
Last active October 3, 2021 17:32
Show Gist options
  • Save wparad/2f5ec426b73eaab903c037101fab49f1 to your computer and use it in GitHub Desktop.
Save wparad/2f5ec426b73eaab903c037101fab49f1 to your computer and use it in GitHub Desktop.
Find Route53 records pointing to unowned IP Addresses
// This is also a config rule: https://eu-west-2.console.aws.amazon.com/lambda/home?region=eu-west-2#/create/app?applicationId=arn:aws:serverlessrepo:eu-west-1:922723803004:applications/Elastic-IP-Config-Rule
const { Route53, EC2, config } = require('aws-sdk');
config.region = 'eu-west-1';
async function findDanglingElasticIpAddresses(dryRun = true) {
const route53 = new Route53();
let hostedZoneIds;
try {
console.log('Looking up hosted zones:');
hostedZoneIds = (await route53.listHostedZones({ }).promise()).HostedZones.map(hz => hz.Id);
console.log(' :', hostedZoneIds);
} catch (error) {
console.log(' Failed to retrieve hosted zones', error);
process.exit(1);
}
const addresses = [];
const regions = await new EC2().describeRegions().promise().then(data => data.Regions.map(r => r.RegionName));
await Promise.all(regions.map(async region => {
const regionalEc2Client = new EC2({ region });
addresses.push(...(await regionalEc2Client.describeAddresses().promise()).Addresses.map(a => a.PublicIp));
}));
const recordAddressMap = {};
await Promise.all(hostedZoneIds.map(async hostedZoneId => {
console.log('Fetching records for zone: ', hostedZoneId);
const params = { HostedZoneId: hostedZoneId };
try {
do {
const response = await route53.listResourceRecordSets(params).promise();
params.StartRecordIdentifier = response.NextRecordIdentifier;
response.ResourceRecordSets
.filter(t => !t.AliasTarget && t.Type === 'A')
.map(r => ({ hostedZoneId, name: r.Name, type: r.Type, originalRecord: r }))
.forEach(r => {
r.originalRecord.ResourceRecords.map(rr => rr.Value).forEach(address => {
if (!recordAddressMap[address]) {
recordAddressMap[address] = [];
}
recordAddressMap[address].push(r);
});
});
} while (params.StartRecordIdentifier);
} catch (error) {
console.log(' Failed to get records for zone', hostedZoneId, error);
}
if (!Object.keys(recordAddressMap).length) {
return;
}
addresses.forEach(a => {
delete recordAddressMap[a];
});
console.log(Object.keys(recordAddressMap));
console.log('Extraneous Ip Addresses:', Object.keys(recordAddressMap));
if (!dryRun) {
console.log(' Updating extraneous records');
const changeSetParams = {
HostedZoneId: hostedZoneId,
ChangeBatch: {
Changes: Object.values(recordAddressMap).flat(1).map(record => {
const deleteRecord = record.originalRecord.ResourceRecords.every(r => recordAddressMap[r.Value]);
const change = {
Action: deleteRecord ? 'DELETE' : 'UPSERT',
ResourceRecordSet: record.originalRecord
};
if (!deleteRecord) {
change.ResourceRecordSet.ResourceRecords = change.ResourceRecordSet.ResourceRecords.filter(r => !recordAddressMap[r.Value]);
}
return change;
})
}
};
console.log(JSON.stringify(changeSetParams, null, 2));
await route53.changeResourceRecordSets(changeSetParams).promise();
}
}));
}
findDanglingElasticIpAddresses(true);