aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Elkins <melkins@tislabs.com>2016-04-21 21:23:25 +0000
committerMichael Elkins <melkins@tislabs.com>2016-04-21 21:23:25 +0000
commit40c34bb6427f634ee4c9fc4fe7539d7f993abc19 (patch)
tree879330015ac72897ec06de39eef4586933958d38
parente7129a3c7e5e7bfaf0bc63140200a3bb847446ac (diff)
Update the GUI to work with the new rcynicdb.
svn path=/branches/tk705/; revision=6365
-rw-r--r--buildtools/debian-skeleton/rpki-ca.postinst2
-rwxr-xr-xca/rpkigui-rcynic18
-rwxr-xr-xrp/rcynic/rcynic-cron8
-rw-r--r--rpki/django_settings/gui.py3
-rw-r--r--rpki/gui/app/check_expired.py10
-rw-r--r--rpki/gui/app/models.py6
-rw-r--r--rpki/gui/app/views.py5
-rw-r--r--rpki/gui/cacheview/forms.py51
-rw-r--r--rpki/gui/cacheview/misc.py31
-rw-r--r--rpki/gui/cacheview/templates/cacheview/addressrange_detail.html18
-rw-r--r--rpki/gui/cacheview/templates/cacheview/cacheview_base.html10
-rw-r--r--rpki/gui/cacheview/templates/cacheview/cert_detail.html105
-rw-r--r--rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html13
-rw-r--r--rpki/gui/cacheview/templates/cacheview/global_summary.html26
-rw-r--r--rpki/gui/cacheview/templates/cacheview/query_result.html21
-rw-r--r--rpki/gui/cacheview/templates/cacheview/roa_detail.html18
-rw-r--r--rpki/gui/cacheview/templates/cacheview/search_form.html17
-rw-r--r--rpki/gui/cacheview/templates/cacheview/search_result.html42
-rw-r--r--rpki/gui/cacheview/templates/cacheview/signedobject_detail.html58
-rw-r--r--rpki/gui/cacheview/tests.py23
-rw-r--r--rpki/gui/cacheview/urls.py32
-rw-r--r--rpki/gui/cacheview/util.py435
-rw-r--r--rpki/gui/cacheview/views.py173
-rw-r--r--rpki/gui/gui_rpki_cache/__init__.py (renamed from rpki/gui/cacheview/__init__.py)0
-rw-r--r--rpki/gui/gui_rpki_cache/migrations/0001_initial.py136
-rw-r--r--rpki/gui/gui_rpki_cache/migrations/0002_auto_20160411_2311.py41
-rw-r--r--rpki/gui/gui_rpki_cache/migrations/0003_auto_20160420_2146.py24
-rw-r--r--rpki/gui/gui_rpki_cache/migrations/__init__.py0
-rw-r--r--rpki/gui/gui_rpki_cache/models.py (renamed from rpki/gui/cacheview/models.py)123
-rw-r--r--rpki/gui/gui_rpki_cache/util.py301
-rw-r--r--rpki/gui/routeview/models.py8
-rw-r--r--rpki/gui/routeview/util.py61
-rw-r--r--rpki/gui/script_util.py6
-rw-r--r--rpki/gui/urls.py3
-rw-r--r--setup.py4
35 files changed, 571 insertions, 1261 deletions
diff --git a/buildtools/debian-skeleton/rpki-ca.postinst b/buildtools/debian-skeleton/rpki-ca.postinst
index 0bc8f9cc..4c72d148 100644
--- a/buildtools/debian-skeleton/rpki-ca.postinst
+++ b/buildtools/debian-skeleton/rpki-ca.postinst
@@ -41,7 +41,7 @@ setup_bpki() {
setup_cron() {
t=$(( $(hexdump -n 1 -e '"%u"' /dev/urandom) % 60 )) || exit
- echo "$t */2 * * * nobody /usr/lib/rpki/rpkigui-import-routes" > /etc/cron.d/rpkigui-routeviews
+ echo "$t */2 * * * rpki /usr/lib/rpki/rpkigui-import-routes" > /etc/cron.d/rpkigui-routeviews
echo "@daily rpki /usr/lib/rpki/rpkigui-check-expired" > /etc/cron.d/rpkigui-check-expired
echo "30 3 * * * rpki /usr/sbin/rpkic update_bpki" > /etc/cron.d/rpki-update-bpki
chmod 644 /etc/cron.d/rpkigui-routeviews /etc/cron.d/rpkigui-check-expired /etc/cron.d/rpki-update-bpki
diff --git a/ca/rpkigui-rcynic b/ca/rpkigui-rcynic
index 79afb15f..c753fc5e 100755
--- a/ca/rpkigui-rcynic
+++ b/ca/rpkigui-rcynic
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# Copyright (C) 2011 SPARTA, Inc. dba Cobham
-# 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
@@ -17,17 +17,13 @@
__version__ = '$Id$'
-# probably should be exported from rpki.gui.cacheview.util
-default_logfile = '/var/rcynic/data/rcynic.xml'
-default_root = '/var/rcynic/data'
-
import logging
import sys
from rpki.gui.script_util import setup
setup()
-from rpki.gui.cacheview.util import import_rcynic_xml
+from rpki.gui.gui_rpki_cache.util import update_cache
if __name__ == '__main__':
import optparse
@@ -35,20 +31,12 @@ if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option("-l", "--level", dest="log_level", default='ERROR',
help="specify the logging level [default: %default]")
- parser.add_option(
- "-f", "--file", dest="logfile",
- help="specify the rcynic XML file to parse [default: %default]",
- default=default_logfile)
- parser.add_option(
- "-r", "--root",
- help="specify the chroot directory for the rcynic jail [default: %default]",
- metavar="DIR", default=default_root)
options, args = parser.parse_args(sys.argv)
v = getattr(logging, options.log_level.upper())
logging.basicConfig(level=v)
logging.info('log level set to %s', logging.getLevelName(v))
- import_rcynic_xml(options.root, options.logfile)
+ update_cache()
logging.shutdown()
diff --git a/rp/rcynic/rcynic-cron b/rp/rcynic/rcynic-cron
index 93abb822..e7e564b3 100755
--- a/rp/rcynic/rcynic-cron
+++ b/rp/rcynic/rcynic-cron
@@ -68,11 +68,9 @@ run(os.path.join(rpki.autoconf.bindir, "rpki-rtr"),
"cronjob",
cwd = os.path.join(rpki.autoconf.RCYNIC_DIR, "rpki-rtr"))
-# rpkigui-rcynic isn't working with rcynicng yet, sorry.
-if False:
- prog = os.path.join(rpki.autoconf.libexecdir, "rpkigui-rcynic")
- if os.path.exists(prog):
- run(prog)
+prog = os.path.join(rpki.autoconf.libexecdir, "rpkigui-rcynic")
+if os.path.exists(prog):
+ run(prog)
if rpki.autoconf.RCYNIC_HTML_DIR and os.path.exists(os.path.dirname(rpki.autoconf.RCYNIC_HTML_DIR)):
run(os.path.join(rpki.autoconf.bindir, "rcynic-html"),
diff --git a/rpki/django_settings/gui.py b/rpki/django_settings/gui.py
index 8e860283..071d845f 100644
--- a/rpki/django_settings/gui.py
+++ b/rpki/django_settings/gui.py
@@ -134,8 +134,9 @@ INSTALLED_APPS.extend((
"django.contrib.sessions",
"django.contrib.staticfiles",
"rpki.gui.app",
- "rpki.gui.cacheview",
+ "rpki.gui.gui_rpki_cache",
"rpki.gui.routeview",
+ "rpki.rcynicdb"
))
TEMPLATE_CONTEXT_PROCESSORS = (
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py
index 61c9e8c8..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,7 +21,7 @@ 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
@@ -61,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..
@@ -77,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():
diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py
index 56ada2ab..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
@@ -35,8 +35,6 @@ class TelephoneField(models.CharField):
models.CharField.__init__(self, **kwargs)
-
-
class Parent(rpki.irdb.models.Parent):
"""proxy model for irdb Parent"""
@@ -261,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/views.py b/rpki/gui/app/views.py
index 1c954765..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
@@ -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
@@ -516,7 +515,7 @@ def child_edit(request, pk):
models.ChildNet.objects.filter(child=child).exclude(pk__in=form.cleaned_data.get('address_ranges')).delete()
Zookeeper(
handle=conf.handle,
- logstream=logstream,
+ logstream=log,
disable_signal_handlers=True
).run_rpkid_now()
return http.HttpResponseRedirect(child.get_absolute_url())
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 c2958c72..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 00298b2c..00000000
--- a/rpki/gui/cacheview/util.py
+++ /dev/null
@@ -1,435 +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.left_right
-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
-
-from lxml.etree import Element, SubElement
-
-logger = logging.getLogger(__name__)
-
-
-def rcynic_cert(cert, obj):
- 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.get(vs.generation)
- 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:
- 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.atomic
-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.atomic
-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()]
- 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, action="list", tenant_handle=h, tag=h)
- z = Zookeeper()
- r_msg = z.call_rpkid(q_msg)
- 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
- qs = models.RepositoryObject.objects.filter(uri=r_pdu.get("uri"))
- if qs:
- # get the current validity state
- valid = qs[0].statuses.filter(status=object_accepted).exists()
- uris[r_pdu.get("uri")] = (r_pdu.get("tenant_handle"), valid, False, None)
- logger.debug('adding %s', r_pdu.get("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 r_pdu.tag == rpki.left_right.tag_report_error:
- logging.error('rpkid reported an error: %s', r_pdu.get("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 451c0d1e..00000000
--- a/rpki/gui/cacheview/views.py
+++ /dev/null
@@ -1,173 +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/cacheview/__init__.py b/rpki/gui/gui_rpki_cache/__init__.py
index e69de29b..e69de29b 100644
--- a/rpki/gui/cacheview/__init__.py
+++ 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 08acfa2d..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,58 +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 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 AddressRange(rpki.gui.models.PrefixV4): pass
-class RepositoryObject(models.Model):
- """
- Represents a globally unique RPKI repository object, specified by its URI.
- """
- uri = models.URLField(unique=True, db_index=True)
+class AddressRangeV6(rpki.gui.models.PrefixV6): pass
-generations = list(enumerate(('current', 'backup')))
-generations_dict = dict((val, key) for (key, val) in generations)
-
-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):
@@ -92,57 +44,41 @@ class SignedObject(models.Model):
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
@@ -209,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',)
@@ -226,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..4798447b
--- /dev/null
+++ b/rpki/gui/gui_rpki_cache/util.py
@@ -0,0 +1,301 @@
+# 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)
+ z = Zookeeper()
+ r_msg = z.call_rpkid(q_msg)
+ 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/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 169fbf00..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
@@ -24,20 +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
-
-try:
- import _mysql_exceptions
-except ImportError:
- class MySQLWarning(Exception):
- "Dummy, nothing will ever raise this."
-else:
- MySQLWarning = _mysql_exceptions.Warning
+from rpki.gui.routeview.models import RouteOrigin
# globals
logger = logging.getLogger(__name__)
@@ -50,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 MySQLWarning:
- 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:
@@ -95,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 MySQLWarning:
- 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
@@ -126,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
@@ -158,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
@@ -236,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 17e5608c..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
@@ -26,9 +26,7 @@ def setup():
import os
- # If this doesn't work, try changing it to "rpki.django_settings.gui".
- os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings.irdb")
- #os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings.gui")
+ os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings.gui")
# Initialize Django.
import django
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'),
diff --git a/setup.py b/setup.py
index 68cab34a..10275ba9 100644
--- a/setup.py
+++ b/setup.py
@@ -60,7 +60,7 @@ if autoconf.RP_TARGET == "rp":
"rpki.rcynicdb",
"rpki.gui",
"rpki.gui.app",
- "rpki.gui.cacheview",
+ "rpki.gui.gui_rpki_cache",
"rpki.gui.api",
"rpki.gui.routeview"]
@@ -104,7 +104,7 @@ if autoconf.CA_TARGET == "ca":
"templates/*/*.html",
"templatetags/*.py"]
- package_data["rpki.gui.cacheview"] = ["templates/*/*.html"]
+ package_data["rpki.gui.gui_rpki_cache"] = ["migrations/*.py"]
data_files += [(autoconf.datarootdir + "/rpki/wsgi",