test_firewall.py 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE.  If not, see <http://www.gnu.org/licenses/>.

Bach Dániel committed
18
from mock import patch
Bach Dániel committed
19
from netaddr import IPSet, AddrFormatError
20

Bach Dániel committed
21
import django.conf
22
from django.contrib.auth.models import User
Bach Dániel committed
23 24 25 26 27 28 29
from django.forms import ValidationError
from django.test import TestCase

from common.tests.celery_mock import MockCeleryMixin
from firewall.admin import HostAdmin
from firewall.fw import dns, ipv6_to_octal
from firewall.iptables import IptRule, IptChain, InvalidRuleExcepion
Bach Dániel committed
30 31
from firewall.models import (Vlan, Domain, Record, Host, VlanGroup, Group,
                             Rule, Firewall)
Bach Dániel committed
32
from firewall.tasks.local_tasks import reloadtask_worker, reloadtask
Bach Dániel committed
33 34

settings = django.conf.settings.FIREWALL_SETTINGS
35

36

37 38 39 40
class MockInstance:
    def __init__(self, groups):
        self.groups = MockGroups(groups)

41

42 43 44 45
class MockGroup:
    def __init__(self, name):
        self.name = name

46

47 48 49 50 51 52 53
class MockGroups:
    def __init__(self, groups):
        self.groups = groups

    def all(self):
        return self.groups

54

55 56 57 58 59 60 61 62 63 64 65 66 67
class HostAdminTestCase(TestCase):
    def test_no_groups(self):
        instance = MockInstance([])
        l = HostAdmin.list_groups(instance)
        self.assertEqual(l, "")

    def test_sigle_group(self):
        instance = MockInstance([MockGroup("alma")])
        l = HostAdmin.list_groups(instance)
        self.assertEqual(l, "alma")

    def test_multiple_groups(self):
        instance = MockInstance([MockGroup("alma"),
68
                                 MockGroup("korte"), MockGroup("szilva")])
69 70
        l = HostAdmin.list_groups(instance)
        self.assertEqual(l, "alma, korte, szilva")
71 72


Bach Dániel committed
73
class GetNewAddressTestCase(MockCeleryMixin, TestCase):
74 75 76 77 78 79
    def setUp(self):
        self.u1 = User.objects.create(username='user1')
        self.u1.save()
        d = Domain(name='example.org', owner=self.u1)
        d.save()
        # /29 = .1-.6 =  6 hosts/subnet + broadcast + network id
80
        self.vlan = Vlan(vid=1, name='test', network4='10.0.0.1/29',
81 82
                         network6='2001:738:2001:4031::/80', domain=d,
                         owner=self.u1)
83
        self.vlan.full_clean()
84 85
        self.vlan.save()
        self.vlan.host_set.all().delete()
86
        for i in range(3, 6):
87 88 89 90
            Host(hostname='h-%d' % i, mac='01:02:03:04:05:%02d' % i,
                 ipv4='10.0.0.%d' % i, vlan=self.vlan,
                 owner=self.u1).save()

Őry Máté committed
91 92 93
    def tearDown(self):
        self.vlan.delete()

94 95 96 97 98 99 100 101 102 103 104
    def test_new_addr_w_empty_vlan(self):
        self.vlan.host_set.all().delete()
        self.vlan.get_new_address()

    def test_all_addr_in_use(self):
        for i in (2, 6):
            Host(hostname='h-%d' % i, mac='01:02:03:04:05:%02d' % i,
                 ipv4='10.0.0.%d' % i, vlan=self.vlan,
                 owner=self.u1).save()
        self.assertRaises(ValidationError, self.vlan.get_new_address)

