diff options
Diffstat (limited to 'rpki/gui/app')
-rw-r--r-- | rpki/gui/app/check_expired.py | 43 | ||||
-rw-r--r-- | rpki/gui/app/forms.py | 2 | ||||
-rw-r--r-- | rpki/gui/app/glue.py | 75 | ||||
-rw-r--r-- | rpki/gui/app/models.py | 37 | ||||
-rwxr-xr-x | rpki/gui/app/range_list.py | 2 | ||||
-rw-r--r-- | rpki/gui/app/templates/app/roarequest_confirm_multi_form.html | 37 | ||||
-rw-r--r-- | rpki/gui/app/templates/app/roarequest_multi_form.html | 11 | ||||
-rw-r--r-- | rpki/gui/app/templates/app/route_detail.html | 8 | ||||
-rw-r--r-- | rpki/gui/app/urls.py | 10 | ||||
-rw-r--r-- | rpki/gui/app/views.py | 87 |
10 files changed, 106 insertions, 206 deletions
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py index a084af79..62292e66 100644 --- a/rpki/gui/app/check_expired.py +++ b/rpki/gui/app/check_expired.py @@ -25,9 +25,10 @@ from rpki.gui.cacheview.models import Cert from rpki.gui.app.models import Conf, ResourceCert, Timestamp, Alert from rpki.gui.app.glue import list_received_resources from rpki.irdb import Zookeeper -from rpki.left_right import report_error_elt, list_published_objects_elt from rpki.x509 import X509 +from rpki.left_right import version, nsmap, tag_msg, tag_list_published_objects +from lxml.etree import Element, SubElement from django.core.mail import send_mail logger = logging.getLogger(__name__) @@ -41,8 +42,8 @@ def check_cert(handle, p, errs): The displayed object name defaults to the class name, but can be overridden using the `object_name` argument. - """ + t = p.certificate.getNotAfter() if t <= expire_time: e = 'expired' if t <= now else 'will expire' @@ -102,30 +103,26 @@ def check_expire(conf, errs): def check_child_certs(conf, errs): """Fetch the list of published objects from rpkid, and inspect the issued resource certs (uri ending in .cer). - """ + z = Zookeeper(handle=conf.handle) - req = list_published_objects_elt.make_pdu(action="list", - tag="list_published_objects", - self_handle=conf.handle) + req = Element(tag_msg, nsmap=nsmap, type="query", version=version) + SubElement(req, tag_list_published_objects, + tag="list_published_objects", self_handle=conf.handle) pdus = z.call_rpkid(req) for pdu in pdus: - if isinstance(pdu, report_error_elt): - logger.error("rpkid reported an error: %s", pdu.error_code) - elif isinstance(pdu, list_published_objects_elt): - if pdu.uri.endswith('.cer'): - cert = X509() - cert.set(Base64=pdu.obj) - t = cert.getNotAfter() - if t <= expire_time: - e = 'expired' if t <= now else 'will expire' - errs.write("%(handle)s's rescert for Child %(child)s %(expire)s on %(date)s uri=%(uri)s subject=%(subject)s\n" % { - 'handle': conf.handle, - 'child': pdu.child_handle, - 'uri': pdu.uri, - 'subject': cert.getSubject(), - 'expire': e, - 'date': t}) + if pdu.get("uri").endswith('.cer'): + cert = X509(Base64=pdu.text) + t = cert.getNotAfter() + if t <= expire_time: + e = 'expired' if t <= now else 'will expire' + errs.write("%(handle)s's rescert for Child %(child)s %(expire)s on %(date)s uri=%(uri)s subject=%(subject)s\n" % { + 'handle': conf.handle, + 'child': pdu.get("child_handle"), + 'uri': pdu.get("uri"), + 'subject': cert.getSubject(), + 'expire': e, + 'date': t}) class NetworkError(Exception): @@ -139,8 +136,8 @@ def notify_expired(expire_days=14, from_email=None): expire_days: the number of days ahead of today to warn from_email: set the From: address for the email - """ + global expire_time # so i don't have to pass it around global now diff --git a/rpki/gui/app/forms.py b/rpki/gui/app/forms.py index a1214297..f173c15d 100644 --- a/rpki/gui/app/forms.py +++ b/rpki/gui/app/forms.py @@ -193,7 +193,7 @@ def ROARequestFormFactory(conf): 'class': 'span1' }) ) - protect_children = forms.BooleanField(required=False) + confirmed = forms.BooleanField(widget=forms.HiddenInput, required=False) def __init__(self, *args, **kwargs): kwargs['auto_id'] = False diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py index a2dddb51..bfade6d8 100644 --- a/rpki/gui/app/glue.py +++ b/rpki/gui/app/glue.py @@ -16,7 +16,6 @@ """ This file contains code that interfaces between the django views implementing the portal gui and the rpki.* modules. - """ from __future__ import with_statement @@ -28,17 +27,19 @@ from datetime import datetime from rpki.resource_set import (resource_set_as, resource_set_ipv4, resource_set_ipv6, resource_range_ipv4, resource_range_ipv6) -from rpki.left_right import list_received_resources_elt, report_error_elt from rpki.irdb.zookeeper import Zookeeper from rpki.gui.app import models from rpki.exceptions import BadIPResource +from rpki.left_right import nsmap, version, tag_msg, tag_list_received_resources +from lxml.etree import Element, SubElement from django.contrib.auth.models import User from django.db.transaction import commit_on_success def ghostbuster_to_vcard(gbr): """Convert a GhostbusterRequest object into a vCard object.""" + import vobject vcard = vobject.vCard() @@ -66,18 +67,6 @@ def ghostbuster_to_vcard(gbr): return vcard.serialize() -class LeftRightError(Exception): - """Class for wrapping report_error_elt errors from Zookeeper.call_rpkid(). - - It expects a single argument, which is the associated report_error_elt instance.""" - - def __str__(self): - return 'Error occurred while communicating with rpkid: handle=%s code=%s text=%s' % ( - self.args[0].self_handle, - self.args[0].error_code, - self.args[0].error_text) - - @commit_on_success def list_received_resources(log, conf): """ @@ -86,11 +75,12 @@ def list_received_resources(log, conf): The semantics are to clear the entire table and populate with the list of certs received. Other models should not reference the table directly with foreign keys. - """ z = Zookeeper(handle=conf.handle, disable_signal_handlers=True) - pdus = z.call_rpkid(list_received_resources_elt.make_pdu(self_handle=conf.handle)) + req = Element(tag_msg, nsmap=nsmap, type="query", version=version) + SubElement(req, tag_list_received_resources, self_handle=conf.handle) + pdus = z.call_rpkid(req) # pdus is sometimes None (see https://trac.rpki.net/ticket/681) if pdus is None: print >>log, 'error: call_rpkid() returned None for handle %s when fetching received resources' % conf.handle @@ -99,34 +89,27 @@ def list_received_resources(log, conf): models.ResourceCert.objects.filter(conf=conf).delete() for pdu in pdus: - if isinstance(pdu, report_error_elt): - # this will cause the db to be rolled back so the above delete() - # won't clobber existing resources - raise LeftRightError(pdu) - elif isinstance(pdu, list_received_resources_elt): - if pdu.parent_handle != conf.handle: - parent = models.Parent.objects.get(issuer=conf, - handle=pdu.parent_handle) - else: - # root cert, self-signed - parent = None - - not_before = datetime.strptime(pdu.notBefore, "%Y-%m-%dT%H:%M:%SZ") - not_after = datetime.strptime(pdu.notAfter, "%Y-%m-%dT%H:%M:%SZ") - - cert = models.ResourceCert.objects.create( - conf=conf, parent=parent, not_before=not_before, - not_after=not_after, uri=pdu.uri) - - for asn in resource_set_as(pdu.asn): - cert.asn_ranges.create(min=asn.min, max=asn.max) - - for rng in resource_set_ipv4(pdu.ipv4): - cert.address_ranges.create(prefix_min=rng.min, - prefix_max=rng.max) - - for rng in resource_set_ipv6(pdu.ipv6): - cert.address_ranges_v6.create(prefix_min=rng.min, - prefix_max=rng.max) + if pdu.get("parent_handle") != conf.handle: + parent = models.Parent.objects.get(issuer=conf, + handle=pdu.get("parent_handle")) else: - print >>log, "error: unexpected pdu from rpkid type=%s" % type(pdu) + # root cert, self-signed + parent = None + + not_before = datetime.strptime(pdu.get("notBefore"), "%Y-%m-%dT%H:%M:%SZ") + not_after = datetime.strptime(pdu.get("notAfter"), "%Y-%m-%dT%H:%M:%SZ") + + cert = models.ResourceCert.objects.create( + conf=conf, parent=parent, not_before=not_before, + not_after=not_after, uri=pdu.get("uri")) + + for asn in resource_set_as(pdu.get("asn")): + cert.asn_ranges.create(min=asn.min, max=asn.max) + + for rng in resource_set_ipv4(pdu.get("ipv4")): + cert.address_ranges.create(prefix_min=rng.min, + prefix_max=rng.max) + + for rng in resource_set_ipv6(pdu.get("ipv6")): + cert.address_ranges_v6.create(prefix_min=rng.min, + prefix_max=rng.max) diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py index 40bdbe2c..ed32e9d2 100644 --- a/rpki/gui/app/models.py +++ b/rpki/gui/app/models.py @@ -18,7 +18,6 @@ __version__ = '$Id$' from django.db import models from django.contrib.auth.models import User from django.core.mail import send_mail -from django.db.models import Q import rpki.resource_set import rpki.exceptions @@ -66,15 +65,6 @@ class Child(rpki.irdb.models.Child): proxy = True verbose_name_plural = 'children' - @property - def routes(self): - "Return a list of RouteOrigin objects (potentially) originated by this child." - query = Q() - for r in self.address_ranges.filter(version='IPv4'): - rng = r.as_resource_range() - query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max) - return RouteOrigin.objects.filter(query) - class ChildASN(rpki.irdb.models.ChildASN): """Proxy model for irdb ChildASN.""" @@ -131,30 +121,17 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def parents(self): """Simulates irdb.models.Parent.objects, but returns app.models.Parent proxy objects. - """ + return Parent.objects.filter(issuer=self) @property def children(self): """Simulates irdb.models.Child.objects, but returns app.models.Child proxy objects. - - When running rootd, we need to exclude the Child object for self. - """ - return Child.objects.filter(issuer=self).exclude(handle=self.handle) - @property - def child_routes(self): - """Return currently announced routes for prefixes covered by child - sub-allocations. - """ - query = Q() - for pfx in ChildNet.objects.filter(child__issuer=self, version='IPv4'): - rng = pfx.as_resource_range() - query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max) - return RouteOrigin.objects.filter(query) + return Child.objects.filter(issuer=self) @property def ghostbusters(self): @@ -172,8 +149,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def routes(self): """Return all IPv4 routes covered by RPKI certs issued to this resource holder. - """ + # build a Q filter to select all RouteOrigin objects covered by # prefixes in the resource holder's certificates prefixes = ResourceRangeAddressV4.objects.filter(cert__conf=self) @@ -190,8 +167,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def routes_v6(self): """Return all IPv6 routes covered by RPKI certs issued to this resource holder. - """ + # build a Q filter to select all RouteOrigin objects covered by # prefixes in the resource holder's certificates prefixes = ResourceRangeAddressV6.objects.filter(cert__conf=self) @@ -206,6 +183,7 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def send_alert(self, subject, message, from_email, severity=Alert.INFO): """Store an alert for this resource holder.""" + self.alerts.create(subject=subject, text=message, severity=severity) send_mail( @@ -221,8 +199,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): Contact emails are extract from any ghostbuster requests, and any linked user accounts. - """ + notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address] notify_emails.extend( [acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email] @@ -247,7 +225,6 @@ class ResourceCert(models.Model): """Represents a resource certificate. This model is used to cache the output of <list_received_resources/>. - """ # Handle to which this cert was issued @@ -275,6 +252,7 @@ class ResourceCert(models.Model): def get_cert_chain(self): """Return a list containing the complete certificate chain for this certificate.""" + cert = self x = [cert] while cert.issuer: @@ -448,7 +426,6 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6): class ConfACL(models.Model): """Stores access control for which users are allowed to manage a given resource handle. - """ conf = models.ForeignKey(Conf) diff --git a/rpki/gui/app/range_list.py b/rpki/gui/app/range_list.py index 21fd1f29..5cb4f5e4 100755 --- a/rpki/gui/app/range_list.py +++ b/rpki/gui/app/range_list.py @@ -70,6 +70,7 @@ class RangeList(list): def difference(self, other): """Return a RangeList object which contains ranges in this object which are not in "other".""" + it = iter(other) try: @@ -85,6 +86,7 @@ class RangeList(list): def V(v): """convert the integer value to the appropriate type for this range""" + return x.__class__.datum_type(v) try: diff --git a/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html b/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html index d1d8171f..4a06a4aa 100644 --- a/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html +++ b/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html @@ -1,4 +1,5 @@ {% extends "app/app_base.html" %} +{% load url from future %} {% load app_extras %} {% block content %} @@ -8,20 +9,17 @@ <div class='row-fluid'> <div class='span6'> - <div class='alert'> - <strong>Please confirm</strong> that you would like to create the following ROA(s). + <div class='alert alert-block-message alert-warning'> + <p><strong>Please confirm</strong> that you would like to create the following ROA(s). The accompanying table indicates how the validation status may change as a result. </div> <table class='table table-condensed table-striped'> - <thead> - <tr> - <th>Prefix</th> - <th>Max Length</th> - <th>AS</th> - </tr> - </thead> - <tbody> + <tr> + <th>Prefix</th> + <th>Max Length</th> + <th>AS</th> + </tr> {% for roa in roas %} <tr> <td>{{ roa.prefix }}</td> @@ -29,7 +27,6 @@ <td>{{ roa.asn }}</td> </tr> {% endfor %} - </tbody> </table> <form method='POST' action='{% url "rpki.gui.app.views.roa_create_multi_confirm" %}'> @@ -50,22 +47,18 @@ <h2>Matched Routes</h2> <table class='table table-striped table-condensed'> - <thead> - <tr> - <th>Prefix</th> - <th>Origin AS</th> - <th>Validation Status</th> - </tr> - </thead> - <tbody> + <tr> + <th>Prefix</th> + <th>Origin AS</th> + <th>Validation Status</th> + </tr> {% for r in routes %} <tr> - <td>{{ r.get_prefix_display }}</td> - <td>{{ r.asn }}</td> + <td>{{ r.get_prefix_display }}</td> + <td>{{ r.asn }}</td> <td>{% validity_label r.newstatus %}</td> </tr> {% endfor %} - </tbody> </table> </div> diff --git a/rpki/gui/app/templates/app/roarequest_multi_form.html b/rpki/gui/app/templates/app/roarequest_multi_form.html index ed2135ed..0fbc49ae 100644 --- a/rpki/gui/app/templates/app/roarequest_multi_form.html +++ b/rpki/gui/app/templates/app/roarequest_multi_form.html @@ -1,28 +1,27 @@ {% extends "app/app_base.html" %} +{% load url from future %} {% block content %} <div class='page-title'> <h1>Create ROAs</h1> </div> -<form class='form-inline' method='POST' action='{{ request.get_full_path }}'> +<form method='POST' action='{{ request.get_full_path }}'> {% csrf_token %} {{ formset.management_form }} {% for form in formset %} - + <div class="controls controls-row"> {{ form.prefix }} {{ form.max_prefixlen }} {{ form.asn }} - <label class="checkbox" title='create additional ROAs for child routes'>{{ form.protect_children }} Protect children</label> - {# <label class="checkbox inline span1">{{ form.DELETE }} Delete</label> #} {% if form.errors %}<span class="help-inline">{{ form.errors }}</span>{% endif %} {% if form.non_field_errors %}<span class="help-inline">{{ form.non_field_errors }}</span>{% endif %} - + </div> {% endfor %} <div class="form-actions"> - <button class='btn btn-primary' type='submit'>Preview</button> + <input class="btn" type="submit" value="Preview"> <a class="btn" href="{% url "rpki.gui.app.views.dashboard" %}">Cancel</a> </div> </form> diff --git a/rpki/gui/app/templates/app/route_detail.html b/rpki/gui/app/templates/app/route_detail.html index 8bd744df..84add4a8 100644 --- a/rpki/gui/app/templates/app/route_detail.html +++ b/rpki/gui/app/templates/app/route_detail.html @@ -42,15 +42,13 @@ </thead> <tbody> {% for pfx in roa_prefixes %} - {% for roa in pfx.roas.all %} <tr> <td>{{ pfx.as_resource_range }}</td> <td>{{ pfx.max_length }}</td> - <td>{{ roa.asid }}</td> - <td>{{ roa.not_after }}</td> - <td>{{ roa.repo.uri }}</td> + <td>{{ pfx.roas.all.0.asid }}</td> + <td>{{ pfx.roas.all.0.not_after }}</td> + <td>{{ pfx.roas.all.0.repo.uri }}</td> </tr> - {% endfor %} {% endfor %} </tbody> </table> diff --git a/rpki/gui/app/urls.py b/rpki/gui/app/urls.py index 81c9b127..f595ea8f 100644 --- a/rpki/gui/app/urls.py +++ b/rpki/gui/app/urls.py @@ -88,14 +88,12 @@ urlpatterns = patterns( #{'post_reset_redirect' : '/user/password/reset/done/'}, {'extra_context': {'form_title': 'Password Reset'}}, name="password_reset"), - url(r'^user/password/reset/done/$', - 'django.contrib.auth.views.password_reset_done', - name='password_reset_done'), + (r'^user/password/reset/done/$', + 'django.contrib.auth.views.password_reset_done'), url(r'^user/password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', #{'post_reset_redirect' : '/user/password/done/'}, name="password_reset_confirm"), - url(r'^user/password/done/$', - 'django.contrib.auth.views.password_reset_complete', - name='password_reset_complete'), + (r'^user/password/done/$', + 'django.contrib.auth.views.password_reset_complete'), ) diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py index bf152f8e..dfd36dbb 100644 --- a/rpki/gui/app/views.py +++ b/rpki/gui/app/views.py @@ -27,7 +27,6 @@ from tempfile import NamedTemporaryFile import cStringIO import csv import logging -import lxml.etree from django.utils.decorators import method_decorator from django.contrib.auth.decorators import login_required @@ -39,8 +38,8 @@ from django.contrib.auth.models import User from django.views.generic import DetailView, ListView, DeleteView, FormView from django.core.paginator import Paginator, InvalidPage from django.forms.formsets import formset_factory, BaseFormSet +import django.db.models from django.contrib import messages -from django.db.models import Q from rpki.irdb import Zookeeper, ChildASN, ChildNet, ROARequestPrefix from rpki.gui.app import models, forms, glue, range_list @@ -147,28 +146,19 @@ def generic_import(request, queryset, configure, form_class=None, # expects it. if handle == '': handle = None - try: - # configure_repository returns None, so can't use tuple expansion - # here. Unpack the tuple below if post_import_redirect is None. - r = configure(z, tmpf.name, handle) - except lxml.etree.XMLSyntaxError as e: - logger.exception('caught XMLSyntaxError while parsing uploaded file') - messages.error( - request, - 'The uploaded file has an invalid XML syntax' - ) + # configure_repository returns None, so can't use tuple expansion + # here. Unpack the tuple below if post_import_redirect is None. + r = configure(z, tmpf.name, handle) + # force rpkid run now + z.synchronize_ca(poke=True) + os.remove(tmpf.name) + if post_import_redirect: + url = post_import_redirect else: - # force rpkid run now - z.synchronize_ca(poke=True) - if post_import_redirect: - url = post_import_redirect - else: - _, handle = r - url = queryset.get(issuer=conf, - handle=handle).get_absolute_url() - return http.HttpResponseRedirect(url) - finally: - os.remove(tmpf.name) + _, handle = r + url = queryset.get(issuer=conf, + handle=handle).get_absolute_url() + return http.HttpResponseRedirect(url) else: form = form_class() @@ -632,6 +622,7 @@ def get_covered_routes(rng, max_prefixlen, asn): return routes + @handle_required def roa_create(request): """Present the user with a form to create a ROA. @@ -717,58 +708,20 @@ def roa_create_multi(request): formset = formset_factory(forms.ROARequestFormFactory(conf), extra=extra)(initial=init) elif request.method == 'POST': formset = formset_factory(forms.ROARequestFormFactory(conf), extra=0)(request.POST, request.FILES) - # We need to check .has_changed() because .is_valid() will return true - # if the user clicks the Preview button without filling in the blanks - # in the ROA form, leaving the form invalid from this view's POV. - if formset.has_changed() and formset.is_valid(): + if formset.is_valid(): routes = [] v = [] - query = Q() # for matching routes - roas = [] for form in formset: asn = form.cleaned_data['asn'] rng = resource_range_ip.parse_str(form.cleaned_data['prefix']) max_prefixlen = int(form.cleaned_data['max_prefixlen']) - protect_children = form.cleaned_data['protect_children'] - - roas.append((rng, max_prefixlen, asn, protect_children)) + # FIXME: This won't do the right thing in the event that a + # route is covered by multiple ROAs created in the form. + # You will see duplicate entries, each with a potentially + # different validation status. + routes.extend(get_covered_routes(rng, max_prefixlen, asn)) v.append({'prefix': str(rng), 'max_prefixlen': max_prefixlen, 'asn': asn}) - - query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max) - - for rt in RouteOrigin.objects.filter(query): - status = rt.status # cache the value - newstatus = status - if status == 'unknown': - # possible change to valid or invalid - for rng, max_prefixlen, asn, protect in roas: - if rng.min <= rt.prefix_min and rng.max >= rt.prefix_max: - # this route is covered - if asn == rt.asn and rt.prefixlen <= max_prefixlen: - newstatus = 'valid' - break # no need to continue for this route - else: - newstatus = 'invalid' - elif status == 'invalid': - # possible change to valid - for rng, max_prefixlen, asn, protect in roas: - if rng.min <= rt.prefix_min and rng.max >= rt.prefix_max: - # this route is covered - if asn == rt.asn and rt.prefixlen <= max_prefixlen: - newstatus = 'valid' - break # no need to continue for this route - - if status != newstatus: - if protect_children and newstatus == 'invalid' and conf.child_routes.filter(pk=rt.pk).exists(): - rng = rt.as_resource_range() - v.append({'prefix': str(rng), - 'max_prefixlen': rng.prefixlen, - 'asn': rt.asn}) - newstatus = 'valid' - rt.newstatus = newstatus # I"M A MUHNKAY!!! - routes.append(rt) - # if there were no rows, skip the confirmation step if v: formset = formset_factory(forms.ROARequestConfirm, extra=0)(initial=v) |