fw.py 16.1 KB
Newer Older
1 2 3 4
from firewall import models
import django.conf

import re
Bach Dániel committed
5
import netaddr
6 7 8 9 10
from datetime import datetime, timedelta
from django.db.models import Q


settings = django.conf.settings.FIREWALL_SETTINGS
11 12


13 14 15 16 17 18 19 20 21
class Firewall:
    def dportsport(self, rule, repl=True):
        retval = ' '
        if rule.proto == 'tcp' or rule.proto == 'udp':
            retval = '-p %s ' % rule.proto
            if rule.sport:
                retval += ' --sport %s ' % rule.sport
            if rule.dport:
                retval += ' --dport %s ' % (rule.nat_dport
22 23
                          if (repl and rule.nat and rule.direction == '1')
                          else rule.dport)
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
        elif rule.proto == 'icmp':
            retval = '-p %s ' % rule.proto
        return retval

    def iptables(self, s):
        """Append rule to filter table."""
        self.RULES.append(s)

    def iptablesnat(self, s):
        """Append rule to NAT table."""
        self.RULES_NAT.append(s)

    def host2vlan(self, host, rule):
        if not rule.foreign_network:
            return

40
        if self.proto == 6 and host.ipv6:
41
            ipaddr = str(host.ipv6) + '/112'
42
        else:
43
            ipaddr = str(host.ipv4)
44 45 46 47 48 49 50 51

        dport_sport = self.dportsport(rule)

        for vlan in rule.foreign_network.vlans.all():
            if rule.accept:
                if rule.direction == '0' and vlan.name == 'PUB':
                    if rule.dport == 25:
                        self.iptables('-A PUB_OUT -s %s %s -p tcp '
52 53
                                      '--dport 25 -j LOG_ACC' %
                                      (ipaddr, rule.extra))
54 55 56 57 58 59 60
                        break
                    action = 'PUB_OUT'
                else:
                    action = 'LOG_ACC'
            else:
                action = 'LOG_DROP'

61 62
            if rule.direction == '1':  # going TO host
                self.iptables('-A %s_%s -d %s %s %s -g %s' %
63
                              (vlan.name, host.vlan.name, ipaddr, dport_sport,
64
                               rule.extra, action))
65
            else:
66
                self.iptables('-A %s_%s -s %s %s %s -g %s' %
67
                              (host.vlan.name, vlan.name, ipaddr, dport_sport,
68
                               rule.extra, action))
69 70 71 72 73 74 75 76

    def fw2vlan(self, rule):
        if not rule.foreign_network:
            return

        dport_sport = self.dportsport(rule)

        for vlan in rule.foreign_network.vlans.all():
77
            if rule.direction == '1':  # going TO host
78
                self.iptables('-A INPUT -i %s %s %s -g %s' %
79
                              (vlan.name, dport_sport, rule.extra,
80
                               'LOG_ACC' if rule.accept else 'LOG_DROP'))
81 82
            else:
                self.iptables('-A OUTPUT -o %s %s %s -g %s' %
83
                              (vlan.name, dport_sport, rule.extra,
84
                               'LOG_ACC' if rule.accept else 'LOG_DROP'))
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

    def vlan2vlan(self, l_vlan, rule):
        if not rule.foreign_network:
            return

        dport_sport = self.dportsport(rule)

        for vlan in rule.foreign_network.vlans.all():
            if rule.accept:
                if rule.direction == '0' and vlan.name == 'PUB':
                    action = 'PUB_OUT'
                else:
                    action = 'LOG_ACC'
            else:
                action = 'LOG_DROP'

101 102
            if rule.direction == '1':  # going TO host
                self.iptables('-A %s_%s %s %s -g %s' %
103 104
                              (vlan.name, l_vlan.name, dport_sport,
                               rule.extra, action))
105
            else:
106
                self.iptables('-A %s_%s %s %s -g %s' % (l_vlan.name, vlan.name,
107 108
                                                        dport_sport,
                                                        rule.extra, action))
109 110 111 112 113 114 115 116 117 118 119 120 121

    def prerun(self):
        self.iptables('*filter')
        self.iptables(':INPUT DROP [88:6448]')
        self.iptables(':FORWARD DROP [0:0]')
        self.iptables(':OUTPUT DROP [50:6936]')

        # initialize logging
        self.iptables('-N LOG_DROP')
        # windows port scan are silently dropped
        self.iptables('-A LOG_DROP -p tcp --dport 445 -j DROP')
        self.iptables('-A LOG_DROP -p udp --dport 137 -j DROP')
        self.iptables('-A LOG_DROP -j LOG --log-level 7 '
122
                      '--log-prefix "[ipt][drop]"')