105 106 107 108 109 110 111 112 113
    def test_all_addr_in_use2(self):
        Host(hostname='h-xd', mac='01:02:03:04:05:06',
             ipv4='10.0.0.6', vlan=self.vlan,
             owner=self.u1).save()
        Host(hostname='h-arni', mac='01:02:03:04:05:02',
             ipv4='100.0.0.1', vlan=self.vlan, external_ipv4='10.0.0.2',
             owner=self.u1).save()
        self.assertRaises(ValidationError, self.vlan.get_new_address)

114 115 116
    def test_new_addr(self):
        used_v4 = IPSet(self.vlan.host_set.values_list('ipv4', flat=True))
        assert self.vlan.get_new_address()['ipv4'] not in used_v4
117 118


Bach Dániel committed
119
class HostGetHostnameTestCase(MockCeleryMixin, TestCase):
120 121 122 123 124 125
    def setUp(self):
        self.u1 = User.objects.create(username='user1')
        self.u1.save()
        self.d = Domain(name='example.org', owner=self.u1)
        self.d.save()
        Record.objects.all().delete()
126
        self.vlan = Vlan(vid=1, name='test', network4='10.0.0.1/24',
127 128 129 130 131 132
                         network6='2001:738:2001:4031::/80', domain=self.d,
                         owner=self.u1, network_type='portforward',
                         snat_ip='10.1.1.1')
        self.vlan.save()
        self.h = Host(hostname='h', mac='01:02:03:04:05:00', ipv4='10.0.0.1',
                      vlan=self.vlan, owner=self.u1, shared_ip=True,
133
                      external_ipv4=self.vlan.snat_ip)
134 135 136 137
        self.h.save()

    def test_issue_93_wo_record(self):
        self.assertEqual(self.h.get_hostname(proto='ipv4', public=True),
138
                         unicode(self.h.external_ipv4))
139 140 141 142 143 144 145

    def test_issue_93_w_record(self):
        self.r = Record(name='vm', type='A', domain=self.d, owner=self.u1,
                        address=self.vlan.snat_ip)
        self.r.save()
        self.assertEqual(self.h.get_hostname(proto='ipv4', public=True),
                         self.r.fqdn)
Bach Dániel committed
146 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 172 173 174 175 176 177 178


class IptablesTestCase(TestCase):
    def setUp(self):
        self.r = [IptRule(priority=4, action='ACCEPT',
                          src=('127.0.0.4', None)),
                  IptRule(priority=4, action='ACCEPT',
                          src=('127.0.0.4', None)),
                  IptRule(priority=2, action='ACCEPT',
                          dst=('127.0.0.2', None),
                          extra='-p icmp'),
                  IptRule(priority=6, action='ACCEPT',
                          dst=('127.0.0.6', None),
                          proto='tcp', dport=80),
                  IptRule(priority=1, action='ACCEPT',
                          dst=('127.0.0.1', None),
                          proto='udp', dport=53),
                  IptRule(priority=5, action='ACCEPT',
                          dst=('127.0.0.5', None),
                          proto='tcp', dport=443),
                  IptRule(priority=2, action='ACCEPT',
                          dst=('127.0.0.2', None),
                          proto='icmp'),
                  IptRule(priority=6, action='ACCEPT',
                          dst=('127.0.0.6', None),
                          proto='tcp', dport='1337')]

    def test_chain_add(self):
        ch = IptChain(name='test')
        ch.add(*self.r)
        self.assertEqual(len(ch), len(self.r) - 1)

    def test_rule_compile_ok(self):
Bach Dániel committed
179
        assert unicode(self.r[5])
Bach Dániel committed
180 181 182 183 184
        self.assertEqual(self.r[5].compile(),
                         '-d 127.0.0.5 -p tcp --dport 443 -g ACCEPT')

    def test_rule_compile_fail(self):
        self.assertRaises(InvalidRuleExcepion,
Bach Dániel committed
185 186
                          IptRule, **{'proto': 'test'})
        self.assertRaises(InvalidRuleExcepion,
Bach Dániel committed
187 188 189 190 191 192 193 194
                          IptRule, **{'priority': 5, 'action': 'ACCEPT',
                                      'dst': '127.0.0.5',
                                      'proto': 'icmp', 'dport': 443})

    def test_chain_compile(self):
        ch = IptChain(name='test')
        ch.add(*self.r)
        compiled = ch.compile()
