models.py 14.9 KB
Newer Older
Őry Máté committed
1 2
# -*- coding: utf8 -*-

Őry Máté committed
3
from django.contrib.auth.models import User
Őry Máté committed
4
from django.db import models
5
from django.forms import fields, ValidationError
Őry Máté committed
6
from django.utils.translation import ugettext_lazy as _
Őry Máté committed
7
from firewall.fields import *
8
from south.modelsinspector import add_introspection_rules
9
from django.core.validators import MinValueValidator, MaxValueValidator
Őry Máté committed
10
from cloud.settings import firewall_settings as settings
11
from django.db.models.signals import post_save
12
import re
Őry Máté committed
13 14

class Rule(models.Model):
15 16
    CHOICES_type = (('host', 'host'), ('firewall', 'firewall'),
            ('vlan', 'vlan'))
x committed
17 18
    CHOICES_proto = (('tcp', 'tcp'), ('udp', 'udp'), ('icmp', 'icmp'))
    CHOICES_dir = (('0', 'out'), ('1', 'in'))
19

20 21
    direction = models.CharField(max_length=1, choices=CHOICES_dir,
            blank=False)
x committed
22
    description = models.TextField(blank=True)
23 24 25 26 27 28 29 30
    foreign_network = models.ForeignKey('VlanGroup',
            related_name="ForeignRules")
    dport = models.IntegerField(blank=True, null=True,
            validators=[MinValueValidator(1), MaxValueValidator(65535)])
    sport = models.IntegerField(blank=True, null=True,
            validators=[MinValueValidator(1), MaxValueValidator(65535)])
    proto = models.CharField(max_length=10, choices=CHOICES_proto,
            blank=True, null=True)
x committed
31 32 33 34 35
    extra = models.TextField(blank=True)
    accept = models.BooleanField(default=False)
    owner = models.ForeignKey(User, blank=True, null=True)
    r_type = models.CharField(max_length=10, choices=CHOICES_type)
    nat = models.BooleanField(default=False)
36 37
    nat_dport = models.IntegerField(blank=True, null=True,
            validators=[MinValueValidator(1), MaxValueValidator(65535)])
Őry Máté committed
38 39
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
x committed
40

41 42 43 44 45 46 47 48 49 50
    vlan = models.ForeignKey('Vlan', related_name="rules", blank=True,
            null=True)
    vlangroup = models.ForeignKey('VlanGroup', related_name="rules",
            blank=True, null=True)
    host = models.ForeignKey('Host', related_name="rules", blank=True,
            null=True)
    hostgroup = models.ForeignKey('Group', related_name="rules",
            blank=True, null=True)
    firewall = models.ForeignKey('Firewall', related_name="rules",
            blank=True, null=True)
Bach Dániel committed
51

x committed
52
    def __unicode__(self):
root committed
53
        return self.desc()
Őry Máté committed
54

Bach Dániel committed
55
    def clean(self):
56 57 58 59 60
        fields = [self.vlan, self.vlangroup, self.host, self.hostgroup,
                self.firewall]
        selected_fields = [field for field in fields if field]
        if len(selected_fields) > 1:
            raise ValidationError(_('Only one field can be selected.'))
x committed
61 62

    def desc(self):
63 64 65 66 67 68 69 70 71 72
        return u'[%(type)s] %(src)s ▸ %(dst)s %(para)s %(desc)s' % {
            'type': self.r_type,
            'src': (unicode(self.foreign_network) if self.direction == '1'
                else self.r_type),
            'dst': (self.r_type if self.direction == '1'
                else unicode(self.foreign_network)),
            'para': ((("proto=%s " % self.proto) if self.proto else '') +
                     (("sport=%s " % self.sport) if self.sport else '') +
                     (("dport=%s " % self.dport) if self.dport else '')),
            'desc': self.description}
Őry Máté committed
73 74 75

class Vlan(models.Model):
    vid = models.IntegerField(unique=True)
76 77
    name = models.CharField(max_length=20, unique=True,
            validators=[val_alfanum])
78 79
    prefix4 = models.IntegerField(default=16)
    prefix6 = models.IntegerField(default=80)