123 124 125
        self.iptables('-A LOG_DROP -j DROP')
        self.iptables('-N LOG_ACC')
        self.iptables('-A LOG_ACC -j LOG --log-level 7 '
126
                      '--log-prefix "[ipt][isok]"')
127 128 129 130
        self.iptables('-A LOG_ACC -j ACCEPT')

        self.iptables('-N PUB_OUT')

131 132
        self.iptables('-A FORWARD -m set --match-set blacklist src,dst '
                      '-j DROP')
133 134
        self.iptables('-A FORWARD -m state --state INVALID -g LOG_DROP')
        self.iptables('-A FORWARD -m state --state ESTABLISHED,RELATED '
135
                      '-j ACCEPT')
136
        self.iptables('-A FORWARD -p icmp --icmp-type echo-request '
137
                      '-g LOG_ACC')
138 139 140 141 142

        self.iptables('-A INPUT -m set --match-set blacklist src -j DROP')
        self.iptables('-A INPUT -m state --state INVALID -g LOG_DROP')
        self.iptables('-A INPUT -i lo -j ACCEPT')
        self.iptables('-A INPUT -m state --state ESTABLISHED,RELATED '
143
                      '-j ACCEPT')
144 145 146 147

        self.iptables('-A OUTPUT -m state --state INVALID -g LOG_DROP')
        self.iptables('-A OUTPUT -o lo -j ACCEPT')
        self.iptables('-A OUTPUT -m state --state ESTABLISHED,RELATED '
148
                      '-j ACCEPT')
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

    def postrun(self):
        self.iptables('-A PUB_OUT -p tcp --dport 25 -j LOG_DROP')
        self.iptables('-A PUB_OUT -p tcp --dport 445 -j LOG_DROP')
        self.iptables('-A PUB_OUT -p udp --dport 445 -j LOG_DROP')

        self.iptables('-A PUB_OUT -g LOG_ACC')
        self.iptables('-A FORWARD -g LOG_DROP')
        self.iptables('-A INPUT -g LOG_DROP')
        self.iptables('-A OUTPUT -g LOG_DROP')
        self.iptables('COMMIT')

    def ipt_nat(self):
        self.iptablesnat('*nat')
        self.iptablesnat(':PREROUTING ACCEPT [0:0]')
        self.iptablesnat(':INPUT ACCEPT [0:0]')
        self.iptablesnat(':OUTPUT ACCEPT [1:708]')
        self.iptablesnat(':POSTROUTING ACCEPT [1:708]')

        # portforward
        for host in self.hosts.exclude(pub_ipv4=None):
            for rule in host.rules.filter(nat=True, direction='1'):
                dport_sport = self.dportsport(rule, False)
                if host.vlan.snat_ip:
                    self.iptablesnat('-A PREROUTING -d %s %s %s -j DNAT '
174 175 176
                                     '--to-destination %s:%s' %
                                     (host.pub_ipv4, dport_sport, rule.extra,
                                      host.ipv4, rule.nat_dport))
177 178 179 180 181

        # rules for machines with dedicated public IP
        for host in self.hosts.exclude(shared_ip=True):
            if host.pub_ipv4:
                self.iptablesnat('-A PREROUTING -d %s -j DNAT '
182 183
                                 '--to-destination %s' %
                                 (host.pub_ipv4, host.ipv4))
184
                self.iptablesnat('-A POSTROUTING -s %s -j SNAT '
185 186
                                 '--to-source %s' %
                                 (host.ipv4, host.pub_ipv4))
187 188 189 190 191 192

        # default NAT rules for VLANs
        for s_vlan in self.vlans:
            if s_vlan.snat_ip:
                for d_vlan in s_vlan.snat_to.all():
                    self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT '
193
                                     '--to-source %s' %
194
                                     (str(s_vlan.network4), d_vlan.name,
195
                                      s_vlan.snat_ip))
196 197 198 199 200 201 202

        self.iptablesnat('COMMIT')

    def ipt_filter(self):

        self.prerun()

Őry Máté committed
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
        self.ipt_filter_firewall()
        self.ipt_filter_zones()
        self.ipt_filter_host_rules()
        self.ipt_filter_vlan_rules()
        self.ipt_filter_vlan_drop()

        self.postrun()

        if self.proto == 6:  # remove ipv4-specific rules
            ipv4_re = re.compile('([0-9]{1,3}\.){3}[0-9]{1,3}')
            self.RULES = [x for x in self.RULES if not ipv4_re.search(x)]
            self.RULES = [x.replace('icmp', 'icmpv6') for x in self.RULES]

    def ipt_filter_firewall(self):
        """Build firewall's own rules."""