Bach Dániel committed
195 196
        compiled_v6 = ch.compile_v6()
        assert unicode(ch)
Bach Dániel committed
197
        self.assertEqual(len(compiled.splitlines()), len(ch))
Bach Dániel committed
198
        self.assertEqual(len(compiled_v6.splitlines()), 0)
Bach Dániel committed
199 200


Bach Dániel committed
201
class ReloadTestCase(MockCeleryMixin, TestCase):
Bach Dániel committed
202 203 204
    def setUp(self):
        self.u1 = User.objects.create(username='user1')
        self.u1.save()
Bach Dániel committed
205
        d = Domain.objects.create(name='example.org', owner=self.u1)
206
        self.vlan = Vlan(vid=1, name='test', network4='10.0.0.1/29',
Bach Dániel committed
207
                         snat_ip='152.66.243.99',
Bach Dániel committed
208
                         network6='2001:738:2001:4031::/80', domain=d,
Bach Dániel committed
209 210
                         owner=self.u1, network_type='portforward',
                         dhcp_pool='manual')
Bach Dániel committed
211
        self.vlan.save()
212
        self.vlan2 = Vlan(vid=2, name='pub', network4='10.1.0.1/29',
Bach Dániel committed
213 214 215 216 217 218 219 220 221 222
                          network6='2001:738:2001:4032::/80', domain=d,
                          owner=self.u1, network_type='public')
        self.vlan2.save()
        self.vlan.snat_to.add(self.vlan2)

        settings["default_vlangroup"] = 'public'
        settings["default_host_groups"] = ['netezhet']
        vlg = VlanGroup.objects.create(name='public')
        vlg.vlans.add(self.vlan, self.vlan2)
        self.hg = Group.objects.create(name='netezhet')
223
        Rule.objects.create(action='accept', hostgroup=self.hg,
Bach Dániel committed
224 225 226
                            foreign_network=vlg)

        firewall = Firewall.objects.create(name='fw')
227
        Rule.objects.create(action='accept', firewall=firewall,
Bach Dániel committed
228 229
                            foreign_network=vlg)

Bach Dániel committed
230
        for i in range(1, 6):
Bach Dániel committed
231 232 233 234 235 236 237 238 239 240 241 242
            h = Host.objects.create(hostname='h-%d' % i, vlan=self.vlan,
                                    mac='01:02:03:04:05:%02d' % i,
                                    ipv4='10.0.0.%d' % i, owner=self.u1)
            h.enable_net()
            h.groups.add(self.hg)
            if i == 5:
                h.vlan = self.vlan2
                h.save()
                self.h5 = h
            if i == 1:
                self.h1 = h

Bach Dániel committed
243 244 245 246 247 248
        self.r1 = Record(name='tst', type='A', address='127.0.0.1',
                         domain=d, owner=self.u1)
        self.rb = Record(name='tst', type='AAAA', address='1.0.0.1',
                         domain=d, owner=self.u1)
        self.r2 = Record(name='ts', type='AAAA', address='2001:123:45::6',
                         domain=d, owner=self.u1)
Bach Dániel committed
249 250 251 252
        self.rm = Record(name='asd', type='MX', address='10:teszthu',
                         domain=d, owner=self.u1)
        self.rt = Record(name='asd', type='TXT', address='ASD',
                         domain=d, owner=self.u1)
Bach Dániel committed
253 254
        self.r1.save()
        self.r2.save()
Bach Dániel committed
255 256 257 258
        with patch('firewall.models.Record.clean'):
            self.rb.save()
        self.rm.save()
        self.rt.save()
Bach Dániel committed
259

