From 357d48dc2eace5d0bb24b9f7d8b785a63209da6b Mon Sep 17 00:00:00 2001 From: Leonidas Poulopoulos Date: Sun, 20 Nov 2011 09:47:28 +0200 Subject: [PATCH] Added templates. Moved functionality out of models --- flowspec/admin.py | 30 +++++++- flowspec/models.py | 192 +++++++++++++++++++++++++++++++++++++++++----------- flowspec/views.py | 27 ++++++++ urls.py | 14 +++- 4 files changed, 221 insertions(+), 42 deletions(-) diff --git a/flowspec/admin.py b/flowspec/admin.py index 13ece6d..283bd64 100644 --- a/flowspec/admin.py +++ b/flowspec/admin.py @@ -1,5 +1,29 @@ from django.contrib import admin from flowspy.flowspec.models import * +from utils import proxy as PR + +class RouteAdmin(admin.ModelAdmin): + + actions = ['deactivate'] + + def deactivate(self, request, queryset): + applier = PR.Applier(route_objects=queryset) + commit, response = applier.apply(configuration=applier.delete_routes()) + if commit: + rows = queryset.update(is_online=False) + queryset.update(response="Successfully removed route from network") + self.message_user(request, "Successfully removed %s routes from network" % rows) + else: + self.message_user(request, "Could not remove routes from network") + deactivate.short_description = "Remove selected routes from network" + + list_display = ('name', 'get_match', 'get_then', 'is_online', 'applier', 'response') + fields = ('name', 'match','then','applier', 'expires') + + #def formfield_for_dbfield(self, db_field, **kwargs): + # if db_field.name == 'password': + # kwargs['widget'] = PasswordInput + # return db_field.formfield(**kwargs) admin.site.register(MatchAddress) admin.site.register(MatchPort) @@ -13,4 +37,8 @@ admin.site.register(MatchTcpFlag) admin.site.register(ThenAction) admin.site.register(ThenStatement) admin.site.register(MatchStatement) -admin.site.register(Route) +admin.site.register(Route, RouteAdmin) + +admin.site.disable_action('delete_selected') + + diff --git a/flowspec/models.py b/flowspec/models.py index cf69432..983d698 100644 --- a/flowspec/models.py +++ b/flowspec/models.py @@ -3,12 +3,15 @@ from django.db import models from django.contrib.auth.models import User - -import nxpy as np -from ncclient import manager -from ncclient.transport.errors import AuthenticationError, SSHError -from lxml import etree as ET +from utils import proxy as PR from ipaddr import * +import logging + +FORMAT = '%(asctime)s %(levelname)s: %(message)s' +logging.basicConfig(format=FORMAT) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + FRAGMENT_CODES = ( ("dont-fragment", "Don't fragment"), @@ -38,7 +41,8 @@ class MatchAddress(models.Model): def clean(self, *args, **kwargs): from django.core.exceptions import ValidationError try: - assert(IPNetwork(self.address)) + address = IPNetwork(self.address) + self.address = address.exploded except Exception: raise ValidationError('Invalid network address format') @@ -108,7 +112,7 @@ class ThenAction(models.Model): class ThenStatement(models.Model): thenaction = models.ManyToManyField(ThenAction) class Meta: - db_table = u'then' + db_table = u'then' class MatchStatement(models.Model): matchDestination = models.ForeignKey(MatchAddress, blank=True, null=True, related_name="matchDestination") @@ -123,7 +127,7 @@ class MatchStatement(models.Model): matchSource = models.ForeignKey(MatchAddress, blank=True, null=True, related_name="matchSource") matchSourcePort = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchSourcePort") matchTcpFlag = models.ForeignKey(MatchTcpFlag, blank=True, null=True) - + # def clean(self, *args, **kwargs): # clean_error = True # from django.core.exceptions import ValidationError @@ -164,7 +168,11 @@ class Route(models.Model): then = models.ForeignKey(ThenStatement) filed = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) + is_online = models.BooleanField(default=False) expires = models.DateTimeField() + response = models.CharField(max_length=512, blank=True, null=True) + + def __unicode__(self): return self.name @@ -172,45 +180,151 @@ class Route(models.Model): db_table = u'route' def save(self, *args, **kwargs): - # Begin translation to device xml configuration - device = np.Device() - flow = np.Flow() - route = np.Route() - flow.routes.append(route) - device.routing_options.append(flow) - route.name = self.name + applier = PR.Applier(route_object=self) + commit, response = applier.apply() + if commit: + self.is_online = True + self.response = response + else: + self.is_online = False + self.response = response + super(Route, self).save(*args, **kwargs) + + 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: + logger.error("No routing options on device. Exception: %s" %e) + return False + for route in routes: + if route.name == self.name: + found = True + logger.info('Found a matching route name') + devicematch = route.match + routematch = self.match + try: + assert(routematch.matchDestination.address) + assert(devicematch['destination'][0]) + if routematch.matchDestination.address == devicematch['destination'][0]: + found = found and True + logger.info('Found a matching destination') + else: + found = False + logger.info('Destination fields do not match') + except: + pass + try: + assert(routematch.matchSource.address) + assert(devicematch['source'][0]) + if routematch.matchSource.address == devicematch['source'][0]: + found = found and True + logger.info('Found a matching source') + else: + found = False + logger.info('Source fields do not match') + except: + pass + try: + assert(routematch.matchfragmenttype.fragmenttype) + assert(devicematch['fragment'][0]) + if routematch.matchfragmenttype.fragmenttype == devicematch['fragment'][0]: + found = found and True + logger.info('Found a matching fragment type') + else: + found = False + logger.info('Fragment type fields do not match') + except: + pass + try: + assert(routematch.matchicmpcode.icmp_code) + assert(devicematch['icmp-code'][0]) + if routematch.matchicmpcode.icmp_code == devicematch['icmp-code'][0]: + found = found and True + logger.info('Found a matching icmp code') + else: + found = False + logger.info('Icmp code fields do not match') + except: + pass + try: + assert(routematch.matchicmptype.icmp_type) + assert(devicematch['icmp-type'][0]) + if routematch.matchicmpcode.icmp_type == devicematch['icmp-type'][0]: + found = found and True + logger.info('Found a matching icmp type') + else: + found = False + logger.info('Icmp type fields do not match') + except: + pass + try: + assert(routematch.matchprotocol.protocol) + assert(devicematch['protocol'][0]) + if routematch.matchprotocol.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 + + return found + + + def get_then(self): + ret = '' + then_statements = self.then.thenaction.all() + for statement in then_statements: + if statement.action_value: + ret = "%s %s:%s
" %(ret, statement.action, statement.action_value) + else: + ret = "%s %s
" %(ret, statement.action) + return ret.rstrip(',') + + get_then.short_description = 'Then statement' + get_then.allow_tags = True + + def get_match(self): + ret = '' match = self.match - if match.matchSource: - route.match['source'].append(match.matchSource.address) if match.matchDestination: - route.match['destination'].append(match.matchDestination.address) + ret = ret = '%s Destination Address:%s
' %(ret, match.matchDestination) + if match.matchfragmenttype: + ret = ret = "%s Fragment Type:%s
" %(ret, match.matchfragmenttype) + if match.matchicmpcode: + ret = ret = "%s ICMP code:%s
" %(ret, match.matchicmpcode) + if match.matchicmptype: + ret = ret = "%s ICMP Type:%s
" %(ret, match.matchicmptype) + if match.matchpacketlength: + ret = ret = "%s Packet Length:%s
" %(ret, match.matchpacketlength) if match.matchprotocol: - route.match['protocol'].append(match.matchprotocol.protocol) + ret = ret = "%s Protocol:%s
" %(ret, match.matchprotocol) + if match.matchSource: + ret = ret = "%s Source Address:%s
" %(ret, match.matchSource) + if match.matchTcpFlag: + ret = ret = "%s TCP flag:%s
" %(ret, match.matchTcpFlag) if match.matchport: for port in match.matchport.all(): - route.match['port'].append(port.port) + ret = "%s Port:%s
" %(ret, port) if match.matchDestinationPort: for port in match.matchDestinationPort.all(): - route.match['destination-port'].append(port.port) + ret = "%s Port:%s
" %(ret, port) if match.matchSourcePort: for port in match.matchSourcePort.all(): - route.match['source-port'].append(port.port) - if match.matchicmpcode: - route.match['icmp-code'].append(match.matchicmpcode.icmp_code) - if match.matchicmptype: - route.match['icmp-type'].append(match.matchicmptype.icmp_type) - if match.matchTcpFlag: - route.match['tcp-flags'].append(match.matchTcpFlag.tcp_flags) + ret = "%s Port:%s
" %(ret, port) if match.matchdscp: for dscp in match.matchdscp.all(): - route.match['dscp'].append(dscp.dscp) - if match.matchfragmenttype: - route.match['fragment'].append(match.matchfragmenttype.fragmenttype) - then = self.then - for thenaction in then.thenaction.all(): - if thenaction.action_value: - route.then[thenaction.action] = thenaction.action_value - else: - route.then[thenaction.action] = True - print ET.tostring(device.export()) - super(Route, self).save(*args, **kwargs) \ No newline at end of file + ret = "%s Port:%s
" %(ret, dscp) + return ret.rstrip('
') + + get_match.short_description = 'Match statement' + get_match.allow_tags = True + diff --git a/flowspec/views.py b/flowspec/views.py index 60f00ef..77ae75a 100644 --- a/flowspec/views.py +++ b/flowspec/views.py @@ -1 +1,28 @@ # Create your views here. +import urllib2 +import re +import socket +from django import forms +from django.core.cache import cache +from django.views.decorators.csrf import csrf_exempt +from django.contrib.auth.decorators import login_required +from django.http import HttpResponseRedirect, HttpResponseForbidden, HttpResponse +from django.shortcuts import get_object_or_404, render_to_response +from django.core.context_processors import request +from django.template.context import RequestContext +from django.template.loader import get_template +from django.utils import simplejson +from django.core.urlresolvers import reverse +from django.contrib import messages + +from flowspy.flowspec.models import * + +def user_routes(request): + if request.user.is_anonymous(): + return HttpResponseRedirect(reverse('login')) + user_routes = Route.objects.filter(applier=request.user) + print user_routes + return render_to_response('user_routes.html', {'routes': user_routes}, + context_instance=RequestContext(request)) + + diff --git a/urls.py b/urls.py index 32ffb38..7fa8548 100644 --- a/urls.py +++ b/urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * - +from django.conf import settings # Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover() @@ -7,10 +7,20 @@ admin.autodiscover() urlpatterns = patterns('', # Example: # (r'^flowspy/', include('flowspy.foo.urls')), - + url(r'^/?$', 'flowspy.flowspec.views.user_routes', name="user-routes"), + url(r'^user/login/?', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name="login"), + url(r'^user/logout/?', 'django.contrib.auth.views.logout', {'next_page': '/'}, name="logout"), + (r'^setlang/?$', 'django.views.i18n.set_language'), # Uncomment the admin/doc line below to enable admin documentation: (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), ) + + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^static/(?P.*)', 'django.views.static.serve',\ + {'document_root': settings.STATIC_URL}), + ) \ No newline at end of file -- 1.7.10.4