219 220 221 222
        for f in self.fw:
            for rule in f.rules.all():
                self.fw2vlan(rule)

Őry Máté committed
223 224 225
    def ipt_filter_zones(self):
        """Jumping to chains between zones."""

226 227
        for s_vlan in self.vlans:
            for d_vlan in self.vlans:
228
                self.iptables('-N %s_%s' % (s_vlan.name, d_vlan.name))
229
                self.iptables('-A FORWARD -i %s -o %s -g %s_%s' %
230 231
                              (s_vlan.name, d_vlan.name, s_vlan.name,
                               d_vlan.name))
232

Őry Máté committed
233 234 235
    def ipt_filter_host_rules(self):
        """Build hosts' rules."""

236 237 238 239 240 241 242 243
        for i_vlan in self.vlans:
            for i_host in i_vlan.host_set.all():
                for group in i_host.groups.all():
                    for rule in group.rules.all():
                        self.host2vlan(i_host, rule)
                for rule in i_host.rules.all():
                    self.host2vlan(i_host, rule)

Őry Máté committed
244 245 246
    def ipt_filter_vlan_rules(self):
        """Enable communication between VLANs."""

247 248 249 250
        for s_vlan in self.vlans:
            for rule in s_vlan.rules.all():
                self.vlan2vlan(s_vlan, rule)

Őry Máté committed
251 252 253
    def ipt_filter_vlan_drop(self):
        """Close intra-VLAN chains."""

254 255
        for s_vlan in self.vlans:
            for d_vlan in self.vlans:
256 257
                self.iptables('-A %s_%s -g LOG_DROP' % (s_vlan.name,
                                                        d_vlan.name))
258

259
    def __init__(self, proto=4):
260 261
        self.RULES = []
        self.RULES_NAT = []
262
        self.proto = proto
263 264 265 266
        self.vlans = models.Vlan.objects.all()
        self.hosts = models.Host.objects.all()
        self.fw = models.Firewall.objects.all()
        self.ipt_filter()
267
        if self.proto != 6:
268 269 270
            self.ipt_nat()

    def get(self):
271
        if self.proto == 6:
272
            return {'filter': self.RULES, }
273
        else:
274
            return {'filter': self.RULES, 'nat': self.RULES_NAT}
275 276

    def show(self):
277
        if self.proto == 6:
278 279 280
            return '\n'.join(self.RULES) + '\n'
        else:
            return ('\n'.join(self.RULES) + '\n' +
281 282
                    '\n'.join(self.RULES_NAT) + '\n')

283 284

def ipset():
285 286 287 288
    week = datetime.now() - timedelta(days=2)
    filter_ban = (Q(type='tempban', modified_at__gte=week) |
                  Q(type='permban')).values('ipv4', 'reason')
    return models.Blacklist.objects.filter(filter_ban)
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304


def ipv6_to_octal(ipv6):
    while len(ipv6.split(':')) < 8:
        ipv6 = ipv6.replace('::', ':::')
    octets = []
    for part in ipv6.split(':'):
        if not part:
            octets.extend([0, 0])
        else:
            # Pad hex part to 4 digits.
            part = '%04x' % int(part, 16)
            octets.append(int(part[:2], 16))
            octets.append(int(part[2:], 16))
    return '\\' + '\\'.join(['%03o' % x for x in octets])

305

306 307 308 309 310 311 312
# =fqdn:ip:ttl          A, PTR
# &fqdn:ip:x:ttl        NS
# ZfqdnSOA
# +fqdn:ip:ttl          A
# ^                     PTR
# C                     CNAME
# :                     generic
313
# 'fqdn:s:ttl           TXT
314

315
def generate_ptr_records():
316 317
    DNS = []

318 319 320 321 322 323 324
    for host in models.Host.objects.order_by('vlan').all():
        rev = host.vlan.reverse_domain
        ipv4 = str(host.pub_ipv4 if host.pub_ipv4 and
                   not host.shared_ip else host.ipv4)
        i = ipv4.split('.', 4)
        reverse = (host.reverse if host.reverse and
                   len(host.reverse) else host.get_fqdn())
325

326 327 328 329 330 331 332 333 334
        # ipv4
        if host.ipv4:
            DNS.append("^%s:%s:%s" % (
                (rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
                        'd': int(i[3])}),
                reverse, models.settings['dns_ttl']))

        # ipv6
        if host.ipv6:
Bach Dániel committed
335
            DNS.append("^%s:%s:%s" % (host.ipv6.reverse_dns,
336 337 338 339 340 341 342 343 344 345
                                      reverse, models.settings['dns_ttl']))
        return DNS