Bach Dániel committed
260 261 262
    def tearDown(self):
        settings["default_host_groups"] = []

Bach Dániel committed
263 264 265 266 267 268 269 270 271
    def test_bad_aaaa_record(self):
        self.assertRaises(AddrFormatError, ipv6_to_octal, self.rb.address)

    def test_good_aaaa_record(self):
        ipv6_to_octal(self.r2.address)

    def test_dns_func(self):
        records = dns()
        self.assertEqual(Host.objects.count() * 2 +         # soa
Bach Dániel committed
272
                         len((self.r1, self.r2, self.rm, self.rt)) + 1,
Bach Dániel committed
273
                         len(records))
Bach Dániel committed
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325

    def test_host_add_port(self):
        h = self.h1
        h.ipv6 = '2001:2:3:4::0'
        assert h.behind_nat
        h.save()
        old_rules = h.rules.count()
        h.add_port('tcp', private=22)
        new_rules = h.rules.count()
        self.assertEqual(new_rules, old_rules + 1)
        self.assertEqual(len(h.list_ports()), old_rules + 1)
        endp = h.get_public_endpoints(22)
        self.assertEqual(endp['ipv4'][0], h.ipv4)
        assert int(endp['ipv4'][1])
        self.assertEqual(endp['ipv6'][0], h.ipv6)
        assert int(endp['ipv6'][1])

    def test_host_add_port2(self):
        h = self.h5
        h.ipv6 = '2001:2:3:4::1'
        h.save()
        assert not h.behind_nat
        old_rules = h.rules.count()
        h.add_port('tcp', private=22)
        new_rules = h.rules.count()
        self.assertEqual(new_rules, old_rules + 1)
        self.assertEqual(len(h.list_ports()), old_rules + 1)
        endp = h.get_public_endpoints(22)
        self.assertEqual(endp['ipv4'][0], h.ipv4)
        assert int(endp['ipv4'][1])
        self.assertEqual(endp['ipv6'][0], h.ipv6)
        assert int(endp['ipv6'][1])

    def test_host_del_port(self):
        h = self.h1
        h.ipv6 = '2001:2:3:4::0'
        h.save()
        h.add_port('tcp', private=22)
        old_rules = h.rules.count()
        h.del_port('tcp', private=22)
        new_rules = h.rules.count()
        self.assertEqual(new_rules, old_rules - 1)

    def test_host_add_port_wo_vlangroup(self):
        VlanGroup.objects.filter(name='public').delete()
        h = self.h1
        old_rules = h.rules.count()
        h.add_port('tcp', private=22)
        new_rules = h.rules.count()
        self.assertEqual(new_rules, old_rules)

    def test_periodic_task(self):
Bach Dániel committed
326
        # TODO
Bach Dániel committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
        cache = patch('firewall.tasks.local_tasks.cache')
        grqn = patch('firewall.models.Firewall.get_remote_queue_name',
                     return_value='fw.firewall')
        worker = patch(
            'firewall.tasks.local_tasks.reloadtask_worker.apply_async')

        dns = patch('firewall.tasks.remote_tasks.reload_dns.apply_async')
        fw = patch(
            'firewall.tasks.remote_tasks.reload_firewall.apply_async')
        fw_vlan = patch(
            'firewall.tasks.remote_tasks.reload_firewall_vlan.apply_async')
        blacklist = patch(
            'firewall.tasks.remote_tasks.reload_blacklist.apply_async')
        dhcp = patch('firewall.tasks.remote_tasks.reload_dhcp.apply_async')

        with cache as cache, grqn, dns, fw, fw_vlan, blacklist, dhcp, worker:
Bach Dániel committed
343 344
            self.test_host_add_port()
            self.test_host_add_port2()
Bach Dániel committed
345
            reloadtask_worker()
Bach Dániel committed
346 347
            reloadtask()
            assert cache.delete.called