fw.py 16.9 KB
Newer Older
1 2 3 4 5 6 7 8 9
from firewall import models
import django.conf

import re
from datetime import datetime, timedelta
from django.db.models import Q


settings = django.conf.settings.FIREWALL_SETTINGS
10 11


12 13 14 15 16 17 18 19 20
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
21 22
                          if (repl and rule.nat and rule.direction == '1')
                          else rule.dport)
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
        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

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

        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 '
51 52
                                      '--dport 25 -j LOG_ACC' %
                                      (ipaddr, rule.extra))
53 54 55 56 57 58 59
                        break
                    action = 'PUB_OUT'
                else:
                    action = 'LOG_ACC'
            else:
                action = 'LOG_DROP'

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

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

        dport_sport = self.dportsport(rule)

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

    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'

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

    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 '
120
                      '--log-prefix "[ipt][drop]"')
121 122 123
        self.iptables('-A LOG_DROP -j DROP')
        self.iptables('-N LOG_ACC')
        self.iptables('-A LOG_ACC -j LOG --log-level 7 '
124
                      '--log-prefix "[ipt][isok]"')
125 126 127 128
        self.iptables('-A LOG_ACC -j ACCEPT')

        self.iptables('-N PUB_OUT')

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

        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 '
141
                      '-j ACCEPT')
142 143 144 145

        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 '
146
                      '-j ACCEPT')
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

    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 '
172 173 174
                                     '--to-destination %s:%s' %
                                     (host.pub_ipv4, dport_sport, rule.extra,
                                      host.ipv4, rule.nat_dport))
175 176 177 178 179

        # 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 '
180 181
                                 '--to-destination %s' %
                                 (host.pub_ipv4, host.ipv4))
182
                self.iptablesnat('-A POSTROUTING -s %s -j SNAT '
183 184
                                 '--to-source %s' %
                                 (host.ipv4, host.pub_ipv4))
185 186 187 188 189 190

        # 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 '
191
                                     '--to-source %s' %
192
                                     (str(s_vlan.network4), d_vlan.name,
193
                                      s_vlan.snat_ip))
194 195 196 197 198 199 200

        self.iptablesnat('COMMIT')

    def ipt_filter(self):

        self.prerun()

Őry Máté committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
        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."""

217 218 219 220
        for f in self.fw:
            for rule in f.rules.all():
                self.fw2vlan(rule)

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

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

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

234 235 236 237 238 239 240 241
        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
242 243 244
    def ipt_filter_vlan_rules(self):
        """Enable communication between VLANs."""

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

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

252 253 254 255
        for s_vlan in self.vlans:
            for d_vlan in self.vlans:
                self.iptables('-A %s_%s -g LOG_DROP' % (s_vlan, d_vlan))

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

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

    def show(self):
274
        if self.proto == 6:
275 276 277
            return '\n'.join(self.RULES) + '\n'
        else:
            return ('\n'.join(self.RULES) + '\n' +
278 279
                    '\n'.join(self.RULES_NAT) + '\n')

280 281

def ipset():
282 283 284 285
    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)
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301


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])

302

303 304 305 306
def ipv4_to_arpa(ipv4, cname=False):
    m2 = re.search(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$', ipv4)
    if cname:
        return ('%s.dns1.%s.%s.%s.in-addr.arpa' %
307
                (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
308 309
    else:
        return ('%s.%s.%s.%s.in-addr.arpa' %
310 311
                (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))

312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336

def ipv6_to_arpa(ipv6):
    while len(ipv6.split(':')) < 8:
        ipv6 = ipv6.replace('::', ':::')
    octets = []
    for part in ipv6.split(':'):
        if not part:
            octets.extend([0, 0, 0, 0])
        else:
            # Pad hex part to 4 digits.
            part = '%04x' % int(part, 16)
            octets.insert(0, int(part[0], 16))
            octets.insert(0, int(part[1], 16))
            octets.insert(0, int(part[2], 16))
            octets.insert(0, int(part[3], 16))
    return '.'.join(['%1x' % x for x in octets]) + '.ip6.arpa'


# =fqdn:ip:ttl          A, PTR
# &fqdn:ip:x:ttl        NS
# ZfqdnSOA
# +fqdn:ip:ttl          A
# ^                     PTR
# C                     CNAME
# :                     generic
337
# 'fqdn:s:ttl           TXT
338

339
def generate_ptr_records():
340 341
    DNS = []

342 343 344 345 346 347 348
    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())
349

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
        # 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:
            DNS.append("^%s:%s:%s" % (ipv6_to_arpa(str(host.ipv6)),
                                      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 = []
370 371

    for r in models.Record.objects.all():
372 373 374
        if r.type == 'A':
            DNS.append("+%s:%s:%s" % (r.fqdn, r.address, r.ttl))
        elif r.type == 'AAAA':
375
            DNS.append(":%s:28:%s:%s" %
376 377 378 379 380 381 382
                       (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)
383
            DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" %
384 385 386 387
                       {'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))
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
        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()
409 410 411 412 413 414

    return DNS


def dhcp():
    regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
415
                       r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
416 417 418 419
    DHCP = []

# /tools/dhcp3/dhcpd.conf.generated

420
    for i_vlan in models.Vlan.objects.all():
421 422 423
        if(i_vlan.dhcp_pool):
            m = regex.search(i_vlan.dhcp_pool)
            if(m or i_vlan.dhcp_pool == "manual"):
424
                DHCP.append('''
425 426 427 428 429 430 431 432 433 434 435
    # %(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;
436
    }''' % {
437 438
                    'net': str(i_vlan.network4.network),
                    'netmask': str(i_vlan.network4.netmask),
439 440 441 442 443
                    'domain': i_vlan.domain,
                    'router': i_vlan.ipv4,
                    'ntp': i_vlan.ipv4,
                    'dnsserver': settings['rdns_ip'],
                    'extra': ("range %s" % i_vlan.dhcp_pool
444
                              if m else "deny unknown-clients"),
445
                    'interface': i_vlan.name,
446 447 448 449 450
                    'name': i_vlan.name,
                    'tftp': i_vlan.ipv4
                })

                for i_host in i_vlan.host_set.all():
451
                    DHCP.append('''
452 453 454 455 456 457 458 459 460 461
                    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
462 463 464 465


def vlan():
    obj = models.Vlan.objects.values('vid', 'name', 'network4', 'network6')
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
    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