def txt_to_octal(txt):
    return '\\' + '\\'.join(['%03o' % ord(x) for x in txt])


def generate_records():
    DNS = []
346 347

    for r in models.Record.objects.all():
348 349 350
        if r.type == 'A':
            DNS.append("+%s:%s:%s" % (r.fqdn, r.address, r.ttl))
        elif r.type == 'AAAA':
351
            DNS.append(":%s:28:%s:%s" %
352 353 354 355 356 357 358
                       (r.fqdn, ipv6_to_octal(r.address), r.ttl))
        elif r.type == 'NS':
            DNS.append("&%s::%s:%s" % (r.fqdn, r.address, r.ttl))
        elif r.type == 'CNAME':
            DNS.append("C%s:%s:%s" % (r.fqdn, r.address, r.ttl))
        elif r.type == 'MX':
            mx = r.address.split(':', 2)
359
            DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" %
360 361 362 363
                       {'fqdn': r.fqdn, 'mx': mx[1], 'dist': mx[0],
                        'ttl': r.ttl})
        elif r.type == 'PTR':
            DNS.append("^%s:%s:%s" % (r.fqdn, r.address, r.ttl))
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
        elif r.type == 'TXT':
            DNS.append("'%s:%s:%s" % (r.fqdn,
                                      txt_to_octal(r.address), r.ttl))

    return DNS


def dns():
    DNS = []

    # host PTR record
    DNS += generate_ptr_records()

    # domain SOA record
    for domain in models.Domain.objects.all():
        DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" %
                   (domain.name, settings['dns_hostname'],
                    models.settings['dns_ttl']))

    # records
    DNS += generate_records()
385 386 387 388 389 390

    return DNS


def dhcp():
    regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
391
                       r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
392 393 394 395
    DHCP = []

# /tools/dhcp3/dhcpd.conf.generated

396
    for i_vlan in models.Vlan.objects.all():
397 398 399
        if(i_vlan.dhcp_pool):
            m = regex.search(i_vlan.dhcp_pool)
            if(m or i_vlan.dhcp_pool == "manual"):
400
                DHCP.append('''
401 402 403 404 405 406 407 408 409 410 411
    # %(name)s - %(interface)s
    subnet %(net)s netmask %(netmask)s {
      %(extra)s;
      option domain-name "%(domain)s";
      option routers %(router)s;
      option domain-name-servers %(dnsserver)s;
      option ntp-servers %(ntp)s;
      next-server %(tftp)s;
      authoritative;
      filename \"pxelinux.0\";
      allow bootp; allow booting;
412
    }''' % {
413 414
                    'net': str(i_vlan.network4.network),
                    'netmask': str(i_vlan.network4.netmask),
415 416 417 418 419
                    'domain': i_vlan.domain,
                    'router': i_vlan.ipv4,
                    'ntp': i_vlan.ipv4,
                    'dnsserver': settings['rdns_ip'],
                    'extra': ("range %s" % i_vlan.dhcp_pool
420
                              if m else "deny unknown-clients"),
421
                    'interface': i_vlan.name,
422 423 424 425 426
                    'name': i_vlan.name,
                    'tftp': i_vlan.ipv4
                })

                for i_host in i_vlan.host_set.all():
427
                    DHCP.append('''
428 429 430 431 432 433 434 435 436 437
                    host %(hostname)s {
                      hardware ethernet %(mac)s;
                      fixed-address %(ipv4)s;
                    }''' % {
                        'hostname': i_host.hostname,
                        'mac': i_host.mac,
                        'ipv4': i_host.ipv4,
                    })

    return DHCP
438 439 440 441


def vlan():
    obj = models.Vlan.objects.values('vid', 'name', 'network4', 'network6')
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    retval = {x['name']: {'tag': x['vid'],
                          'type': 'internal',
                          'interfaces': [x['name']],
                          'addresses': [str(x['network4']),
                                        str(x['network6'])]}
              for x in obj}
    for p in models.SwitchPort.objects.all():
        eth_count = p.ethernet_devices.count()
        if eth_count > 1:
            name = 'bond%d' % p.id
        elif eth_count == 1:
            name = p.ethernet_devices.get().name
        else:  # 0
            continue
        tag = p.untagged_vlan.vid
        retval[name] = {'tag': tag}
        if p.tagged_vlans is not None:
            trunk = list(p.tagged_vlans.vlans.values_list('vid', flat=True))
            retval[name]['trunks'] = sorted(trunk)
        retval[name]['interfaces'] = list(
            p.ethernet_devices.values_list('name', flat=True))
    return retval