X-Git-Url: https://code.grnet.gr/git/flowspy/blobdiff_plain/9cad4715db5a211de9a15e5e3a5e480819f92a4a..a9afab2127aa96676092131be4eb8b20967491e8:/flowspec/models.py diff --git a/flowspec/models.py b/flowspec/models.py index 257c660..165964b 100644 --- a/flowspec/models.py +++ b/flowspec/models.py @@ -4,13 +4,18 @@ from django.db import models from django.conf import settings from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ from utils import proxy as PR from ipaddr import * -from datetime import * +import datetime import logging -from flowspec.tasks import * from time import sleep +import beanstalkc +from flowspy.utils.randomizer import id_generator as id_gen + +from flowspec.tasks import * + FORMAT = '%(asctime)s %(levelname)s: %(message)s' logging.basicConfig(format=FORMAT) logger = logging.getLogger(__name__) @@ -35,11 +40,38 @@ THEN_CHOICES = ( ("sample", "Sample") ) +MATCH_PROTOCOL = ( + ("ah", "ah"), + ("egp", "egp"), + ("esp", "esp"), + ("gre", "gre"), + ("icmp", "icmp"), + ("icmp6", "icmp6"), + ("igmp", "igmp"), + ("ipip", "ipip"), + ("ospf", "ospf"), + ("pim", "pim"), + ("rsvp", "rsvp"), + ("sctp", "sctp"), + ("tcp", "tcp"), + ("udp", "udp"), +) -def days_offset(): return datetime.now() + timedelta(days = settings.EXPIRATION_DAYS_OFFSET) +ROUTE_STATES = ( + ("ACTIVE", "ACTIVE"), + ("ERROR", "ERROR"), + ("EXPIRED", "EXPIRED"), + ("PENDING", "PENDING"), + ("OUTOFSYNC", "OUTOFSYNC"), + ("INACTIVE", "INACTIVE"), + ("ADMININACTIVE", "ADMININACTIVE"), +) + + +def days_offset(): return datetime.date.today() + datetime.timedelta(days = settings.EXPIRATION_DAYS_OFFSET) class MatchPort(models.Model): - port = models.CharField(max_length=24) + port = models.CharField(max_length=24, unique=True) def __unicode__(self): return self.port class Meta: @@ -52,47 +84,66 @@ class MatchDscp(models.Model): class Meta: db_table = u'match_dscp' +class MatchProtocol(models.Model): + protocol = models.CharField(max_length=24, unique=True) + def __unicode__(self): + return self.protocol + class Meta: + db_table = u'match_protocol' + class ThenAction(models.Model): action = models.CharField(max_length=60, choices=THEN_CHOICES, verbose_name="Action") action_value = models.CharField(max_length=255, blank=True, null=True, verbose_name="Action Value") def __unicode__(self): - return "%s: %s" %(self.action, self.action_value) + ret = "%s:%s" %(self.action, self.action_value) + return ret.rstrip(":") class Meta: db_table = u'then_action' + ordering = ['action', 'action_value'] + unique_together = ("action", "action_value") class Route(models.Model): - name = models.CharField(max_length=128) + name = models.SlugField(max_length=128, verbose_name=_("Name")) applier = models.ForeignKey(User, blank=True, null=True) - source = models.CharField(max_length=32, blank=True, null=True, help_text=u"Network address. Use address/CIDR notation", verbose_name="Source Address") - sourceport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchSourcePort", verbose_name="Source Port") - destination = models.CharField(max_length=32, blank=True, null=True, help_text=u"Network address. Use address/CIDR notation", verbose_name="Destination Address") - destinationport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchDestinationPort", verbose_name="Destination Port") - port = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchPort", verbose_name="Port" ) + source = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address")) + sourceport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchSourcePort", verbose_name=_("Source Port")) + destination = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address")) + destinationport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchDestinationPort", verbose_name=_("Destination Port")) + port = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchPort", verbose_name=_("Port")) dscp = models.ManyToManyField(MatchDscp, blank=True, null=True, verbose_name="DSCP") fragmenttype = models.CharField(max_length=20, choices=FRAGMENT_CODES, blank=True, null=True, verbose_name="Fragment Type") icmpcode = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Code") icmptype = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Type") packetlength = models.IntegerField(blank=True, null=True, verbose_name="Packet Length") - protocol = models.CharField(max_length=32, blank=True, null=True, verbose_name="Protocol") + protocol = models.ManyToManyField(MatchProtocol, blank=True, null=True, verbose_name=_("Protocol")) tcpflag = models.CharField(max_length=128, blank=True, null=True, verbose_name="TCP flag") - then = models.ManyToManyField(ThenAction, verbose_name="Then") + then = models.ManyToManyField(ThenAction, verbose_name=_("Then")) filed = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) - is_online = models.BooleanField(default=False) - is_active = models.BooleanField(default=False) - expires = models.DateField(default=days_offset, blank=True, null=True,) - response = models.CharField(max_length=512, blank=True, null=True) - comments = models.TextField(null=True, blank=True, verbose_name="Comments") + status = models.CharField(max_length=20, choices=ROUTE_STATES, blank=True, null=True, verbose_name=_("Status"), default="PENDING") +# is_online = models.BooleanField(default=False) +# is_active = models.BooleanField(default=False) + expires = models.DateField(default=days_offset, verbose_name=_("Expires")) + response = models.CharField(max_length=512, blank=True, null=True, verbose_name=_("Response")) + comments = models.TextField(null=True, blank=True, verbose_name=_("Comments")) def __unicode__(self): return self.name class Meta: - unique_together = (("name", "is_active"),) db_table = u'route' + verbose_name = "Rule" + verbose_name_plural = "Rules" + def save(self, *args, **kwargs): + if not self.pk: + hash = id_gen() + self.name = "%s_%s" %(self.name, hash) + super(Route, self).save(*args, **kwargs) # Call the "real" save() method. + + def clean(self, *args, **kwargs): from django.core.exceptions import ValidationError if self.destination: @@ -100,47 +151,63 @@ class Route(models.Model): address = IPNetwork(self.destination) self.destination = address.exploded except Exception: - raise ValidationError('Invalid network address format at Destination Field') + raise ValidationError(_('Invalid network address format at Destination Field')) if self.source: try: address = IPNetwork(self.source) self.source = address.exploded except Exception: - raise ValidationError('Invalid network address format at Source Field') - -# def save(self, *args, **kwargs): -# edit = False -# if self.pk: -# #This is an edit -# edit = True -# super(Route, self).save(*args, **kwargs) -# if not edit: -# response = add.delay(self) -# logger.info("Got save job id: %s" %response) - + raise ValidationError(_('Invalid network address format at Source Field')) + def commit_add(self, *args, **kwargs): + peer = self.applier.get_profile().peer.domain_name + send_message("[%s] Adding rule %s. Please wait..." %(self.applier.username, self.name), peer) response = add.delay(self) - logger.info("Got save job id: %s" %response) -# -# def delete(self, *args, **kwargs): -# response = delete.delay(self) -# logger.info("Got delete job id: %s" %response) + logger.info("Got add job id: %s" %response) + def commit_edit(self, *args, **kwargs): + peer = self.applier.get_profile().peer.domain_name + send_message("[%s] Editing rule %s. Please wait..." %(self.applier.username, self.name), peer) + response = edit.delay(self) + logger.info("Got edit job id: %s" %response) + def commit_delete(self, *args, **kwargs): + reason_text = '' + reason = '' + if "reason" in kwargs: + reason = kwargs['reason'] + reason_text = "Reason: %s. " %reason + peer = self.applier.get_profile().peer.domain_name + send_message("[%s] Suspending rule %s. %sPlease wait..." %(self.applier.username, self.name, reason_text), peer) + response = delete.delay(self, reason=reason) + logger.info("Got delete job id: %s" %response) + + def has_expired(self): + today = datetime.date.today() + if today > self.expires: + return True + return False + + def check_sync(self): + if not self.is_synced(): + self.status = "OUTOFSYNC" + self.save() + def is_synced(self): - found = False get_device = PR.Retriever() device = get_device.fetch_device() try: routes = device.routing_options[0].routes except Exception as e: + self.status = "EXPIRED" + self.save() logger.error("No routing options on device. Exception: %s" %e) - return False + return True for route in routes: if route.name == self.name: found = True - logger.info('Found a matching route name') + logger.info('Found a matching rule name') devicematch = route.match try: assert(self.destination) @@ -197,21 +264,13 @@ class Route(models.Model): logger.info('Icmp type fields do not match') except: pass - try: - assert(self.protocol) - assert(devicematch['protocol'][0]) - if self.protocol == devicematch['protocol'][0]: - found = found and True - logger.info('Found a matching protocol') - else: - found = False - logger.info('Protocol fields do not match') - except: - pass - if found and not self.is_online: - logger.error('Rule is applied on device but appears as offline') - found = False - + if found and self.status != "ACTIVE": + logger.error('Rule is applied on device but appears as offline') + self.status = "ACTIVE" + self.save() + found = True + if self.status == "ADMININACTIVE" or self.status == "INACTIVE" or self.status == "EXPIRED": + found = True return found def get_then(self): @@ -230,35 +289,63 @@ class Route(models.Model): def get_match(self): ret = '' if self.destination: - ret = ret = '%s Destination Address:%s
' %(ret, self.destination) + ret = '%s Dst Addr:%s
' %(ret, self.destination) if self.fragmenttype: - ret = ret = "%s Fragment Type:%s
" %(ret, self.fragmenttype) + ret = "%s Fragment Type:%s
" %(ret, self.fragmenttype) if self.icmpcode: - ret = ret = "%s ICMP code:%s
" %(ret, self.icmpcode) + ret = "%s ICMP code:%s
" %(ret, self.icmpcode) if self.icmptype: - ret = ret = "%s ICMP Type:%s
" %(ret, self.icmptype) + ret = "%s ICMP Type:%s
" %(ret, self.icmptype) if self.packetlength: - ret = ret = "%s Packet Length:%s
" %(ret, self.packetlength) - if self.protocol: - ret = ret = "%s Protocol:%s
" %(ret, self.protocol) + ret = "%s Packet Length:%s
" %(ret, self.packetlength) if self.source: - ret = ret = "%s Source Address:%s
" %(ret, self.source) + ret = "%s Src Addr:%s
" %(ret, self.source) if self.tcpflag: - ret = ret = "%s TCP flag:%s
" %(ret, self.tcpflag) + ret = "%s TCP flag:%s
" %(ret, self.tcpflag) if self.port: for port in self.port.all(): - ret = "%s Port:%s
" %(ret, port) + ret = ret + "Port:%s
" %(port) + if self.protocol: + for protocol in self.protocol.all(): + ret = ret + "Protocol:%s
" %(protocol) if self.destinationport: for port in self.destinationport.all(): - ret = "%s Port:%s
" %(ret, port) + ret = ret + "Dst Port:%s
" %(port) if self.sourceport: for port in self.sourceport.all(): - ret = "%s Port:%s
" %(ret, port) + ret = ret +"Src Port:%s
" %(port) if self.dscp: for dscp in self.dscp.all(): - ret = "%s Port:%s
" %(ret, dscp) + ret = ret + "%s Port:%s
" %(ret, dscp) return ret.rstrip('
') get_match.short_description = 'Match statement' get_match.allow_tags = True + + @property + def applier_peer(self): + try: + applier_peer = self.applier.get_profile().peer + except: + applier_peer = None + return applier_peer + + @property + def days_to_expire(self): + if self.status not in ['EXPIRED', 'ADMININACTIVE', 'ERROR', 'INACTIVE']: + expiration_days = (self.expires - datetime.date.today()).days + if expiration_days < settings.EXPIRATION_NOTIFY_DAYS: + return "%s" %expiration_days + else: + return False + else: + return False +def send_message(msg, user): +# username = user.username + peer = user + b = beanstalkc.Connection() + b.use(settings.POLLS_TUBE) + tube_message = json.dumps({'message': str(msg), 'username':peer}) + b.put(tube_message) + b.close()