Őry Máté committed
80 81 82 83 84
    interface = models.CharField(max_length=20, unique=True)
    net4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
    net6 = models.GenericIPAddressField(protocol='ipv6', unique=True)
    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
    ipv6 = models.GenericIPAddressField(protocol='ipv6', unique=True)
85 86 87 88
    snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True,
            null=True)
    snat_to = models.ManyToManyField('self', symmetrical=False, blank=True,
            null=True)
Őry Máté committed
89 90
    description = models.TextField(blank=True)
    comment = models.TextField(blank=True)
91 92
    domain = models.ForeignKey('Domain')
    reverse_domain = models.TextField(validators=[val_reverse_domain])
Őry Máté committed
93
    dhcp_pool = models.TextField(blank=True)
Őry Máté committed
94
    created_at = models.DateTimeField(auto_now_add=True)
95
    owner = models.ForeignKey(User, blank=True, null=True)
Őry Máté committed
96
    modified_at = models.DateTimeField(auto_now=True)
97

Őry Máté committed
98 99
    def __unicode__(self):
        return self.name
100

Őry Máté committed
101
    def net_ipv6(self):
x committed
102
        return self.net6 + "/" + unicode(self.prefix6)
103

Őry Máté committed
104
    def net_ipv4(self):
x committed
105
        return self.net4 + "/" + unicode(self.prefix4)
Bach Dániel committed
106 107 108

class VlanGroup(models.Model):
    name = models.CharField(max_length=20, unique=True)
109 110
    vlans = models.ManyToManyField('Vlan', symmetrical=False, blank=True,
            null=True)
