def _from_xml(cls, address_book_elt): addrbook = cls() abe = address_book_elt addrbook.name = abe.find('name').text # address book for elt in abe: if elt.tag == 'name': addrbook.name = elt.text elif elt.tag == 'address': ip = IPSet([IP(elt.findtext('ip-prefix'))]) name = elt.findtext('name') addrbook.addresses[name] = ip elif elt.tag == 'address-set': # note: assumes address-sets follow addresses ip = IPSet() for setaddr in elt.findall('address'): setname = setaddr.findtext('name') ip += addrbook.addresses[setname] name = elt.findtext('name') addrbook.addresses[name] = ip elif elt.tag == 'attach': attaches = [] for z in elt: attaches.append(z.findtext('name')) addrbook.attaches = attaches return addrbook
def test_ippairs_sub_empty(): ten = IPSet([IP('10.0.0.0/8')]) twenty = IPSet([IP('20.0.0.0/8')]) for pairs in [ IPPairs(), IPPairs((ten, twenty)), IPPairs((ten, twenty), (twenty, ten)), ]: eq_(pairs - IPPairs(), pairs)
def test_ipset_and_ip_list(): eq_( ten24s & IPSet([ IP('10.0.0.99'), IP('10.0.1.10'), IP('10.0.3.40'), IP('11.1.1.99'), ]), IPSet([ IP('10.0.1.10'), IP('10.0.3.40'), ]))
def _parse_addrbook(addrbook): addresses = {} for addr in addrbook: name = addr.findtext('name') if addr.tag == 'address': ip = IPSet([IP(addr.findtext('ip-prefix'))]) else: # note: assumes address-sets follow addresses ip = IPSet() for setaddr in addr.findall('address'): setname = setaddr.findtext('name') ip += addresses[setname] addresses[name] = ip return addresses
def run(cfg, fwunit_cfg): address_spaces = {} for name, ip_space in cfg['address_spaces'].iteritems(): if not isinstance(ip_space, list): ip_space = [ip_space] address_spaces[name] = IPSet([IP(s) for s in ip_space]) # Add an "unmanaged" address space for any IP space not mentioned. managed_space = IPSet([]) for sp in address_spaces.itervalues(): managed_space += sp unmanaged_space = IPSet([IP('0.0.0.0/0')]) - managed_space if unmanaged_space: address_spaces['unmanaged'] = unmanaged_space # parse the routes configuration, expanding wildcards sources = dict() routes = {} for src in address_spaces: for dst in address_spaces: routes[src, dst] = set() for srcdest, rulesources in cfg['routes'].iteritems(): if not isinstance(rulesources, list): rulesources = [rulesources] mo = re.match(r'(.*?) ?(<?)-> ?(.*)', srcdest) if not mo: raise RuntimeError("invalid route name {:r}".format(srcdest)) srcs, bidir, dsts = mo.groups() # expand wildcards srcs = address_spaces.keys() if srcs == '*' else [srcs] dsts = address_spaces.keys() if dsts == '*' else [dsts] for src in srcs: if src not in address_spaces: raise RuntimeError('Unknown address space %s' % src) for dst in dsts: if dst not in address_spaces: raise RuntimeError('Unknown address space %s' % dst) rt = routes[src, dst] for rs in rulesources: rt.add(rs) sources[rs] = None if bidir: rt = routes[dst, src] for rs in rulesources: rt.add(rs) # load the rules for all of the rule sources referenced for rs in sources: sources[rs] = get_rules(fwunit_cfg, rs) return process.combine(address_spaces, routes, sources)
def test_simplify_combine_iterations(): """A set of rules that requires a few passes to simplify is fully simplified""" rules = { 'testapp': [ r('10.0.0.0', IPSet([IP('20.0.0.0')])), r('10.0.0.0', IPSet([IP('30.0.0.0')])), r('11.0.0.0', IPSet([IP('20.0.0.0'), IP('30.0.0.0')])), r('12.0.0.0', IPSet([IP('20.0.0.0'), IP('30.0.0.0')])), r(IPSet([IP('10.0.0.0'), IP('11.0.0.0'), IP('12.0.0.0')]), IPSet([IP('30.0.0.0'), IP('40.0.0.0')])), ] } exp = { 'testapp': [ r(IPSet([IP('10.0.0.0'), IP('11.0.0.0'), IP('12.0.0.0')]), IPSet([IP('20.0.0.0'), IP('30.0.0.0'), IP('40.0.0.0')])), ] } eq_(simplify_rules(rules), exp)
def process_address_sets_per_policy(zones, policies_by_zone_pair, addrbooks_per_zone, global_addrbook): logger.info("computing address sets per policy") src_per_policy = {} dst_per_policy = {} for (from_zone, to_zone), zpolicies in policies_by_zone_pair.iteritems(): from_addrbook = addrbooks_per_zone.get(from_zone, global_addrbook) get_from = lambda a: a if isinstance(a, IPSet) else from_addrbook[a] to_addrbook = addrbooks_per_zone.get(to_zone, global_addrbook) get_to = lambda a: a if isinstance(a, IPSet) else to_addrbook[a] for pol in zpolicies: src_per_policy[pol] = sum( (get_from(a) for a in pol.source_addresses), IPSet()) dst_per_policy[pol] = sum( (get_to(a) for a in pol.destination_addresses), IPSet()) return src_per_policy, dst_per_policy
def test_parse_addrbook_attached(): f = FakeSRX() ab = f.add_addrbook('general-stuff') f.add_address(ab, 'trustedhost', '10.0.9.2/32') f.add_attach(ab, 'untrust') f.add_attach(ab, 'trust') elt = parse_xml(f.fake_show('configuration security address-book'), './/address-book') z = parse.AddressBook._from_xml(elt) eq_(z.attaches, ['untrust', 'trust']) eq_(sorted(z.addresses.keys()), sorted(['any', 'any-ipv4', 'any-ipv6', 'trustedhost'])) eq_(z.addresses['any'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv4'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv6'], IPSet([])) eq_(z.addresses['trustedhost'], IPSet([IP('10.0.9.2')]))
def test_parse_zone(): f = FakeSRX() z = f.add_zone('untrust') f.add_address(z, 'host1', '9.0.9.1/32') f.add_address(z, 'host2', '9.0.9.2/32') f.add_address(z, 'puppet', '9.0.9.3/32') f.add_address_set(z, 'hosts', 'host1', 'host2') f.add_interface(z, 'reth0') elt = parse_xml(f.fake_show('configuration security zones'), './/security-zone') z = parse.Zone._from_xml(elt) eq_(z.interfaces, ['reth0']) eq_( sorted(z.addresses.keys()), sorted([ 'any', 'any-ipv4', 'any-ipv6', 'host1', 'host2', 'hosts', 'puppet' ])) eq_(z.addresses['any'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv4'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv6'], IPSet([])) eq_(z.addresses['host1'], IPSet([IP('9.0.9.1')])) eq_(z.addresses['host2'], IPSet([IP('9.0.9.2')])) eq_(z.addresses['puppet'], IPSet([IP('9.0.9.3')])) eq_(z.addresses['hosts'], IPSet([IP('9.0.9.1'), IP('9.0.9.2')]))
def test_ippairs(): any = IPSet([IP('0.0.0.0/0')]) ten = IPSet([IP('10.0.0.0/8')]) not_ten = any - ten ten26 = IPSet([IP('10.26.0.0/16')]) not_ten26 = any - ten26 ten33 = IPSet([IP('10.33.0.0/16')]) eq_(IPPairs((any, any)) - IPPairs((any, ten)), IPPairs((any, not_ten))) eq_( IPPairs((any, any)) - IPPairs((any, ten)) - IPPairs((any, ten26)), IPPairs((any, not_ten))) eq_( IPPairs((any, any)) - IPPairs((any, ten)) - IPPairs((ten26, any)), IPPairs((not_ten26, not_ten))) eq_(IPPairs((any, ten26 + ten33)) - IPPairs((any, ten)), IPPairs()) eq_( IPPairs((ten, ten)) - IPPairs((ten26, ten26)), IPPairs((ten, ten - ten26), (ten - ten26, ten26)))
def process_interface_ips(routes): # figure out the IPSet routed via each interface, by starting with the most # specific and only considering IP space not already allocated to an # interface. This has the effect of leaving a "swiss cheese" default route # containing all IPs that aren't routed by a more-specific route. logger.info("calculating interface IP ranges") routes = routes[:] routes.sort(key=lambda r: -r.destination.prefixlen()) matched = IPSet() interface_ips = {} for r in routes: destset = IPSet([r.destination]) if r.interface and not r.reject: interface_ips[r.interface] = interface_ips.get( r.interface, IPSet()) + (destset - matched) # consider the route matched even if it didn't have an # interface or is a blackhole matched = matched + destset return interface_ips
def test_simplify_combine(): """Rules with the same source are combined""" rules = { 'testapp': [ r('10.0.0.0', '20.0.0.0'), r('20.0.0.0', '30.0.0.0'), r('10.0.0.0', '40.0.0.0'), ] } exp = { 'testapp': [ r('10.0.0.0', IPSet([IP('20.0.0.0'), IP('40.0.0.0')])), r('20.0.0.0', '30.0.0.0'), ] } eq_(simplify_rules(rules), exp)
def process_zone_nets(zones, interface_ips): # figure out the IPSet of IPs for each security zone. This makes the # assumption (just like RFP) that each IP will communicate on exactly one # firewall interface. Each interface is in exactly one zone, so this means # that each IP is in exactly one zone. logger.info("calculating zone IP ranges") zone_nets = {} for zone in zones: net = IPSet() for itfc in zone.interfaces: try: net += interface_ips[itfc] except KeyError: # if the interface doesn't have any attached subnet, continue on # (this can happen for backup interfaces, for example) pass zone_nets[zone.name] = net return zone_nets
def make_rules(sgid, local): sg = security_groups[sgid] for dir, sgrules in [('in', sg.rules), ('out', sg.rules_egress)]: for sgrule in sgrules: if sgrule.app == '*/any': apps = all_apps | set(['@@other']) else: apps = [sgrule.app] for app in apps: for grant in sgrule.grants: if grant.cidr_ip: remote = IPSet([IP(grant.cidr_ip)]) else: remote = ips_by_sg.get(grant.group_id, None) if not remote: continue src, dst = (remote, local) if dir == 'in' else (local, remote) name = "{}/{}".format(sg.name, dir) # first make rules involving non-managed space, leaving # only managed-to-managed if dir == 'in': unmanaged_src = src & unmanaged_ip_space if unmanaged_src: rules.setdefault(app, []).append( Rule(src=unmanaged_src, dst=dst, app=app, name=name)) src = src & managed_ip_space else: unmanaged_dst = dst & unmanaged_ip_space if unmanaged_dst: rules.setdefault(app, []).append( Rule(src=src, dst=unmanaged_dst, app=app, name=name)) dst = dst & managed_ip_space if src and dst: to_intersect.setdefault(app, {}).setdefault( dir, []).append((src, dst, name))
def sourcesFor(self, dst, app, ignore_sources=None): dst = _ipset(dst) log.info("sourcesFor(%s, %r, ignore_sources=%s)" % (dst, app, ignore_sources)) rv = IPSet() for rule in self.rulesForApp(app): if rule.dst & dst: src = rule.src if ignore_sources: src = src - ignore_sources if src: log.info( "matched policy {t.cyan}{name}{t.normal}\n{t.yellow}{src}{t.normal} " "-> {t.magenta}{dst}{t.normal}".format(t=terminal, name=rule.name, src=src, dst=rule.dst & dst)) rv = rv + src return rv
def test_parse_addrbook_global(): f = FakeSRX() ab = f.add_addrbook('global') f.add_address(ab, 'host1', '9.0.9.1/32') f.add_address(ab, 'host2', '9.0.9.2/32') f.add_address_set(ab, 'hosts', 'host1', 'host2') elt = parse_xml(f.fake_show('configuration security address-book'), './/address-book') z = parse.AddressBook._from_xml(elt) eq_(z.attaches, []) eq_(sorted(z.addresses.keys()), sorted(['any', 'any-ipv4', 'any-ipv6', 'host1', 'host2', 'hosts'])) eq_(z.addresses['any'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv4'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv6'], IPSet([])) eq_(z.addresses['host1'], IPSet([IP('9.0.9.1')])) eq_(z.addresses['host2'], IPSet([IP('9.0.9.2')])) eq_(z.addresses['hosts'], IPSet([IP('9.0.9.1'), IP('9.0.9.2')]))
def test_parse_zone_no_sets(): f = FakeSRX() z = f.add_zone('untrust') f.add_address(z, 'trustedhost', '10.0.9.2/32') f.add_address(z, 'dmz', '10.1.0.0/16') f.add_address(z, 'shadow', '10.1.99.99/32') f.add_interface(z, 'reth1') elt = parse_xml(f.fake_show('configuration security zones'), './/security-zone') z = parse.Zone._from_xml(elt) eq_(z.interfaces, ['reth1']) eq_( sorted(z.addresses.keys()), sorted(['any', 'any-ipv4', 'any-ipv6', 'trustedhost', 'dmz', 'shadow'])) eq_(z.addresses['any'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv4'], IPSet([IP('0.0.0.0/0')])) eq_(z.addresses['any-ipv6'], IPSet([])) eq_(z.addresses['trustedhost'], IPSet([IP('10.0.9.2')])) eq_(z.addresses['dmz'], IPSet([IP('10.1.0.0/16')])) eq_(z.addresses['shadow'], IPSet([IP('10.1.99.99')]))
('web2', '172.16.1.2', ['admin_ssh', 'webapp']), ]), ('dynamic', '172.16.2.0/24', [ ('dynhost', '172.16.2.17', ['admin_ssh']), ('dynhost', '172.16.2.20', ['admin_ssh']), ]), ('perhost', '172.16.3.0/24', [ ('server4', '172.16.3.1', ['admin_ssh']), ]), ]), ]), ] RULES = { 'ssh': [ Rule(src=IPSet([IP('172.16.1.0/24'), IP('172.16.3.0/24')]) - IPSet([IP('172.16.1.1'), IP('172.16.1.2'), IP('172.16.3.1')]), dst=IPSet([IP('0.0.0.0/0')]) - IPSet([IP('172.16.1.0/24'), IP('172.16.2.0/24'), IP('172.16.3.0/24')]), app='ssh', name='unoccupied/out'), Rule(src=IPSet([IP('192.168.10.0/24')]), dst=IPSet([IP('172.16.1.1'), IP('172.16.1.2'), IP('172.16.2.0/24'), IP('172.16.3.1')]), app='ssh', name='admin_ssh/in'), ], 'web': [ Rule(src=IPSet([IP('172.16.1.0/24'), IP('172.16.3.0/24')]) - IPSet([IP('172.16.1.1'), IP('172.16.1.2'), IP('172.16.3.1')]), dst=IPSet([IP('0.0.0.0/0')]) - IPSet([IP('172.16.1.0/24'), IP('172.16.2.0/24'), IP('172.16.3.0/24')]) + IPSet([IP('172.16.1.1'), IP('172.16.1.2')]), app='web', name='unoccupied/out+webapp/in'), Rule(src=IPSet([IP('0.0.0.0/0')]) - IPSet([IP('172.16.1.0/24'), IP('172.16.2.0/24'), IP('172.16.3.0/24')]), dst=IPSet([IP('172.16.1.1'), IP('172.16.1.2')]), app='web', name='webapp/in'), ],
IP('1.1.1.1'), IP('1.1.1.3'), IP('1.1.2.0/24'), ]).isdisjoint(IPSet([ IP('1.1.2.2'), IP('1.1.1.4'), ]))) def test_ipset_and_single_ip(): eq_(ten24s & IPSet([IP('10.0.1.10')]), IPSet([IP('10.0.1.10')])) ten24s = IPSet([ IP('10.0.1.0/24'), IP('10.0.3.0/24'), IP('10.0.5.0/24'), IP('10.0.7.0/24'), ]) def test_ipset_and_ip_list(): eq_( ten24s & IPSet([ IP('10.0.0.99'), IP('10.0.1.10'), IP('10.0.3.40'), IP('11.1.1.99'), ]), IPSet([ IP('10.0.1.10'), IP('10.0.3.40'), ]))
def process_attached_networks(routes): # return a list of networks to which this firewall is directly connected, # so there is no "next hop". logger.info("calculating attached networks") networks = [IPSet([r.destination]) for r in routes if r.is_local] return networks
def test_ipset_isdisjoint(): a = IP('128.0.0.0/16') b = IP('129.0.0.0/16') c = IP('130.0.0.0/16') assert_true(IPSet([a, b]).isdisjoint(IPSet([c]))) assert_false(IPSet([a, b]).isdisjoint(IPSet([b, c]))) assert_false(IPSet([a]).isdisjoint(IPSet([a, b]))) assert_false(IPSet([a]).isdisjoint(IPSet([a, c]))) assert_false(IPSet([a, b]).isdisjoint(IPSet([b, c]))) assert_true( IPSet([IP('0.0.0.0/1')]).isdisjoint(IPSet([IP('128.0.0.0/1')]))) assert_false(IPSet([IP('0.0.0.0/1')]).isdisjoint(IPSet([IP('0.0.0.0/2')]))) assert_false(IPSet([IP('0.0.0.0/2')]).isdisjoint(IPSet([IP('0.0.0.0/1')]))) assert_false(IPSet([IP('0.0.0.0/2')]).isdisjoint(IPSet([IP('0.1.2.3')]))) assert_false(IPSet([IP('0.1.2.3')]).isdisjoint(IPSet([IP('0.0.0.0/2')]))) assert_true( IPSet([IP('1.1.1.1'), IP('1.1.1.3')]).isdisjoint(IPSet([IP('1.1.1.2'), IP('1.1.1.4')]))) assert_false( IPSet([ IP('1.1.1.1'), IP('1.1.1.3'), IP('1.1.2.0/24'), ]).isdisjoint(IPSet([ IP('1.1.2.2'), IP('1.1.1.4'), ])))
def test_ipset_and_single_ip(): eq_(ten24s & IPSet([IP('10.0.1.10')]), IPSet([IP('10.0.1.10')]))
def test_ipset_and_larger_net(): eq_(ten24s & IPSet([IP('10.0.0.0/22')]), IPSet([IP('10.0.1.0/24'), IP('10.0.3.0/24')]))
def ipset_from_jsonable(ipset, _cache={}): ipset = tuple(ipset) if ipset not in _cache: _cache[ipset] = rv = IPSet([IP(pfx) for pfx in ipset]) return rv return _cache[ipset]
def _ipset(ip): if isinstance(ip, basestring): ip = IP(ip) if isinstance(ip, IP): ip = IPSet([ip]) return ip
def test_run_no_globals(): fake_cfg = { 'firewall': 'fw', 'ssh_username': '******', 'ssh_password': '******', 'application-map': { 'junos-ssh': 'ssh', 'junos-ping': 'ping' }, } z = F.add_zone('untrust') F.add_address(z, 'host1', '9.0.9.1/32') F.add_address(z, 'host2', '9.0.9.2/32') F.add_address(z, 'puppet', '9.0.9.2/32') F.add_address_set(z, 'hosts', 'host1', 'host2') F.add_interface(z, 'reth0') z = F.add_zone('trust') F.add_address(z, 'trustedhost', '10.0.9.2/32') F.add_address(z, 'dmz', '10.1.0.0/16') F.add_address(z, 'shadow', '10.1.99.99/32') F.add_interface(z, 'reth1') F.add_policy(('trust', 'trust'), dict(sequence=1, name='ssh', src='any', dst='any', app='junos-ssh', action='permit')) F.add_policy(('trust', 'trust'), dict(sequence=10, name='deny', src='any', dst='any', app='any', action='deny')) F.add_policy(('trust', 'untrust'), dict(sequence=1, name='ssh', src='any', dst='any', app='junos-ssh', action='permit')) F.add_policy(('trust', 'untrust'), dict(sequence=2, name='puppet', src='any', dst='puppet', app='puppet', action='permit')) F.add_policy(('trust', 'untrust'), dict(sequence=10, name='deny', src='any', dst='any', app='any', action='deny')) F.add_policy(('untrust', 'trust'), dict(sequence=1, name='no-shadow-ping', src='any', dst='shadow', app='junos-ping', action='deny')) F.add_policy(('untrust', 'trust'), dict(sequence=2, name='ping', src='any', dst='dmz', app='junos-ping', action='permit')) F.add_policy(('untrust', 'trust'), dict(sequence=3, name='admin-puppet', src='puppet', dst='trustedhost', app='junos-ssh', action='permit')) F.add_policy(('untrust', 'trust'), dict(sequence=4, name='admin', src='any-ipv6', dst='trustedhost', app='junos-ssh', action='permit')) F.add_policy(('untrust', 'trust'), dict(sequence=10, name='deny', src='any', dst='any', app='any', action='deny')) F.add_policy(('untrust', 'untrust'), dict(sequence=10, name='deny', src='any', dst='any', app='any', action='deny')) rules = scripts.run(fake_cfg, {}) for r in rules.itervalues(): r.sort() exp = { 'ping': [ Rule(src=IPSet([IP('0.0.0.0/0')]) - IPSet([IP('10.0.0.0/8')]), dst=IPSet([IP('10.1.0.0/16')]) - IPSet([IP('10.1.99.99')]), app='ping', name='ping'), ], 'puppet': [ Rule(src=IPSet([IP('10.0.0.0/8')]), dst=IPSet([IP('9.0.9.2')]), app='puppet', name='puppet'), ], 'ssh': [ Rule(src=IPSet([IP('9.0.9.2')]), dst=IPSet([IP('10.0.9.2')]), app='ssh', name='admin-puppet'), Rule(src=IPSet([IP('10.0.0.0/8')]), dst=IPSet([IP('0.0.0.0/0')]), app='ssh', name='ssh'), ], } eq_(rules, exp)
def test_run_global_policies_and_addrbook(): fake_cfg = { 'firewall': 'fw', 'ssh_username': '******', 'ssh_password': '******', 'application-map': { 'junos-ssh': 'ssh', 'junos-ping': 'ping' }, } z = F.add_zone('untrust') F.add_address(z, 'untrust-host', '9.0.9.1/32') F.add_interface(z, 'reth0') z = F.add_zone('trust') F.add_address(z, 'trust-host', '10.9.1.1/32') F.add_interface(z, 'reth1') ab = F.add_addrbook('global') F.add_address(ab, 'global-host', '10.1.1.1/32') ab = F.add_addrbook('public') F.add_address(ab, 'public-host', '2.2.1.1/32') F.add_attach(ab, 'trust') F.add_attach(ab, 'untrust') F.add_policy(('trust', 'untrust'), dict(sequence=1, name='ssh', src='trust-host', dst='untrust-host', app='junos-ssh', action='permit')) F.add_policy(('untrust', 'trust'), dict(sequence=1, name='puppet', src='public-host', dst='global-host', app='puppet', action='permit')) F.add_policy( 'global', dict(sequence=10, name='ping', src='any', dst='any', app='junos-ping', action='permit')) F.add_policy( 'global', dict(sequence=11, name='deny', src='any', dst='any', app='any', action='deny')) rules = scripts.run(fake_cfg, {}) for r in rules.itervalues(): r.sort() exp = { 'ping': [ Rule(src=IPSet([IP('0.0.0.0/0')]), dst=IPSet([IP('0.0.0.0/0')]), app='ping', name='ping'), ], 'puppet': [ Rule(src=IPSet([IP('2.2.1.1')]), dst=IPSet([IP('10.1.1.1')]), app='puppet', name='puppet'), ], 'ssh': [ Rule(src=IPSet([IP('10.9.1.1')]), dst=IPSet([IP('9.0.9.1')]), app='ssh', name='ssh'), ], } eq_(rules, exp)
def get_rules(aws, app_map, regions, dynamic_subnets): if not regions: logger.info("Getting all regions") regions = aws.all_regions() logger.info("collecting subnets") subnets = [] managed_ip_space = IPSet([]) for id, subnet in aws.get_all_subnets(regions).iteritems(): name = subnet.tags.get('Name', id) dynamic = name in dynamic_subnets or id in dynamic_subnets cidr_block = IP(subnet.cidr_block) subnet = Subnet(cidr_block=cidr_block, name=name, dynamic=dynamic) subnets.append(subnet) managed_ip_space = managed_ip_space + IPSet([cidr_block]) unmanaged_ip_space = IPSet([IP('0.0.0.0/0')]) - managed_ip_space logger.info("collecting dynamic subnet IP ranges") dynamic_ipsets = {} per_host_subnet_ips = IPSet() for subnet in subnets: if subnet.dynamic: ipset = dynamic_ipsets.get(subnet.name, IPSet([])) ipset += IPSet([subnet.cidr_block]) dynamic_ipsets[subnet.name] = ipset else: per_host_subnet_ips += IPSet([subnet.cidr_block]) # sort by IP subnet, so we can use a binary search logger.info("sorting subnets by IP") subnets.sort(key=lambda s: s.cidr_block) _subnet_blocks = [s.cidr_block for s in subnets] def subnet_by_ip(ip): i = bisect.bisect_right(_subnet_blocks, ip) if i and ip in _subnet_blocks[i - 1]: return subnets[i - 1] logger.info("examining instances") sgids_by_dynamic_subnet = {} # {subnet name: set of SecurityGroupIds} sgids_by_instance = {} # {instance_name: [ip, set of SecurityGroupIds]} all_sgids = set() ips_by_sg = {} # {group id: IPSet} for id, instance in aws.get_all_instances(regions).iteritems(): if instance.state == 'terminated' or instance.state == 'shutting-down': continue # meh, who cares if not instance.vpc_id: continue # not in vpc; ignored if not instance.private_ip_address: logger.debug( "ignoring instance with no private_ip_address: %s, tags %r", instance.id, instance.tags) continue ip = IP(instance.private_ip_address) for g in instance.groups: ips_by_sg[g.id] = ips_by_sg.get(g.id, IPSet([])) + IPSet([IP(ip)]) subnet = subnet_by_ip(ip) if not subnet: logger.debug( "ignoring instance with no matching subnet for %s: %s, tags %r", ip, instance.id, instance.tags) continue if subnet.dynamic: sgset = sgids_by_dynamic_subnet.setdefault(subnet.name, set()) else: inst_name = instance.tags.get('Name', instance.id) if inst_name in sgids_by_instance: inst_name = inst_name + ' ({})'.format(instance.id) sgset = set() sgids_by_instance[inst_name] = [ip, sgset] new_sgids = set( SecurityGroupId(g.id, instance.region.name) for g in instance.groups) sgset.update(new_sgids) all_sgids.update(new_sgids) logger.info("accumulating security groups") all_apps = set(app_map.values()) security_groups = {} for sgid in all_sgids: sg = security_groups[sgid] = aws.get_security_group(sgid) assert sg, "no security group with id {}".format(sgid) # pre-process all of the rules' apps now for sgrule in itertools.chain(sg.rules, sg.rules_egress): proto = str(sgrule.ip_protocol) if proto == '-1': proto = 'any' if sgrule.from_port == sgrule.to_port: if str(sgrule.from_port) in ("None", "-1"): app = "*/{}".format(proto) else: app = '{}/{}'.format(sgrule.from_port, proto) else: app = '{}-{}/{}'.format(sgrule.from_port, sgrule.to_port, proto) app = app_map[app] sgrule.app = app all_apps.add(app) rules = {} to_intersect = {} def make_rules(sgid, local): sg = security_groups[sgid] for dir, sgrules in [('in', sg.rules), ('out', sg.rules_egress)]: for sgrule in sgrules: if sgrule.app == '*/any': apps = all_apps | set(['@@other']) else: apps = [sgrule.app] for app in apps: for grant in sgrule.grants: if grant.cidr_ip: remote = IPSet([IP(grant.cidr_ip)]) else: remote = ips_by_sg.get(grant.group_id, None) if not remote: continue src, dst = (remote, local) if dir == 'in' else (local, remote) name = "{}/{}".format(sg.name, dir) # first make rules involving non-managed space, leaving # only managed-to-managed if dir == 'in': unmanaged_src = src & unmanaged_ip_space if unmanaged_src: rules.setdefault(app, []).append( Rule(src=unmanaged_src, dst=dst, app=app, name=name)) src = src & managed_ip_space else: unmanaged_dst = dst & unmanaged_ip_space if unmanaged_dst: rules.setdefault(app, []).append( Rule(src=src, dst=unmanaged_dst, app=app, name=name)) dst = dst & managed_ip_space if src and dst: to_intersect.setdefault(app, {}).setdefault( dir, []).append((src, dst, name)) logger.info("writing rules for dynamic subnets") for subnet_name, sgids in sgids_by_dynamic_subnet.iteritems(): subnet = dynamic_ipsets[subnet_name] logger.debug(" subnet %s, %s", subnet_name, subnet) for sgid in sgids: make_rules(sgid, subnet) logger.info("writing rules for instances in per-host subnets") per_host_host_ips = IPSet() for inst_name, info in sgids_by_instance.iteritems(): ip, sgids = info logger.debug(" instance %s at %s", inst_name, ip) host_ip = IPSet([ip]) per_host_host_ips += host_ip for sgid in sgids: make_rules(sgid, host_ip) logger.info( "assuming unrestricted outbound access from unoccupied IPs in per-host subnets" ) unoccupied = per_host_subnet_ips - per_host_host_ips for app in all_apps: rules.setdefault(app, []).append( Rule(src=unoccupied, dst=unmanaged_ip_space, app=app, name='unoccupied/out')) to_intersect.setdefault(app, {}).setdefault('out', []).append( (unoccupied, managed_ip_space, 'unoccupied/out')) # traffic within the manage Ip space is governed both by outbound rules on # the source and inbound rules on the destination. logger.info("intersecting inbound and outbound rules") for app, dirs in to_intersect.iteritems(): in_rules = dirs.get('in', []) out_rules = dirs.get('out', []) logger.debug("..for %s", app) new_rules = [] for inr in in_rules: for outr in out_rules: src = inr[0] & outr[0] if not src: continue dst = inr[1] & outr[1] if not dst: continue new_rules.append( Rule(src=src, dst=dst, app=app, name=combine_names(inr[2], outr[2]))) # simplify now, within this app, to save space and time new_rules = simplify_rules({app: new_rules})[app] rules.setdefault(app, []).extend(new_rules) rules = simplify_rules(rules) return rules
def _parse_addrbook(addrbook): addresses = {} for addr in addrbook: name = addr.findtext('name') if addr.tag == 'address': ip = IPSet([IP(addr.findtext('ip-prefix'))]) else: # note: assumes address-sets follow addresses ip = IPSet() for setaddr in addr.findall('address'): setname = setaddr.findtext('name') ip += addresses[setname] addresses[name] = ip return addresses _default_addresses = { 'any': IPSet([IP('0.0.0.0/0')]), 'any-ipv4': IPSet([IP('0.0.0.0/0')]), # fwunit doesn't handle ipv6, so this is an empty set 'any-ipv6': IPSet([]), } class Zone(object): """Parse out zone names and the corresponding interfaces""" def __init__(self): #: list of interface names self.interfaces = [] #: name -> ipset, based on the zone's address book self.addresses = _default_addresses.copy()
def ipset(*ips): """Create an IPSet out of the given strings""" return IPSet(map(IP, ips))