From 43391a3d82b09418822068a79b6bb335656b2967 Mon Sep 17 00:00:00 2001 From: Őry Máté <ory.mate@cloud.bme.hu> Date: Wed, 4 Sep 2013 23:23:07 +0200 Subject: [PATCH] firewall: merge from network-gui --- circle/firewall/admin.py | 46 +++++++++++++++++++++++++++++----------------- circle/firewall/fields.py | 31 ++++++++++++++++++++++++++++--- circle/firewall/fw.py | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------------- circle/firewall/locale/hu/LC_MESSAGES/django.po | 572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ circle/firewall/models.py | 700 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- circle/firewall/tasks.py | 14 ++++++++++---- circle/firewall/tests.py | 6 +++++- circle/firewall/views.py | 61 ++++++++++++++++++++++++++++++++++--------------------------- 8 files changed, 1295 insertions(+), 340 deletions(-) create mode 100644 circle/firewall/locale/hu/LC_MESSAGES/django.po diff --git a/circle/firewall/admin.py b/circle/firewall/admin.py index 448f619..0eabebf 100644 --- a/circle/firewall/admin.py +++ b/circle/firewall/admin.py @@ -1,19 +1,23 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from django.contrib import admin -from firewall.models import * +from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall, + Domain, Record, Blacklist) from django import contrib class RuleInline(contrib.admin.TabularInline): model = Rule + class RecordInline(contrib.admin.TabularInline): model = Record + class HostAdmin(admin.ModelAdmin): list_display = ('hostname', 'vlan', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', - 'shared_ip', 'owner', 'description', 'reverse', 'list_groups') + 'shared_ip', 'owner', 'description', 'reverse', + 'list_groups') ordering = ('hostname', ) list_filter = ('owner', 'vlan', 'groups') search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac') @@ -26,42 +30,46 @@ class HostAdmin(admin.ModelAdmin): names = [group.name for group in instance.groups.all()] return u', '.join(names) + class HostInline(contrib.admin.TabularInline): model = Host fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip', - 'owner', 'reverse') + 'owner', 'reverse') + class VlanAdmin(admin.ModelAdmin): list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6', - 'description', 'domain', 'snat_ip', ) + 'description', 'domain', 'snat_ip', ) ordering = ('vid', ) inlines = (RuleInline, ) + class RuleAdmin(admin.ModelAdmin): list_display = ('r_type', 'color_desc', 'owner', 'extra', 'direction', - 'accept', 'proto', 'sport', 'dport', 'nat', 'nat_dport', 'used_in') + 'accept', 'proto', 'sport', 'dport', 'nat', + 'nat_dport', 'used_in') list_filter = ('r_type', 'vlan', 'owner', 'direction', 'accept', - 'proto', 'nat') + 'proto', 'nat') def color_desc(self, instance): """Returns a colorful description of the instance.""" return (u'<span style="color: #FF0000;">[%(type)s]</span> ' u'%(src)s<span style="color: #0000FF;"> ▸ </span>%(dst)s ' u'%(para)s %(desc)s') % { - 'type': instance.r_type, - 'src': (instance.foreign_network.name - if instance.direction == '1' else instance.r_type), - 'dst': (instance.r_type if instance.direction == '1' - else instance.foreign_network.name), - 'para': (u'<span style="color: #00FF00;">' + + 'type': instance.r_type, + 'src': (instance.foreign_network.name + if instance.direction == '1' else instance.r_type), + 'dst': (instance.r_type if instance.direction == '1' + else instance.foreign_network.name), + 'para': (u'<span style="color: #00FF00;">' + (('proto=%s ' % instance.proto) if instance.proto else '') + (('sport=%s ' % instance.sport) if instance.sport else '') + (('dport=%s ' % instance.dport) if instance.dport else '') + - '</span>'), - 'desc': instance.description} + '</span>'), + 'desc': instance.description} color_desc.allow_tags = True @staticmethod @@ -73,7 +81,7 @@ class RuleAdmin(admin.ModelAdmin): @staticmethod def used_in(instance): for field in [instance.vlan, instance.vlangroup, instance.host, - instance.hostgroup, instance.firewall]: + instance.hostgroup, instance.firewall]: if field: return unicode(field) + ' ' + field._meta.object_name @@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin): class AliasAdmin(admin.ModelAdmin): list_display = ('alias', 'host') + class GroupAdmin(admin.ModelAdmin): list_display = ('name', 'owner', 'description') inlines = (RuleInline, ) + class FirewallAdmin(admin.ModelAdmin): inlines = (RuleInline, ) + class DomainAdmin(admin.ModelAdmin): list_display = ('name', 'owner') + class RecordAdmin(admin.ModelAdmin): list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner') @@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin): a = instance.get_data() return a['name'] if a else None + class BlacklistAdmin(admin.ModelAdmin): list_display = ('ipv4', 'reason', 'created_at', 'modified_at') @@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin) admin.site.register(Domain, DomainAdmin) admin.site.register(Record, RecordAdmin) admin.site.register(Blacklist, BlacklistAdmin) - diff --git a/circle/firewall/fields.py b/circle/firewall/fields.py index e2348dc..f0bca9c 100644 --- a/circle/firewall/fields.py +++ b/circle/firewall/fields.py @@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address from south.modelsinspector import add_introspection_rules import re -mac_re = re.compile(r'^([0-9a-fA-F]{2}([:-]?|$)){6}$') + +mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$') alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$') domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$') ipv4_re = re.compile('^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$') reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$') + class MACAddressFormField(fields.RegexField): default_error_messages = { 'invalid': _(u'Enter a valid MAC address.'), @@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField): def __init__(self, *args, **kwargs): super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs) + class MACAddressField(models.Field): empty_strings_allowed = False + def __init__(self, *args, **kwargs): kwargs['max_length'] = 17 super(MACAddressField, self).__init__(*args, **kwargs) @@ -35,47 +39,68 @@ class MACAddressField(models.Field): return super(MACAddressField, self).formfield(**defaults) add_introspection_rules([], ["firewall\.fields\.MACAddressField"]) + def val_alfanum(value): """Validate whether the parameter is a valid alphanumeric value.""" if not alfanum_re.match(value): raise ValidationError(_(u'%s - only letters, numbers, underscores ' - 'and hyphens are allowed!') % value) + 'and hyphens are allowed!') % value) + def is_valid_domain(value): """Check whether the parameter is a valid domain name.""" return domain_re.match(value) is not None + def val_domain(value): """Validate whether the parameter is a valid domin name.""" if not is_valid_domain(value): raise ValidationError(_(u'%s - invalid domain name') % value) + def is_valid_reverse_domain(value): """Check whether the parameter is a valid reverse domain name.""" return reverse_domain_re.match(value) is not None + def val_reverse_domain(value): """Validate whether the parameter is a valid reverse domain name.""" if not is_valid_reverse_domain(value): raise ValidationError(u'%s - invalid reverse domain name' % value) + def is_valid_ipv4_address(value): """Check whether the parameter is a valid IPv4 address.""" return ipv4_re.match(value) is not None + def val_ipv4(value): """Validate whether the parameter is a valid IPv4 address.""" if not is_valid_ipv4_address(value): raise ValidationError(_(u'%s - not an IPv4 address') % value) + def val_ipv6(value): """Validate whether the parameter is a valid IPv6 address.""" if not is_valid_ipv6_address(value): raise ValidationError(_(u'%s - not an IPv6 address') % value) + +def val_mx(value): + """Validate whether the parameter is a valid MX address definition. + + Expected form is <priority>:<hostname>. + """ + mx = value.split(':', 1) + if not (len(mx) == 2 and mx[0].isdigit() and + domain_re.match(mx[1])): + raise ValidationError(_("Bad MX address format. " + "Should be: <priority>:<hostname>")) + + def ipv4_2_ipv6(ipv4): """Convert IPv4 address string to IPv6 address string.""" val_ipv4(ipv4) m = ipv4_re.match(ipv4) return ("2001:738:2001:4031:%s:%s:%s:0" % - (m.group(1), m.group(2), m.group(3))) + (m.group(1), m.group(2), m.group(3))) diff --git a/circle/firewall/fw.py b/circle/firewall/fw.py index c4b2814..1c757c9 100644 --- a/circle/firewall/fw.py +++ b/circle/firewall/fw.py @@ -1,23 +1,21 @@ -from django.contrib import auth from firewall import models -import os import django.conf import subprocess import re -import json from datetime import datetime, timedelta from django.db.models import Q settings = django.conf.settings.FIREWALL_SETTINGS + + class Firewall: - IPV6=False + IPV6 = False RULES = None RULES_NAT = [] vlans = None - dmz = None pub = None hosts = None fw = None @@ -30,13 +28,12 @@ class Firewall: retval += ' --sport %s ' % rule.sport if rule.dport: retval += ' --dport %s ' % (rule.nat_dport - if (repl and rule.nat and rule.direction == '1') - else rule.dport) + if (repl and rule.nat and rule.direction == '1') + else rule.dport) elif rule.proto == 'icmp': retval = '-p %s ' % rule.proto return retval - def iptables(self, s): """Append rule to filter table.""" self.RULES.append(s) @@ -61,8 +58,8 @@ class Firewall: if rule.direction == '0' and vlan.name == 'PUB': if rule.dport == 25: self.iptables('-A PUB_OUT -s %s %s -p tcp ' - '--dport 25 -j LOG_ACC' % - (ipaddr, rule.extra)) + '--dport 25 -j LOG_ACC' % + (ipaddr, rule.extra)) break action = 'PUB_OUT' else: @@ -70,13 +67,14 @@ class Firewall: else: action = 'LOG_DROP' - 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)) + 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)) else: - self.iptables('-A %s_%s -s %s %s %s -g %s' % (host.vlan, - vlan, ipaddr, dport_sport, rule.extra, action)) - + self.iptables('-A %s_%s -s %s %s %s -g %s' % + (host.vlan, vlan, ipaddr, dport_sport, + rule.extra, action)) def fw2vlan(self, rule): if not rule.foreign_network: @@ -85,14 +83,14 @@ class Firewall: dport_sport = self.dportsport(rule) for vlan in rule.foreign_network.vlans.all(): - if rule.direction == '1': # going TO host + if rule.direction == '1': # going TO host self.iptables('-A INPUT -i %s %s %s -g %s' % - (vlan.interface, dport_sport, rule.extra, - 'LOG_ACC' if rule.accept else 'LOG_DROP')) + (vlan.interface, dport_sport, rule.extra, + 'LOG_ACC' if rule.accept else 'LOG_DROP')) else: self.iptables('-A OUTPUT -o %s %s %s -g %s' % - (vlan.interface, dport_sport, rule.extra, - 'LOG_ACC' if rule.accept else 'LOG_DROP')) + (vlan.interface, dport_sport, rule.extra, + 'LOG_ACC' if rule.accept else 'LOG_DROP')) def vlan2vlan(self, l_vlan, rule): if not rule.foreign_network: @@ -109,13 +107,13 @@ class Firewall: else: action = 'LOG_DROP' - if rule.direction == '1': # going TO host - self.iptables('-A %s_%s %s %s -g %s' % (vlan, l_vlan, - dport_sport, rule.extra, action)) + if rule.direction == '1': # going TO host + self.iptables('-A %s_%s %s %s -g %s' % + (vlan, l_vlan, dport_sport, rule.extra, action)) else: self.iptables('-A %s_%s %s %s -g %s' % (l_vlan, vlan, - dport_sport, rule.extra, action)) - + dport_sport, + rule.extra, action)) def prerun(self): self.iptables('*filter') @@ -129,39 +127,39 @@ class Firewall: 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 ' - '--log-prefix "[ipt][drop]"') + '--log-prefix "[ipt][drop]"') self.iptables('-A LOG_DROP -j DROP') self.iptables('-N LOG_ACC') self.iptables('-A LOG_ACC -j LOG --log-level 7 ' - '--log-prefix "[ipt][isok]"') + '--log-prefix "[ipt][isok]"') self.iptables('-A LOG_ACC -j ACCEPT') self.iptables('-N PUB_OUT') - self.iptables('-A FORWARD -m set --match-set blacklist src,dst -j DROP') + self.iptables('-A FORWARD -m set --match-set blacklist src,dst ' + '-j DROP') self.iptables('-A FORWARD -m state --state INVALID -g LOG_DROP') self.iptables('-A FORWARD -m state --state ESTABLISHED,RELATED ' - '-j ACCEPT') + '-j ACCEPT') self.iptables('-A FORWARD -p icmp --icmp-type echo-request ' - '-g LOG_ACC') + '-g LOG_ACC') 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 ' - '-j ACCEPT') + '-j ACCEPT') 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 ' - '-j ACCEPT') - + '-j ACCEPT') def postrun(self): self.iptables('-A PUB_OUT -s 152.66.243.160/27 -p tcp --dport 25 ' - '-j LOG_ACC') + '-j LOG_ACC') self.iptables('-A PUB_OUT -s 152.66.243.160/27 -p tcp --dport 445 ' - '-j LOG_ACC') + '-j LOG_ACC') 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') @@ -172,9 +170,6 @@ class Firewall: self.iptables('-A OUTPUT -g LOG_DROP') self.iptables('COMMIT') - - - def ipt_nat(self): self.iptablesnat('*nat') self.iptablesnat(':PREROUTING ACCEPT [0:0]') @@ -188,34 +183,37 @@ class Firewall: dport_sport = self.dportsport(rule, False) if host.vlan.snat_ip: self.iptablesnat('-A PREROUTING -d %s %s %s -j DNAT ' - '--to-destination %s:%s' % (host.pub_ipv4, - dport_sport, rule.extra, host.ipv4, - rule.nat_dport)) + '--to-destination %s:%s' % + (host.pub_ipv4, dport_sport, rule.extra, + host.ipv4, rule.nat_dport)) # 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 ' - '--to-destination %s' % (host.pub_ipv4, host.ipv4)) + '--to-destination %s' % + (host.pub_ipv4, host.ipv4)) self.iptablesnat('-A POSTROUTING -s %s -j SNAT ' - '--to-source %s' % (host.ipv4, host.pub_ipv4)) + '--to-source %s' % + (host.ipv4, host.pub_ipv4)) # 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 ' - '--to-source %s' % (s_vlan.net_ipv4(), - d_vlan.interface, s_vlan.snat_ip)) - + '--to-source %s' % + (s_vlan.net_ipv4(), d_vlan.interface, + s_vlan.snat_ip)) # hard-wired rules self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0003 -j SNAT ' - '--to-source 10.3.255.254') # man elerheto legyen - self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0008 -j SNAT ' - '--to-source 10.0.0.247') # wolf network for printing - self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -o vlan0002 -j SNAT ' - '--to-source %s' % self.pub.ipv4) # kulonben nemmegy a du + '--to-source 10.3.255.254') # man elerheto legyen + self.iptablesnat('-A POSTROUTING -o vlan0008 -j SNAT ' + '--to-source 10.0.0.247') # wolf network for printing + self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -p udp --dport 53 ' + '-o vlan0002 -j SNAT ''--to-source %s' % + self.pub.ipv4) # kulonben nem megy a dns man-ban self.iptablesnat('COMMIT') @@ -235,7 +233,8 @@ class Firewall: 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' % - (s_vlan.interface, d_vlan.interface, s_vlan, d_vlan)) + (s_vlan.interface, d_vlan.interface, s_vlan, + d_vlan)) # hosts' rules for i_vlan in self.vlans: @@ -264,12 +263,11 @@ class Firewall: self.RULES = [x.replace('icmp', 'icmpv6') for x in self.RULES] def __init__(self, IPV6=False): - self.RULES=[] - self.RULES_NAT=[] + self.RULES = [] + self.RULES_NAT = [] self.IPV6 = IPV6 self.vlans = models.Vlan.objects.all() self.hosts = models.Host.objects.all() - self.dmz = models.Vlan.objects.get(name='DMZ') self.pub = models.Vlan.objects.get(name='PUB') self.fw = models.Firewall.objects.all() self.ipt_filter() @@ -279,32 +277,37 @@ class Firewall: def reload(self): if self.IPV6: process = subprocess.Popen(['/usr/bin/ssh', 'fw2', - '/usr/bin/sudo', '/sbin/ip6tables-restore', '-c'], - shell=False, stdin=subprocess.PIPE) + '/usr/bin/sudo', + '/sbin/ip6tables-restore', '-c'], + shell=False, stdin=subprocess.PIPE) process.communicate('\n'.join(self.RULES) + '\n') else: process = subprocess.Popen(['/usr/bin/ssh', 'fw2', - '/usr/bin/sudo', '/sbin/iptables-restore', '-c'], - shell=False, stdin=subprocess.PIPE) + '/usr/bin/sudo', + '/sbin/iptables-restore', '-c'], + shell=False, stdin=subprocess.PIPE) process.communicate('\n'.join(self.RULES) + '\n' + - '\n'.join(self.RULES_NAT) + '\n') + '\n'.join(self.RULES_NAT) + '\n') def get(self): if self.IPV6: - return { 'filter': self.RULES, } + return {'filter': self.RULES, } else: - return { 'filter': self.RULES, 'nat': self.RULES_NAT } + return {'filter': self.RULES, 'nat': self.RULES_NAT} def show(self): if self.IPV6: return '\n'.join(self.RULES) + '\n' else: return ('\n'.join(self.RULES) + '\n' + - '\n'.join(self.RULES_NAT) + '\n') + '\n'.join(self.RULES_NAT) + '\n') + def ipset(): - week = datetime.now()-timedelta(days=2) - return models.Blacklist.objects.filter(Q(type='tempban', modified_at__gte=week) | Q(type='permban')).values('ipv4', 'reason') + 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) def ipv6_to_octal(ipv6): @@ -321,14 +324,16 @@ def ipv6_to_octal(ipv6): octets.append(int(part[2:], 16)) return '\\' + '\\'.join(['%03o' % x for x in octets]) + 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' % - (m2.group(4), m2.group(3), m2.group(2), m2.group(1))) + (m2.group(4), m2.group(3), m2.group(2), m2.group(1))) else: return ('%s.%s.%s.%s.in-addr.arpa' % - (m2.group(4), m2.group(3), m2.group(2), m2.group(1))) + (m2.group(4), m2.group(3), m2.group(2), m2.group(1))) + def ipv6_to_arpa(ipv6): while len(ipv6.split(':')) < 8: @@ -357,11 +362,11 @@ def ipv6_to_arpa(ipv6): def dns(): vlans = models.Vlan.objects.all() - regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$') + # regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$') DNS = [] for i_vlan in vlans: - m = regex.search(i_vlan.net4) + # m = regex.search(i_vlan.net4) rev = i_vlan.reverse_domain for i_host in i_vlan.host_set.all(): @@ -369,31 +374,32 @@ def dns(): not i_host.shared_ip else i_host.ipv4) i = ipv4.split('.', 4) reverse = (i_host.reverse if i_host.reverse and - len(i_host.reverse) else i_host.get_fqdn()) + len(i_host.reverse) else i_host.get_fqdn()) # ipv4 if i_host.ipv4: DNS.append("^%s:%s:%s" % ( - (rev % { 'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]), - 'd': int(i[3]) }), + (rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]), + 'd': int(i[3])}), reverse, models.settings['dns_ttl'])) # ipv6 if i_host.ipv6: DNS.append("^%s:%s:%s" % (ipv6_to_arpa(i_host.ipv6), - reverse, models.settings['dns_ttl'])) + reverse, models.settings['dns_ttl'])) 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'])) + DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" % + (domain.name, settings['dns_hostname'], + models.settings['dns_ttl'])) for r in models.Record.objects.all(): d = r.get_data() if d['type'] == 'A': DNS.append("+%s:%s:%s" % (d['name'], d['address'], d['ttl'])) elif d['type'] == 'AAAA': - DNS.append(":%s:28:%s:%s" % (d['name'], - ipv6_to_octal(d['address']), d['ttl'])) + DNS.append(":%s:28:%s:%s" % + (d['name'], ipv6_to_octal(d['address']), d['ttl'])) elif d['type'] == 'NS': DNS.append("&%s::%s:%s" % (d['name'], d['address'], d['ttl'])) elif d['type'] == 'CNAME': @@ -401,15 +407,16 @@ def dns(): elif d['type'] == 'MX': mx = d['address'].split(':', 2) DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" % - {'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0], - 'ttl': d['ttl']}) + {'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0], + 'ttl': d['ttl']}) elif d['type'] == 'PTR': DNS.append("^%s:%s:%s" % (d['name'], d['address'], d['ttl'])) return DNS process = subprocess.Popen(['/usr/bin/ssh', 'tinydns@%s' % - settings['dns_hostname']], shell=False, stdin=subprocess.PIPE) - process.communicate("\n".join(DNS)+"\n") + settings['dns_hostname']], + shell=False, stdin=subprocess.PIPE) + process.communicate("\n".join(DNS) + "\n") # print "\n".join(DNS)+"\n" @@ -422,10 +429,11 @@ def prefix_to_mask(prefix): t[i] = 256 - (2 ** ((i + 1) * 8 - prefix)) return ".".join([str(i) for i in t]) + def dhcp(): vlans = models.Vlan.objects.all() regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+' - r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$') + r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$') DHCP = [] # /tools/dhcp3/dhcpd.conf.generated @@ -434,7 +442,7 @@ def dhcp(): if(i_vlan.dhcp_pool): m = regex.search(i_vlan.dhcp_pool) if(m or i_vlan.dhcp_pool == "manual"): - DHCP.append (''' + DHCP.append(''' # %(name)s - %(interface)s subnet %(net)s netmask %(netmask)s { %(extra)s; @@ -446,7 +454,7 @@ def dhcp(): authoritative; filename \"pxelinux.0\"; allow bootp; allow booting; - }''' % { + }''' % { 'net': i_vlan.net4, 'netmask': prefix_to_mask(i_vlan.prefix4), 'domain': i_vlan.domain, @@ -454,14 +462,14 @@ def dhcp(): 'ntp': i_vlan.ipv4, 'dnsserver': settings['rdns_ip'], 'extra': ("range %s" % i_vlan.dhcp_pool - if m else "deny unknown-clients"), + if m else "deny unknown-clients"), 'interface': i_vlan.interface, 'name': i_vlan.name, 'tftp': i_vlan.ipv4 }) for i_host in i_vlan.host_set.all(): - DHCP.append (''' + DHCP.append(''' host %(hostname)s { hardware ethernet %(mac)s; fixed-address %(ipv4)s; @@ -473,23 +481,8 @@ def dhcp(): return DHCP process = subprocess.Popen(['/usr/bin/ssh', 'fw2', - 'cat > /tools/dhcp3/dhcpd.conf.generated;' - 'sudo /etc/init.d/isc-dhcp-server restart'], shell=False, - stdin=subprocess.PIPE) + 'cat > /tools/dhcp3/dhcpd.conf.generated;' + 'sudo /etc/init.d/isc-dhcp-server restart'], + shell=False, stdin=subprocess.PIPE) # print "\n".join(DHCP)+"\n" - process.communicate("\n".join(DHCP)+"\n") - - -''' -i=2 -for mac, name, ipend in [("18:a9:05:64:19:aa", "mega6", 16), ("00:1e:0b:e9:79:1e", "blade1", 21), ("00:22:64:9c:fd:34", "blade2", 22), ("00:1e:0b:ec:65:46", "blade3", 23), ("b4:b5:2f:61:d2:5a", "cloud-man", 1)]: - h1 = models.Host(hostname= name, vlan=models.Vlan.objects.get(vid=3), mac=mac, ipv4="10.3.1.%d" % ipend, ipv6="2001:738:2001:4031:3:1:%d:0" % ipend, owner=auth.models.User.objects.get(username="bd")) - try: - h1.save() - h1.groups.add(models.Group.objects.get(name="netezhet manbol")) - h1.save() -# i = i + 1 - except: - print "nemok %s" % name -''' - + process.communicate("\n".join(DHCP) + "\n") diff --git a/circle/firewall/locale/hu/LC_MESSAGES/django.po b/circle/firewall/locale/hu/LC_MESSAGES/django.po new file mode 100644 index 0000000..a49222a --- /dev/null +++ b/circle/firewall/locale/hu/LC_MESSAGES/django.po @@ -0,0 +1,572 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-08-21 12:29+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: fields.py:19 +msgid "Enter a valid MAC address." +msgstr "" + +#: fields.py:46 +#, python-format +msgid "%s - only letters, numbers, underscores and hyphens are allowed!" +msgstr "" + +#: fields.py:58 +#, python-format +msgid "%s - invalid domain name" +msgstr "" + +#: fields.py:80 +#, python-format +msgid "%s - not an IPv4 address" +msgstr "%s - nem egy IPv4 cím" + +#: fields.py:86 +#, python-format +msgid "%s - not an IPv6 address" +msgstr "" + +#: fields.py:97 +msgid "Bad MX address format. Should be: <priority>:<hostname>" +msgstr "" + +#: models.py:33 +msgid "direction" +msgstr "" + +#: models.py:34 +msgid "If the rule matches egress or ingress packets." +msgstr "" + +#: models.py:36 models.py:222 models.py:284 models.py:307 models.py:366 +#: models.py:591 models.py:615 +msgid "description" +msgstr "" + +#: models.py:37 +msgid "Why is the rule needed, or how does it work." +msgstr "" + +#: models.py:40 +msgid "foreign network" +msgstr "" + +#: models.py:41 +msgid "" +"The group of vlans the matching packet goes to (direction out) or from (in)." +msgstr "" + +#: models.py:45 +msgid "dest. port" +msgstr "" + +#: models.py:47 +msgid "Destination port number of packets that match." +msgstr "" + +#: models.py:49 +msgid "source port" +msgstr "" + +#: models.py:51 +msgid "Source port number of packets that match." +msgstr "" + +#: models.py:53 +msgid "protocol" +msgstr "" + +#: models.py:54 +msgid "Protocol of packets that match." +msgstr "" + +#: models.py:55 +msgid "extra arguments" +msgstr "" + +#: models.py:56 +msgid "Additional arguments passed literally to the iptables-rule." +msgstr "" + +#: models.py:58 +msgid "accept" +msgstr "" + +#: models.py:59 +msgid "Accept the matching packets (or deny if not checked)." +msgstr "" + +#: models.py:62 models.py:253 models.py:287 models.py:310 models.py:377 +#: models.py:585 models.py:614 +msgid "owner" +msgstr "" + +#: models.py:63 +msgid "The user responsible for this rule." +msgstr "" + +#: models.py:65 +msgid "Rule type" +msgstr "" + +#: models.py:67 +msgid "The type of entity the rule belongs to." +msgstr "" + +#: models.py:69 +msgid "NAT" +msgstr "" + +#: models.py:70 +msgid "If network address translation shoud be done." +msgstr "" + +#: models.py:74 +msgid "Rewrite destination port number to." +msgstr "" + +#: models.py:79 models.py:251 models.py:289 models.py:312 models.py:385 +msgid "created at" +msgstr "" + +#: models.py:82 models.py:255 models.py:291 models.py:314 models.py:387 +msgid "modified at" +msgstr "" + +#: models.py:85 models.py:374 +msgid "vlan" +msgstr "" + +#: models.py:86 +msgid "Vlan the rule applies to (if type is vlan)." +msgstr "" + +#: models.py:90 +msgid "vlan group" +msgstr "" + +#: models.py:91 +msgid "Group of vlans the rule applies to (if type is vlan)." +msgstr "" + +#: models.py:94 models.py:608 models.py:730 +msgid "host" +msgstr "" + +#: models.py:95 +msgid "Host the rule applies to (if type is host)." +msgstr "" + +#: models.py:98 +msgid "host group" +msgstr "" + +#: models.py:99 +msgid "Group of hosts the rule applies to (if type is host)." +msgstr "" + +#: models.py:102 +msgid "firewall" +msgstr "" + +#: models.py:103 +msgid "Firewall the rule applies to (if type is firewall)." +msgstr "" + +#: models.py:115 +msgid "Only one field can be selected." +msgstr "" + +#: models.py:136 +msgid "rule" +msgstr "" + +#: models.py:137 +msgid "rules" +msgstr "" + +#: models.py:163 +msgid "VID" +msgstr "" + +#: models.py:164 +msgid "The vlan ID of the subnet." +msgstr "" + +#: models.py:169 +msgid "Name" +msgstr "" + +#: models.py:170 +msgid "The short name of the subnet." +msgstr "" + +#: models.py:173 +msgid "IPv4 prefix length" +msgstr "" + +#: models.py:174 +msgid "The prefix length of the IPv4 subnet." +msgstr "" + +#: models.py:176 +msgid "IPv6 prefix length" +msgstr "" + +#: models.py:177 +msgid "The prefix length of the IPv6 subnet." +msgstr "" + +#: models.py:179 +msgid "interface" +msgstr "" + +#: models.py:180 +msgid "" +"The name of network interface the gateway should serve this network on. For " +"example vlan0004 or eth2." +msgstr "" + +#: models.py:184 +msgid "IPv4 network" +msgstr "" + +#: models.py:185 +msgid "The network address of the IPv4 subnet." +msgstr "" + +#: models.py:188 +msgid "IPv6 network" +msgstr "" + +#: models.py:189 +msgid "The network address of the IPv6 subnet." +msgstr "" + +#: models.py:192 models.py:346 +msgid "IPv4 address" +msgstr "" + +#: models.py:194 +msgid "" +"The IPv4 address of the gateway. Recommended value is the last valid address " +"of the subnet, for example 10.4.255.254 for 10.4.0.0/16." +msgstr "" + +#: models.py:201 models.py:357 +msgid "IPv6 address" +msgstr "" + +#: models.py:203 +msgid "The IPv6 address of the gateway." +msgstr "" + +#: models.py:207 +msgid "NAT IP address" +msgstr "" + +#: models.py:209 +msgid "" +"Common IPv4 address used for address translation of connections to the " +"networks selected below (typically to the internet)." +msgstr "" + +#: models.py:215 +msgid "NAT to" +msgstr "" + +#: models.py:217 +msgid "" +"Connections to these networks should be network address translated, i.e. " +"their source address is rewritten to the value of NAT IP address." +msgstr "" + +#: models.py:224 +msgid "Description of the goals and elements of the vlan network." +msgstr "" + +#: models.py:226 +msgid "comment" +msgstr "" + +#: models.py:228 +msgid "Notes, comments about the network" +msgstr "" + +#: models.py:229 +msgid "domain name" +msgstr "" + +#: models.py:230 +msgid "Domain name of the members of this network." +msgstr "" + +#: models.py:234 +msgid "reverse domain" +msgstr "" + +#: models.py:235 +#, python-format +msgid "" +"Template of the IPv4 reverse domain name that should be generated for each " +"host. The template should contain four tokens: \"%(a)d\", \"%(b)d\", \"%(c)d" +"\", and \"%(d)d\", representing the four bytes of the address, respectively, " +"in decimal notation. For example, the template for the standard reverse " +"address is: \"%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa\"." +msgstr "" + +#: models.py:243 +msgid "DHCP pool" +msgstr "" + +#: models.py:245 +msgid "" +"The address range of the DHCP pool: empty for no DHCP service, \"manual\" " +"for no DHCP pool, or the first and last address of the range separated by a " +"space." +msgstr "" + +#: models.py:278 models.py:305 models.py:576 models.py:584 models.py:605 +msgid "name" +msgstr "" + +#: models.py:279 models.py:306 +msgid "The name of the group." +msgstr "" + +#: models.py:281 +msgid "vlans" +msgstr "" + +#: models.py:282 +msgid "The vlans which are members of the group." +msgstr "" + +#: models.py:285 models.py:308 +msgid "Description of the group." +msgstr "" + +#: models.py:330 +msgid "hostname" +msgstr "" + +#: models.py:331 +msgid "The alphanumeric hostname of the host, the first part of the FQDN." +msgstr "" + +#: models.py:336 +msgid "reverse" +msgstr "" + +#: models.py:337 +msgid "" +"The fully qualified reverse hostname of the host, if different than hostname." +"domain." +msgstr "" + +#: models.py:341 +msgid "MAC address" +msgstr "" + +#: models.py:342 +msgid "" +"The MAC (Ethernet) address of the network interface. For example: 99:AA:BB:" +"CC:DD:EE." +msgstr "" + +#: models.py:348 +msgid "The real IPv4 address of the host, for example 10.5.1.34." +msgstr "" + +#: models.py:352 +msgid "WAN IPv4 address" +msgstr "" + +#: models.py:353 +msgid "" +"The public IPv4 address of the host on the wide area network, if different." +msgstr "" + +#: models.py:359 +msgid "The global IPv6 address of the host, for example 2001:500:88:200::10." +msgstr "" + +#: models.py:362 +msgid "shared IP" +msgstr "" + +#: models.py:364 +msgid "If the given WAN IPv4 address is used by multiple hosts." +msgstr "" + +#: models.py:367 +msgid "What is this host for, what kind of machine is it." +msgstr "" + +#: models.py:370 +msgid "Notes" +msgstr "" + +#: models.py:371 +msgid "location" +msgstr "" + +#: models.py:373 +msgid "The physical location of the machine." +msgstr "" + +#: models.py:376 +msgid "Vlan network that the host is part of." +msgstr "" + +#: models.py:379 +msgid "The person responsible for this host." +msgstr "" + +#: models.py:381 +msgid "groups" +msgstr "" + +#: models.py:383 +msgid "Host groups the machine is part of." +msgstr "" + +#: models.py:398 +msgid "If shared_ip has been checked, pub_ipv4 has to be unique." +msgstr "" + +#: models.py:401 +msgid "You can't use another host's NAT'd address as your own IPv4." +msgstr "" + +#: models.py:454 +#, python-format +msgid "All %s ports are already in use." +msgstr "" + +#: models.py:471 +#, python-format +msgid "Port %(proto)s %(public)s is already in use." +msgstr "" + +#: models.py:479 +msgid "Only ports above 1024 can be used." +msgstr "" + +#: models.py:587 models.py:617 models.py:741 +msgid "created_at" +msgstr "" + +#: models.py:589 models.py:619 models.py:743 +msgid "modified_at" +msgstr "" + +#: models.py:590 models.py:613 +msgid "ttl" +msgstr "" + +#: models.py:606 +msgid "domain" +msgstr "" + +#: models.py:610 models.py:738 +msgid "type" +msgstr "" + +#: models.py:612 +msgid "address" +msgstr "" + +#: models.py:627 +msgid "(empty)" +msgstr "" + +#: models.py:638 +msgid "Can't specify address for A or AAAA records if host is set!" +msgstr "" + +#: models.py:641 +msgid "Can't specify name for A or AAAA records if host is set!" +msgstr "" + +#: models.py:645 +msgid "Name must be specified for CNAME records if host is set!" +msgstr "" + +#: models.py:648 +msgid "Can't specify address for CNAME records if host is set!" +msgstr "" + +#: models.py:656 +msgid "Address must be specified!" +msgstr "" + +#: models.py:666 +msgid "Unknown record type." +msgstr "" + +#: models.py:731 +msgid "reason" +msgstr "" + +#: models.py:733 +msgid "short message" +msgstr "" + +#: views.py:26 +#, python-format +msgid "" +"Dear %s, you've signed in as administrator!<br />Reloading in 10 seconds..." +msgstr "" + +#: views.py:30 +#, python-format +msgid "Dear %s, you've signed in!" +msgstr "" + +#: views.py:32 +msgid "Dear anonymous, you've not signed in yet!" +msgstr "" + +#: views.py:43 +msgid "Wrong password." +msgstr "" + +#: views.py:77 views.py:118 +msgid "OK" +msgstr "" + +#: views.py:80 +msgid "Only vm-net and war can be used." +msgstr "" + +#: views.py:111 +msgid "Unknown command." +msgstr "" + +#: views.py:114 +#, python-format +msgid "" +"Something went wrong!\n" +"%s\n" +msgstr "" + +#: views.py:116 +msgid "Something went wrong!\n" +msgstr "" diff --git a/circle/firewall/models.py b/circle/firewall/models.py index 1508d9e..864e11c 100644 --- a/circle/firewall/models.py +++ b/circle/firewall/models.py @@ -1,160 +1,390 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from django.contrib.auth.models import User from django.db import models -from django.forms import fields, ValidationError +from django.forms import ValidationError from django.utils.translation import ugettext_lazy as _ -from firewall.fields import * -from south.modelsinspector import add_introspection_rules +from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain, + val_domain, val_ipv4, val_ipv6, val_mx, + ipv4_2_ipv6) from django.core.validators import MinValueValidator, MaxValueValidator import django.conf from django.db.models.signals import post_save -import re import random settings = django.conf.settings.FIREWALL_SETTINGS + + class Rule(models.Model): - """ - Common firewall rule - Rule can be applied to: Host, Firewall, Vlan + """ + A rule of a packet filter, changing the behavior of a host, vlan or + firewall. + Some rules accept or deny packets matching some criteria. + Others set address translation or other free-form iptables parameters. """ CHOICES_type = (('host', 'host'), ('firewall', 'firewall'), - ('vlan', 'vlan')) + ('vlan', 'vlan')) CHOICES_proto = (('tcp', 'tcp'), ('udp', 'udp'), ('icmp', 'icmp')) CHOICES_dir = (('0', 'out'), ('1', 'in')) direction = models.CharField(max_length=1, choices=CHOICES_dir, - blank=False) - description = models.TextField(blank=True) - 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)]) + blank=False, verbose_name=_("direction"), + help_text=_("If the rule matches egress " + "or ingress packets.")) + description = models.TextField(blank=True, verbose_name=_('description'), + help_text=_("Why is the rule needed, " + "or how does it work.")) + foreign_network = models.ForeignKey( + 'VlanGroup', verbose_name=_("foreign network"), + help_text=_("The group of vlans the matching packet goes to " + "(direction out) or from (in)."), + related_name="ForeignRules") + dport = models.IntegerField( + blank=True, null=True, verbose_name=_("dest. port"), + validators=[MinValueValidator(1), MaxValueValidator(65535)], + help_text=_("Destination port number of packets that match.")) + sport = models.IntegerField( + blank=True, null=True, verbose_name=_("source port"), + validators=[MinValueValidator(1), MaxValueValidator(65535)], + help_text=_("Source port number of packets that match.")) proto = models.CharField(max_length=10, choices=CHOICES_proto, - blank=True, null=True) - 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) + blank=True, null=True, verbose_name=_("protocol"), + help_text=_("Protocol of packets that match.")) + extra = models.TextField(blank=True, verbose_name=_("extra arguments"), + help_text=_("Additional arguments passed " + "literally to the iptables-rule.")) + accept = models.BooleanField(default=False, verbose_name=_("accept"), + help_text=_("Accept the matching packets " + "(or deny if not checked).")) + owner = models.ForeignKey(User, blank=True, null=True, + verbose_name=_("owner"), + help_text=_("The user responsible for " + "this rule.")) + r_type = models.CharField(max_length=10, verbose_name=_("Rule type"), + choices=CHOICES_type, + help_text=_("The type of entity the rule " + "belongs to.")) + nat = models.BooleanField(default=False, verbose_name=_("NAT"), + help_text=_("If network address translation " + "shoud be done.")) nat_dport = models.IntegerField(blank=True, null=True, - validators=[MinValueValidator(1), MaxValueValidator(65535)]) - created_at = models.DateTimeField(auto_now_add=True) - modified_at = models.DateTimeField(auto_now=True) + help_text=_( + "Rewrite destination port number to."), + validators=[MinValueValidator(1), + MaxValueValidator(65535)]) + created_at = models.DateTimeField( + auto_now_add=True, + verbose_name=_("created at")) + modified_at = models.DateTimeField( + auto_now=True, + verbose_name=_("modified at")) vlan = models.ForeignKey('Vlan', related_name="rules", blank=True, - null=True) + null=True, verbose_name=_("vlan"), + help_text=_("Vlan the rule applies to " + "(if type is vlan).")) vlangroup = models.ForeignKey('VlanGroup', related_name="rules", - blank=True, null=True) + blank=True, null=True, verbose_name=_( + "vlan group"), + help_text=_("Group of vlans the rule " + "applies to (if type is vlan).")) 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) + verbose_name=_('host'), null=True, + help_text=_("Host the rule applies to " + "(if type is host).")) + hostgroup = models.ForeignKey( + 'Group', related_name="rules", verbose_name=_("host group"), + blank=True, null=True, help_text=_("Group of hosts the rule applies " + "to (if type is host).")) + firewall = models.ForeignKey( + 'Firewall', related_name="rules", verbose_name=_("firewall"), + help_text=_("Firewall the rule applies to " + "(if type is firewall)."), + blank=True, null=True) def __unicode__(self): return self.desc() def clean(self): fields = [self.vlan, self.vlangroup, self.host, self.hostgroup, - self.firewall] + self.firewall] selected_fields = [field for field in fields if field] if len(selected_fields) > 1: raise ValidationError(_('Only one field can be selected.')) def desc(self): + """Return a short string representation of the current rule. + """ 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), + else self.r_type), 'dst': (self.r_type if self.direction == '1' - else unicode(self.foreign_network)), + 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} + @models.permalink + def get_absolute_url(self): + return ('network.rule', None, {'pk': self.pk}) + + class Meta: + verbose_name = _("rule") + verbose_name_plural = _("rules") + ordering = ( + 'r_type', + 'direction', + 'proto', + 'sport', + 'dport', + 'nat_dport', + 'host', + ) + + class Vlan(models.Model): - vid = models.IntegerField(unique=True) - name = models.CharField(max_length=20, unique=True, - validators=[val_alfanum]) - prefix4 = models.IntegerField(default=16) - prefix6 = models.IntegerField(default=80) - 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) + + """ + A vlan of the network, + + Networks controlled by this framework are split into separated subnets. + These networks are izolated by the vlan (virtual lan) technology, which is + commonly used by managed network switches to partition the network. + + Each vlan network has a unique identifier, a name, a unique IPv4 and IPv6 + range. The gateway also has an IP address in each range. + """ + + vid = models.IntegerField(unique=True, + verbose_name=_('VID'), + help_text=_('The vlan ID of the subnet.'), + validators=[MinValueValidator(1), + MaxValueValidator(4095)]) + name = models.CharField(max_length=20, + unique=True, + verbose_name=_('Name'), + help_text=_('The short name of the subnet.'), + validators=[val_alfanum]) + prefix4 = models.IntegerField( + default=16, verbose_name=_('IPv4 prefix length'), + help_text=_('The prefix length of the IPv4 subnet.')) + prefix6 = models.IntegerField( + default=80, verbose_name=_('IPv6 prefix length'), + help_text=_('The prefix length of the IPv6 subnet.')) + interface = models.CharField(max_length=20, unique=True, + verbose_name=_('interface'), help_text=_( + 'The name of network interface the ' + 'gateway should serve this network on. ' + 'For example vlan0004 or eth2.')) + net4 = models.GenericIPAddressField(protocol='ipv4', unique=True, + verbose_name=_('IPv4 network'), + help_text=_('The network address of ' + 'the IPv4 subnet.')) + net6 = models.GenericIPAddressField(protocol='ipv6', unique=True, + verbose_name=_('IPv6 network'), + help_text=_('The network address of ' + 'the IPv6 subnet.')) + ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True, + verbose_name=_('IPv4 address'), + help_text=_( + 'The IPv4 address of the gateway. ' + 'Recommended value is the last ' + 'valid address of the subnet, ' + 'for example ' + '10.4.255.254 for 10.4.0.0/16.')) + ipv6 = models.GenericIPAddressField(protocol='ipv6', + unique=True, + verbose_name=_('IPv6 address'), + help_text=_( + 'The IPv6 address of the ' + 'gateway.')) snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True, - null=True) + null=True, + verbose_name=_('NAT IP address'), + help_text=_( + 'Common IPv4 address used for ' + 'address translation of ' + 'connections to the networks ' + 'selected below ' + '(typically to the internet).')) snat_to = models.ManyToManyField('self', symmetrical=False, blank=True, - null=True) - description = models.TextField(blank=True) - comment = models.TextField(blank=True) - domain = models.ForeignKey('Domain') - reverse_domain = models.TextField(validators=[val_reverse_domain]) - dhcp_pool = models.TextField(blank=True) - created_at = models.DateTimeField(auto_now_add=True) - owner = models.ForeignKey(User, blank=True, null=True) - modified_at = models.DateTimeField(auto_now=True) + null=True, verbose_name=_('NAT to'), + help_text=_( + 'Connections to these networks ' + 'should be network address ' + 'translated, i.e. their source ' + 'address is rewritten to the value ' + 'of NAT IP address.')) + description = models.TextField(blank=True, verbose_name=_('description'), + help_text=_( + 'Description of the goals and elements ' + 'of the vlan network.')) + comment = models.TextField(blank=True, verbose_name=_('comment'), + help_text=_( + 'Notes, comments about the network')) + domain = models.ForeignKey('Domain', verbose_name=_('domain name'), + help_text=_('Domain name of the members of ' + 'this network.')) + reverse_domain = models.TextField( + validators=[val_reverse_domain], + verbose_name=_('reverse domain'), + help_text=_('Template of the IPv4 reverse domain name that ' + 'should be generated for each host. The template ' + 'should contain four tokens: "%(a)d", "%(b)d", ' + '"%(c)d", and "%(d)d", representing the four bytes ' + 'of the address, respectively, in decimal notation. ' + 'For example, the template for the standard reverse ' + 'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'), + default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa") + dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'), + help_text=_( + 'The address range of the DHCP pool: ' + 'empty for no DHCP service, "manual" for ' + 'no DHCP pool, or the first and last ' + 'address of the range separated by a ' + 'space.')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created at')) + owner = models.ForeignKey(User, blank=True, null=True, + verbose_name=_('owner')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified at')) def __unicode__(self): return self.name def net_ipv6(self): + """String representation of selected IPv6 network.""" return self.net6 + "/" + unicode(self.prefix6) def net_ipv4(self): + """String representation of selected IPv4 network.""" return self.net4 + "/" + unicode(self.prefix4) + @models.permalink + def get_absolute_url(self): + return ('network.vlan', None, {'vid': self.vid}) + + class VlanGroup(models.Model): - name = models.CharField(max_length=20, unique=True) + """ + A group of Vlans. + """ + + name = models.CharField(max_length=20, unique=True, verbose_name=_('name'), + help_text=_('The name of the group.')) vlans = models.ManyToManyField('Vlan', symmetrical=False, blank=True, - null=True) - 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) + null=True, verbose_name=_('vlans'), + help_text=_('The vlans which are members ' + 'of the group.')) + description = models.TextField(blank=True, verbose_name=_('description'), + help_text=_('Description of the group.')) + owner = models.ForeignKey(User, blank=True, null=True, + verbose_name=_('owner')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified at')) def __unicode__(self): return self.name + @models.permalink + def get_absolute_url(self): + return ('network.vlan_group', None, {'pk': self.pk}) + + class Group(models.Model): - name = models.CharField(max_length=20, unique=True) - 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) + """ + A group of hosts. + """ + name = models.CharField(max_length=20, unique=True, verbose_name=_('name'), + help_text=_('The name of the group.')) + description = models.TextField(blank=True, verbose_name=_('description'), + help_text=_('Description of the group.')) + owner = models.ForeignKey(User, blank=True, null=True, + verbose_name=_('owner')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified at')) def __unicode__(self): return self.name + @models.permalink + def get_absolute_url(self): + return ('network.group', None, {'pk': self.pk}) + + class Host(models.Model): + """ + A host of the network. + """ + hostname = models.CharField(max_length=40, unique=True, - validators=[val_alfanum]) + verbose_name=_('hostname'), + help_text=_('The alphanumeric hostname of ' + 'the host, the first part of ' + 'the FQDN.'), + validators=[val_alfanum]) reverse = models.CharField(max_length=40, validators=[val_domain], - blank=True, null=True) - mac = MACAddressField(unique=True) - ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True) - pub_ipv4 = models.GenericIPAddressField(protocol='ipv4', blank=True, - null=True) + verbose_name=_('reverse'), + help_text=_('The fully qualified reverse ' + 'hostname of the host, if ' + 'different than hostname.domain.'), + blank=True, null=True) + mac = MACAddressField(unique=True, verbose_name=_('MAC address'), + help_text=_('The MAC (Ethernet) address of the ' + 'network interface. For example: ' + '99:AA:BB:CC:DD:EE.')) + ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True, + verbose_name=_('IPv4 address'), + help_text=_( + 'The real IPv4 address of the ' + 'host, for example 10.5.1.34.')) + pub_ipv4 = models.GenericIPAddressField( + protocol='ipv4', blank=True, null=True, + verbose_name=_('WAN IPv4 address'), + help_text=_('The public IPv4 address of the host on the wide ' + 'area network, if different.')) ipv6 = models.GenericIPAddressField(protocol='ipv6', unique=True, - blank=True, null=True) - shared_ip = models.BooleanField(default=False) - description = models.TextField(blank=True) - comment = models.TextField(blank=True) - location = models.TextField(blank=True) - vlan = models.ForeignKey('Vlan') - owner = models.ForeignKey(User) + blank=True, null=True, + verbose_name=_('IPv6 address'), + help_text=_( + 'The global IPv6 address of the ' + 'host, for example ' + '2001:500:88:200::10.')) + shared_ip = models.BooleanField(default=False, verbose_name=_('shared IP'), + help_text=_( + 'If the given WAN IPv4 address is ' + 'used by multiple hosts.')) + description = models.TextField(blank=True, verbose_name=_('description'), + help_text=_('What is this host for, what ' + 'kind of machine is it.')) + comment = models.TextField(blank=True, + verbose_name=_('Notes')) + location = models.TextField(blank=True, verbose_name=_('location'), + help_text=_( + 'The physical location of the machine.')) + vlan = models.ForeignKey('Vlan', verbose_name=_('vlan'), + help_text=_( + 'Vlan network that the host is part of.')) + owner = models.ForeignKey(User, verbose_name=_('owner'), + help_text=_( + 'The person responsible for this host.')) groups = models.ManyToManyField('Group', symmetrical=False, blank=True, - null=True) - created_at = models.DateTimeField(auto_now_add=True) - modified_at = models.DateTimeField(auto_now=True) + null=True, verbose_name=_('groups'), + help_text=_( + 'Host groups the machine is part of.')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified at')) def __unicode__(self): return self.hostname @@ -166,89 +396,144 @@ class Host(models.Model): if (not self.shared_ip and self.pub_ipv4 and Host.objects. exclude(id=self.id).filter(pub_ipv4=self.pub_ipv4)): raise ValidationError(_("If shared_ip has been checked, " - "pub_ipv4 has to be unique.")) + "pub_ipv4 has to be unique.")) if Host.objects.exclude(id=self.id).filter(pub_ipv4=self.ipv4): raise ValidationError(_("You can't use another host's NAT'd " - "address as your own IPv4.")) + "address as your own IPv4.")) self.full_clean() super(Host, self).save(*args, **kwargs) if not id: Record(domain=self.vlan.domain, host=self, type='A', - owner=self.owner).save() + owner=self.owner).save() if self.ipv6: Record(domain=self.vlan.domain, host=self, type='AAAA', - owner=self.owner).save() + owner=self.owner).save() def enable_net(self): self.groups.add(Group.objects.get(name="netezhet")) - def add_port(self, proto, public=None, private=None): - proto = "tcp" if proto == "tcp" else "udp" - if self.shared_ip: - used_ports = Rule.objects.filter(host__pub_ipv4=self.pub_ipv4, - nat=True, proto=proto).values_list('dport', flat=True) - - if public is None: - public = random.randint(1024, 21000) - if public in used_ports: - for i in range(1024, 21000) + range(24000, 65535): - if i not in used_ports: - public = i - break - else: - raise ValidationError(_("Port %s %s is already in use.") % - (proto, public)) + def _get_ports_used(self, proto): + """ + Gives a list of port numbers used for the public IP address of current + host for the given protocol. + :param proto: The transport protocol of the generated port (tcp|udp). + :type proto: str. + :returns: list -- list of int port numbers used. + """ + if self.shared_ip: + ports = Rule.objects.filter(host__pub_ipv4=self.pub_ipv4, + nat=True, proto=proto) + else: + ports = self.rules.filter(proto=proto, ) + return ports.values_list('dport', flat=True) + + def _get_random_port(self, proto, used_ports=None): + """ + Get a random unused port for given protocol for current host's public + IP address. + + :param proto: The transport protocol of the generated port (tcp|udp). + :type proto: str. + :param used_ports: Optional list of used ports returned by + _get_ports_used. + :returns: int -- the generated port number. + :raises: ValidationError + """ + if used_ports is None: + used_ports = self._get_ports_used(proto) + + public = random.randint(1024, 21000) # pick a random port + if public in used_ports: # if it's in use, select smallest free one + for i in range(1024, 21000) + range(24000, 65535): + if i not in used_ports: + public = i + break else: - if public < 1024: - raise ValidationError(_("Only ports above 1024 can be used.")) - if public in used_ports: - raise ValidationError(_("Port %s %s is already in use.") % - (proto, public)) + raise ValidationError( + _("All %s ports are already in use.") % proto) + + def add_port(self, proto, public=None, private=None): + """ + Allow inbound traffic to a port. + + If the host uses a shared IP address, also set up port forwarding. + + :param proto: The transport protocol (tcp|udp). + :type proto: str. + :param public: Preferred public port number for forwarding (optional). + :param private: Port number of host in subject. + """ + assert proto in ('tcp', 'udp', ) + if public: + if public in self._get_ports_used(proto): + raise ValidationError( + _("Port %(proto)s %(public)s is already in use.") % + {'proto': proto, 'public': public}) + else: + public = self._get_random_port(proto) + vg = VlanGroup.objects.get(name=settings["default_vlangroup"]) + if self.shared_ip: + if public < 1024: + raise ValidationError(_("Only ports above 1024 can be used.")) 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"])) + proto=proto, nat=True, accept=True, r_type="host", + nat_dport=private, host=self, foreign_network=vg) else: - if self.rules.filter(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=False, accept=True, r_type="host", - host=self, foreign_network=VlanGroup.objects - .get(name=settings["default_vlangroup"])) + proto=proto, nat=False, accept=True, r_type="host", + host=self, foreign_network=vg) rule.full_clean() rule.save() def del_port(self, proto, private): + """ + Remove rules about inbound traffic to a given port. + + If the host uses a shared IP address, also set up port forwarding. + + :param proto: The transport protocol (tcp|udp). + :type proto: str. + :param private: Port number of host in subject. + """ + if self.shared_ip: self.rules.filter(owner=self.owner, proto=proto, host=self, - nat_dport=private).delete() + nat_dport=private).delete() else: self.rules.filter(owner=self.owner, proto=proto, host=self, - dport=private).delete() + dport=private).delete() def get_hostname(self, proto): + """ + Get a hostname for public ip address. + + :param proto: The IP version (ipv4|ipv6). + :type proto: str. + """ + assert proto in ('ipv6', 'ipv4', ) try: if proto == 'ipv6': res = self.record_set.filter(type='AAAA') elif proto == 'ipv4': if self.shared_ip: res = Record.objects.filter(type='A', - address=self.pub_ipv4) + address=self.pub_ipv4) else: res = self.record_set.filter(type='A') return unicode(res[0].get_data()['name']) except: -# raise if self.shared_ip: return self.pub_ipv4 else: return self.ipv4 def list_ports(self): + """ + Return a list of ports with forwarding rules set. + """ retval = [] for rule in self.rules.filter(owner=self.owner): private = rule.nat_dport if self.shared_ip else rule.dport @@ -267,7 +552,7 @@ class Host(models.Model): 'host': self.get_hostname(proto='ipv4'), 'port': public4, } - if self.ipv6: # ipv6 + if self.ipv6: # ipv6 forward['ipv6'] = { 'host': self.get_hostname(proto='ipv6'), 'port': public6, @@ -276,40 +561,62 @@ class Host(models.Model): return retval def get_fqdn(self): + """ + Get fully qualified host name of host. + """ return self.hostname + u'.' + unicode(self.vlan.domain) + @models.permalink + def get_absolute_url(self): + return ('network.host', None, {'pk': self.pk}) + class Firewall(models.Model): - name = models.CharField(max_length=20, unique=True) + name = models.CharField(max_length=20, unique=True, + verbose_name=_('name')) def __unicode__(self): return self.name + 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) + name = models.CharField(max_length=40, validators=[val_domain], + verbose_name=_('name')) + owner = models.ForeignKey(User, verbose_name=_('owner')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created_at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified_at')) + ttl = models.IntegerField(default=600, verbose_name=_('ttl')) + description = models.TextField(blank=True, verbose_name=_('description')) def __unicode__(self): return self.name + @models.permalink + def get_absolute_url(self): + return ('network.domain', None, {'pk': self.pk}) + + class Record(models.Model): CHOICES_type = (('A', 'A'), ('CNAME', 'CNAME'), ('AAAA', 'AAAA'), - ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT')) + ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT')) name = models.CharField(max_length=40, validators=[val_domain], - blank=True, null=True) - 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) + blank=True, null=True, verbose_name=_('name')) + domain = models.ForeignKey('Domain', verbose_name=_('domain')) + host = models.ForeignKey('Host', blank=True, null=True, + verbose_name=_('host')) + type = models.CharField(max_length=6, choices=CHOICES_type, + verbose_name=_('type')) + address = models.CharField(max_length=40, blank=True, null=True, + verbose_name=_('address')) + ttl = models.IntegerField(default=600, verbose_name=_('ttl')) + owner = models.ForeignKey(User, verbose_name=_('owner')) + description = models.TextField(blank=True, verbose_name=_('description')) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created_at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified_at')) def __unicode__(self): return self.desc() @@ -323,46 +630,54 @@ class Record(models.Model): self.full_clean() super(Record, self).save(*args, **kwargs) + def _validate_w_host(self): + """Validate a record with host set.""" + assert 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': + if not self.name: + 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!")) + + def _validate_wo_host(self): + """Validate a record without a host set.""" + assert self.host is None + + if not self.address: + raise ValidationError(_("Address must be specified!")) + if self.type == 'A': + val_ipv4(self.address) + elif self.type == 'AAAA': + val_ipv6(self.address) + elif self.type in ['CNAME', 'NS', 'PTR', 'TXT']: + val_domain(self.address) + elif self.type == 'MX': + val_mx(self.address) + else: + raise ValidationError(_("Unknown record type.")) + def clean(self): + """Validate the Record to be saved. + """ 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': - if not self.name: - 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 - if not self.address: - raise ValidationError(_("Address must be specified!")) + self._validate_w_host() + else: + self._validate_wo_host() - if self.type == 'A': - val_ipv4(self.address) - elif self.type == 'AAAA': - val_ipv6(self.address) - elif self.type in ['CNAME', 'NS', 'PTR', 'TXT']: - val_domain(self.address) - elif self.type == 'MX': - mx = self.address.split(':', 1) - if not (len(mx) == 2 and mx[0].isdigit() and - domain_re.match(mx[1])): - raise ValidationError(_("Bad address format. " - "Should be: <priority>:<hostname>")) - else: - raise ValidationError(_("Unknown record type.")) - def get_name(self): - return self.__get_name() - def __get_name(self): + @property + def fqdn(self): if self.host and self.type != 'MX': if self.type in ['A', 'AAAA']: return self.host.get_fqdn() @@ -390,7 +705,7 @@ class Record(models.Model): return self.address def get_data(self): - name = self.__get_name() + name = self.fqdn address = self.__get_address() if self.host and self.type == 'AAAA' and not self.host.ipv6: return None @@ -402,22 +717,43 @@ class Record(models.Model): 'ttl': self.ttl, 'address': address} + @models.permalink + def get_absolute_url(self): + return ('network.record', None, {'pk': self.pk}) + + class Blacklist(models.Model): - CHOICES_type = (('permban', 'permanent ban'), ('tempban', 'temporary ban'), ('whitelist', 'whitelist'), ('tempwhite', 'tempwhite')) + CHOICES_type = (('permban', 'permanent ban'), ('tempban', 'temporary ban'), + ('whitelist', 'whitelist'), ('tempwhite', 'tempwhite')) ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True) - host = models.ForeignKey('Host', blank=True, null=True) - reason = models.TextField(blank=True) - snort_message = models.TextField(blank=True) - type = models.CharField(max_length=10, choices=CHOICES_type, default='tempban') - created_at = models.DateTimeField(auto_now_add=True) - modified_at = models.DateTimeField(auto_now=True) + host = models.ForeignKey('Host', blank=True, null=True, + verbose_name=_('host')) + reason = models.TextField(blank=True, verbose_name=_('reason')) + snort_message = models.TextField(blank=True, + verbose_name=_('short message')) + type = models.CharField( + max_length=10, + choices=CHOICES_type, + default='tempban', + verbose_name=_('type') + ) + created_at = models.DateTimeField(auto_now_add=True, + verbose_name=_('created_at')) + modified_at = models.DateTimeField(auto_now=True, + verbose_name=_('modified_at')) def save(self, *args, **kwargs): self.full_clean() super(Blacklist, self).save(*args, **kwargs) + def __unicode__(self): return self.ipv4 + @models.permalink + def get_absolute_url(self): + return ('network.blacklist', None, {'pk': self.pk}) + + def send_task(sender, instance, created, **kwargs): from firewall.tasks import ReloadTask ReloadTask.apply_async(args=[sender.__name__]) diff --git a/circle/firewall/tasks.py b/circle/firewall/tasks.py index 7735cbf..fbfd879 100644 --- a/circle/firewall/tasks.py +++ b/circle/firewall/tasks.py @@ -1,26 +1,32 @@ from celery.task import Task, PeriodicTask import celery from django.core.cache import cache -import os -import time from firewall.fw import * import django.conf settings = django.conf.settings.FIREWALL_SETTINGS + @celery.task def reload_dns_task(data): pass + + @celery.task def reload_firewall_task(data4, data6): pass + + @celery.task def reload_dhcp_task(data): pass + + @celery.task def reload_blacklist_task(data): pass + class Periodic(PeriodicTask): run_every = timedelta(seconds=10) @@ -48,10 +54,11 @@ class Periodic(PeriodicTask): reload_blacklist_task.delay(list(ipset())) print "blacklist ujratoltese kesz" + class ReloadTask(Task): def run(self, type='Host'): - if type in ["Host", "Records", "Domain", "Vlan"]: + if type in ["Host", "Record", "Domain", "Vlan"]: cache.add("dns_lock", "true", 30) if type == "Host": @@ -64,4 +71,3 @@ class ReloadTask(Task): cache.add("blacklist_lock", "true", 30) print type - diff --git a/circle/firewall/tests.py b/circle/firewall/tests.py index 5ab4383..c9765c3 100644 --- a/circle/firewall/tests.py +++ b/circle/firewall/tests.py @@ -1,14 +1,17 @@ from django.test import TestCase from admin import HostAdmin + class MockInstance: def __init__(self, groups): self.groups = MockGroups(groups) + class MockGroup: def __init__(self, name): self.name = name + class MockGroups: def __init__(self, groups): self.groups = groups @@ -16,6 +19,7 @@ class MockGroups: def all(self): return self.groups + class HostAdminTestCase(TestCase): def test_no_groups(self): instance = MockInstance([]) @@ -29,6 +33,6 @@ class HostAdminTestCase(TestCase): def test_multiple_groups(self): instance = MockInstance([MockGroup("alma"), - MockGroup("korte"), MockGroup("szilva")]) + MockGroup("korte"), MockGroup("szilva")]) l = HostAdmin.list_groups(instance) self.assertEqual(l, "alma, korte, szilva") diff --git a/circle/firewall/views.py b/circle/firewall/views.py index 9b1923a..6fd70b5 100644 --- a/circle/firewall/views.py +++ b/circle/firewall/views.py @@ -2,12 +2,10 @@ import base64 import datetime import json import re -import sys from django.conf import settings from django.db import IntegrityError from django.http import HttpResponse -from django.shortcuts import render_to_response from django.template.loader import render_to_string from django.utils import translation from django.utils.timezone import utc @@ -15,13 +13,13 @@ from django.utils.translation import ugettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from celery.task.control import inspect from tasks import * from firewall.fw import * from firewall.models import * from one.tasks import SendMailTask + def reload_firewall(request): if request.user.is_authenticated(): if request.user.is_superuser: @@ -34,36 +32,46 @@ def reload_firewall(request): html = _("Dear anonymous, you've not signed in yet!") return HttpResponse(html) + @csrf_exempt @require_POST def firewall_api(request): try: - data=json.loads(base64.b64decode(request.POST["data"])) + data = json.loads(base64.b64decode(request.POST["data"])) command = request.POST["command"] if data["password"] != "bdmegintelrontottaanetet": raise Exception(_("Wrong password.")) if command == "blacklist": obj, created = Blacklist.objects.get_or_create(ipv4=data["ip"]) - obj.reason=data["reason"] - obj.snort_message=data["snort_message"] + obj.reason = data["reason"] + obj.snort_message = data["snort_message"] if created: try: - obj.host = models.Host.objects.get(ipv4=data["ip"]) + obj.host = Host.objects.get(ipv4=data["ip"]) user = obj.host.owner lang = user.person_set.all()[0].language translation.activate(lang) - msg = render_to_string('mails/notification-ban-now.txt', - { 'user': user, - 'bl': obj, - 'instance:': obj.host.instance_set.get(), - 'url': settings.CLOUD_URL} ) - SendMailTask.delay(to=obj.host.owner.email, subject='[IK Cloud] %s' % obj.host.instance_set.get().name, msg=msg, sender=u'cloud@ik.bme.hu') - except (Host.DoesNotExist, ValidationError, IntegrityError, AttributeError): + msg = render_to_string( + 'mails/notification-ban-now.txt', + { + 'user': user, + 'bl': obj, + 'instance:': obj.host.instance_set.get(), + 'url': settings.CLOUD_URL + }) + SendMailTask.delay( + to=obj.host.owner.email, + subject='[IK Cloud] %s' % + obj.host.instance_set.get().name, + msg=msg, sender=u'cloud@ik.bme.hu') + except (Host.DoesNotExist, ValidationError, + IntegrityError, AttributeError): pass - print obj.modified_at + datetime.timedelta(minutes=5) - print datetime.datetime.utcnow().replace(tzinfo=utc) - if obj.type == 'tempwhite' and obj.modified_at + datetime.timedelta(minutes=1) < datetime.datetime.utcnow().replace(tzinfo=utc): + + modified = obj.modified_at + datetime.timedelta(minutes=1) + now = datetime.dateime.utcnow().replace(tzinfo=utc) + if obj.type == 'tempwhite' and modified < now: obj.type = 'tempban' obj.save() return HttpResponse(unicode(_("OK"))) @@ -76,28 +84,27 @@ def firewall_api(request): if command == "create": data["owner"] = "opennebula" owner = auth.models.User.objects.get(username=data["owner"]) - host = models.Host(hostname=data["hostname"], - vlan=models.Vlan.objects.get(name=data["vlan"]), - mac=data["mac"], ipv4=data["ip"], owner=owner, - description=data["description"], pub_ipv4=models. + host = Host(hostname=data["hostname"], + vlan=Vlan.objects.get(name=data["vlan"]), + mac=data["mac"], ipv4=data["ip"], owner=owner, + description=data["description"], pub_ipv4= Vlan.objects.get(name=data["vlan"]).snat_ip, - shared_ip=True) + shared_ip=True) host.full_clean() host.save() host.enable_net() for p in data["portforward"]: - host.add_port(proto=p["proto"], - public=int(p["public_port"]), - private=int(p["private_port"])) + host.add_port(proto=p["proto"], public=int(p["public_port"]), + private=int(p["private_port"])) elif command == "destroy": data["owner"] = "opennebula" print data["hostname"] owner = auth.models.User.objects.get(username=data["owner"]) - host = models.Host.objects.get(hostname=data["hostname"], - owner=owner) + host = Host.objects.get(hostname=data["hostname"], + owner=owner) host.delete() else: -- libgit2 0.26.0