Bach Dániel committed
111 112 113 114 115 116 117
    description = models.TextField(blank=True)
    owner = models.ForeignKey(User, blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.name
Őry Máté committed
118 119 120

class Group(models.Model):
    name = models.CharField(max_length=20, unique=True)
Bach Dániel committed
121 122
    description = models.TextField(blank=True)
    owner = models.ForeignKey(User, blank=True, null=True)
Őry Máté committed
123 124
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
125

Őry Máté committed
126 127 128 129
    def __unicode__(self):
        return self.name

class Host(models.Model):
130 131 132 133
    hostname = models.CharField(max_length=40, unique=True,
            validators=[val_alfanum])
    reverse = models.CharField(max_length=40, validators=[val_domain],
            blank=True, null=True)
Őry Máté committed
134 135
    mac = MACAddressField(unique=True)
    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
136 137 138 139
    pub_ipv4 = models.GenericIPAddressField(protocol='ipv4', blank=True,
            null=True)
    ipv6 = models.GenericIPAddressField(protocol='ipv6', unique=True,
            blank=True, null=True)
140
    shared_ip = models.BooleanField(default=False)
Őry Máté committed
141 142 143 144 145
    description = models.TextField(blank=True)
    comment = models.TextField(blank=True)
    location = models.TextField(blank=True)
    vlan = models.ForeignKey('Vlan')
    owner = models.ForeignKey(User)
146 147
    groups = models.ManyToManyField('Group', symmetrical=False, blank=True,
            null=True)
Őry Máté committed
148 149
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
150

Őry Máté committed
151 152
    def __unicode__(self):
        return self.hostname
153

Őry Máté committed
154
    def save(self, *args, **kwargs):
155
        id = self.id
156
        if not self.id and self.ipv6 == "auto":
Őry Máté committed
157
            self.ipv6 = ipv4_2_ipv6(self.ipv4)
158 159
        if (not self.shared_ip and self.pub_ipv4 and Host.objects.
                exclude(id=self.id).filter(pub_ipv4=self.pub_ipv4)):
160 161
            raise ValidationError(_("If shared_ip has been checked, "
                "pub_ipv4 has to be unique."))
x committed
162
        if Host.objects.exclude(id=self.id).filter(pub_ipv4=self.ipv4):
163 164
            raise ValidationError(_("You can't use another host's NAT'd "
                "address as your own IPv4."))
Bach Dániel committed
165
        self.full_clean()
Őry Máté committed
166
        super(Host, self).save(*args, **kwargs)
167
        if not id:
Dudás Ádám committed
168
            Record(domain=self.vlan.domain, host=self, type='A',
169
                    owner=self.owner).save()
Dudás Ádám committed
170 171 172
            if self.ipv6:
                Record(domain=self.vlan.domain, host=self, type='AAAA',
                        owner=self.owner).save()
Bach Dániel committed
173

Őry Máté committed
174
    def enable_net(self):
x committed
175
        self.groups.add(Group.objects.get(name="netezhet"))
176

177
    def add_port(self, proto, public, private = 0):
178
        proto = "tcp" if proto == "tcp" else "udp"
179 180 181 182 183 184 185 186 187 188 189 190 191
        if self.shared_ip:
            if public < 1024:
                raise ValidationError(_("Only ports above 1024 can be used."))
            for host in Host.objects.filter(pub_ipv4=self.pub_ipv4):
                if host.rules.filter(nat=True, proto=proto, dport=public):
                    raise ValidationError(_("Port %s %s is already in use.") %
                            (proto, public))
            rule = Rule(direction='1', owner=self.owner, dport=public,
                    proto=proto, nat=True, accept=True, r_type="host",
                    nat_dport=private, host=self, foreign_network=VlanGroup.
                        objects.get(name=settings["default_vlangroup"]))
        else:
            if self.rules.filter(proto=proto, dport=public):
192
                raise ValidationError(_("Port %s %s is already in use.") %
193 194
                    (proto, public))
            rule = Rule(direction='1', owner=self.owner, dport=public,
195 196 197
                    proto=proto, nat=False, accept=True, r_type="host",
                    host=self, foreign_network=VlanGroup.objects
                        .get(name=settings["default_vlangroup"]))
198

x committed
199 200
        rule.full_clean()
        rule.save()
201

Őry Máté committed
202
    def del_port(self, proto, public):
203
        self.rules.filter(owner=self.owner, proto=proto, host=self,
204
                dport=public).delete()
205

Őry Máté committed
206
    def list_ports(self):
207 208 209 210
        return [{'proto': rule.proto,
                 'public': rule.dport,
                 'private': rule.nat_dport} for rule in
                self.rules.filter(owner=self.owner)]
211

212
    def get_fqdn(self):
213
        return self.hostname + u'.' + unicode(self.vlan.domain)
Őry Máté committed
214

215

Őry Máté committed
216 217
class Firewall(models.Model):
    name = models.CharField(max_length=20, unique=True)
218

Őry Máté committed
219 220
    def __unicode__(self):
        return self.name
Őry Máté committed
221

222 223 224 225 226 227 228 229 230 231 232 233
class Domain(models.Model):
    name = models.CharField(max_length=40, validators=[val_domain])
    owner = models.ForeignKey(User)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
    ttl = models.IntegerField(default=600)
    description = models.TextField(blank=True)

    def __unicode__(self):
        return self.name

class Record(models.Model):
234 235 236 237
    CHOICES_type = (('A', 'A'), ('CNAME', 'CNAME'), ('AAAA', 'AAAA'),
            ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT'))
    name = models.CharField(max_length=40, validators=[val_domain],
            blank=True, null=True)
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
    domain = models.ForeignKey('Domain')
    host = models.ForeignKey('Host', blank=True, null=True)
    type = models.CharField(max_length=6, choices=CHOICES_type)
    address = models.CharField(max_length=40, blank=True, null=True)
    ttl = models.IntegerField(default=600)
    owner = models.ForeignKey(User)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.desc()

    def desc(self):
        a = self.get_data()
253 254
        return (u' '.join([a['name'], a['type'], a['address']])
                if a else _('(empty)'))
255 256 257 258 259 260

    def save(self, *args, **kwargs):
        self.full_clean()
        super(Record, self).save(*args, **kwargs)

    def clean(self):
261 262 263 264 265 266 267 268 269 270 271 272
        if self.name:
            self.name = self.name.rstrip(".")    # remove trailing dots

        if self.host:
            if self.type in ['A', 'AAAA']:
                if self.address:
                    raise ValidationError(_("Can't specify address for "
                        "A or AAAA records if host is set!"))
                if self.name:
                    raise ValidationError(_("Can't specify name for "
                        "A or AAAA records if host is set!"))
            elif self.type == 'CNAME':
273
                if not self.name:
274 275 276 277 278 279
                    raise ValidationError(_("Name must be specified for "
                        "CNAME records if host is set!"))
                if self.address:
                    raise ValidationError(_("Can't specify address for "
                        "CNAME records if host is set!"))
        else:    # if self.host is None
280
            if not self.address:
281
                raise ValidationError(_("Address must be specified!"))
282 283

            if self.type == 'A':
284
                val_ipv4(self.address)
285
            elif self.type == 'AAAA':
286 287 288
                val_ipv6(self.address)
            elif self.type in ['CNAME', 'NS', 'PTR', 'TXT']:
                val_domain(self.address)
289 290
            elif self.type == 'MX':
                mx = self.address.split(':', 1)
291 292
                if not (len(mx) == 2 and mx[0].isdigit() and
                        domain_re.match(mx[1])):
293 294
                    raise ValidationError(_("Bad address format. "
                        "Should be: <priority>:<hostname>"))
295
            else:
296
                raise ValidationError(_("Unknown record type."))
297

298 299 300 301 302 303 304 305 306
    def __get_name(self):
        if self.host:
            if self.type in ['A', 'AAAA']:
                return self.host.get_fqdn()
            elif self.type == 'CNAME':
                return self.name + '.' + unicode(self.domain)
            else:
                return self.name
        else:    # if self.host is None
307
            if self.name:
308
                return self.name + '.' + unicode(self.domain)
309 310
            else:
                return unicode(self.domain)
311 312 313

    def __get_address(self):
        if self.host:
314
            if self.type == 'A':
315
                return (self.host.pub_ipv4
316 317
                        if self.host.pub_ipv4 and not self.host.shared_ip
                        else self.host.ipv4)
318
            elif self.type == 'AAAA':
319
                return self.host.ipv6
320
            elif self.type == 'CNAME':
321 322 323 324 325
                return self.host.get_fqdn()
        # otherwise:
        return self.address

    def get_data(self):
326 327
        name = self.__get_name()
        address = self.__get_address()
328 329
        if self.host and self.type == 'AAAA' and not self.host.ipv6:
            return None
330
        elif not address or not name:
331
            return None
332 333 334 335 336
        else:
            return {'name': name,
                    'type': self.type,
                    'ttl': self.ttl,
                    'address': address}
337

Bach Dániel committed
338
class Blacklist(models.Model):
339
    CHOICES_type = (('permban', 'permanent ban'), ('tempban', 'temporary ban'), ('whitelist', 'whitelist'), ('tempwhite', 'tempwhite'))
Bach Dániel committed
340
    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
341
    host = models.ForeignKey('Host', blank=True, null=True)
Bach Dániel committed
342
    reason = models.TextField(blank=True)
343 344
    snort_message = models.TextField(blank=True)
    type = models.CharField(max_length=10, choices=CHOICES_type, default='tempban')
Bach Dániel committed
345 346
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
347

348 349 350 351 352 353
    def save(self, *args, **kwargs):
        self.full_clean()
        super(Blacklist, self).save(*args, **kwargs)
    def __unicode__(self):
        return self.ipv4

354 355 356
def send_task(sender, instance, created, **kwargs):
    from firewall.tasks import ReloadTask
    ReloadTask.apply_async(args=[sender.__name__])
357

358 359 360 361 362 363 364 365 366

post_save.connect(send_task, sender=Host)
post_save.connect(send_task, sender=Rule)
post_save.connect(send_task, sender=Domain)
post_save.connect(send_task, sender=Record)
post_save.connect(send_task, sender=Vlan)
post_save.connect(send_task, sender=Firewall)
post_save.connect(send_task, sender=Group)
post_save.connect(send_task, sender=Host)
Bach Dániel committed
367
post_save.connect(send_task, sender=Blacklist)