Commit db086eaf authored by Benjamin Graillot's avatar Benjamin Graillot
Browse files

Initial commit

parents
{
"DEFAULT_SERVICES": {
"srv": [["in", "ssh"]]
},
"NAT": {
"srv-nat": "185.230.79.64/26",
"adh-nat": "185.230.77.0/24"
}
}
#!/bin/env python3
import argparse
import ipaddress
import json
import os
import subprocess
import jinja2
import ldap
path = os.path.dirname(os.path.abspath(__file__))
def nat_map(private_subnet, public_subnet):
nat_subnets = [ nat_subnet for nat_subnet in private_subnet.subnets(prefixlen_diff=public_subnet.num_addresses.bit_length()-1) ]
return list(zip(nat_subnets, public_subnet))
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate firewall from LDAP",
)
parser.add_argument("-e", "--export", help="Exporte le contenu des pare-feu", action="store_true")
args = parser.parse_args()
with open(os.path.join(path, "firewall.json")) as config_file:
config = json.load(config_file)
base = ldap.initialize('ldaps://172.16.1.1/')
base.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
base.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
hosts_query_id = base.search("ou=hosts,dc=crans,dc=org", ldap.SCOPE_SUBTREE, "objectClass=ipHost")
hosts_query = base.result(hosts_query_id)
services_query_id = base.search("ou=services,dc=crans,dc=org", ldap.SCOPE_SUBTREE, "objectClass=ipService")
services_query = base.result(services_query_id)
networks_query_id = base.search("ou=networks,dc=crans,dc=org", ldap.SCOPE_SUBTREE, "objectClass=ipNetwork")
networks_query = base.result(networks_query_id)
networks = {}
for dn, entry in networks_query[1]:
networks[entry['cn'][0].decode('utf-8')] = ipaddress.ip_network(entry['ipNetworkNumber'][0].decode('utf-8') + '/' + entry['ipNetmaskNumber'][0].decode('utf-8'))
services = {}
for dn, entry in services_query[1]:
if 'description' in entry:
ports = (int(entry['ipServicePort'][0]), int(entry['description'][0]))
else:
port = int(entry['ipServicePort'][0])
ports = (port, port)
protocols = { protocol.decode('utf-8') for protocol in entry['ipServiceProtocol'] }
services[entry['cn'][0].decode('utf-8')] = (ports, protocols)
ports_openings = []
for dn, entry in hosts_query[1]:
domain = dn.split(',', 1)[0]
domain = domain.split('.')
if len(domain) == 3:
subnet = 'srv'
elif len(domain) == 4:
subnet = domain[1]
if 'description' not in entry:
if subnet in config['DEFAULT_SERVICES']:
opening = config['DEFAULT_SERVICES'][subnet]
else:
continue
else:
opening = entry['description'][0].decode('utf-8').strip()
opening = opening.split(',')
opening = [ tuple(open.split(':')) for open in opening ]
ip_addresses = [ipaddress.ip_address(ip.decode('utf-8')) for ip in entry['ipHostNumber']]
opening_in = { open[1] for open in opening if open[0] == 'in' }
opening_out = { open[1] for open in opening if open[0] == 'out' }
for ip in ip_addresses:
tcp_ports_in = [ services[service][0] for service in opening_in if 'tcp' in services[service][1] ]
tcp_ports_in = ','.join( '{}-{}'.format(port[0], port[1]) if port[0] != port[1] else str(port[0]) for port in tcp_ports_in )
ports_openings.append('ip{ip_version} daddr {ip} tcp dport {{ {ports} }} accept'.format(ip_version='' if ip.version == 4 else '6', ip=ip, ports=tcp_ports_in))
udp_ports_in = [ services[service][0] for service in opening_in if 'udp' in services[service][1] ]
udp_ports_in = ','.join( '{}-{}'.format(port[0], port[1]) if port[0] != port[1] else str(port[0]) for port in udp_ports_in )
ports_openings.append('ip{ip_version} daddr {ip} udp dport {{ {ports} }} accept'.format(ip_version='' if ip.version == 4 else '6', ip=ip, ports=udp_ports_in))
tcp_ports_out = [ services[service][0] for service in opening_out if 'tcp' in services[service][1] ]
tcp_ports_out = ','.join( '{}-{}'.format(port[0], port[1]) if port[0] != port[1] else str(port[0]) for port in tcp_ports_out )
ports_openings.append('ip{ip_version} saddr {ip} tcp dport {{ {ports} }} accept'.format(ip_version='' if ip.version == 4 else '6', ip=ip, ports=tcp_ports_out))
udp_ports_out = [ services[service][0] for service in opening_out if 'udp' in services[service][1] ]
udp_ports_out = ','.join( '{}-{}'.format(port[0], port[1]) if port[0] != port[1] else str(port[0]) for port in udp_ports_out )
ports_openings.append('ip{ip_version} saddr {ip} udp dport {{ {ports} }} accept'.format(ip_version='' if ip.version == 4 else '6', ip=ip, ports=udp_ports_out))
with open(os.path.join(path, 'templates', 'nftables.conf.j2')) as firewall_template:
template = jinja2.Template(firewall_template.read())
nat = { subnet: nat_map(networks[subnet], ipaddress.ip_network(config['NAT'][subnet])) for subnet in config['NAT'] }
if args.export:
print(template.render(nat=nat, ports_openings=ports_openings))
else:
with open('/etc/nftables.conf') as nftables:
nftables.write(template.render(nat=nat, ports_openings=ports_openings))
subprocess.run(['systemctl', 'reload', 'nftables'])
#!/usr/sbin/nft -f
flush ruleset
table ip nat {
{% for nat_subnet in nat %}
set {{ nat_subnet }} {
type ipv4_addr
flags interval
elements = { {% for private_subnet, public_address in nat[nat_subnet] %}{{ private_subnet }},{% endfor %} }
}
map {{ nat_subnet }}-pub {
type ipv4_addr : ipv4_addr
flags interval
elements = { {% for private_subnet, public_address in nat[nat_subnet] %}{{ private_subnet }}:{{ public_address }},{% endfor %} }
}
{% endfor %}
chain prerouting {
type nat hook prerouting priority 0; policy accept;
log prefix "LOG_ALL "
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
{% for nat_subnet in nat %}
ip saddr @{{ nat_subnet }} ip protocol icmp snat to ip saddr map @{{ nat_subnet }}-pub
ip saddr @{{ nat_subnet }} ip protocol udp snat to ip saddr map @{{ nat_subnet }}-pub
ip saddr @{{ nat_subnet }} ip protocol tcp snat to ip saddr map @{{ nat_subnet }}-pub
{% endfor %}
}
}
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
iifname "lo" accept
ct state established,related accept
tcp dport ssh accept
ip protocol icmp accept
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering } accept
ip saddr 172.16.0.0/16 accept
ip saddr 100.64.0.0/16 accept
ip6 saddr fd00::/4 accept
ip6 saddr 2a0c:700::/32 accept
reject
}
chain forward {
type filter hook forward priority 0; policy accept;
ct state new log prefix "LOG_ALL "
ct state established,related accept
ip protocol icmp accept
ip protocol ipv6-icmp accept
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering } accept
ip6 saddr 2a0c:700::/32 accept
# Ouvetures de ports des serveurs du CRANS
{% for rule in ports_openings %} {{ rule }}
{% endfor %}
reject
}
chain output {
type filter hook output priority 0; policy accept
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment