Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
executable file 185 lines (153 sloc) 6.28 KB
#!/usr/bin/env python3
# Parse, filter and lookup the published Amazon IP Ranges.
# Useful for creating Security Groups and other firewall rules.
# E.g. incoming only from CloudFront
# or outgoing only to non-EC2 ranges in US-WEST-1 region
# Author: Michael Ludvig <>
import json
import sys
import argparse
import socket
import ipaddress
from httplib2 import Http
def fatal(message):
print("ERROR: %s" % message, file=sys.stderr)
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
Parse and Filter the published Amazon IP Ranges.
parser.add_argument('--url', '-u', metavar="URL", default="", help='URL for ip-ranges.json download. Default: %(default)s')
parser.add_argument('--file', '-f', type=argparse.FileType('r'), help='Path to ip-ranges.json. Overrides --url')
parser.add_argument('--min-serial', type=int, metavar="SERIAL", help='Only process the file id serial number (aka syncToken) is greater than SERIAL. No output if less or equal.')
parser.add_argument('--ipv4', '-4', action="store_true", help='Lookup / display only IPv4 addresses. By default both IPv4 and IPv6 are considered.')
parser.add_argument('--ipv6', '-6', action="store_true", help='Lookup / display only IPv6 addresses. By default both IPv4 and IPv6 are considered.')
parser.add_argument('--quiet', action="store_true", help='Do not print any extra info, e.g. serial number, etc.')
parser.add_argument('--verbose', '-v', action="store_true", help='Print the Region and Services along with each IP range.')
parser.usage = parser.format_usage().rstrip()[7:]
parser.usage += " [FILTER [...]]"
parser.epilog = """
The IP Range prefixes can be filtered based on Services and Regions.
Keywords can be combined, for example: +AMAZON -EC2 us-west-1
+KEYWORD Include prefixes with matching keyword (AMAZON).
The '+' sign is optional, +AMAZON and AMAZON works the same.
-KEYWORD Exclude prefixes with service or region matching KEYWORD.
=KEYWORD Include prefixes that have only one service matching KEYWORD.
Sometimes prefixes overlap and belong to multiple services.
The KEYWORD can be a Service (e.g. AMAZON, EC2, ...) or Region (us-west-1,
ap-southeast-2, GLOBAL).
KEYWORD can also be an IP address in which case the matching subnet
will be selected.
Supports both IPv4 (default) and IPv6 addresses (with --ipv6 parameter).
$ filter-ip-ranges ap-southeast-2 =AMAZON
$ filter-ip-ranges -v ap-southeast-2 AMAZON
Michael Ludvig --
args, extra = parser.parse_known_args()
if args.file:
ipranges = json.load(args.file)
except ValueError:
fatal("File is not ip-ranges.json")
resp, content = Http().request(args.url)
if resp.status != 200:
fatal("Unable to load %s - %d %s" % (args.url, resp.status, resp.reason))
content = content.decode('latin1')
ipranges = json.loads(content)
except Exception as e:
fatal("Unable to load %s - %s" % (args.url, e))
if len(ipranges['prefixes']) + len(ipranges['ipv6_prefixes']) < 1:
fatal("No prefixes found")
if args.min_serial and int(ipranges['syncToken']) <= args.min_serial:
# Serial number is not greater than required by --min-serial=NNN
def list_prefixes(ipv4=True):
if ipv4:
address_family = socket.AF_INET
prefixes_label = 'prefixes'
ip_prefix_label = 'ip_prefix'
address_family = socket.AF_INET6
prefixes_label = 'ipv6_prefixes'
ip_prefix_label = 'ipv6_prefix'
pfx_dict = {}
for prefix in ipranges[prefixes_label]:
ip_prefix = prefix[ip_prefix_label]
if ip_prefix not in pfx_dict:
pfx_dict[ip_prefix] = {}
pfx_dict[ip_prefix]['net'] = ip_prefix
pfx_dict[ip_prefix]['rgn'] = prefix['region']
pfx_dict[ip_prefix]['svc'] = [ prefix['service'] ]
pfx_vals = list(pfx_dict.values())
pfx_vals = sorted(pfx_vals, key=lambda x: socket.inet_pton(address_family, x['net'].split('/')[0]))
return pfx_vals
prefixes = []
if not args.ipv6:
if not args.ipv4:
ips = []
for xarg in extra:
# If xarg is an IP address we store it for later
# and move on to the next xarg
except ValueError:
_pfx = []
if xarg.startswith('='):
# Filter records that have ONLY this service/region
_arg = xarg[1:]
for prefix in prefixes:
if prefix['svc'].count(_arg) and len(prefix['svc']) == 1:
elif prefix['rgn'] == _arg:
elif xarg.startswith('-'):
# Exclude this service/region
_arg = xarg[1:]
for prefix in prefixes:
if prefix['svc'].count(_arg) == 0 and prefix['rgn'] != _arg:
# Include this service/region
_arg = xarg.startswith('+') and xarg[1:] or xarg
for prefix in prefixes:
if prefix['svc'].count(_arg):
elif prefix['rgn'] == _arg:
prefixes = _pfx
if not args.quiet:
print('# SERIAL=%s' % ipranges['syncToken'])
# Now it's the time to process the IPs found in 'extra', if any
if ips:
_pfx = []
for ip in ips:
for prefix in prefixes:
net = ipaddress.ip_network(prefix['net'])
if net.overlaps(ip):
prefixes = _pfx
if args.verbose and not args.quiet:
print('# %d prefixes found / %d prefixes matching' % (
len(ipranges['prefixes']), len(prefixes)))
for prefix in prefixes:
if args.verbose:
print("%s %s %s" % (prefix['net'], prefix['rgn'], " ".join(prefix['svc'])))
print("%s" % prefix['net'])