diff --git a/flowspec/admin.py b/flowspec/admin.py
index 18c9a5193d5f009b288a46388832d3ac5d02a394..ddb49aadc9bed29f0a307518b193371f9519e81a 100644
--- a/flowspec/admin.py
+++ b/flowspec/admin.py
@@ -18,7 +18,7 @@
#
from django.contrib import admin
-from flowspec.models import MatchPort, MatchDscp, MatchProtocol, FragmentType, ThenAction, Route
+from flowspec.models import MatchPort, MatchDscp, MatchProtocol, FragmentType, ThenAction, Route, Rule
from accounts.models import UserProfile
from utils import proxy as PR
from tasks import *
@@ -29,19 +29,25 @@ from flowspec.forms import *
from longerusername.forms import UserCreationForm, UserChangeForm
+class RuleAdmin(admin.ModelAdmin):
+ form = RuleForm
+
class RouteAdmin(admin.ModelAdmin):
form = RouteForm
actions = ['deactivate']
- search_fields = ['destination', 'name', 'applier__username']
+ search_fields = ['destination', 'name', 'applier_username']
def deactivate(self, request, queryset):
- queryset = queryset.filter(status='ACTIVE')
+ queryset = queryset.filter(rule__status='ACTIVE')
response = batch_delete.delay(queryset, reason="ADMININACTIVE")
self.message_user(request, "Added request %s to job que. Check in a while for result" % response)
deactivate.short_description = "Remove selected routes from network"
def save_model(self, request, obj, form, change):
- obj.status = "PENDING"
+ rule = Rule()
+ rule.save()
+ obj.rule = rule
+ obj.rule.status = "PENDING"
obj.save()
if change:
obj.commit_edit()
@@ -51,14 +57,11 @@ class RouteAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
- list_display = ('name', 'status', 'applier_username', 'applier_peers', 'get_match', 'get_then', 'response', "expires", "comments")
+ list_display = ('name', 'status', 'applier_username', 'applier_peers', 'get_match', 'get_then', 'response', "comments")
fieldsets = [
- (None, {'fields': ['name', 'applier']}),
("Match", {'fields': ['source', 'sourceport', 'destination', 'destinationport', 'port']}),
('Advanced Match Statements', {'fields': ['dscp', 'fragmenttype', 'icmpcode', 'icmptype', 'packetlength', 'protocol', 'tcpflag'], 'classes': ['collapse']}),
- ("Then", {'fields': ['then']}),
- ("Expires", {'fields': ['expires']}),
(None, {'fields': ['comments', ]}),
]
diff --git a/flowspec/forms.py b/flowspec/forms.py
index 856ab6ce97e74ad423b5d0247a7ecefd7509c3d5..0f9872b53efe07f9aad506c477b4fc716e947a1f 100644
--- a/flowspec/forms.py
+++ b/flowspec/forms.py
@@ -86,12 +86,9 @@ class UserProfileForm(forms.ModelForm):
model = UserProfile
-class RouteForm(forms.ModelForm):
- sourceport = PortRangeForm()
- destinationport = PortRangeForm()
- port = PortRangeForm()
+class RuleForm(forms.ModelForm):
class Meta:
- model = Route
+ model = Rule
def clean_applier(self):
applier = self.cleaned_data['applier']
@@ -100,31 +97,11 @@ class RouteForm(forms.ModelForm):
else:
raise forms.ValidationError('This field is required.')
- def clean_source(self):
- # run validator which is used by rest framework too
- source = self.cleaned_data['source']
- res = clean_source(
- User.objects.get(pk=self.data['applier']),
- source
- )
- if res != source:
- raise forms.ValidationError(res)
- else:
- return res
-
- def clean_destination(self):
- destination = self.cleaned_data.get('destination')
- res = clean_destination(
- User.objects.get(pk=self.data['applier']),
- destination
- )
- if destination != res:
- raise forms.ValidationError(res)
- else:
- return res
def clean_expires(self):
date = self.cleaned_data['expires']
+ if not date:
+ raise forms.ValidationError('This field is required.')
res = clean_expires(date)
if date != res:
raise forms.ValidationError(res)
@@ -143,8 +120,72 @@ class RouteForm(forms.ModelForm):
peers = Peer.objects.all()
else:
peers = user.userprofile.peers.all()
+
+ existing_routes = self.routes.all()
+ #existing_routes = existing_routes.filter(rule__applier__userprofile__peers__in=peers)
+ name = self.cleaned_data.get('name', None)
+ protocols = self.cleaned_data.get('protocol', None)
+ source = self.cleaned_data.get('source', None)
+ sourceports = self.cleaned_data.get('sourceport', None)
+ port = self.cleaned_data.get('port', None)
+ destination = self.cleaned_data.get('destination', None)
+ destinationports = self.cleaned_data.get('destinationport', None)
+ #user = self.cleaned_data.get('applier', None)
+
+ for route in existing_routes:
+ if name != route.name:
+ existing_url = reverse('edit-route', args=[route.name])
+ if IPNetwork(destination) in IPNetwork(route.destination) or IPNetwork(route.destination) in IPNetwork(destination):
+ raise forms.ValidationError('Found an exact %s rule, %s with destination prefix %s<br>To avoid overlapping try editing rule <a href=\'%s\'>%s</a>' % (route.status, route.name, route.destination, existing_url, route.name))
+ return self.cleaned_data
+
+class RouteForm(forms.ModelForm):
+ sourceport = PortRangeForm()
+ destinationport = PortRangeForm()
+ port = PortRangeForm()
+ class Meta:
+ model = Route
+
+ def clean_source(self):
+ # run validator which is used by rest framework too
+ source = self.cleaned_data['source']
+ #res = clean_source(
+ # User.objects.get(pk=self.data['applier']),
+ # source
+ #)
+ #if res != source:
+ # raise forms.ValidationError(res)
+ #else:
+ # return res
+ return source
+
+ def clean_destination(self):
+ destination = self.cleaned_data.get('destination')
+ #res = clean_destination(
+ # User.objects.get(pk=self.data['applier']),
+ # destination
+ #)
+ #if destination != res:
+ # raise forms.ValidationError(res)
+ #else:
+ # return res
+ return destination
+
+ def clean(self):
+ if self.errors:
+ raise forms.ValidationError(_('Errors in form. Please review and fix them: %s' % ", ".join(self.errors)))
+ error = clean_route_form(self.cleaned_data)
+ if error:
+ raise forms.ValidationError(error)
+
+ ## check if same rule exists with other name
+ #user = self.cleaned_data['applier']
+ #if user.is_superuser:
+ # peers = Peer.objects.all()
+ #else:
+ # peers = user.userprofile.peers.all()
existing_routes = Route.objects.all()
- existing_routes = existing_routes.filter(applier__userprofile__peers__in=peers)
+ #existing_routes = existing_routes.filter(rule__applier__userprofile__peers__in=peers)
name = self.cleaned_data.get('name', None)
protocols = self.cleaned_data.get('protocol', None)
source = self.cleaned_data.get('source', None)
@@ -152,7 +193,7 @@ class RouteForm(forms.ModelForm):
port = self.cleaned_data.get('port', None)
destination = self.cleaned_data.get('destination', None)
destinationports = self.cleaned_data.get('destinationport', None)
- user = self.cleaned_data.get('applier', None)
+ #user = self.cleaned_data.get('applier', None)
if source:
source = IPNetwork(source).compressed
diff --git a/flowspec/migrations/0008_auto__add_rule__del_field_route_status__del_field_route_last_updated__.py b/flowspec/migrations/0008_auto__add_rule__del_field_route_status__del_field_route_last_updated__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a88475317597b5641a1e8bfd8d2f64fe2de4b09
--- /dev/null
+++ b/flowspec/migrations/0008_auto__add_rule__del_field_route_status__del_field_route_last_updated__.py
@@ -0,0 +1,221 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Rule'
+ db.create_table(u'flowspec_rule', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.SlugField')(max_length=128)),
+ ('applier', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+ ('filed', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, auto_now_add=True, blank=True)),
+ ('last_updated', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, auto_now=True, blank=True)),
+ ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ('requesters_address', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
+ ('expires', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2018, 7, 10, 0, 0))),
+ ('status', self.gf('django.db.models.fields.CharField')(default='INACTIVE', max_length=20, null=True, blank=True)),
+ ))
+ db.send_create_signal('flowspec', ['Rule'])
+
+ # Adding M2M table for field then on 'Rule'
+ db.create_table(u'flowspec_rule_then', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('rule', models.ForeignKey(orm['flowspec.rule'], null=False)),
+ ('thenaction', models.ForeignKey(orm['flowspec.thenaction'], null=False))
+ ))
+ db.create_unique(u'flowspec_rule_then', ['rule_id', 'thenaction_id'])
+
+ # Deleting field 'Route.status'
+ db.delete_column(u'route', 'status')
+
+ # Deleting field 'Route.last_updated'
+ db.delete_column(u'route', 'last_updated')
+
+ # Deleting field 'Route.requesters_address'
+ db.delete_column(u'route', 'requesters_address')
+
+ # Deleting field 'Route.expires'
+ db.delete_column(u'route', 'expires')
+
+ # Deleting field 'Route.filed'
+ db.delete_column(u'route', 'filed')
+
+ # Deleting field 'Route.applier'
+ db.delete_column(u'route', 'applier_id')
+
+ # Adding field 'Route.rule'
+ db.add_column(u'route', 'rule',
+ self.gf('django.db.models.fields.related.ForeignKey')(related_name='routes', null=True, to=orm['flowspec.Rule']),
+ keep_default=False)
+
+ # Removing M2M table for field then on 'Route'
+ db.delete_table('route_then')
+
+
+ # Changing field 'Route.destinationport'
+ db.alter_column(u'route', 'destinationport', self.gf('django.db.models.fields.CharField')(max_length=65535, null=True))
+
+ # Changing field 'Route.sourceport'
+ db.alter_column(u'route', 'sourceport', self.gf('django.db.models.fields.CharField')(max_length=65535, null=True))
+
+ # Changing field 'Route.port'
+ db.alter_column(u'route', 'port', self.gf('django.db.models.fields.CharField')(max_length=65535, null=True))
+
+ def backwards(self, orm):
+ # Deleting model 'Rule'
+ db.delete_table(u'flowspec_rule')
+
+ # Removing M2M table for field then on 'Rule'
+ db.delete_table('flowspec_rule_then')
+
+ # Adding field 'Route.status'
+ db.add_column(u'route', 'status',
+ self.gf('django.db.models.fields.CharField')(default='PENDING', max_length=20, null=True, blank=True),
+ keep_default=False)
+
+
+ # User chose to not deal with backwards NULL issues for 'Route.last_updated'
+ raise RuntimeError("Cannot reverse this migration. 'Route.last_updated' and its values cannot be restored.")
+ # Adding field 'Route.requesters_address'
+ db.add_column(u'route', 'requesters_address',
+ self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Route.expires'
+ db.add_column(u'route', 'expires',
+ self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2017, 2, 8, 0, 0)),
+ keep_default=False)
+
+
+ # User chose to not deal with backwards NULL issues for 'Route.filed'
+ raise RuntimeError("Cannot reverse this migration. 'Route.filed' and its values cannot be restored.")
+ # Adding field 'Route.applier'
+ db.add_column(u'route', 'applier',
+ self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True),
+ keep_default=False)
+
+ # Deleting field 'Route.rule'
+ db.delete_column(u'route', 'rule_id')
+
+ # Adding M2M table for field then on 'Route'
+ db.create_table(u'route_then', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('route', models.ForeignKey(orm['flowspec.route'], null=False)),
+ ('thenaction', models.ForeignKey(orm['flowspec.thenaction'], null=False))
+ ))
+ db.create_unique(u'route_then', ['route_id', 'thenaction_id'])
+
+
+ # Changing field 'Route.destinationport'
+ db.alter_column(u'route', 'destinationport', self.gf('django.db.models.fields.CharField')(max_length=50, null=True))
+
+ # Changing field 'Route.sourceport'
+ db.alter_column(u'route', 'sourceport', self.gf('django.db.models.fields.CharField')(max_length=50, null=True))
+
+ # Changing field 'Route.port'
+ db.alter_column(u'route', 'port', self.gf('django.db.models.fields.CharField')(max_length=50, null=True))
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'flowspec.fragmenttype': {
+ 'Meta': {'object_name': 'FragmentType'},
+ 'fragmenttype': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'flowspec.matchdscp': {
+ 'Meta': {'object_name': 'MatchDscp', 'db_table': "u'match_dscp'"},
+ 'dscp': ('django.db.models.fields.CharField', [], {'max_length': '24'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'flowspec.matchport': {
+ 'Meta': {'object_name': 'MatchPort', 'db_table': "u'match_port'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'port': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '24'})
+ },
+ 'flowspec.matchprotocol': {
+ 'Meta': {'object_name': 'MatchProtocol', 'db_table': "u'match_protocol'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'protocol': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '24'})
+ },
+ 'flowspec.route': {
+ 'Meta': {'object_name': 'Route', 'db_table': "u'route'"},
+ 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'destination': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'destinationport': ('django.db.models.fields.CharField', [], {'max_length': '65535', 'null': 'True', 'blank': 'True'}),
+ 'dscp': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['flowspec.MatchDscp']", 'null': 'True', 'blank': 'True'}),
+ 'fragmenttype': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['flowspec.FragmentType']", 'null': 'True', 'blank': 'True'}),
+ 'icmpcode': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'icmptype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '128'}),
+ 'packetlength': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'port': ('django.db.models.fields.CharField', [], {'max_length': '65535', 'null': 'True', 'blank': 'True'}),
+ 'protocol': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['flowspec.MatchProtocol']", 'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+ 'rule': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'routes'", 'null': 'True', 'to': "orm['flowspec.Rule']"}),
+ 'source': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'sourceport': ('django.db.models.fields.CharField', [], {'max_length': '65535', 'null': 'True', 'blank': 'True'}),
+ 'tcpflag': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
+ },
+ 'flowspec.rule': {
+ 'Meta': {'object_name': 'Rule'},
+ 'applier': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'expires': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2018, 7, 10, 0, 0)'}),
+ 'filed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '128'}),
+ 'requesters_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'INACTIVE'", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'then': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['flowspec.ThenAction']", 'symmetrical': 'False'})
+ },
+ 'flowspec.thenaction': {
+ 'Meta': {'ordering': "['action', 'action_value']", 'unique_together': "(('action', 'action_value'),)", 'object_name': 'ThenAction', 'db_table': "u'then_action'"},
+ 'action': ('django.db.models.fields.CharField', [], {'max_length': '60'}),
+ 'action_value': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ }
+ }
+
+ complete_apps = ['flowspec']
\ No newline at end of file
diff --git a/flowspec/models.py b/flowspec/models.py
index 64319df607266e890d56b6347dbc5d27e05d216f..4f0677037e72dbc44b2f95ca79584086b3f352b3 100644
--- a/flowspec/models.py
+++ b/flowspec/models.py
@@ -22,7 +22,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse, NoReverseMatch
from flowspec.helpers import send_new_mail, get_peer_techc_mails
from utils import proxy as PR
@@ -135,31 +135,22 @@ class ThenAction(models.Model):
unique_together = ("action", "action_value")
-class Route(models.Model):
+class Rule(models.Model):
name = models.SlugField(max_length=128, verbose_name=_("Name"))
applier = models.ForeignKey(User, blank=True, null=True)
- source = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address"))
- sourceport = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Source Port"))
- destination = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address"))
- destinationport = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Destination Port"))
- port = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Port"))
- dscp = models.ManyToManyField(MatchDscp, blank=True, null=True, verbose_name="DSCP")
- fragmenttype = models.ManyToManyField(FragmentType, 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.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"))
- filed = models.DateTimeField(auto_now_add=True)
- last_updated = models.DateTimeField(auto_now=True)
- 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"))
+ filed = models.DateTimeField(auto_now_add=True, default=datetime.datetime.now, null=False)
+ last_updated = models.DateTimeField(auto_now=True, default=datetime.datetime.now, null=False)
comments = models.TextField(null=True, blank=True, verbose_name=_("Comments"))
requesters_address = models.CharField(max_length=255, blank=True, null=True)
+ expires = models.DateField(default=days_offset, verbose_name=_("Expires"))
+ status = models.CharField(max_length=20, choices=ROUTE_STATES, blank=True, null=True, verbose_name=_("Status"), default="INACTIVE")
+
+
+ class Meta:
+ db_table = u'flowspec_rule'
+ verbose_name = "Rule"
+ verbose_name_plural = "Rules"
@property
def applier_username(self):
@@ -168,35 +159,32 @@ class Route(models.Model):
else:
return None
- def __unicode__(self):
- return self.name
-
- class Meta:
- 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.
+ super(Rule, self).save(*args, **kwargs) # Call the "real" save() method.
- def clean(self, *args, **kwargs):
- from django.core.exceptions import ValidationError
- if self.destination:
- try:
- address = IPNetwork(self.destination)
- self.destination = address.exploded
- except Exception:
- 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 _send_mail(self, *args, **kwargs):
+ args = kwargs.get("args")
+ fqdn = Site.objects.get_current().domain
+ try:
+ admin_url = 'https://%s%s' % (fqdn, reverse(args.get("url_path"), kwargs={args.get("url_id"): self.name}))
+ except NoReverseMatch:
+ admin_url = "Unknown"
+ args["url"] = admin_url
+ mail_body = render_to_string('rule_action.txt', args)
+ user_mail = '%s' % self.applier.email
+ user_mail = user_mail.split(';')
+ send_new_mail(
+ args.get("subject"),
+ mail_body,
+ settings.SERVER_EMAIL, user_mail,
+ get_peer_techc_mails(self.applier, args.get("peer"))
+ )
+ return mail_body
+
def commit_add(self, *args, **kwargs):
peers = self.applier.get_profile().peers.all()
username = None
@@ -205,9 +193,10 @@ class Route(models.Model):
break
for network in peer.networks.all():
net = IPNetwork(network)
- if IPNetwork(self.destination) in net:
- username = peer
- break
+ for route in self.routes.all():
+ if IPNetwork(route.destination) in net:
+ username = peer
+ break
if username:
peer = username.peer_tag
else:
@@ -215,29 +204,17 @@ class Route(models.Model):
send_message("[%s] Adding rule %s. Please wait..." % (self.applier.username, self.name), peer)
response = add.delay(self)
logger.info('Got add job id: %s' % response)
- fqdn = Site.objects.get_current().domain
- admin_url = 'https://%s%s' % (
- fqdn,
- reverse('edit-route', kwargs={'route_slug': self.name})
- )
- mail_body = render_to_string(
- 'rule_action.txt',
- {
- 'route': self,
+ mail_body = self._send_mail(args={
+ 'url_path': 'edit-route',
+ 'url_id': 'route_slug',
+ 'rule': self,
+ 'routes': self.routes.all(),
'address': self.requesters_address,
'action': 'creation',
- 'url': admin_url,
- 'peer': username
- }
- )
- user_mail = '%s' % self.applier.email
- user_mail = user_mail.split(';')
- send_new_mail(
- settings.EMAIL_SUBJECT_PREFIX + 'Rule %s creation request submitted by %s' % (self.name, self.applier.username),
- mail_body,
- settings.SERVER_EMAIL, user_mail,
- get_peer_techc_mails(self.applier, username)
- )
+ 'peer': username,
+ 'subject': settings.EMAIL_SUBJECT_PREFIX + 'Rule %s creation request submitted by %s' % (self.name, self.applier.username),
+ })
+
d = {
'clientip': '%s' % self.requesters_address,
'user': self.applier.username
@@ -252,9 +229,10 @@ class Route(models.Model):
break
for network in peer.networks.all():
net = IPNetwork(network)
- if IPNetwork(self.destination) in net:
- username = peer
- break
+ for route in self.routes.all():
+ if IPNetwork(route.destination) in net:
+ username = peer
+ break
if username:
peer = username.peer_tag
else:
@@ -268,31 +246,17 @@ class Route(models.Model):
)
response = edit.delay(self)
logger.info('Got edit job id: %s' % response)
- fqdn = Site.objects.get_current().domain
- admin_url = 'https://%s%s' % (
- fqdn,
- reverse(
- 'edit-route',
- kwargs={'route_slug': self.name}
- )
- )
- mail_body = render_to_string(
- 'rule_action.txt',
- {
- 'route': self,
+ mail_body = self._send_mail(args={
+ 'url_path': 'edit-route',
+ 'url_id': 'route_slug',
+ 'rule': self,
+ 'routes': self.routes.all(),
'address': self.requesters_address,
'action': 'edit',
- 'url': admin_url,
- 'peer': username
- }
- )
- user_mail = '%s' % self.applier.email
- user_mail = user_mail.split(';')
- send_new_mail(
- settings.EMAIL_SUBJECT_PREFIX + 'Rule %s edit request submitted by %s' % (self.name, self.applier.username),
- mail_body, settings.SERVER_EMAIL, user_mail,
- get_peer_techc_mails(self.applier, username)
- )
+ 'peer': username,
+ 'subject': settings.EMAIL_SUBJECT_PREFIX + 'Rule %s edit request submitted by %s' % (self.name, self.applier.username),
+ })
+
d = {
'clientip': self.requesters_address,
'user': self.applier.username
@@ -312,9 +276,10 @@ class Route(models.Model):
break
for network in peer.networks.all():
net = IPNetwork(network)
- if IPNetwork(self.destination) in net:
- username = peer
- break
+ for route in self.routes.all():
+ if IPNetwork(route.destination) in net:
+ username = peer
+ break
if username:
peer = username.peer_tag
else:
@@ -328,45 +293,92 @@ class Route(models.Model):
)
response = delete.delay(self, reason=reason)
logger.info('Got delete job id: %s' % response)
- fqdn = Site.objects.get_current().domain
- admin_url = 'https://%s%s' % (
- fqdn,
- reverse(
- 'edit-route',
- kwargs={'route_slug': self.name}
- )
- )
- mail_body = render_to_string(
- 'rule_action.txt',
- {
- 'route': self,
+
+ mail_body = self._send_mail(args={
+ 'url_path': 'edit-route',
+ 'url_id': 'route_slug',
+ 'rule': self,
+ 'routes': self.routes.all(),
'address': self.requesters_address,
'action': 'removal',
- 'url': admin_url,
- 'peer': username
- }
- )
- user_mail = '%s' % self.applier.email
- user_mail = user_mail.split(';')
- send_new_mail(
- settings.EMAIL_SUBJECT_PREFIX + 'Rule %s removal request submitted by %s' % (self.name, self.applier.username),
- mail_body,
- settings.SERVER_EMAIL,
- user_mail,
- get_peer_techc_mails(self.applier, username)
- )
+ 'peer': username,
+ 'subject': settings.EMAIL_SUBJECT_PREFIX + 'Rule %s removal request submitted by %s' % (self.name, self.applier.username),
+ })
d = {
'clientip': self.requesters_address,
'user': self.applier.username
}
logger.info(mail_body, extra=d)
+
+class Route(models.Model):
+ name = models.SlugField(max_length=128, verbose_name=_("Name"))
+ rule = models.ForeignKey(Rule, related_name='routes', null=True)
+ source = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address"))
+ sourceport = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Source Port"))
+ destination = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address"))
+ destinationport = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Destination Port"))
+ port = models.CharField(max_length=65535, blank=True, null=True, verbose_name=_("Port"))
+ dscp = models.ManyToManyField(MatchDscp, blank=True, null=True, verbose_name="DSCP")
+ fragmenttype = models.ManyToManyField(FragmentType, 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.ManyToManyField(MatchProtocol, blank=True, null=True, verbose_name=_("Protocol"))
+ tcpflag = models.CharField(max_length=128, blank=True, null=True, verbose_name="TCP flag")
+# is_online = models.BooleanField(default=False)
+# is_active = models.BooleanField(default=False)
+ response = models.CharField(max_length=512, blank=True, null=True, verbose_name=_("Response"))
+ comments = models.TextField(null=True, blank=True, verbose_name=_("Comments"))
+
+ @property
+ def applier_username(self):
+ if self.rule and self.rule.applier:
+ return self.rule.applier.username
+ else:
+ return None
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ db_table = u'route'
+ verbose_name = "Route"
+ verbose_name_plural = "Routes"
+
+ 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:
+ try:
+ address = IPNetwork(self.destination)
+ self.destination = address.exploded
+ except Exception:
+ 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 has_expired(self):
today = datetime.date.today()
if today > self.expires:
return True
return False
+ def status(self):
+ if self.rule:
+ return self.rule.status
+ else:
+ return ROUTE_STATES["INACTIVE"]
+
def check_sync(self):
if not self.is_synced():
self.status = "OUTOFSYNC"
diff --git a/flowspec/serializers.py b/flowspec/serializers.py
index 287b53355dd47b8edfa158520b80e122d986a545..28bb9a5c3d4f6d0d0b2137077606ac7cab65a23f 100644
--- a/flowspec/serializers.py
+++ b/flowspec/serializers.py
@@ -1,6 +1,10 @@
+# -*- coding: utf-8 -*- vim:fileencoding=utf-8:
+# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
+
from rest_framework import serializers
from flowspec.models import (
Route,
+ Rule,
MatchPort,
ThenAction,
FragmentType,
@@ -14,9 +18,52 @@ from flowspec.validators import (
)
-class RouteSerializer(serializers.HyperlinkedModelSerializer):
+class PeerSerializer(serializers.HyperlinkedModelSerializer):
+ pass
+
+class RuleSerializer(serializers.HyperlinkedModelSerializer):
applier = serializers.CharField(source='applier_username', read_only=True)
+ def validate_expires(self, attrs, source):
+ print("validate expires ")
+ value = attrs[source]
+ if not value:
+ raise serializers.ValidationError('This field is required')
+ res = clean_expires(value)
+ if res != value:
+ raise serializers.ValidationError(res)
+ return attrs
+
+ def validate_then(self, attrs, source):
+ if not source:
+ raise serializers.ValidationError('This field is required')
+ return attrs
+
+ def validate(self, data):
+ user = self.context.get('request').user
+ return data
+
+
+ class Meta:
+ model = Rule
+ fields = (
+ 'name',
+ 'id',
+ 'comments',
+ 'applier',
+ 'then',
+ 'routes',
+ 'filed',
+ 'last_updated',
+ 'expires',
+ 'status',
+ 'requesters_address',
+ 'url'
+ )
+ read_only_fields = ('requesters_address', )
+
+class RouteSerializer(serializers.HyperlinkedModelSerializer):
+
def validate(self, data):
user = self.context.get('request').user
# validate source
@@ -37,14 +84,6 @@ class RouteSerializer(serializers.HyperlinkedModelSerializer):
if res != destination:
raise serializers.ValidationError(res)
- # validate expires
- expires = data.get('expires')
- res = clean_expires(
- expires
- )
- if res != expires:
- raise serializers.ValidationError(res)
-
# check if rule already exists with different name
fields = {
'source': data.get('source'),
@@ -60,8 +99,7 @@ class RouteSerializer(serializers.HyperlinkedModelSerializer):
fields = (
'name',
'id',
- 'comments',
- 'applier',
+ 'rule',
'source',
'sourceport',
'destination',
@@ -73,16 +111,10 @@ class RouteSerializer(serializers.HyperlinkedModelSerializer):
'packetlength',
'protocol',
'tcpflag',
- 'then',
- 'filed',
- 'last_updated',
- 'status',
- 'expires',
'response',
- 'comments',
- 'requesters_address',
+ 'url',
)
- read_only_fields = ('status', 'expires', 'requesters_address', 'response')
+ read_only_fields = ('response', 'id')
class PortSerializer(serializers.HyperlinkedModelSerializer):
diff --git a/flowspec/validators.py b/flowspec/validators.py
index 96bd4e7dcb47922707ddb1735d5646f66194bbf7..dcd0c9975d81c0ef974332a2a91193c28678ddd9 100644
--- a/flowspec/validators.py
+++ b/flowspec/validators.py
@@ -4,7 +4,7 @@ from django.conf import settings
from django.core.mail import send_mail
from django.utils.translation import ugettext as _
from peers.models import PeerRange, Peer
-from flowspec.models import Route
+from flowspec.models import Route, Rule
from django.core.urlresolvers import reverse
@@ -149,5 +149,5 @@ def check_if_rule_exists(fields):
destination=IPNetwork(fields.get('destination')).compressed,
)
for route in routes:
- return _('Rule exists with id %s and status %s. Please edit it.' % (route.id, route.status))
+ return _('Rule exists with id {id} and status {status}. Please edit it.'.format(id=route.id, status=route.status))
return False
diff --git a/flowspec/viewsets.py b/flowspec/viewsets.py
index ee6f9ea17eaaae7e6fd23d2b9cf22145e8d0eeda..4b16a915b0841fe1e5e5855d22f570d92569ee7d 100644
--- a/flowspec/viewsets.py
+++ b/flowspec/viewsets.py
@@ -1,19 +1,26 @@
from django.shortcuts import get_object_or_404
from django.conf import settings
+from django.http import HttpResponse
from rest_framework.exceptions import PermissionDenied
+import json
from rest_framework import viewsets
from flowspec.models import (
Route,
+ Rule,
+ User,
MatchPort,
ThenAction,
FragmentType,
MatchProtocol
)
+from peers.models import PeerRange
from flowspec.serializers import (
+ RuleSerializer,
RouteSerializer,
PortSerializer,
+ PeerSerializer,
ThenActionSerializer,
FragmentTypeSerializer,
MatchProtocolSerializer,
@@ -21,6 +28,71 @@ from flowspec.serializers import (
from rest_framework.response import Response
+class PeerViewSet(viewsets.ViewSet):
+ queryset = User.objects.all()
+ def get_queryset(self):
+ if self.request.user.is_authenticated: # and not self.request.user.is_anonymous:
+ pr = PeerRange.objects.filter(peer__user_profile__peers=self.request.user)
+ return [str(net) for net in pr]
+ else:
+ raise PermissionDenied('User is not Authenticated')
+
+ def list(self, request):
+ return HttpResponse(json.dumps({"networks": self.get_queryset()}), content_type="application/json")
+
+class RuleViewSet(viewsets.ModelViewSet):
+ queryset = Rule.objects.all()
+ serializer_class = RuleSerializer
+
+ def get_queryset(self):
+ if settings.DEBUG:
+ if self.request.user.is_anonymous():
+ return Rule.objects.all()
+ elif self.request.user.is_authenticated():
+ return Rule.objects.filter(applier=self.request.user)
+ else:
+ raise PermissionDenied('User is not Authenticated')
+
+ if self.request.user.is_superuser:
+ return Rule.objects.all()
+ elif self.request.user.is_authenticated and not self.request.user.is_anonymous:
+ return Rule.objects.filter(applier=self.request.user)
+
+ def list(self, request):
+ serializer = RuleSerializer(self.get_queryset(), many=True, context={'request': request})
+ return Response(serializer.data)
+
+ def create(self, request):
+ serializer = RuleSerializer(context={'request': request})
+ return super(RuleViewSet, self).create(request)
+
+ def retrieve(self, request, pk=None):
+ rule = get_object_or_404(self.get_queryset(), pk=pk)
+ serializer = RuleSerializer(rule)
+ return Response(serializer.data)
+
+ def pre_save(self, obj):
+ # DEBUG
+ if settings.DEBUG:
+ if self.request.user.is_anonymous():
+ from django.contrib.auth.models import User
+ obj.applier = User.objects.all()[0]
+ elif self.request.user.is_authenticated():
+ obj.applier = self.request.user
+ else:
+ raise PermissionDenied('User is not Authenticated')
+ else:
+ obj.applier = self.request.user
+
+ def post_save(self, obj, created):
+ if created:
+ obj.commit_add()
+ else:
+ if obj.status not in ['EXPIRED', 'INACTIVE', 'ADMININACTIVE']:
+ obj.commit_edit()
+
+ def pre_delete(self, obj):
+ obj.commit_delete()
class RouteViewSet(viewsets.ModelViewSet):
queryset = Route.objects.all()
@@ -31,7 +103,7 @@ class RouteViewSet(viewsets.ModelViewSet):
if self.request.user.is_anonymous():
return Route.objects.all()
elif self.request.user.is_authenticated():
- return Route.objects.filter(applier=self.request.user)
+ return Route.objects.filter(rule__applier=self.request.user)
else:
raise PermissionDenied('User is not Authenticated')
@@ -66,15 +138,15 @@ class RouteViewSet(viewsets.ModelViewSet):
else:
obj.applier = self.request.user
- def post_save(self, obj, created):
- if created:
- obj.commit_add()
- else:
- if obj.status not in ['EXPIRED', 'INACTIVE', 'ADMININACTIVE']:
- obj.commit_edit()
+ #def post_save(self, obj, created):
+ # if created:
+ # obj.commit_add()
+ # else:
+ # if obj.status not in ['EXPIRED', 'INACTIVE', 'ADMININACTIVE']:
+ # obj.commit_edit()
- def pre_delete(self, obj):
- obj.commit_delete()
+ #def pre_delete(self, obj):
+ # obj.commit_delete()
class PortViewSet(viewsets.ModelViewSet):
diff --git a/flowspy/urls.py.dist b/flowspy/urls.py.dist
index 5e69d9e514baccfe2e46375a974f8ef92259ded3..adfa4820731608eabada615214996a96477ec405 100644
--- a/flowspy/urls.py.dist
+++ b/flowspy/urls.py.dist
@@ -4,8 +4,10 @@ from django.contrib import admin
from rest_framework import routers
from graphs import urls as graphs_urls
from flowspec.viewsets import (
+ RuleViewSet,
RouteViewSet,
PortViewSet,
+ PeerViewSet,
ThenActionViewSet,
FragmentTypeViewSet,
MatchProtocolViewSet,
@@ -15,8 +17,10 @@ admin.autodiscover()
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
+router.register(r'rules', RuleViewSet, base_name='rule')
router.register(r'routes', RouteViewSet, base_name='route')
router.register(r'ports', PortViewSet)
+router.register(r'peers', PeerViewSet)
router.register(r'thenactions', ThenActionViewSet)
router.register(r'fragmentypes', FragmentTypeViewSet)
router.register(r'matchprotocol', MatchProtocolViewSet)
diff --git a/templates/rule_action.txt b/templates/rule_action.txt
index e88b2d5dece2e4a3857678e16249e4b24487ca76..56b4508f1d8e411ea07b7cc863004d8bf626af5b 100644
--- a/templates/rule_action.txt
+++ b/templates/rule_action.txt
@@ -1,10 +1,11 @@
-{% load tofqdn %}{% ifequal action 'expires' %}Rule {{route.name}} expires {% ifequal expiration_days 0 %}today{% else%}in {{expiration_days}} day{{ expiration_days|pluralize }}{% endifequal %}{% else %}A new rule {{action}} job has spawned
+{% load tofqdn %}{% ifequal action 'expires' %}Rule {{rule.name}} expires {% ifequal expiration_days 0 %}today{% else%}in {{expiration_days}} day{{ expiration_days|pluralize }}{% endifequal %}{% else %}A new rule {{action}} job has spawned
Peer: {{peer.peer_name}}
User {{route.applier.username}} requested the {{action}} of the following rule from address {{address}} {% if address|tofqdn %}({{address|tofqdn}}){% endif %}:
-Rule name: {{route.name}}{% endifequal %}
+Rule name: {{rule.name}}{% endifequal %}
+{% for route in routes %}
Match Statements:
* Source Address: {{route.source}}
* Destination Address: {{route.destination}}
@@ -14,13 +15,15 @@ Match Statements:
* Destination Ports: {% if route.port %}same as ports{% else %}{% if route.destinationport %}{{ route.destinationport }}{% else %}any{% endif %}{% endif %}
* Fragment Types: {% if route.fragmenttype.all %}{% for fragment in route.fragmenttype.all %}{{ fragment }}{% if not forloop.last %}, {% endif %}{% endfor %}{% else %}-{% endif %}
+{% endfor %}
+
Then Actions:
-* Action:{% for then in route.then.all %}{{ then }}{% if not forloop.last %}, {% endif %}{% endfor %}
+* Action:{% for then in rule.then.all %}{{ then }}{% if not forloop.last %}, {% endif %}{% endfor %}
-Comments: {% if route.comments %}{{route.comments}}{% else %}-{% endif %}
+Comments: {% if rule.comments %}{{rule.comments}}{% else %}-{% endif %}
-Expires: {% ifequal action 'removal' %}Now, removal requested{%else%}{{route.expires}}{% endifequal %}
+Expires: {% ifequal action 'removal' %}Now, removal requested{%else%}{{rule.expires}}{% endifequal %}
Rule url: {{url}}