diff options
Diffstat (limited to 'rpki/gui')
46 files changed, 1283 insertions, 1938 deletions
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py index a084af79..65f4315f 100644 --- a/rpki/gui/app/check_expired.py +++ b/rpki/gui/app/check_expired.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012, 2013, 2014 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2013, 2014, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -21,13 +21,14 @@ from cStringIO import StringIO import logging import datetime -from rpki.gui.cacheview.models import Cert +from rpki.gui.gui_rpki_cache.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' @@ -60,8 +61,8 @@ def check_expire(conf, errs): # get certs for `handle' cert_set = ResourceCert.objects.filter(conf=conf) for cert in cert_set: - # look up cert in cacheview db - obj_set = Cert.objects.filter(repo__uri=cert.uri) + # look up cert in gui_rpki_cache db + obj_set = Cert.objects.filter(uri=cert.uri) if not obj_set: # since the <list_received_resources/> output is cached, this can # occur if the cache is out of date as well.. @@ -76,7 +77,7 @@ def check_expire(conf, errs): f = '*' else: f = ' ' - msg.append("%s [%d] uri=%s ski=%s name=%s expires=%s" % (f, n, c.repo.uri, c.keyid, c.name, c.not_after)) + msg.append("%s [%d] uri=%s expires=%s" % (f, n, c.uri, c.not_after)) # find ghostbuster records attached to this cert for gbr in c.ghostbusters.all(): @@ -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", tenant_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..4a95c8da 100644 --- a/rpki/gui/app/forms.py +++ b/rpki/gui/app/forms.py @@ -170,105 +170,105 @@ def ROARequestFormFactory(conf): """ class Cls(forms.Form): - """Form for entering a ROA request. - - Handles both IPv4 and IPv6.""" - - prefix = forms.CharField( - widget=forms.TextInput(attrs={ - 'autofocus': 'true', 'placeholder': 'Prefix', - 'class': 'span4' - }) - ) - max_prefixlen = forms.CharField( - required=False, - widget=forms.TextInput(attrs={ - 'placeholder': 'Max len', - 'class': 'span1' - }) - ) - asn = forms.IntegerField( - widget=forms.TextInput(attrs={ - 'placeholder': 'ASN', - 'class': 'span1' - }) - ) + """Form for entering a ROA request. + + Handles both IPv4 and IPv6.""" + + prefix = forms.CharField( + widget=forms.TextInput(attrs={ + 'autofocus': 'true', 'placeholder': 'Prefix', + 'class': 'span4' + }) + ) + max_prefixlen = forms.CharField( + required=False, + widget=forms.TextInput(attrs={ + 'placeholder': 'Max len', + 'class': 'span1' + }) + ) + asn = forms.IntegerField( + widget=forms.TextInput(attrs={ + 'placeholder': 'ASN', + 'class': 'span1' + }) + ) protect_children = forms.BooleanField(required=False) - def __init__(self, *args, **kwargs): - kwargs['auto_id'] = False - super(Cls, self).__init__(*args, **kwargs) - self.conf = conf # conf is the arg to ROARequestFormFactory - self.inline = True - self.use_table = False - - def _as_resource_range(self): - """Convert the prefix in the form to a - rpki.resource_set.resource_range_ip object. - - If there is no mask provided, assume the closest classful mask. - - """ - prefix = self.cleaned_data.get('prefix') - if '/' not in prefix: - p = IPAddress(prefix) - - # determine the first nonzero bit starting from the lsb and - # subtract from the address size to find the closest classful - # mask that contains this single address - prefixlen = 0 - while (p != 0) and (p & 1) == 0: - prefixlen = prefixlen + 1 - p = p >> 1 - mask = p.bits - (8 * (prefixlen / 8)) - prefix = prefix + '/' + str(mask) - - return resource_range_ip.parse_str(prefix) - - def clean_asn(self): - value = self.cleaned_data.get('asn') - if value < 0: - raise forms.ValidationError('AS must be a positive value or 0') - return value - - def clean_prefix(self): - try: - r = self._as_resource_range() - except: - raise forms.ValidationError('invalid prefix') - - manager = models.ResourceRangeAddressV4 if r.version == 4 else models.ResourceRangeAddressV6 - if not manager.objects.filter(cert__conf=self.conf, - prefix_min__lte=r.min, - prefix_max__gte=r.max).exists(): - raise forms.ValidationError('prefix is not allocated to you') - return str(r) - - def clean_max_prefixlen(self): - v = self.cleaned_data.get('max_prefixlen') - if v: - if v[0] == '/': - v = v[1:] # allow user to specify /24 - try: - if int(v) < 0: - raise forms.ValidationError('max prefix length must be positive or 0') - except ValueError: - raise forms.ValidationError('invalid integer value') - return v - - def clean(self): - if 'prefix' in self.cleaned_data: - r = self._as_resource_range() - max_prefixlen = self.cleaned_data.get('max_prefixlen') - max_prefixlen = int(max_prefixlen) if max_prefixlen else r.prefixlen() - if max_prefixlen < r.prefixlen(): - raise forms.ValidationError( - 'max prefix length must be greater than or equal to the prefix length') - if max_prefixlen > r.min.bits: - raise forms.ValidationError( - 'max prefix length (%d) is out of range for IP version (%d)' % (max_prefixlen, r.min.bits)) - self.cleaned_data['max_prefixlen'] = str(max_prefixlen) - return self.cleaned_data + def __init__(self, *args, **kwargs): + kwargs['auto_id'] = False + super(Cls, self).__init__(*args, **kwargs) + self.conf = conf # conf is the arg to ROARequestFormFactory + self.inline = True + self.use_table = False + + def _as_resource_range(self): + """Convert the prefix in the form to a + rpki.resource_set.resource_range_ip object. + + If there is no mask provided, assume the closest classful mask. + + """ + prefix = self.cleaned_data.get('prefix') + if '/' not in prefix: + p = IPAddress(prefix) + + # determine the first nonzero bit starting from the lsb and + # subtract from the address size to find the closest classful + # mask that contains this single address + prefixlen = 0 + while (p != 0) and (p & 1) == 0: + prefixlen = prefixlen + 1 + p = p >> 1 + mask = p.bits - (8 * (prefixlen / 8)) + prefix = prefix + '/' + str(mask) + + return resource_range_ip.parse_str(prefix) + + def clean_asn(self): + value = self.cleaned_data.get('asn') + if value < 0: + raise forms.ValidationError('AS must be a positive value or 0') + return value + + def clean_prefix(self): + try: + r = self._as_resource_range() + except: + raise forms.ValidationError('invalid prefix') + + manager = models.ResourceRangeAddressV4 if r.version == 4 else models.ResourceRangeAddressV6 + if not manager.objects.filter(cert__conf=self.conf, + prefix_min__lte=r.min, + prefix_max__gte=r.max).exists(): + raise forms.ValidationError('prefix is not allocated to you') + return str(r) + + def clean_max_prefixlen(self): + v = self.cleaned_data.get('max_prefixlen') + if v: + if v[0] == '/': + v = v[1:] # allow user to specify /24 + try: + if int(v) < 0: + raise forms.ValidationError('max prefix length must be positive or 0') + except ValueError: + raise forms.ValidationError('invalid integer value') + return v + + def clean(self): + if 'prefix' in self.cleaned_data: + r = self._as_resource_range() + max_prefixlen = self.cleaned_data.get('max_prefixlen') + max_prefixlen = int(max_prefixlen) if max_prefixlen else r.prefixlen() + if max_prefixlen < r.prefixlen(): + raise forms.ValidationError( + 'max prefix length must be greater than or equal to the prefix length') + if max_prefixlen > r.min.bits: + raise forms.ValidationError( + 'max prefix length (%d) is out of range for IP version (%d)' % (max_prefixlen, r.min.bits)) + self.cleaned_data['max_prefixlen'] = str(max_prefixlen) + return self.cleaned_data return Cls diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py index a2dddb51..c312618f 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 +from django.db.transaction import atomic def ghostbuster_to_vcard(gbr): """Convert a GhostbusterRequest object into a vCard object.""" + import vobject vcard = vobject.vCard() @@ -66,19 +67,7 @@ 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 +@atomic def list_received_resources(log, conf): """ Query rpkid for this resource handle's received resources. @@ -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, tenant_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/migrations/0001_initial.py b/rpki/gui/app/migrations/0001_initial.py index 80877901..79d21324 100644 --- a/rpki/gui/app/migrations/0001_initial.py +++ b/rpki/gui/app/migrations/0001_initial.py @@ -1,192 +1,249 @@ # -*- 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 'ResourceCert' - db.create_table('app_resourcecert', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='certs', to=orm['irdb.Parent'])), - ('not_before', self.gf('django.db.models.fields.DateTimeField')()), - ('not_after', self.gf('django.db.models.fields.DateTimeField')()), - ('uri', self.gf('django.db.models.fields.CharField')(max_length=255)), - )) - db.send_create_signal('app', ['ResourceCert']) - - # Adding model 'ResourceRangeAddressV4' - db.create_table('app_resourcerangeaddressv4', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('prefix_min', self.gf('rpki.gui.models.IPv4AddressField')(db_index=True)), - ('prefix_max', self.gf('rpki.gui.models.IPv4AddressField')(db_index=True)), - ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges', to=orm['app.ResourceCert'])), - )) - db.send_create_signal('app', ['ResourceRangeAddressV4']) - - # Adding model 'ResourceRangeAddressV6' - db.create_table('app_resourcerangeaddressv6', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('prefix_min', self.gf('rpki.gui.models.IPv6AddressField')(db_index=True)), - ('prefix_max', self.gf('rpki.gui.models.IPv6AddressField')(db_index=True)), - ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges_v6', to=orm['app.ResourceCert'])), - )) - db.send_create_signal('app', ['ResourceRangeAddressV6']) - - # Adding model 'ResourceRangeAS' - db.create_table('app_resourcerangeas', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('min', self.gf('django.db.models.fields.PositiveIntegerField')()), - ('max', self.gf('django.db.models.fields.PositiveIntegerField')()), - ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='asn_ranges', to=orm['app.ResourceCert'])), - )) - db.send_create_signal('app', ['ResourceRangeAS']) - - # Adding model 'GhostbusterRequest' - db.create_table('app_ghostbusterrequest', ( - ('ghostbusterrequest_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['irdb.GhostbusterRequest'], unique=True, primary_key=True)), - ('full_name', self.gf('django.db.models.fields.CharField')(max_length=40)), - ('family_name', self.gf('django.db.models.fields.CharField')(max_length=20)), - ('given_name', self.gf('django.db.models.fields.CharField')(max_length=20)), - ('additional_name', self.gf('django.db.models.fields.CharField')(max_length=20, null=True, blank=True)), - ('honorific_prefix', self.gf('django.db.models.fields.CharField')(max_length=10, null=True, blank=True)), - ('honorific_suffix', self.gf('django.db.models.fields.CharField')(max_length=10, null=True, blank=True)), - ('email_address', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True, blank=True)), - ('organization', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), - ('telephone', self.gf('rpki.gui.app.models.TelephoneField')(max_length=40, null=True, blank=True)), - ('box', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), - ('extended', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), - ('street', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), - ('city', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), - ('region', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), - ('code', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), - ('country', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), - )) - db.send_create_signal('app', ['GhostbusterRequest']) - - # Adding model 'Timestamp' - db.create_table('app_timestamp', ( - ('name', self.gf('django.db.models.fields.CharField')(max_length=30, primary_key=True)), - ('ts', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal('app', ['Timestamp']) - - - def backwards(self, orm): - # Deleting model 'ResourceCert' - db.delete_table('app_resourcecert') - - # Deleting model 'ResourceRangeAddressV4' - db.delete_table('app_resourcerangeaddressv4') - - # Deleting model 'ResourceRangeAddressV6' - db.delete_table('app_resourcerangeaddressv6') - - # Deleting model 'ResourceRangeAS' - db.delete_table('app_resourcerangeas') - - # Deleting model 'GhostbusterRequest' - db.delete_table('app_ghostbusterrequest') - - # Deleting model 'Timestamp' - db.delete_table('app_timestamp') - - - models = { - 'app.ghostbusterrequest': { - 'Meta': {'ordering': "('family_name', 'given_name')", 'object_name': 'GhostbusterRequest', '_ormbases': ['irdb.GhostbusterRequest']}, - 'additional_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), - 'box': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'code': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'country': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), - 'extended': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), - 'family_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}), - 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'ghostbusterrequest_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.GhostbusterRequest']", 'unique': 'True', 'primary_key': 'True'}), - 'given_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}), - 'honorific_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), - 'honorific_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), - 'organization': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), - 'region': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'street': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), - 'telephone': ('rpki.gui.app.models.TelephoneField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}) - }, - 'app.resourcecert': { - 'Meta': {'object_name': 'ResourceCert'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'not_after': ('django.db.models.fields.DateTimeField', [], {}), - 'not_before': ('django.db.models.fields.DateTimeField', [], {}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'to': "orm['irdb.Parent']"}), - 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - }, - 'app.resourcerangeaddressv4': { - 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV4'}, - 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': "orm['app.ResourceCert']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'prefix_max': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}), - 'prefix_min': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}) - }, - 'app.resourcerangeaddressv6': { - 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV6'}, - 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges_v6'", 'to': "orm['app.ResourceCert']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'prefix_max': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}), - 'prefix_min': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}) - }, - 'app.resourcerangeas': { - 'Meta': {'ordering': "('min', 'max')", 'object_name': 'ResourceRangeAS'}, - 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asn_ranges'", 'to': "orm['app.ResourceCert']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'max': ('django.db.models.fields.PositiveIntegerField', [], {}), - 'min': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'app.timestamp': { - 'Meta': {'object_name': 'Timestamp'}, - 'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}), - 'ts': ('django.db.models.fields.DateTimeField', [], {}) - }, - 'irdb.ghostbusterrequest': { - 'Meta': {'object_name': 'GhostbusterRequest'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'to': "orm['irdb.ResourceHolderCA']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'null': 'True', 'to': "orm['irdb.Parent']"}), - 'vcard': ('django.db.models.fields.TextField', [], {}) - }, - 'irdb.parent': { - 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Parent', '_ormbases': ['irdb.Turtle']}, - 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), - 'child_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), - 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), - 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parents'", 'to': "orm['irdb.ResourceHolderCA']"}), - 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), - 'referral_authorization': ('rpki.irdb.models.SignedReferralField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), - 'referrer': ('rpki.irdb.models.HandleField', [], {'max_length': '120', 'null': 'True', 'blank': 'True'}), - 'repository_type': ('rpki.irdb.models.EnumField', [], {}), - 'ta': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), - 'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'irdb.resourceholderca': { - 'Meta': {'object_name': 'ResourceHolderCA'}, - 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), - 'handle': ('rpki.irdb.models.HandleField', [], {'unique': 'True', 'max_length': '120'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'last_crl_update': ('rpki.irdb.models.SundialField', [], {}), - 'latest_crl': ('rpki.irdb.models.CRLField', [], {'default': 'None', 'blank': 'True'}), - 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}), - 'next_crl_update': ('rpki.irdb.models.SundialField', [], {}), - 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}), - 'private_key': ('rpki.irdb.models.RSAKeyField', [], {'default': 'None', 'blank': 'True'}) - }, - 'irdb.turtle': { - 'Meta': {'object_name': 'Turtle'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['app']
\ No newline at end of file +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings +import rpki.gui.models +import rpki.gui.app.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('irdb', '0001_initial'), + ('routeview', '__first__'), + ] + + operations = [ + migrations.CreateModel( + name='Alert', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('severity', models.SmallIntegerField(default=0, choices=[(0, b'info'), (1, b'warning'), (2, b'error')])), + ('when', models.DateTimeField(auto_now_add=True)), + ('seen', models.BooleanField(default=False)), + ('subject', models.CharField(max_length=66)), + ('text', models.TextField()), + ], + ), + migrations.CreateModel( + name='ConfACL', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ], + ), + migrations.CreateModel( + name='GhostbusterRequest', + fields=[ + ('ghostbusterrequest_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='irdb.GhostbusterRequest')), + ('full_name', models.CharField(max_length=40)), + ('family_name', models.CharField(max_length=20)), + ('given_name', models.CharField(max_length=20)), + ('additional_name', models.CharField(max_length=20, null=True, blank=True)), + ('honorific_prefix', models.CharField(max_length=10, null=True, blank=True)), + ('honorific_suffix', models.CharField(max_length=10, null=True, blank=True)), + ('email_address', models.EmailField(max_length=254, null=True, blank=True)), + ('organization', models.CharField(max_length=255, null=True, blank=True)), + ('telephone', rpki.gui.app.models.TelephoneField(max_length=40, null=True, blank=True)), + ('box', models.CharField(max_length=40, null=True, verbose_name=b'P.O. Box', blank=True)), + ('extended', models.CharField(max_length=255, null=True, blank=True)), + ('street', models.CharField(max_length=255, null=True, blank=True)), + ('city', models.CharField(max_length=40, null=True, blank=True)), + ('region', models.CharField(help_text=b'state or province', max_length=40, null=True, blank=True)), + ('code', models.CharField(max_length=40, null=True, verbose_name=b'Postal Code', blank=True)), + ('country', models.CharField(max_length=40, null=True, blank=True)), + ], + options={ + 'ordering': ('family_name', 'given_name'), + }, + bases=('irdb.ghostbusterrequest',), + ), + migrations.CreateModel( + name='ResourceCert', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('not_before', models.DateTimeField()), + ('not_after', models.DateTimeField()), + ('uri', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='ResourceRangeAddressV4', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ('cert', models.ForeignKey(related_name='address_ranges', to='app.ResourceCert')), + ], + options={ + 'ordering': ('prefix_min',), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ResourceRangeAddressV6', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ('cert', models.ForeignKey(related_name='address_ranges_v6', to='app.ResourceCert')), + ], + options={ + 'ordering': ('prefix_min',), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ResourceRangeAS', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('min', models.BigIntegerField(validators=[rpki.gui.models.validate_asn])), + ('max', models.BigIntegerField(validators=[rpki.gui.models.validate_asn])), + ('cert', models.ForeignKey(related_name='asn_ranges', to='app.ResourceCert')), + ], + options={ + 'ordering': ('min', 'max'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Timestamp', + fields=[ + ('name', models.CharField(max_length=30, serialize=False, primary_key=True)), + ('ts', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='Child', + fields=[ + ], + options={ + 'proxy': True, + 'verbose_name_plural': 'children', + }, + bases=('irdb.child',), + ), + migrations.CreateModel( + name='ChildASN', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.childasn',), + ), + migrations.CreateModel( + name='ChildNet', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.childnet',), + ), + migrations.CreateModel( + name='Client', + fields=[ + ], + options={ + 'verbose_name': 'Client', + 'proxy': True, + }, + bases=('irdb.client',), + ), + migrations.CreateModel( + name='Conf', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.resourceholderca',), + ), + migrations.CreateModel( + name='Parent', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.parent',), + ), + migrations.CreateModel( + name='Repository', + fields=[ + ], + options={ + 'verbose_name': 'Repository', + 'proxy': True, + 'verbose_name_plural': 'Repositories', + }, + bases=('irdb.repository',), + ), + migrations.CreateModel( + name='ROARequest', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.roarequest',), + ), + migrations.CreateModel( + name='ROARequestPrefix', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('irdb.roarequestprefix',), + ), + migrations.CreateModel( + name='RouteOrigin', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('routeview.routeorigin',), + ), + migrations.CreateModel( + name='RouteOriginV6', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('routeview.routeoriginv6',), + ), + migrations.AddField( + model_name='resourcecert', + name='conf', + field=models.ForeignKey(related_name='certs', to='app.Conf'), + ), + migrations.AddField( + model_name='resourcecert', + name='parent', + field=models.ForeignKey(related_name='certs', to='app.Parent', null=True), + ), + migrations.AddField( + model_name='confacl', + name='conf', + field=models.ForeignKey(to='app.Conf'), + ), + migrations.AddField( + model_name='confacl', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='alert', + name='conf', + field=models.ForeignKey(related_name='alerts', to='app.Conf'), + ), + migrations.AlterUniqueTogether( + name='confacl', + unique_together=set([('user', 'conf')]), + ), + ] diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py index 40bdbe2c..fb1cafff 100644 --- a/rpki/gui/app/models.py +++ b/rpki/gui/app/models.py @@ -1,5 +1,5 @@ # Copyright (C) 2010 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2012 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -26,7 +26,6 @@ import rpki.irdb.models import rpki.gui.models import rpki.gui.routeview.models import rpki.oids -from south.modelsinspector import add_introspection_rules class TelephoneField(models.CharField): @@ -35,8 +34,6 @@ class TelephoneField(models.CharField): kwargs['max_length'] = 40 models.CharField.__init__(self, **kwargs) -add_introspection_rules([], [r'^rpki\.gui\.app\.models\.TelephoneField']) - class Parent(rpki.irdb.models.Parent): """proxy model for irdb Parent""" @@ -123,7 +120,7 @@ class Alert(models.Model): class Conf(rpki.irdb.models.ResourceHolderCA): """This is the center of the universe, also known as a place to - have a handle on a resource-holding entity. It's the <self> + have a handle on a resource-holding entity. It's the <tenant/> in the rpkid schema. """ @@ -262,7 +259,7 @@ class ResourceCert(models.Model): not_after = models.DateTimeField() # Locator for this object. Used to look up the validation status, expiry - # of ancestor certs in cacheview + # of ancestor certs in gui_rpki_cache uri = models.CharField(max_length=255) def __unicode__(self): 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/south_migrations/0001_initial.py b/rpki/gui/app/south_migrations/0001_initial.py new file mode 100644 index 00000000..80877901 --- /dev/null +++ b/rpki/gui/app/south_migrations/0001_initial.py @@ -0,0 +1,192 @@ +# -*- 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 'ResourceCert' + db.create_table('app_resourcecert', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='certs', to=orm['irdb.Parent'])), + ('not_before', self.gf('django.db.models.fields.DateTimeField')()), + ('not_after', self.gf('django.db.models.fields.DateTimeField')()), + ('uri', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('app', ['ResourceCert']) + + # Adding model 'ResourceRangeAddressV4' + db.create_table('app_resourcerangeaddressv4', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('prefix_min', self.gf('rpki.gui.models.IPv4AddressField')(db_index=True)), + ('prefix_max', self.gf('rpki.gui.models.IPv4AddressField')(db_index=True)), + ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges', to=orm['app.ResourceCert'])), + )) + db.send_create_signal('app', ['ResourceRangeAddressV4']) + + # Adding model 'ResourceRangeAddressV6' + db.create_table('app_resourcerangeaddressv6', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('prefix_min', self.gf('rpki.gui.models.IPv6AddressField')(db_index=True)), + ('prefix_max', self.gf('rpki.gui.models.IPv6AddressField')(db_index=True)), + ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges_v6', to=orm['app.ResourceCert'])), + )) + db.send_create_signal('app', ['ResourceRangeAddressV6']) + + # Adding model 'ResourceRangeAS' + db.create_table('app_resourcerangeas', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('min', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('max', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('cert', self.gf('django.db.models.fields.related.ForeignKey')(related_name='asn_ranges', to=orm['app.ResourceCert'])), + )) + db.send_create_signal('app', ['ResourceRangeAS']) + + # Adding model 'GhostbusterRequest' + db.create_table('app_ghostbusterrequest', ( + ('ghostbusterrequest_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['irdb.GhostbusterRequest'], unique=True, primary_key=True)), + ('full_name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('family_name', self.gf('django.db.models.fields.CharField')(max_length=20)), + ('given_name', self.gf('django.db.models.fields.CharField')(max_length=20)), + ('additional_name', self.gf('django.db.models.fields.CharField')(max_length=20, null=True, blank=True)), + ('honorific_prefix', self.gf('django.db.models.fields.CharField')(max_length=10, null=True, blank=True)), + ('honorific_suffix', self.gf('django.db.models.fields.CharField')(max_length=10, null=True, blank=True)), + ('email_address', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True, blank=True)), + ('organization', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('telephone', self.gf('rpki.gui.app.models.TelephoneField')(max_length=40, null=True, blank=True)), + ('box', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), + ('extended', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('street', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('city', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), + ('region', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), + ('code', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), + ('country', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), + )) + db.send_create_signal('app', ['GhostbusterRequest']) + + # Adding model 'Timestamp' + db.create_table('app_timestamp', ( + ('name', self.gf('django.db.models.fields.CharField')(max_length=30, primary_key=True)), + ('ts', self.gf('django.db.models.fields.DateTimeField')()), + )) + db.send_create_signal('app', ['Timestamp']) + + + def backwards(self, orm): + # Deleting model 'ResourceCert' + db.delete_table('app_resourcecert') + + # Deleting model 'ResourceRangeAddressV4' + db.delete_table('app_resourcerangeaddressv4') + + # Deleting model 'ResourceRangeAddressV6' + db.delete_table('app_resourcerangeaddressv6') + + # Deleting model 'ResourceRangeAS' + db.delete_table('app_resourcerangeas') + + # Deleting model 'GhostbusterRequest' + db.delete_table('app_ghostbusterrequest') + + # Deleting model 'Timestamp' + db.delete_table('app_timestamp') + + + models = { + 'app.ghostbusterrequest': { + 'Meta': {'ordering': "('family_name', 'given_name')", 'object_name': 'GhostbusterRequest', '_ormbases': ['irdb.GhostbusterRequest']}, + 'additional_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'box': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'code': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'extended': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'family_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'ghostbusterrequest_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.GhostbusterRequest']", 'unique': 'True', 'primary_key': 'True'}), + 'given_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'honorific_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'honorific_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'organization': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'region': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'street': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'telephone': ('rpki.gui.app.models.TelephoneField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}) + }, + 'app.resourcecert': { + 'Meta': {'object_name': 'ResourceCert'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'not_after': ('django.db.models.fields.DateTimeField', [], {}), + 'not_before': ('django.db.models.fields.DateTimeField', [], {}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'to': "orm['irdb.Parent']"}), + 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'app.resourcerangeaddressv4': { + 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV4'}, + 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': "orm['app.ResourceCert']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'prefix_max': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}), + 'prefix_min': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}) + }, + 'app.resourcerangeaddressv6': { + 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV6'}, + 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges_v6'", 'to': "orm['app.ResourceCert']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'prefix_max': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}), + 'prefix_min': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}) + }, + 'app.resourcerangeas': { + 'Meta': {'ordering': "('min', 'max')", 'object_name': 'ResourceRangeAS'}, + 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asn_ranges'", 'to': "orm['app.ResourceCert']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'min': ('django.db.models.fields.PositiveIntegerField', [], {}) + }, + 'app.timestamp': { + 'Meta': {'object_name': 'Timestamp'}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}), + 'ts': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'irdb.ghostbusterrequest': { + 'Meta': {'object_name': 'GhostbusterRequest'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'to': "orm['irdb.ResourceHolderCA']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'null': 'True', 'to': "orm['irdb.Parent']"}), + 'vcard': ('django.db.models.fields.TextField', [], {}) + }, + 'irdb.parent': { + 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Parent', '_ormbases': ['irdb.Turtle']}, + 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), + 'child_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), + 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parents'", 'to': "orm['irdb.ResourceHolderCA']"}), + 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}), + 'referral_authorization': ('rpki.irdb.models.SignedReferralField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'referrer': ('rpki.irdb.models.HandleField', [], {'max_length': '120', 'null': 'True', 'blank': 'True'}), + 'repository_type': ('rpki.irdb.models.EnumField', [], {}), + 'ta': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), + 'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'irdb.resourceholderca': { + 'Meta': {'object_name': 'ResourceHolderCA'}, + 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}), + 'handle': ('rpki.irdb.models.HandleField', [], {'unique': 'True', 'max_length': '120'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_crl_update': ('rpki.irdb.models.SundialField', [], {}), + 'latest_crl': ('rpki.irdb.models.CRLField', [], {'default': 'None', 'blank': 'True'}), + 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}), + 'next_crl_update': ('rpki.irdb.models.SundialField', [], {}), + 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}), + 'private_key': ('rpki.irdb.models.RSAKeyField', [], {'default': 'None', 'blank': 'True'}) + }, + 'irdb.turtle': { + 'Meta': {'object_name': 'Turtle'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['app']
\ No newline at end of file diff --git a/rpki/gui/app/migrations/0002_auto__add_field_resourcecert_conf.py b/rpki/gui/app/south_migrations/0002_auto__add_field_resourcecert_conf.py index d3326f90..d3326f90 100644 --- a/rpki/gui/app/migrations/0002_auto__add_field_resourcecert_conf.py +++ b/rpki/gui/app/south_migrations/0002_auto__add_field_resourcecert_conf.py diff --git a/rpki/gui/app/migrations/0003_set_conf_from_parent.py b/rpki/gui/app/south_migrations/0003_set_conf_from_parent.py index a90a11cc..a90a11cc 100644 --- a/rpki/gui/app/migrations/0003_set_conf_from_parent.py +++ b/rpki/gui/app/south_migrations/0003_set_conf_from_parent.py diff --git a/rpki/gui/app/migrations/0004_auto__chg_field_resourcecert_conf.py b/rpki/gui/app/south_migrations/0004_auto__chg_field_resourcecert_conf.py index a236ad4a..a236ad4a 100644 --- a/rpki/gui/app/migrations/0004_auto__chg_field_resourcecert_conf.py +++ b/rpki/gui/app/south_migrations/0004_auto__chg_field_resourcecert_conf.py diff --git a/rpki/gui/app/migrations/0005_auto__chg_field_resourcecert_parent.py b/rpki/gui/app/south_migrations/0005_auto__chg_field_resourcecert_parent.py index 11e9c814..11e9c814 100644 --- a/rpki/gui/app/migrations/0005_auto__chg_field_resourcecert_parent.py +++ b/rpki/gui/app/south_migrations/0005_auto__chg_field_resourcecert_parent.py diff --git a/rpki/gui/app/migrations/0006_add_conf_acl.py b/rpki/gui/app/south_migrations/0006_add_conf_acl.py index 88fe8171..88fe8171 100644 --- a/rpki/gui/app/migrations/0006_add_conf_acl.py +++ b/rpki/gui/app/south_migrations/0006_add_conf_acl.py diff --git a/rpki/gui/app/migrations/0007_default_acls.py b/rpki/gui/app/south_migrations/0007_default_acls.py index 40656d0f..40656d0f 100644 --- a/rpki/gui/app/migrations/0007_default_acls.py +++ b/rpki/gui/app/south_migrations/0007_default_acls.py diff --git a/rpki/gui/app/migrations/0008_add_alerts.py b/rpki/gui/app/south_migrations/0008_add_alerts.py index 77af68d2..77af68d2 100644 --- a/rpki/gui/app/migrations/0008_add_alerts.py +++ b/rpki/gui/app/south_migrations/0008_add_alerts.py diff --git a/rpki/gui/cacheview/__init__.py b/rpki/gui/app/south_migrations/__init__.py index e69de29b..e69de29b 100644 --- a/rpki/gui/cacheview/__init__.py +++ b/rpki/gui/app/south_migrations/__init__.py diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py index bf152f8e..03c7c168 100644 --- a/rpki/gui/app/views.py +++ b/rpki/gui/app/views.py @@ -1,5 +1,5 @@ # Copyright (C) 2010, 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2012, 2014 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2014, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -23,7 +23,6 @@ __version__ = '$Id$' import os import os.path -from tempfile import NamedTemporaryFile import cStringIO import csv import logging @@ -42,7 +41,8 @@ from django.forms.formsets import formset_factory, BaseFormSet from django.contrib import messages from django.db.models import Q -from rpki.irdb import Zookeeper, ChildASN, ChildNet, ROARequestPrefix +from rpki.irdb import Zookeeper +from rpki.irdb.models import ChildASN, ChildNet, ROARequestPrefix from rpki.gui.app import models, forms, glue, range_list from rpki.resource_set import (resource_range_as, resource_range_ip, roa_prefix_ipv4) @@ -50,7 +50,6 @@ from rpki import sundial import rpki.exceptions import rpki.csv_utils -from rpki.gui.cacheview.models import ROA from rpki.gui.routeview.models import RouteOrigin from rpki.gui.decorators import tls_required @@ -136,10 +135,6 @@ def generic_import(request, queryset, configure, form_class=None, if request.method == 'POST': form = form_class(request.POST, request.FILES) if form.is_valid(): - tmpf = NamedTemporaryFile(prefix='import', suffix='.xml', - delete=False) - tmpf.write(form.cleaned_data['xml'].read()) - tmpf.close() z = Zookeeper(handle=conf.handle) handle = form.cleaned_data.get('handle') # CharField uses an empty string for the empty value, rather than @@ -148,27 +143,25 @@ def generic_import(request, queryset, configure, form_class=None, 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) + # configure_repository returns None, so can't use tuple expansion + # here. Unpack the tuple below if post_import_redirect is None. + r = configure(z, form.cleaned_data['xml'], handle) except lxml.etree.XMLSyntaxError as e: - logger.exception('caught XMLSyntaxError while parsing uploaded file') + logger.exception('caught XMLSyntaxError while parsing uploaded file') messages.error( request, 'The uploaded file has an invalid XML syntax' ) 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) + # 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) else: form = form_class() @@ -298,10 +291,10 @@ def serve_xml(content, basename, ext='xml'): `basename` is the prefix to specify for the XML filename. - `csv` is the type (default: xml) + `ext` is the type (default: xml) """ - resp = http.HttpResponse(content, mimetype='application/%s' % ext) + resp = http.HttpResponse(content, content_type='application/%s' % ext) resp['Content-Disposition'] = 'attachment; filename=%s.%s' % (basename, ext) return resp @@ -332,13 +325,10 @@ def import_asns(request): if request.method == 'POST': form = forms.ImportCSVForm(request.POST, request.FILES) if form.is_valid(): - f = NamedTemporaryFile(prefix='asns', suffix='.csv', delete=False) - f.write(request.FILES['csv'].read()) - f.close() z = Zookeeper(handle=conf.handle, disable_signal_handlers=True) try: z.load_asns( - f.name, + request.FILES['csv'], ignore_missing_children=form.cleaned_data['ignore_missing_children'] ) except rpki.irdb.models.Child.DoesNotExist: @@ -353,8 +343,6 @@ def import_asns(request): z.run_rpkid_now() messages.success(request, 'Successfully imported AS delgations from CSV file.') return redirect(dashboard) - finally: - os.unlink(f.name) else: form = forms.ImportCSVForm() return render(request, 'app/import_resource_form.html', { @@ -381,13 +369,10 @@ def import_prefixes(request): if request.method == 'POST': form = forms.ImportCSVForm(request.POST, request.FILES) if form.is_valid(): - f = NamedTemporaryFile(prefix='prefixes', suffix='.csv', delete=False) - f.write(request.FILES['csv'].read()) - f.close() z = Zookeeper(handle=conf.handle, disable_signal_handlers=True) try: z.load_prefixes( - f.name, + request.FILES['csv'], ignore_missing_children=form.cleaned_data['ignore_missing_children'] ) except rpki.irdb.models.Child.DoesNotExist: @@ -399,8 +384,6 @@ def import_prefixes(request): z.run_rpkid_now() messages.success(request, 'Successfully imported AS delgations from CSV file.') return redirect(dashboard) - finally: - os.unlink(f.name) else: form = forms.ImportCSVForm() return render(request, 'app/import_resource_form.html', { @@ -474,10 +457,10 @@ def child_add_prefix(request, pk): child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max), version=version) Zookeeper( - handle=conf.handle, - logstream=logstream, - disable_signal_handlers=True - ).run_rpkid_now() + handle=conf.handle, + logstream=logstream, + disable_signal_handlers=True + ).run_rpkid_now() return http.HttpResponseRedirect(child.get_absolute_url()) else: form = forms.AddNetForm(child=child) @@ -497,10 +480,10 @@ def child_add_asn(request, pk): r = resource_range_as.parse_str(asns) child.asns.create(start_as=r.min, end_as=r.max) Zookeeper( - handle=conf.handle, - logstream=logstream, - disable_signal_handlers=True - ).run_rpkid_now() + handle=conf.handle, + logstream=logstream, + disable_signal_handlers=True + ).run_rpkid_now() return http.HttpResponseRedirect(child.get_absolute_url()) else: form = forms.AddASNForm(child=child) @@ -531,10 +514,10 @@ def child_edit(request, pk): models.ChildASN.objects.filter(child=child).exclude(pk__in=form.cleaned_data.get('as_ranges')).delete() models.ChildNet.objects.filter(child=child).exclude(pk__in=form.cleaned_data.get('address_ranges')).delete() Zookeeper( - handle=conf.handle, - logstream=logstream, - disable_signal_handlers=True - ).run_rpkid_now() + handle=conf.handle, + logstream=log, + disable_signal_handlers=True + ).run_rpkid_now() return http.HttpResponseRedirect(child.get_absolute_url()) else: form = form_class(initial={ @@ -713,27 +696,27 @@ def roa_create_multi(request): v = [] rng.chop_into_prefixes(v) init.extend([{'asn': asn, 'prefix': str(p)} for p in v]) - extra = 0 if init else 1 + extra = 0 if init else 1 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. + # 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(): 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']) + 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)) - v.append({'prefix': str(rng), 'max_prefixlen': max_prefixlen, - 'asn': asn}) + v.append({'prefix': str(rng), 'max_prefixlen': max_prefixlen, + 'asn': asn}) query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max) @@ -903,14 +886,10 @@ def roa_import(request): if request.method == 'POST': form = forms.ImportCSVForm(request.POST, request.FILES) if form.is_valid(): - import tempfile - tmp = tempfile.NamedTemporaryFile(suffix='.csv', prefix='roas', delete=False) - tmp.write(request.FILES['csv'].read()) - tmp.close() z = Zookeeper(handle=request.session['handle'], disable_signal_handlers=True) try: - z.load_roa_requests(tmp.name) + z.load_roa_requests(request.FILES['csv']) except rpki.csv_utils.BadCSVSyntax as e: messages.error(request, 'CSV has bad syntax: %s' % (e,)) @@ -918,8 +897,6 @@ def roa_import(request): z.run_rpkid_now() messages.success(request, 'Successfully imported ROAs.') return redirect(dashboard) - finally: - os.unlink(tmp.name) else: form = forms.ImportCSVForm() return render(request, 'app/import_resource_form.html', { @@ -939,7 +916,7 @@ def roa_export(request): # each roa prefix gets a unique group so rpkid will issue separate roas for group, roapfx in enumerate(ROARequestPrefix.objects.filter(roa_request__issuer=conf)): csv_writer.writerow([str(roapfx.as_roa_prefix()), roapfx.roa_request.asn, '%s-%d' % (conf.handle, group)]) - resp = http.HttpResponse(f.getvalue(), mimetype='application/csv') + resp = http.HttpResponse(f.getvalue(), content_type='application/csv') resp['Content-Disposition'] = 'attachment; filename=roas.csv' return resp @@ -1215,7 +1192,7 @@ def resource_holder_delete(request, pk): form = forms.Empty(request.POST) if form.is_valid(): z = Zookeeper(handle=conf.handle, logstream=log) - z.delete_self() + z.delete_tenant() z.synchronize_deleted_ca() return redirect(resource_holder_list) else: @@ -1239,22 +1216,13 @@ def resource_holder_create(request): zk_child = Zookeeper(handle=handle, logstream=log) identity_xml = zk_child.initialize_resource_bpki() if parent: - # FIXME etree_wrapper should allow us to deal with file objects - t = NamedTemporaryFile(delete=False) - t.close() - - identity_xml.save(t.name) zk_parent = Zookeeper(handle=parent.handle, logstream=log) - parent_response, _ = zk_parent.configure_child(t.name) - parent_response.save(t.name) + parent_response, _ = zk_parent.configure_child(identity_xml) zk_parent.synchronize_ca() - repo_req, _ = zk_child.configure_parent(t.name) - repo_req.save(t.name) - repo_resp, _ = zk_parent.configure_publication_client(t.name) - repo_resp.save(t.name) + repo_req, _ = zk_child.configure_parent(parent_response) + repo_resp, _ = zk_parent.configure_publication_client(repo_req) zk_parent.synchronize_pubd() - zk_child.configure_repository(t.name) - os.remove(t.name) + zk_child.configure_repository(repo_resp) zk_child.synchronize_ca() return redirect(resource_holder_list) else: @@ -1460,14 +1428,9 @@ class RouterImportView(FormView): def form_valid(self, form): conf = get_conf(self.request.user, self.request.session['handle']) - tmpf = NamedTemporaryFile(prefix='import', suffix='.xml', - delete=False) - tmpf.write(form.cleaned_data['xml'].read()) - tmpf.close() z = Zookeeper(handle=conf.handle, disable_signal_handlers=True) - z.add_router_certificate_request(tmpf.name) + z.add_router_certificate_request(form.cleaned_data['xml']) z.run_rpkid_now() - os.remove(tmpf.name) return super(RouterImportView, self).form_valid(form) def get_context_data(self, **kwargs): diff --git a/rpki/gui/cacheview/forms.py b/rpki/gui/cacheview/forms.py deleted file mode 100644 index 7ae3601f..00000000 --- a/rpki/gui/cacheview/forms.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2013 SPARTA, Inc. a Parsons Company -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -__version__ = '$Id$' - -from django import forms - -from rpki.gui.cacheview.misc import parse_ipaddr -from rpki.exceptions import BadIPResource -from rpki.resource_set import resource_range_as - - -class SearchForm(forms.Form): - asn = forms.CharField(required=False, help_text='AS or range', label='AS') - addr = forms.CharField(required=False, max_length=40, help_text='range/CIDR', label='IP Address') - - def clean(self): - asn = self.cleaned_data.get('asn') - addr = self.cleaned_data.get('addr') - if (asn and addr) or ((not asn) and (not addr)): - raise forms.ValidationError('Please specify either an AS or IP range, not both') - - if asn: - try: - resource_range_as.parse_str(asn) - except ValueError: - raise forms.ValidationError('invalid AS range') - - if addr: - #try: - parse_ipaddr(addr) - #except BadIPResource: - # raise forms.ValidationError('invalid IP address range/prefix') - - return self.cleaned_data - - -class SearchForm2(forms.Form): - resource = forms.CharField(required=True) diff --git a/rpki/gui/cacheview/misc.py b/rpki/gui/cacheview/misc.py deleted file mode 100644 index 54431224..00000000 --- a/rpki/gui/cacheview/misc.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -from rpki.resource_set import resource_range_ipv4, resource_range_ipv6 -from rpki.exceptions import BadIPResource - -def parse_ipaddr(s): - # resource_set functions only accept str - if isinstance(s, unicode): - s = s.encode() - s = s.strip() - r = resource_range_ipv4.parse_str(s) - try: - r = resource_range_ipv4.parse_str(s) - return 4, r - except BadIPResource: - r = resource_range_ipv6.parse_str(s) - return 6, r - -# vim:sw=4 ts=8 expandtab diff --git a/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html b/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html deleted file mode 100644 index 76edc1ba..00000000 --- a/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block content %} -<h1>{% block title %}IP Range Detail{% endblock %}</h1> - -<p> -IP Range: {{ object }} -</p> - -<p>Covered by the following resource certs:</p> - -<ul> -{% for cert in object.certs.all %} -<li><a href="{{ cert.get_absolute_url }}">{{ cert }}</a></li> -{% endfor %} -</ul> - -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/cacheview_base.html b/rpki/gui/cacheview/templates/cacheview/cacheview_base.html deleted file mode 100644 index ec71d740..00000000 --- a/rpki/gui/cacheview/templates/cacheview/cacheview_base.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base.html" %} -{% load url from future %} - -{% block sidebar %} -<form method='post' action='{% url 'res-search' %}'> - {% csrf_token %} - <input type='text' id='id_resource' name='resource' placeholder='prefix or AS'> - <button type='submit'>Search</button> -</form> -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/cert_detail.html b/rpki/gui/cacheview/templates/cacheview/cert_detail.html deleted file mode 100644 index 256e7780..00000000 --- a/rpki/gui/cacheview/templates/cacheview/cert_detail.html +++ /dev/null @@ -1,105 +0,0 @@ -{% extends "cacheview/signedobject_detail.html" %} - -{% block title %} -Resource Certificate Detail -{% endblock %} - -{% block detail %} - -<h2>RFC3779 Resources</h2> - -<table class='table table-striped'> - <thead> - <tr><th>AS Ranges</th><th>IP Ranges</th></tr> - </thead> - <tbody> - <tr> - <td style='text-align:left;vertical-align:top'> - <ul class='compact'> - {% for asn in object.asns.all %} - <li><a href="{{ asn.get_absolute_url }}">{{ asn }}</a></li> - {% endfor %} - </ul> - </td> - <td style='text-align:left;vertical-align:top'> - <ul class='compact'> - {% for rng in object.addresses.all %} - <li><a href="{{ rng.get_absolute_url }}">{{ rng }}</a></li> - {% endfor %} - </ul> - </td> - </tr> - </tbody> -</table> - -<div class='section'> -<h2>Issued Objects</h2> -<ul> - -{% if object.ghostbusters.all %} - <li> -<h3>Ghostbusters</h3> - -<table class='table table-striped'> - <thead> - <tr><th>Name</th><th>Expires</th></tr> - </thead> - <tbody> - -{% for g in object.ghostbusters.all %} - <tr class='{{ g.status_id }}'> - <td><a href="{{ g.get_absolute_url }}">{{ g }}</a></td> - <td>{{ g.not_after }}</td> - </tr> - </tbody> -{% endfor %} - -</table> -{% endif %} - -{% if object.roas.all %} - <li> -<h3>ROAs</h3> -<table class='table table-striped'> - <thead> - <tr><th>#</th><th>Prefix</th><th>AS</th><th>Expires</th></tr> - </thead> - <tbody> - {% for roa in object.roas.all %} - {% for pfx in roa.prefixes.all %} - <tr class='{{ roa.status_id }}'> - <td><a href="{{ roa.get_absolute_url }}">#</a></td> - <td>{{ pfx }}</td> - <td>{{ roa.asid }}</td> - <td>{{ roa.not_after }}</td> - </tr> - {% endfor %} - {% endfor %} - </tbody> -</table> -{% endif %} - -{% if object.children.all %} -<li> -<h3>Children</h3> -<table class='table table-striped'> - <thead> - <tr><th>Name</th><th>Expires</th></tr> - </thead> - <tbody> - - {% for child in object.children.all %} - <tr class='{{ child.status_id }}'> - <td><a href="{{ child.get_absolute_url }}">{{ child.name }}</a></td> - <td>{{ child.not_after }}</td> - </tr> - {% endfor %} - </tbody> -</table> -{% endif %} - -</ul> - -</div><!--issued objects--> - -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html b/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html deleted file mode 100644 index 4215f757..00000000 --- a/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "cacheview/signedobject_detail.html" %} - -{% block title %}Ghostbuster Detail{% endblock %} - -{% block detail %} -<p> -<table class='table'> - <tr><td>Full Name</td><td>{{ object.full_name }}</td></tr> - <tr><td>Organization</td><td>{{ object.organization }}</td></tr> - <tr><td>Email</td><td>{{ object.email_address }}</td></tr> - <tr><td>Telephone</td><td>{{ object.telephone }}</td></tr> -</table> -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/global_summary.html b/rpki/gui/cacheview/templates/cacheview/global_summary.html deleted file mode 100644 index 0dbd0ffc..00000000 --- a/rpki/gui/cacheview/templates/cacheview/global_summary.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block content %} -<div class='page-header'> - <h1>Browse Global RPKI</h1> -</div> - -<table class="table table-striped"> - <thead> - <tr> - <th>Name</th> - <th>Expires</th> - <th>URI</th> - </tr> - </thead> - <tbody> - {% for r in roots %} - <tr> - <td><a href="{{ r.get_absolute_url }}">{{ r.name }}</a></td> - <td>{{ r.not_after }}</td> - <td>{{ r.repo.uri }}</td> - </tr> - {% endfor %} - </tbody> -</table> -{% endblock content %} diff --git a/rpki/gui/cacheview/templates/cacheview/query_result.html b/rpki/gui/cacheview/templates/cacheview/query_result.html deleted file mode 100644 index 0694c531..00000000 --- a/rpki/gui/cacheview/templates/cacheview/query_result.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block content %} - -<h1>{% block title %}Query Results{% endblock %}</h1> - -<table> - <tr><th>Prefix</th><th>AS</th><th>Valid</th><th>Until</th></tr> - {% for object in object_list %} - <tr class='{{ object.1.status.kind_as_str }}'> - <td>{{ object.0 }}</td> - <td>{{ object.1.asid }}</td> - <td><a href="{{ object.1.get_absolute_url }}">{{ object.1.ok }}</a></td> - <td>{{ object.1.not_after }}</td> - </tr> - {% endfor %} -</table> - -<p><a href="{% url rpki.gui.cacheview.views.query_view %}">new query</a></p> - -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/roa_detail.html b/rpki/gui/cacheview/templates/cacheview/roa_detail.html deleted file mode 100644 index 39cc547b..00000000 --- a/rpki/gui/cacheview/templates/cacheview/roa_detail.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "cacheview/signedobject_detail.html" %} - -{% block title %}ROA Detail{% endblock %} - -{% block detail %} -<p> -<table> - <tr><td>AS</td><td>{{ object.asid }}</td></tr> -</table> - -<h2>Prefixes</h2> - -<ul> -{% for pfx in object.prefixes.all %} -<li>{{ pfx }} -{% endfor %} -</ul> -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/search_form.html b/rpki/gui/cacheview/templates/cacheview/search_form.html deleted file mode 100644 index 1141615d..00000000 --- a/rpki/gui/cacheview/templates/cacheview/search_form.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block title %} -{{ search_type }} Search -{% endblock %} - -{% block content %} - -<h1>{{search_type}} Search</h1> - -<form method='post' action='{{ request.url }}'> - {% csrf_token %} - {{ form.as_p }} - <input type='submit' name='Search'> -</form> - -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/search_result.html b/rpki/gui/cacheview/templates/cacheview/search_result.html deleted file mode 100644 index 7cbf852e..00000000 --- a/rpki/gui/cacheview/templates/cacheview/search_result.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block content %} - -<div class='page-header'> - <h1>Search Results <small>{{ resource }}</small></h1> -</div> - -<h2>Matching Resource Certificates</h2> -{% if certs %} -<ul> -{% for cert in certs %} -<li><a href="{{ cert.get_absolute_url }}">{{ cert }}</a> -{% endfor %} -</ul> -{% else %} -<p>none</p> -{% endif %} - -<h2>Matching ROAs</h2> -{% if roas %} -<table class='table table-striped'> - <thead> - <tr> - <th>#</th><th>Prefix</th><th>AS</th> - </tr> - </thead> - <tbody> -{% for roa in roas %} -<tr> - <td><a href="{{ roa.get_absolute_url }}">#</a></td> - <td>{{ roa.prefixes.all.0 }}</td> - <td>{{ roa.asid }}</td> -</tr> -{% endfor %} -</tbody> -</table> -{% else %} -<p>none</p> -{% endif %} - -{% endblock %} diff --git a/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html b/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html deleted file mode 100644 index 22ae3d27..00000000 --- a/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "cacheview/cacheview_base.html" %} - -{% block content %} -<div class='page-header'> -<h1>{% block title %}Signed Object Detail{% endblock %}</h1> -</div> - -<h2>Cert Info</h2> -<table class='table table-striped'> - <tr><td>Subject Name</td><td>{{ object.name }}</td></tr> - <tr><td>SKI</td><td>{{ object.keyid }}</td></tr> - {% if object.sia %} - <tr><td>SIA</td><td>{{ object.sia }}</td></tr> - {% endif %} - <tr><td>Not Before</td><td>{{ object.not_before }}</td></tr> - <tr><td>Not After</td><td>{{ object.not_after }}</td></tr> -</table> - -<h2>Metadata</h2> - -<table class='table table-striped'> - <tr><td>URI</td><td>{{ object.repo.uri }}</td></tr> - <tr><td>Last Modified</td><td>{{ object.mtime_as_datetime|date:"DATETIME_FORMAT" }}</td></tr> -</table> - -<h2>Validation Status</h2> -<table class='table table-striped'> - <thead> - <tr><th>Timestamp</th><th>Generation</th><th>Status</th></tr> - </thead> - <tbody> - {% for status in object.repo.statuses.all %} - <tr class="{{ status.status.get_kind_display }}"><td>{{ status.timestamp }}</td><td>{{ status.get_generation_display }}</td><td>{{ status.status.status }}</td></tr> - {% endfor %} - </tbody> -</table> - -<h2>X.509 Certificate Chain</h2> - -<table class='table table-striped'> - <thead> - <tr><th>Depth</th><th>Name</th></tr> - </thead> - <tbody> - -{% for cert in chain %} -<tr class='{{ cert.1.status_id }}'> - <td>{{ cert.0 }}</td> - <td><a href="{{ cert.1.get_absolute_url }}">{{ cert.1.name }}</a></td> -</tr> -{% endfor %} -</tbody> - -</table> - -{% block detail %}{% endblock %} - -{% endblock %} diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py deleted file mode 100644 index 2247054b..00000000 --- a/rpki/gui/cacheview/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) - -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -"""} - diff --git a/rpki/gui/cacheview/urls.py b/rpki/gui/cacheview/urls.py deleted file mode 100644 index cc03a587..00000000 --- a/rpki/gui/cacheview/urls.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2013 SPARTA, Inc. a Parsons Company -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -__version__ = '$Id$' - -from django.conf.urls import patterns, url -from rpki.gui.cacheview.views import (CertDetailView, RoaDetailView, - GhostbusterDetailView) - -urlpatterns = patterns('', - url(r'^search$', 'rpki.gui.cacheview.views.search_view', - name='res-search'), - url(r'^cert/(?P<pk>[^/]+)$', CertDetailView.as_view(), name='cert-detail'), - url(r'^gbr/(?P<pk>[^/]+)$', GhostbusterDetailView.as_view(), - name='ghostbuster-detail'), - url(r'^roa/(?P<pk>[^/]+)$', RoaDetailView.as_view(), name='roa-detail'), - (r'^$', 'rpki.gui.cacheview.views.global_summary'), -) - -# vim:sw=4 ts=8 expandtab diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py deleted file mode 100644 index 47425c8c..00000000 --- a/rpki/gui/cacheview/util.py +++ /dev/null @@ -1,441 +0,0 @@ -# Copyright (C) 2011 SPARTA, Inc. dba Cobham -# Copyright (C) 2012, 2013 SPARTA, Inc. a Parsons Company -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -__version__ = '$Id$' -__all__ = ('import_rcynic_xml') - -default_logfile = '/var/rcynic/data/rcynic.xml' -default_root = '/var/rcynic/data' -object_accepted = None # set by import_rcynic_xml() - -import time -import vobject -import logging -import os -import stat -from socket import getfqdn -from cStringIO import StringIO - -from django.db import transaction -import django.db.models - -import rpki -import rpki.gui.app.timestamp -from rpki.gui.app.models import Conf, Alert -from rpki.gui.cacheview import models -from rpki.rcynic import rcynic_xml_iterator, label_iterator -from rpki.sundial import datetime -from rpki.irdb.zookeeper import Zookeeper - -logger = logging.getLogger(__name__) - - -class SomeoneShowMeAWayToGetOuttaHere(Exception): - "'Cause I constantly pray I'll get outta here." - - -def rcynic_cert(cert, obj): - if not cert.sia_directory_uri: - raise SomeoneShowMeAWayToGetOuttaHere - - obj.sia = cert.sia_directory_uri - - # object must be saved for the related manager methods below to work - obj.save() - - # for the root cert, we can't set inst.issuer = inst until - # after inst.save() has been called. - if obj.issuer is None: - obj.issuer = obj - obj.save() - - # resources can change when a cert is updated - obj.asns.clear() - obj.addresses.clear() - - if cert.resources.asn.inherit: - # FIXME: what happens when the parent's resources change and the child - # cert is not reissued? - obj.asns.add(*obj.issuer.asns.all()) - else: - for asr in cert.resources.asn: - logger.debug('processing %s', asr) - - attrs = {'min': asr.min, 'max': asr.max} - q = models.ASRange.objects.filter(**attrs) - if not q: - obj.asns.create(**attrs) - else: - obj.asns.add(q[0]) - - # obj.issuer is None the first time we process the root cert in the - # hierarchy, so we need to guard against dereference - for cls, addr_obj, addrset, parentset in ( - models.AddressRange, obj.addresses, cert.resources.v4, - obj.issuer.addresses.all() if obj.issuer else [] - ), ( - models.AddressRangeV6, obj.addresses_v6, cert.resources.v6, - obj.issuer.addresses_v6.all() if obj.issuer else [] - ): - if addrset.inherit: - addr_obj.add(*parentset) - else: - for rng in addrset: - logger.debug('processing %s', rng) - - attrs = {'prefix_min': rng.min, 'prefix_max': rng.max} - q = cls.objects.filter(**attrs) - if not q: - addr_obj.create(**attrs) - else: - addr_obj.add(q[0]) - - -def rcynic_roa(roa, obj): - obj.asid = roa.asID - # object must be saved for the related manager methods below to work - obj.save() - obj.prefixes.clear() - obj.prefixes_v6.clear() - for pfxset in roa.prefix_sets: - if pfxset.__class__.__name__ == 'roa_prefix_set_ipv6': - roa_cls = models.ROAPrefixV6 - prefix_obj = obj.prefixes_v6 - else: - roa_cls = models.ROAPrefixV4 - prefix_obj = obj.prefixes - - for pfx in pfxset: - attrs = {'prefix_min': pfx.min(), - 'prefix_max': pfx.max(), - 'max_length': pfx.max_prefixlen} - q = roa_cls.objects.filter(**attrs) - if not q: - prefix_obj.create(**attrs) - else: - prefix_obj.add(q[0]) - - -def rcynic_gbr(gbr, obj): - vcard = vobject.readOne(gbr.vcard) - obj.full_name = vcard.fn.value if hasattr(vcard, 'fn') else None - obj.email_address = vcard.email.value if hasattr(vcard, 'email') else None - obj.telephone = vcard.tel.value if hasattr(vcard, 'tel') else None - obj.organization = vcard.org.value[0] if hasattr(vcard, 'org') else None - obj.save() - -LABEL_CACHE = {} - -# dict keeping mapping of uri to (handle, old status, new status) for objects -# published by the local rpkid -uris = {} - -dispatch = { - 'rcynic_certificate': rcynic_cert, - 'rcynic_roa': rcynic_roa, - 'rcynic_ghostbuster': rcynic_gbr -} - -model_class = { - 'rcynic_certificate': models.Cert, - 'rcynic_roa': models.ROA, - 'rcynic_ghostbuster': models.Ghostbuster -} - - -def save_status(repo, vs): - timestamp = datetime.fromXMLtime(vs.timestamp).to_sql() - status = LABEL_CACHE[vs.status] - g = models.generations_dict[vs.generation] if vs.generation else None - repo.statuses.create(generation=g, timestamp=timestamp, status=status) - - # if this object is in our interest set, update with the current validation - # status - if repo.uri in uris: - x, y, z, q = uris[repo.uri] - valid = z or (status is object_accepted) # don't clobber previous True value - uris[repo.uri] = x, y, valid, repo - - if status is not object_accepted: - return - - cls = model_class[vs.file_class.__name__] - # find the instance of the signedobject subclass that is associated with - # this repo instance (may be empty when not accepted) - inst_qs = cls.objects.filter(repo=repo) - - logger.debug('processing %s', vs.filename) - - if not inst_qs: - inst = cls(repo=repo) - logger.debug('object not found in db, creating new object cls=%s id=%s', - cls, id(inst)) - else: - inst = inst_qs[0] - - try: - # determine if the object is changed/new - mtime = os.stat(vs.filename)[stat.ST_MTIME] - except OSError as e: - logger.error('unable to stat %s: %s %s', - vs.filename, type(e), e) - # treat as if missing from rcynic.xml - # use inst_qs rather than deleting inst so that we don't raise an - # exception for newly created objects (inst_qs will be empty) - inst_qs.delete() - return - - if mtime != inst.mtime: - inst.mtime = mtime - try: - obj = vs.obj # causes object to be lazily loaded - except Exception, e: - logger.warning('Caught %s while processing %s: %s', - type(e), vs.filename, e) - return - - inst.not_before = obj.notBefore.to_sql() - inst.not_after = obj.notAfter.to_sql() - inst.name = obj.subject - inst.keyid = obj.ski - - # look up signing cert - if obj.issuer == obj.subject: - # self-signed cert (TA) - assert isinstance(inst, models.Cert) - inst.issuer = None - else: - # if an object has moved in the repository, the entry for - # the old location will still be in the database, but - # without any object_accepted in its validtion status - qs = models.Cert.objects.filter( - keyid=obj.aki, - name=obj.issuer, - repo__statuses__status=object_accepted - ) - ncerts = len(qs) - if ncerts == 0: - logger.warning('unable to find signing cert with ski=%s (%s)', obj.aki, obj.issuer) - return - else: - if ncerts > 1: - # multiple matching certs, all of which are valid - logger.warning('Found multiple certs matching ski=%s sn=%s', obj.aki, obj.issuer) - for c in qs: - logger.warning(c.repo.uri) - # just use the first match - inst.issuer = qs[0] - - try: - # do object-specific tasks - dispatch[vs.file_class.__name__](obj, inst) - except SomeoneShowMeAWayToGetOuttaHere: - logger.error("something wrong with %s, skipping", vs.filename) - inst_qs.delete() - return - except: - logger.error('caught exception while processing rcynic_object:\n' - 'vs=' + repr(vs) + '\nobj=' + repr(obj)) - # .show() writes to stdout - obj.show() - raise - - logger.debug('object saved id=%s', id(inst)) - else: - logger.debug('object is unchanged') - - -@transaction.commit_on_success -def process_cache(root, xml_file): - - last_uri = None - repo = None - - logger.info('clearing validation statuses') - models.ValidationStatus.objects.all().delete() - - logger.info('updating validation status') - for vs in rcynic_xml_iterator(root, xml_file): - if vs.uri != last_uri: - repo, created = models.RepositoryObject.objects.get_or_create(uri=vs.uri) - last_uri = vs.uri - save_status(repo, vs) - - # garbage collection - # remove all objects which have no ValidationStatus references, which - # means they did not appear in the last XML output - logger.info('performing garbage collection') - - # Delete all objects that have zero validation status elements. - models.RepositoryObject.objects.annotate(num_statuses=django.db.models.Count('statuses')).filter(num_statuses=0).delete() - - # Delete all SignedObject instances that were not accepted. There may - # exist rows for objects that were previously accepted. - # See https://trac.rpki.net/ticket/588#comment:30 - # - # We have to do this here rather than in save_status() because the - # <validation_status/> elements are not guaranteed to be consecutive for a - # given URI. see https://trac.rpki.net/ticket/625#comment:5 - models.SignedObject.objects.exclude(repo__statuses__status=object_accepted).delete() - - # ROAPrefixV* objects are M2M so they are not automatically deleted when - # their ROA object disappears - models.ROAPrefixV4.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete() - models.ROAPrefixV6.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete() - logger.info('done with garbage collection') - - -@transaction.commit_on_success -def process_labels(xml_file): - logger.info('updating labels...') - - for label, kind, desc in label_iterator(xml_file): - logger.debug('label=%s kind=%s desc=%s', label, kind, desc) - if kind: - q = models.ValidationLabel.objects.filter(label=label) - if not q: - obj = models.ValidationLabel(label=label) - else: - obj = q[0] - - obj.kind = models.kinds_dict[kind] - obj.status = desc - obj.save() - - LABEL_CACHE[label] = obj - - -def fetch_published_objects(): - """Query rpkid for all objects published by local users, and look up the - current validation status of each object. The validation status is used - later to send alerts for objects which have transitioned to invalid. - - """ - logger.info('querying for published objects') - - handles = [conf.handle for conf in Conf.objects.all()] - req = [rpki.left_right.list_published_objects_elt.make_pdu(action='list', self_handle=h, tag=h) for h in handles] - z = Zookeeper() - pdus = z.call_rpkid(*req) - for pdu in pdus: - if isinstance(pdu, rpki.left_right.list_published_objects_elt): - # Look up the object in the rcynic cache - qs = models.RepositoryObject.objects.filter(uri=pdu.uri) - if qs: - # get the current validity state - valid = qs[0].statuses.filter(status=object_accepted).exists() - uris[pdu.uri] = (pdu.self_handle, valid, False, None) - logger.debug('adding ' + pdu.uri) - else: - # this object is not in the cache. it was either published - # recently, or disappared previously. if it disappeared - # previously, it has already been alerted. in either case, we - # omit the uri from the list since we are interested only in - # objects which were valid and are no longer valid - pass - elif isinstance(pdu, rpki.left_right.report_error_elt): - logging.error('rpkid reported an error: %s', pdu.error_code) - - -class Handle(object): - def __init__(self): - self.invalid = [] - self.missing = [] - - def add_invalid(self, v): - self.invalid.append(v) - - def add_missing(self, v): - self.missing.append(v) - - -def notify_invalid(): - """Send email alerts to the addresses registered in ghostbuster records for - any invalid objects that were published by users of this system. - - """ - - logger.info('sending notifications for invalid objects') - - # group invalid objects by user - notify = {} - for uri, v in uris.iteritems(): - handle, old_status, new_status, obj = v - - if obj is None: - # object went missing - n = notify.get(handle, Handle()) - n.add_missing(uri) - # only select valid->invalid - elif old_status and not new_status: - n = notify.get(handle, Handle()) - n.add_invalid(obj) - - for handle, v in notify.iteritems(): - conf = Conf.objects.get(handle) - - msg = StringIO() - msg.write('This is an alert about problems with objects published by ' - 'the resource handle %s.\n\n' % handle) - - if v.invalid: - msg.write('The following objects were previously valid, but are ' - 'now invalid:\n') - - for o in v.invalid: - msg.write('\n') - msg.write(o.repo.uri) - msg.write('\n') - for s in o.statuses.all(): - msg.write('\t') - msg.write(s.status.label) - msg.write(': ') - msg.write(s.status.status) - msg.write('\n') - - if v.missing: - msg.write('The following objects were previously valid but are no ' - 'longer in the cache:\n') - - for o in v.missing: - msg.write(o) - msg.write('\n') - - msg.write("""-- -You are receiving this email because your address is published in a Ghostbuster -record, or is the default email address for this resource holder account on -%s.""" % getfqdn()) - - from_email = 'root@' + getfqdn() - subj = 'invalid RPKI object alert for resource handle %s' % conf.handle - conf.send_alert(subj, msg.getvalue(), from_email, severity=Alert.ERROR) - - -def import_rcynic_xml(root=default_root, logfile=default_logfile): - """Load the contents of rcynic.xml into the rpki.gui.cacheview database.""" - - global object_accepted - - start = time.time() - process_labels(logfile) - object_accepted = LABEL_CACHE['object_accepted'] - fetch_published_objects() - process_cache(root, logfile) - notify_invalid() - - rpki.gui.app.timestamp.update('rcynic_import') - - stop = time.time() - logger.info('elapsed time %d seconds.', (stop - start)) diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py deleted file mode 100644 index 94870eb2..00000000 --- a/rpki/gui/cacheview/views.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2013 SPARTA, Inc. a Parsons Company -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -__version__ = '$Id$' - -from django.views.generic import DetailView -from django.shortcuts import render -from django.db.models import F - -from rpki.gui.cacheview import models, forms, misc -from rpki.resource_set import resource_range_as, resource_range_ip -from rpki.POW import IPAddress -from rpki.exceptions import BadIPResource - - -def cert_chain(obj): - """ - returns an iterator covering all certs from the root cert down to the EE. - """ - chain = [obj] - while obj != obj.issuer: - obj = obj.issuer - chain.append(obj) - return zip(range(len(chain)), reversed(chain)) - - -class SignedObjectDetailView(DetailView): - def get_context_data(self, **kwargs): - context = super(SignedObjectDetailView, - self).get_context_data(**kwargs) - context['chain'] = cert_chain(self.object) - return context - - -class RoaDetailView(SignedObjectDetailView): - model = models.ROA - - -class CertDetailView(SignedObjectDetailView): - model = models.Cert - - -class GhostbusterDetailView(SignedObjectDetailView): - model = models.Ghostbuster - - -def search_view(request): - certs = None - roas = None - - if request.method == 'POST': - form = forms.SearchForm2(request.POST, request.FILES) - if form.is_valid(): - resource = form.cleaned_data.get('resource') - # try to determine the type of input given - try: - r = resource_range_as.parse_str(resource) - certs = models.Cert.objects.filter(asns__min__gte=r.min, - asns__max__lte=r.max) - roas = models.ROA.objects.filter(asid__gte=r.min, - asid__lte=r.max) - except: - try: - r = resource_range_ip.parse_str(resource) - if r.version == 4: - certs = models.Cert.objects.filter( - addresses__prefix_min__lte=r.min, - addresses__prefix_max__gte=r.max) - roas = models.ROA.objects.filter( - prefixes__prefix_min__lte=r.min, - prefixes__prefix_max__gte=r.max) - else: - certs = models.Cert.objects.filter( - addresses_v6__prefix_min__lte=r.min, - addresses_v6__prefix_max__gte=r.max) - roas = models.ROA.objects.filter( - prefixes_v6__prefix_min__lte=r.min, - prefixes_v6__prefix_max__gte=r.max) - except BadIPResource: - pass - - return render(request, 'cacheview/search_result.html', - {'resource': resource, 'certs': certs, 'roas': roas}) - - -def cmp_prefix(x, y): - r = cmp(x[0].family, y[0].family) - if r == 0: - r = cmp(x[2], y[2]) # integer address - if r == 0: - r = cmp(x[0].bits, y[0].bits) - if r == 0: - r = cmp(x[0].max_length, y[0].max_length) - if r == 0: - r = cmp(x[1].asid, y[1].asid) - return r - - -#def cmp_prefix(x,y): -# for attr in ('family', 'prefix', 'bits', 'max_length'): -# r = cmp(getattr(x[0], attr), getattr(y[0], attr)) -# if r: -# return r -# return cmp(x[1].asid, y[1].asid) - - -def query_view(request): - """ - Allow the user to search for an AS or prefix, and show all published ROA - information. - """ - - if request.method == 'POST': - form = forms.SearchForm(request.POST, request.FILES) - if form.is_valid(): - certs = None - roas = None - - addr = form.cleaned_data.get('addr') - asn = form.cleaned_data.get('asn') - - if addr: - family, r = misc.parse_ipaddr(addr) - prefixes = models.ROAPrefix.objects.filter(family=family, prefix=str(r.min)) - - prefix_list = [] - for pfx in prefixes: - for roa in pfx.roas.all(): - prefix_list.append((pfx, roa)) - elif asn: - r = resource_range_as.parse_str(asn) - roas = models.ROA.objects.filter(asid__gte=r.min, asid__lte=r.max) - - # display the results sorted by prefix - prefix_list = [] - for roa in roas: - for pfx in roa.prefixes.all(): - addr = IPAddress(pfx.prefix.encode()) - prefix_list.append((pfx, roa, addr)) - prefix_list.sort(cmp=cmp_prefix) - - return render('cacheview/query_result.html', - {'object_list': prefix_list}, request) - else: - form = forms.SearchForm() - - return render('cacheview/search_form.html', { - 'form': form, 'search_type': 'ROA '}, request) - - -def global_summary(request): - """Display a table summarizing the state of the global RPKI.""" - - roots = models.Cert.objects.filter(issuer=F('pk')) # self-signed - - return render(request, 'cacheview/global_summary.html', { - 'roots': roots - }) - -# vim:sw=4 ts=8 expandtab diff --git a/rpki/gui/default_settings.py b/rpki/gui/default_settings.py deleted file mode 100644 index a30b0362..00000000 --- a/rpki/gui/default_settings.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -This module contains static configuration settings for the web portal. -""" - -__version__ = '$Id$' - -import os -import random -import string -import socket - -import rpki.config -import rpki.autoconf - -# Where to put static files. -STATIC_ROOT = rpki.autoconf.datarootdir + '/rpki/media' - -# Must end with a slash! -STATIC_URL = '/media/' - -# Where to email server errors. -ADMINS = (('Administrator', 'root@localhost'),) - -LOGGING = { - 'version': 1, - 'formatters': { - 'verbose': { - # see http://docs.python.org/2.7/library/logging.html#logging.LogRecord - 'format': '%(levelname)s %(asctime)s %(name)s %(message)s' - }, - }, - 'handlers': { - 'stderr': { - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'verbose', - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - }, - }, - 'loggers': { - 'rpki.async': { - # enabled for tracking https://trac.rpki.net/ticket/681 - # need to change this to WARNING once ticket is closed - 'level': 'DEBUG', - }, - # The Django default LOGGING configuration disables propagate on these - # two loggers. Re-enable propagate so they will hit our root logger. - 'django.request': { - 'propagate': True, - }, - 'django.security': { - 'propagate': True, - }, - }, - 'root': { - 'level': 'WARNING', - 'handlers': ['stderr', 'mail_admins'], - }, -} - -# Load the SQL authentication bits from the system rpki.conf. -rpki_config = rpki.config.parser(section='web_portal') - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': rpki_config.get('sql-database'), - 'USER': rpki_config.get('sql-username'), - 'PASSWORD': rpki_config.get('sql-password'), - - # Ensure the default storage engine is InnoDB since we need - # foreign key support. The Django documentation suggests - # removing this after the syncdb is performed as an optimization, - # but there isn't an easy way to do this automatically. - - # Setting charset to latin1 is a disgusting kludge, but without - # this MySQL 5.6 (and, proably, later) gets tetchy about ASN.1 - # DER stored in BLOB columns not being well-formed UTF8 (sic). - # If you know of a better solution, tell us. - - 'OPTIONS': { - 'init_command': 'SET storage_engine=INNODB', - 'charset': 'latin1', - } - } -} - - -def select_tz(): - "Find a supported timezone that looks like UTC" - for tz in ('UTC', 'GMT', 'Etc/UTC', 'Etc/GMT'): - if os.path.exists('/usr/share/zoneinfo/' + tz): - return tz - # Can't determine the proper timezone, fall back to UTC and let Django - # report the error to the user. - return 'UTC' - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = select_tz() - -def get_secret_key(): - """Retrieve the secret-key value from rpki.conf or generate a random value - if it is not present.""" - d = string.letters + string.digits - val = ''.join([random.choice(d) for _ in range(50)]) - return rpki_config.get('secret-key', val) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = get_secret_key() - -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -# for details on why you might need this. -def get_allowed_hosts(): - allowed_hosts = set(rpki_config.multiget("allowed-hosts")) - allowed_hosts.add(socket.getfqdn()) - try: - import netifaces - for interface in netifaces.interfaces(): - addresses = netifaces.ifaddresses(interface) - for af in (netifaces.AF_INET, netifaces.AF_INET6): - if af in addresses: - for address in addresses[af]: - if "addr" in address: - allowed_hosts.add(address["addr"]) - except ImportError: - pass - return list(allowed_hosts) - -ALLOWED_HOSTS = get_allowed_hosts() - -DOWNLOAD_DIRECTORY = rpki_config.get('download-directory', '/var/tmp') - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - 'django.template.loaders.eggs.Loader' -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware' -) - -ROOT_URLCONF = 'rpki.gui.urls' - -INSTALLED_APPS = ( - 'django.contrib.auth', - #'django.contrib.admin', - #'django.contrib.admindocs', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.staticfiles', - 'rpki.irdb', - 'rpki.gui.app', - 'rpki.gui.cacheview', - 'rpki.gui.routeview', - 'south', -) - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.contrib.messages.context_processors.messages", - "django.core.context_processors.request", - "django.core.context_processors.static" -) - -# Allow local site to override any setting above -- but if there's -# anything that local sites routinely need to modify, please consider -# putting that configuration into rpki.conf and just adding code here -# to read that configuration. -try: - from local_settings import * -except: - pass diff --git a/rpki/gui/gui_rpki_cache/__init__.py b/rpki/gui/gui_rpki_cache/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/rpki/gui/gui_rpki_cache/__init__.py diff --git a/rpki/gui/gui_rpki_cache/migrations/0001_initial.py b/rpki/gui/gui_rpki_cache/migrations/0001_initial.py new file mode 100644 index 00000000..23625f56 --- /dev/null +++ b/rpki/gui/gui_rpki_cache/migrations/0001_initial.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import rpki.gui.gui_rpki_cache.models +import rpki.gui.models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='AddressRange', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ], + options={ + 'ordering': ('prefix_min',), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AddressRangeV6', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ], + options={ + 'ordering': ('prefix_min',), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ASRange', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('min', models.BigIntegerField(validators=[rpki.gui.models.validate_asn])), + ('max', models.BigIntegerField(validators=[rpki.gui.models.validate_asn])), + ], + options={ + 'ordering': ('min', 'max'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Cert', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('uri', models.TextField()), + ('sha256', models.SlugField(unique=True, max_length=64)), + ('not_before', models.DateTimeField()), + ('not_after', models.DateTimeField()), + ('ski', models.SlugField(max_length=40)), + ('addresses', models.ManyToManyField(related_name='certs', to='gui_rpki_cache.AddressRange')), + ('addresses_v6', models.ManyToManyField(related_name='certs', to='gui_rpki_cache.AddressRangeV6')), + ('asns', models.ManyToManyField(related_name='certs', to='gui_rpki_cache.ASRange')), + ('issuer', models.ForeignKey(related_name='children', to='gui_rpki_cache.Cert', null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Ghostbuster', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('uri', models.TextField()), + ('sha256', models.SlugField(unique=True, max_length=64)), + ('not_before', models.DateTimeField()), + ('not_after', models.DateTimeField()), + ('full_name', models.CharField(max_length=40)), + ('email_address', models.EmailField(max_length=254, null=True, blank=True)), + ('organization', models.CharField(max_length=255, null=True, blank=True)), + ('telephone', rpki.gui.gui_rpki_cache.models.TelephoneField(max_length=255, null=True, blank=True)), + ('issuer', models.ForeignKey(related_name='ghostbusters', to='gui_rpki_cache.Cert')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ROA', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('uri', models.TextField()), + ('sha256', models.SlugField(unique=True, max_length=64)), + ('not_before', models.DateTimeField()), + ('not_after', models.DateTimeField()), + ('asid', models.PositiveIntegerField()), + ('issuer', models.ForeignKey(related_name='roas', to='gui_rpki_cache.Cert')), + ], + options={ + 'ordering': ('asid',), + }, + ), + migrations.CreateModel( + name='ROAPrefixV4', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ('max_length', models.PositiveSmallIntegerField()), + ], + options={ + 'ordering': ('prefix_min',), + }, + ), + migrations.CreateModel( + name='ROAPrefixV6', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('prefix_min', rpki.gui.models.IPAddressField(db_index=True)), + ('prefix_max', rpki.gui.models.IPAddressField(db_index=True)), + ('max_length', models.PositiveSmallIntegerField()), + ], + options={ + 'ordering': ('prefix_min',), + }, + ), + migrations.AddField( + model_name='roa', + name='prefixes', + field=models.ManyToManyField(related_name='roas', to='gui_rpki_cache.ROAPrefixV4'), + ), + migrations.AddField( + model_name='roa', + name='prefixes_v6', + field=models.ManyToManyField(related_name='roas', to='gui_rpki_cache.ROAPrefixV6'), + ), + ] diff --git a/rpki/gui/gui_rpki_cache/migrations/0002_auto_20160411_2311.py b/rpki/gui/gui_rpki_cache/migrations/0002_auto_20160411_2311.py new file mode 100644 index 00000000..e9ceaac0 --- /dev/null +++ b/rpki/gui/gui_rpki_cache/migrations/0002_auto_20160411_2311.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gui_rpki_cache', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='cert', + name='sha256', + ), + migrations.RemoveField( + model_name='ghostbuster', + name='sha256', + ), + migrations.RemoveField( + model_name='roa', + name='sha256', + ), + migrations.AlterField( + model_name='cert', + name='issuer', + field=models.ForeignKey(to='gui_rpki_cache.Cert', null=True), + ), + migrations.AlterField( + model_name='ghostbuster', + name='issuer', + field=models.ForeignKey(to='gui_rpki_cache.Cert', null=True), + ), + migrations.AlterField( + model_name='roa', + name='issuer', + field=models.ForeignKey(to='gui_rpki_cache.Cert', null=True), + ), + ] diff --git a/rpki/gui/gui_rpki_cache/migrations/0003_auto_20160420_2146.py b/rpki/gui/gui_rpki_cache/migrations/0003_auto_20160420_2146.py new file mode 100644 index 00000000..e43ab1de --- /dev/null +++ b/rpki/gui/gui_rpki_cache/migrations/0003_auto_20160420_2146.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gui_rpki_cache', '0002_auto_20160411_2311'), + ] + + operations = [ + migrations.AlterField( + model_name='ghostbuster', + name='issuer', + field=models.ForeignKey(related_name='ghostbusters', to='gui_rpki_cache.Cert', null=True), + ), + migrations.AlterField( + model_name='roa', + name='issuer', + field=models.ForeignKey(related_name='roas', to='gui_rpki_cache.Cert', null=True), + ), + ] diff --git a/rpki/gui/gui_rpki_cache/migrations/__init__.py b/rpki/gui/gui_rpki_cache/migrations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/rpki/gui/gui_rpki_cache/migrations/__init__.py diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/gui_rpki_cache/models.py index c3ee8421..dd0739c0 100644 --- a/rpki/gui/cacheview/models.py +++ b/rpki/gui/gui_rpki_cache/models.py @@ -1,5 +1,5 @@ # Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2012 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -13,16 +13,13 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '$Id$' - -from datetime import datetime -import time +__version__ = '$Id: $' from django.db import models -from django.core.urlresolvers import reverse import rpki.resource_set import rpki.gui.models +import rpki.rcynicdb.models class TelephoneField(models.CharField): @@ -31,56 +28,13 @@ class TelephoneField(models.CharField): models.CharField.__init__(self, *args, **kwargs) -class AddressRange(rpki.gui.models.PrefixV4): - @models.permalink - def get_absolute_url(self): - return ('rpki.gui.cacheview.views.addressrange_detail', [str(self.pk)]) - - -class AddressRangeV6(rpki.gui.models.PrefixV6): - @models.permalink - def get_absolute_url(self): - return ('rpki.gui.cacheview.views.addressrange_detail_v6', - [str(self.pk)]) - - -class ASRange(rpki.gui.models.ASN): - @models.permalink - def get_absolute_url(self): - return ('rpki.gui.cacheview.views.asrange_detail', [str(self.pk)]) - -kinds = list(enumerate(('good', 'warn', 'bad'))) -kinds_dict = dict((v, k) for k, v in kinds) - +class AddressRange(rpki.gui.models.PrefixV4): pass -class ValidationLabel(models.Model): - """ - Represents a specific error condition defined in the rcynic XML - output file. - """ - label = models.CharField(max_length=79, db_index=True, unique=True) - status = models.CharField(max_length=255) - kind = models.PositiveSmallIntegerField(choices=kinds) - - def __unicode__(self): - return self.label - - -class RepositoryObject(models.Model): - """ - Represents a globally unique RPKI repository object, specified by its URI. - """ - uri = models.URLField(unique=True, db_index=True) -generations = list(enumerate(('current', 'backup'))) -generations_dict = dict((val, key) for (key, val) in generations) +class AddressRangeV6(rpki.gui.models.PrefixV6): pass -class ValidationStatus(models.Model): - timestamp = models.DateTimeField() - generation = models.PositiveSmallIntegerField(choices=generations, null=True) - status = models.ForeignKey(ValidationLabel) - repo = models.ForeignKey(RepositoryObject, related_name='statuses') +class ASRange(rpki.gui.models.ASN): pass class SignedObject(models.Model): @@ -89,58 +43,47 @@ class SignedObject(models.Model): The signing certificate is ommitted here in order to give a proper value for the 'related_name' attribute. """ - repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True) - - # on-disk file modification time - mtime = models.PositiveIntegerField(default=0) - # SubjectName - name = models.CharField(max_length=255) + class Meta: + abstract = True - # value from the SKI extension - keyid = models.CharField(max_length=60, db_index=True) + # Duplicate of rpki.rcynicdb.models.RPKIObject + uri = models.TextField() # validity period from EE cert which signed object not_before = models.DateTimeField() not_after = models.DateTimeField() - def mtime_as_datetime(self): - """ - convert the local timestamp to UTC and convert to a datetime object - """ - return datetime.utcfromtimestamp(self.mtime + time.timezone) - - def status_id(self): - """ - Returns a HTML class selector for the current object based on its validation status. - The selector is chosen based on the current generation only. If there is any bad status, - return bad, else if there are any warn status, return warn, else return good. - """ - for x in reversed(kinds): - if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]): - return x[1] - return None # should not happen - def __unicode__(self): - return u'%s' % self.name + return u'%s' % self.uri + + def __repr__(self): + return u'<%s name=%s uri=%s>' % (self.__class__.__name__, self.uri) class Cert(SignedObject): """ - Object representing a resource certificate. + Object representing a resource CA certificate. """ + # Duplicate of rpki.rcynicdb.models.RPKIObject + ski = models.SlugField(max_length=40) # hex SHA-1 + addresses = models.ManyToManyField(AddressRange, related_name='certs') addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs') asns = models.ManyToManyField(ASRange, related_name='certs') - issuer = models.ForeignKey('self', related_name='children', null=True) - sia = models.CharField(max_length=255) - def get_absolute_url(self): - return reverse('cert-detail', args=[str(self.pk)]) + issuer = models.ForeignKey('self', on_delete=models.CASCADE, null=True) + + def __repr__(self): + return u'<Cert uri=%s ski=%s not_before=%s not_after=%s>' % (self.uri, self.ski, self.not_before, self.not_after) + + def __unicode__(self): + return u'RPKI CA Cert %s' % (self.uri,) def get_cert_chain(self): """Return a list containing the complete certificate chain for this certificate.""" + cert = self x = [cert] while cert != cert.issuer: @@ -180,6 +123,7 @@ class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4): @property def routes(self): """return all routes covered by this roa prefix""" + return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min, prefix_max__lte=self.prefix_max) @@ -201,10 +145,7 @@ class ROA(SignedObject): asid = models.PositiveIntegerField() prefixes = models.ManyToManyField(ROAPrefixV4, related_name='roas') prefixes_v6 = models.ManyToManyField(ROAPrefixV6, related_name='roas') - issuer = models.ForeignKey('Cert', related_name='roas') - - def get_absolute_url(self): - return reverse('roa-detail', args=[str(self.pk)]) + issuer = models.ForeignKey(Cert, on_delete=models.CASCADE, null=True, related_name='roas') class Meta: ordering = ('asid',) @@ -218,11 +159,7 @@ class Ghostbuster(SignedObject): email_address = models.EmailField(blank=True, null=True) organization = models.CharField(blank=True, null=True, max_length=255) telephone = TelephoneField(blank=True, null=True) - issuer = models.ForeignKey('Cert', related_name='ghostbusters') - - def get_absolute_url(self): - # note that ghostbuster-detail is different from gbr-detail! sigh - return reverse('ghostbuster-detail', args=[str(self.pk)]) + issuer = models.ForeignKey(Cert, on_delete=models.CASCADE, null=True, related_name='ghostbusters') def __unicode__(self): if self.full_name: diff --git a/rpki/gui/gui_rpki_cache/util.py b/rpki/gui/gui_rpki_cache/util.py new file mode 100644 index 00000000..0bc4fa5d --- /dev/null +++ b/rpki/gui/gui_rpki_cache/util.py @@ -0,0 +1,308 @@ +# Copyright (C) 2011 SPARTA, Inc. dba Cobham +# Copyright (C) 2012, 2013, 2016 SPARTA, Inc. a Parsons Company +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +__version__ = '$Id: util.py 6335 2016-03-29 03:09:13Z sra $' + +import logging +import time +import vobject +from socket import getfqdn +from cStringIO import StringIO + +if __name__ == '__main__': + import os + logging.basicConfig(level=logging.DEBUG) + os.environ.update(DJANGO_SETTINGS_MODULE='rpki.django_settings.gui') + import django + django.setup() + +import os.path + +logger = logging.getLogger(__name__) + +from django.db import transaction +import django.db.models + +import rpki +import rpki.resource_set +import rpki.left_right +import rpki.gui.app.timestamp +from rpki.gui.app.models import Conf, Alert +from rpki.gui.gui_rpki_cache import models +from rpki.irdb.zookeeper import Zookeeper + +from lxml.etree import Element, SubElement + + +def process_certificate(auth, obj): + cert = models.Cert.objects.filter(ski=obj.ski).first() + if cert: + logger.debug('cache hit for CA cert uri=%s ski=%s' % (cert.uri, cert.ski)) + return cert # cache hit + + logger.debug('parsing cert at %s' % (obj.uri,)) + + """Process Resource CA Certificates""" + x509 = rpki.POW.X509.derRead(obj.der) + + # ensure this is a resource CA Certificate (ignore Router certs) + bc = x509.getBasicConstraints() + is_ca = bc is not None and bc[0] + if not is_ca: + return + + # locate the parent certificate + if obj.aki and obj.aki != obj.ski: + try: + issuer = models.Cert.objects.get(ski=obj.aki) + except models.Cert.DoesNotExist: + # process parent cert first + issuer = process_certificate(auth, rpki.rcynicdb.models.RPKIObject.objects.get(ski=obj.aki, authenticated=auth)) + else: + issuer = None # root + + asns, v4, v6 = x509.getRFC3779() + + cert = models.Cert.objects.create( + uri=obj.uri, + ski=obj.ski, + not_before=x509.getNotBefore(), + not_after=x509.getNotAfter(), + issuer=issuer + ) + + if issuer is None: + cert.issuer = cert # self-signed + cert.save() + + if asns == 'inherit': + cert.asns.add(issuer.asns.all()) + elif asns: + for asmin, asmax in asns: + asr, _ = models.ASRange.objects.get_or_create(min=asmin, max=asmax) + cert.asns.add(asr) + + if v4 == 'inherit': + cert.addresses.add(issuer.addresses.all()) + elif v4: + for v4min, v4max in v4: + pfx, _ = models.AddressRange.objects.get_or_create(prefix_min=v4min, prefix_max=v4max) + cert.addresses.add(pfx) + + if v6 == 'inherit': + cert.addresses_v6.add(issuer.addresses_v6.all()) + elif v6: + for v6min, v6max in v6: + pfx, _ = models.AddressRangeV6.objects.get_or_create(prefix_min=v6min, prefix_max=v6max) + cert.addresses_v6.add(pfx) + + return cert + +def process_roa(auth, obj): + logger.debug('parsing roa at %s' % (obj.uri,)) + + r = rpki.POW.ROA.derRead(obj.der) + r.verify() # required in order to extract asID + ee = r.certs()[0] # rpki.POW.X509 + aki = ee.getAKI().encode('hex') + + logger.debug('looking for ca cert with ski=%s' % (aki,)) + + # Locate the Resource CA cert that issued the EE that signed this ROA + issuer = models.Cert.objects.get(ski=aki) + + roa = models.ROA.objects.create( + uri=obj.uri, + asid=r.getASID(), + not_before=ee.getNotBefore(), + not_after=ee.getNotAfter(), + issuer=issuer) + + prefixes = r.getPrefixes() + if prefixes[0]: # v4 + for p in prefixes[0]: + v = rpki.resource_set.roa_prefix_ipv4(*p) + roapfx, _ = models.ROAPrefixV4.objects.get_or_create(prefix_min=v.min(), prefix_max=v.max(), max_length=v.max_prefixlen) + roa.prefixes.add(roapfx) + if prefixes[1]: # v6 + for p in prefixes[1]: + v = rpki.resource_set.roa_prefix_ipv6(*p) + roapfx, _ = models.ROAPrefixV6.objects.get_or_create(prefix_min=v.min(), prefix_max=v.max(), max_length=v.max_prefixlen) + roa.prefixes_v6.add(roapfx) + + return roa + +def process_ghostbuster(auth, obj): + logger.debug('parsing ghostbuster at %s' % (obj.uri,)) + g = rpki.POW.CMS.derRead(obj.der) + ee = g.certs()[0] # rpki.POW.X509 + aki = ee.getAKI().encode('hex') + vcard = vobject.readOne(g.verify()) + + # Locate the Resource CA cert that issued the EE that signed this ROA + issuer = models.Cert.objects.get(ski=aki) + + gbr = models.Ghostbuster.objects.create( + uri=obj.uri, + issuer=issuer, + not_before=ee.getNotBefore(), + not_after=ee.getNotAfter(), + full_name = vcard.fn.value if hasattr(vcard, 'fn') else None, + email_address = vcard.email.value if hasattr(vcard, 'email') else None, + telephone = vcard.tel.value if hasattr(vcard, 'tel') else None, + organization = vcard.org.value[0] if hasattr(vcard, 'org') else None + ) + + return gbr + +@transaction.atomic +def process_cache(): + logger.info('processing rpki cache') + + # foreign key constraints should cause all other objects to be removed + models.Cert.objects.all().delete() + + # certs must be processed first in order to build proper foreign keys for roa/gbr + dispatch = { + '.cer': process_certificate, + '.gbr': process_ghostbuster, + '.roa': process_roa + } + + auth = rpki.rcynicdb.models.Authenticated.objects.order_by('started').first() + + # Resource CA Certs are processed first in order to attach ROAs and Ghostbusters + for suffix in ('.cer', '.roa', '.gbr'): + cb = dispatch[suffix] + + for rpkiobj in auth.rpkiobject_set.filter(uri__endswith=suffix): + cb(auth, rpkiobj) + + # Garbage collection - remove M2M relations for certs/ROAs which no longer exist + models.ASRange.objects.annotate(num_certs=django.db.models.Count('certs')).filter(num_certs=0).delete() + models.AddressRange.objects.annotate(num_certs=django.db.models.Count('certs')).filter(num_certs=0).delete() + models.AddressRangeV6.objects.annotate(num_certs=django.db.models.Count('certs')).filter(num_certs=0).delete() + + models.ROAPrefixV4.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete() + models.ROAPrefixV6.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete() + + +# dict mapping resource handle to list of published objects, use for notifying objects which have become invalid +uris = {} +model_map = { '.cer': models.Cert, '.roa': models.ROA, '.gbr': models.Ghostbuster } + +def fetch_published_objects(): + """Query rpkid for all objects published by local users, and look up the + current validation status of each object. The validation status is used + later to send alerts for objects which have transitioned to invalid. + """ + logger.info('querying for published objects') + + handles = [conf.handle for conf in Conf.objects.all()] + q_msg = Element(rpki.left_right.tag_msg, nsmap = rpki.left_right.nsmap, + type = "query", version = rpki.left_right.version) + for h in handles: + SubElement(q_msg, rpki.left_right.tag_list_published_objects, tenant_handle=h, tag=h) + try: + z = Zookeeper() + r_msg = z.call_rpkid(q_msg) + except Exception as err: + logger.error('Unable to connect to rpkid to fetch list of published objects') + logger.exception(err) + # Should be safe to continue processing the rcynic cache, we just don't do any notifications + return + + for r_pdu in r_msg: + if r_pdu.tag == rpki.left_right.tag_list_published_objects: + # Look up the object in the rcynic cache + uri = r_pdu.get('uri') + ext = os.path.splitext(uri)[1] + if ext in model_map: + model = model_map[ext] + handle = r_pdu.get('tenant_handle') + + if model.objects.filter(uri=uri).exists(): + v = uris.setdefault(handle, []) + v.append(uri) + logger.debug('adding %s', uri) + #else: + # this object is not in the cache. it was either published + # recently, or disappared previously. if it disappeared + # previously, it has already been alerted. in either case, we + # omit the uri from the list since we are interested only in + # objects which were valid and are no longer valid + else: + logger.debug('skipping object ext=%s uri=%s' % (ext, uri)) + + elif r_pdu.tag == rpki.left_right.tag_report_error: + logging.error('rpkid reported an error: %s', r_pdu.get("error_code")) + + +def notify_invalid(): + """Send email alerts to the addresses registered in ghostbuster records for + any invalid objects that were published by users of this system. + """ + + logger.info('sending notifications for invalid objects') + + for handle, published_objects in uris.iteritems(): + missing = [] + for u in published_objects: + ext = os.path.splitext(u)[1] + model = model_map[ext] + if not model.objects.filter(uri=u).exists(): + missing.append(u) + + if missing: + conf = Conf.objects.get(handle) + + msg = StringIO() + msg.write('This is an alert about problems with objects published by ' + 'the resource handle %s.\n\n' % handle) + + msg.write('The following objects were previously valid, but are ' + 'now invalid:\n') + + for u in missing: + msg.write('\n') + msg.write(u) + msg.write('\n') + + msg.write("""-- +You are receiving this email because your address is published in a Ghostbuster +record, or is the default email address for this resource holder account on +%s.""" % getfqdn()) + + from_email = 'root@' + getfqdn() + subj = 'invalid RPKI object alert for resource handle %s' % conf.handle + conf.send_alert(subj, msg.getvalue(), from_email, severity=Alert.ERROR) + + +def update_cache(): + """Cache information from the current rcynicdb for display by the gui""" + + start = time.time() + fetch_published_objects() + process_cache() + notify_invalid() + + rpki.gui.app.timestamp.update('rcynic_import') + + stop = time.time() + logger.info('elapsed time %d seconds.', (stop - start)) + + +if __name__ == '__main__': + process_cache() diff --git a/rpki/gui/models.py b/rpki/gui/models.py index 184383c0..4d56c18e 100644 --- a/rpki/gui/models.py +++ b/rpki/gui/models.py @@ -19,57 +19,72 @@ Common classes for reuse in apps. __version__ = '$Id$' from django.db import models +from django.core.exceptions import ValidationError import rpki.resource_set import rpki.POW -from south.modelsinspector import add_introspection_rules -class IPv6AddressField(models.Field): - "Field large enough to hold a 128-bit unsigned integer." - - __metaclass__ = models.SubfieldBase - - def db_type(self, connection): - return 'binary(16)' - - def to_python(self, value): - if isinstance(value, rpki.POW.IPAddress): +class IPAddressField(models.CharField): + """ + Field class for rpki.POW.IPAddress, stored as zero-padded + hexadecimal so lexicographic order is identical to numeric order. + """ + + # Django's CharField type doesn't distinguish between the length + # of the human readable form and the length of the storage form, + # so we have to leave room for IPv6 punctuation even though we + # only store hexadecimal digits and thus will never use the full + # width of the database field. Price we pay for portability. + # + # Documentation on the distinction between the various conversion + # methods is fairly opaque, to put it politely, and we have to + # handle database engines which sometimes return buffers or other + # classes instead of strings, so the conversions are a bit + # finicky. If this goes haywire, your best bet is probably to + # litter the code with logging.debug() calls and debug by printf. + + def __init__(self, *args, **kwargs): + kwargs["max_length"] = 40 + super(IPAddressField, self).__init__(*args, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super(IPAddressField, self).deconstruct() + del kwargs["max_length"] + return name, path, args, kwargs + + @staticmethod + def _value_to_ipaddress(value): + if value is None or isinstance(value, rpki.POW.IPAddress): return value - return rpki.POW.IPAddress.fromBytes(value) - - def get_db_prep_value(self, value, connection, prepared): - """ - Note that we add a custom conversion to encode long values as hex - strings in SQL statements. See settings.get_conv() for details. - - """ - return value.toBytes() + value = str(value) + if ":" in value or "." in value: + return rpki.POW.IPAddress(value) + else: + return rpki.POW.IPAddress.fromBytes(value.decode("hex")) - -class IPv4AddressField(models.Field): - "Wrapper around rpki.POW.IPAddress." - - __metaclass__ = models.SubfieldBase - - def db_type(self, connection): - return 'int UNSIGNED' + def from_db_value(self, value, expression, connection, context): + # Can't use super() here, see Django documentation. + return self._value_to_ipaddress(value) def to_python(self, value): + return self._value_to_ipaddress( + super(IPAddressField, self).to_python(value)) + + @staticmethod + def _hex_from_ipaddress(value): if isinstance(value, rpki.POW.IPAddress): + return value.toBytes().encode("hex") + else: return value - return rpki.POW.IPAddress(value, version=4) - def get_db_prep_value(self, value, connection, prepared): - return long(value) + def get_prep_value(self, value): + return super(IPAddressField, self).get_prep_value( + self._hex_from_ipaddress(value)) -add_introspection_rules( - [ - ([IPv4AddressField, IPv6AddressField], [], {}) - ], - [r'^rpki\.gui\.models\.IPv4AddressField', - r'^rpki\.gui\.models\.IPv6AddressField'] -) + def get_db_prep_value(self, value, connection, prepared = False): + return self._hex_from_ipaddress( + super(IPAddressField, self).get_db_prep_value(value, connection, prepared)) class Prefix(models.Model): @@ -82,6 +97,7 @@ class Prefix(models.Model): """ Returns the prefix as a rpki.resource_set.resource_range_ip object. """ + return self.range_cls(self.prefix_min, self.prefix_max) @property @@ -96,6 +112,7 @@ class Prefix(models.Model): def __unicode__(self): """This method may be overridden by subclasses. The default implementation calls get_prefix_display(). """ + return self.get_prefix_display() class Meta: @@ -110,8 +127,8 @@ class PrefixV4(Prefix): range_cls = rpki.resource_set.resource_range_ipv4 - prefix_min = IPv4AddressField(db_index=True, null=False) - prefix_max = IPv4AddressField(db_index=True, null=False) + prefix_min = IPAddressField(db_index=True, null=False) + prefix_max = IPAddressField(db_index=True, null=False) class Meta(Prefix.Meta): abstract = True @@ -122,20 +139,25 @@ class PrefixV6(Prefix): range_cls = rpki.resource_set.resource_range_ipv6 - prefix_min = IPv6AddressField(db_index=True, null=False) - prefix_max = IPv6AddressField(db_index=True, null=False) + prefix_min = IPAddressField(db_index=True, null=False) + prefix_max = IPAddressField(db_index=True, null=False) class Meta(Prefix.Meta): abstract = True +def validate_asn(value): + if value < 0 or value > 0xFFFFFFFFL: + raise ValidationError('%s is not valid autonomous sequence number' % value) + + class ASN(models.Model): """Represents a range of ASNs. This model is abstract, and is intended to be reused by applications.""" - min = models.PositiveIntegerField(null=False) - max = models.PositiveIntegerField(null=False) + min = models.BigIntegerField(null=False, validators=[validate_asn]) + max = models.BigIntegerField(null=False, validators=[validate_asn]) class Meta: abstract = True diff --git a/rpki/gui/routeview/api.py b/rpki/gui/routeview/api.py index cf699c9a..b4ff297a 100644 --- a/rpki/gui/routeview/api.py +++ b/rpki/gui/routeview/api.py @@ -29,8 +29,8 @@ def route_list(request): By default, only returns up to 10 matching routes, but the client may request a different limit with the 'count=' query string parameter. - """ + hard_limit = 100 if request.method == 'GET' and 'prefix__in' in request.GET: diff --git a/rpki/gui/routeview/models.py b/rpki/gui/routeview/models.py index 052860c4..35039136 100644 --- a/rpki/gui/routeview/models.py +++ b/rpki/gui/routeview/models.py @@ -1,5 +1,5 @@ # Copyright (C) 2010, 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2012 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -31,7 +31,7 @@ class RouteOrigin(rpki.gui.models.PrefixV4): @property def roas(self): "Return a queryset of ROAs which cover this route." - return rpki.gui.cacheview.models.ROA.objects.filter( + return rpki.gui.gui_rpki_cache.models.ROA.objects.filter( prefixes__prefix_min__lte=self.prefix_min, prefixes__prefix_max__gte=self.prefix_max ) @@ -39,7 +39,7 @@ class RouteOrigin(rpki.gui.models.PrefixV4): @property def roa_prefixes(self): "Return a queryset of ROA prefixes which cover this route." - return rpki.gui.cacheview.models.ROAPrefixV4.objects.filter( + return rpki.gui.gui_rpki_cache.models.ROAPrefixV4.objects.filter( prefix_min__lte=self.prefix_min, prefix_max__gte=self.prefix_max ) @@ -78,4 +78,4 @@ class RouteOriginV6(rpki.gui.models.PrefixV6): # this goes at the end of the file to avoid problems with circular imports -import rpki.gui.cacheview.models +import rpki.gui.gui_rpki_cache.models diff --git a/rpki/gui/routeview/util.py b/rpki/gui/routeview/util.py index 1340e9fa..14ac3cf9 100644 --- a/rpki/gui/routeview/util.py +++ b/rpki/gui/routeview/util.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012, 2013 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2013, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -16,7 +16,6 @@ __version__ = '$Id$' __all__ = ('import_routeviews_dump') import itertools -import _mysql_exceptions import os.path import subprocess import time @@ -25,12 +24,13 @@ import urlparse import bz2 from urllib import urlretrieve, unquote -from django.db import transaction, connection +from django.db import transaction from django.conf import settings from rpki.resource_set import resource_range_ipv4, resource_range_ipv6 from rpki.exceptions import BadIPResource import rpki.gui.app.timestamp +from rpki.gui.routeview.models import RouteOrigin # globals logger = logging.getLogger(__name__) @@ -43,28 +43,17 @@ class ParseError(Exception): pass class RouteDumpParser(object): """Base class for parsing various route dump formats.""" - table = 'routeview_routeorigin' - sql = "INSERT INTO %s_new SET asn=%%s, prefix_min=%%s, prefix_max=%%s" % table range_class = resource_range_ipv4 def __init__(self, path, *args, **kwargs): + transaction.set_autocommit(False) + self.path = path - self.cursor = connection.cursor() self.last_prefix = None self.asns = set() def parse(self): - try: - logger.info('Dropping existing staging table...') - self.cursor.execute('DROP TABLE IF EXISTS %s_new' % self.table) - except _mysql_exceptions.Warning: - pass - - logger.info('Creating staging table...') - self.cursor.execute('CREATE TABLE %(table)s_new LIKE %(table)s' % {'table': self.table}) - - logger.info('Disabling autocommit...') - self.cursor.execute('SET autocommit=0') + RouteOrigin.objects.all().delete() logger.info('Adding rows to table...') for line in self.input: @@ -88,25 +77,13 @@ class RouteDumpParser(object): self.ins_routes() # process data from last line - logger.info('Committing...') - self.cursor.execute('COMMIT') - - try: - logger.info('Dropping old table...') - self.cursor.execute('DROP TABLE IF EXISTS %s_old' % self.table) - except _mysql_exceptions.Warning: - pass - - logger.info('Swapping staging table with live table...') - self.cursor.execute('RENAME TABLE %(table)s TO %(table)s_old, %(table)s_new TO %(table)s' % {'table': self.table}) - self.cleanup() # allow cleanup function to throw prior to COMMIT - transaction.commit_unless_managed() - logger.info('Updating timestamp metadata...') rpki.gui.app.timestamp.update('bgp_v4_import') + transaction.commit() # not sure if requried, or if transaction.commit() will do it + def parse_line(self, row): "Parse one line of input. Return a (prefix, origin_as) tuple." return None @@ -119,9 +96,8 @@ class RouteDumpParser(object): if self.last_prefix is not None: try: rng = self.range_class.parse_str(self.last_prefix) - rmin = long(rng.min) - rmax = long(rng.max) - self.cursor.executemany(self.sql, [(asn, rmin, rmax) for asn in self.asns]) + for asn in self.asns: + RouteOrigin.objects.create(asn=asn, prefix_min=rng.min, prefix_max=rng.max) except BadIPResource: logger.warning('skipping bad prefix: ' + self.last_prefix) self.asns = set() # reset @@ -151,6 +127,10 @@ class TextDumpParser(RouteDumpParser): except ValueError: raise ParseError('bad AS value') + # FIXME Django doesn't have a field for positive integers up to 2^32-1 + if origin_as < 0 or origin_as > 2147483647: + raise ParseError('AS value out of supported database range') + prefix = cols[1] # validate the prefix since the "sh ip bgp" output is sometimes @@ -215,8 +195,8 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='text'): filename [optional]: the full path to the downloaded file to parse filetype [optional]: 'text' or 'mrt' - """ + start_time = time.time() tmpname = None @@ -229,10 +209,8 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='text'): logger.info("Downloading %s to %s", filename, tmpname) if os.path.exists(tmpname): - os.remove(tmpname) - # filename is replaced with a local filename containing cached copy of - # URL - filename, headers = urlretrieve(filename, tmpname) + os.remove(tmpname) + filename, headers = urlretrieve(filename, tmpname) try: dispatch = {'text': TextDumpParser, 'mrt': MrtDumpParser} diff --git a/rpki/gui/script_util.py b/rpki/gui/script_util.py index c8248527..289dbbb7 100644 --- a/rpki/gui/script_util.py +++ b/rpki/gui/script_util.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 SPARTA, Inc. a Parsons Company +# Copyright (C) 2013, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -16,13 +16,6 @@ This module contains utility functions for use in standalone scripts. """ -import django - -from django.conf import settings - -from rpki import config -from rpki import autoconf - __version__ = '$Id$' @@ -30,29 +23,11 @@ def setup(): """ Configure Django enough to use the ORM. """ - cfg = config.parser(section='web_portal') - # INSTALLED_APPS doesn't seem necessary so long as you are only accessing - # existing tables. - # - # Setting charset to latin1 is a disgusting kludge, but without - # this MySQL 5.6 (and, proably, later) gets tetchy about ASN.1 DER - # stored in BLOB columns not being well-formed UTF8 (sic). If you - # know of a better solution, tell us. - settings.configure( - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': cfg.get('sql-database'), - 'USER': cfg.get('sql-username'), - 'PASSWORD': cfg.get('sql-password'), - 'OPTIONS': { - 'charset': 'latin1', - } - } - }, - MIDDLEWARE_CLASSES = (), - DOWNLOAD_DIRECTORY = cfg.get('download-directory', '/var/tmp'), - ) - if django.VERSION >= (1, 7): - from django.apps import apps - apps.populate(settings.INSTALLED_APPS) + + import os + + os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings.gui") + + # Initialize Django. + import django + django.setup() diff --git a/rpki/gui/urls.py b/rpki/gui/urls.py index 955092f5..ac1d2916 100644 --- a/rpki/gui/urls.py +++ b/rpki/gui/urls.py @@ -1,5 +1,5 @@ # Copyright (C) 2010, 2011 SPARTA, Inc. dba Cobham Analytic Solutions -# Copyright (C) 2012, 2013 SPARTA, Inc. a Parsons Company +# Copyright (C) 2012, 2013, 2016 SPARTA, Inc. a Parsons Company # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -28,7 +28,6 @@ urlpatterns = patterns( #(r'^admin/', include(admin.site.urls)), (r'^api/', include('rpki.gui.api.urls')), - (r'^cacheview/', include('rpki.gui.cacheview.urls')), (r'^rpki/', include('rpki.gui.app.urls')), (r'^accounts/login/$', 'rpki.gui.views.login'), |