aboutsummaryrefslogtreecommitdiff
path: root/rpki/gui/gui_rpki_cache
diff options
context:
space:
mode:
Diffstat (limited to 'rpki/gui/gui_rpki_cache')
-rw-r--r--rpki/gui/gui_rpki_cache/__init__.py0
-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.py174
-rw-r--r--rpki/gui/gui_rpki_cache/util.py301
7 files changed, 676 insertions, 0 deletions
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/gui_rpki_cache/models.py b/rpki/gui/gui_rpki_cache/models.py
new file mode 100644
index 00000000..dd0739c0
--- /dev/null
+++ b/rpki/gui/gui_rpki_cache/models.py
@@ -0,0 +1,174 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions
+# 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
+# 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.db import models
+
+import rpki.resource_set
+import rpki.gui.models
+import rpki.rcynicdb.models
+
+
+class TelephoneField(models.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = 255
+ models.CharField.__init__(self, *args, **kwargs)
+
+
+class AddressRange(rpki.gui.models.PrefixV4): pass
+
+
+class AddressRangeV6(rpki.gui.models.PrefixV6): pass
+
+
+class ASRange(rpki.gui.models.ASN): pass
+
+
+class SignedObject(models.Model):
+ """
+ Abstract class to hold common metadata for all signed objects.
+ The signing certificate is ommitted here in order to give a proper
+ value for the 'related_name' attribute.
+ """
+
+ class Meta:
+ abstract = 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 __unicode__(self):
+ 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 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', 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:
+ cert = cert.issuer
+ x.append(cert)
+ x.reverse()
+ return x
+ cert_chain = property(get_cert_chain)
+
+
+class ROAPrefix(models.Model):
+ "Abstract base class for ROA mixin."
+
+ max_length = models.PositiveSmallIntegerField()
+
+ class Meta:
+ abstract = True
+
+ def as_roa_prefix(self):
+ "Return value as a rpki.resource_set.roa_prefix_ip object."
+ rng = self.as_resource_range()
+ return self.roa_cls(rng.min, rng.prefixlen(), self.max_length)
+
+ def __unicode__(self):
+ p = self.as_resource_range()
+ if p.prefixlen() == self.max_length:
+ return str(p)
+ return '%s-%s' % (str(p), self.max_length)
+
+
+# ROAPrefix is declared first, so subclass picks up __unicode__ from it.
+class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4):
+ "One v4 prefix in a ROA."
+
+ roa_cls = rpki.resource_set.roa_prefix_ipv4
+
+ @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)
+
+ class Meta:
+ ordering = ('prefix_min',)
+
+
+# ROAPrefix is declared first, so subclass picks up __unicode__ from it.
+class ROAPrefixV6(ROAPrefix, rpki.gui.models.PrefixV6):
+ "One v6 prefix in a ROA."
+
+ roa_cls = rpki.resource_set.roa_prefix_ipv6
+
+ class Meta:
+ ordering = ('prefix_min',)
+
+
+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, on_delete=models.CASCADE, null=True, related_name='roas')
+
+ class Meta:
+ ordering = ('asid',)
+
+ def __unicode__(self):
+ return u'ROA for AS%d' % self.asid
+
+
+class Ghostbuster(SignedObject):
+ full_name = models.CharField(max_length=40)
+ 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, on_delete=models.CASCADE, null=True, related_name='ghostbusters')
+
+ def __unicode__(self):
+ if self.full_name:
+ return self.full_name
+ if self.organization:
+ return self.organization
+ if self.email_address:
+ return self.email_address
+ return self.telephone
+
+
+from rpki.gui.routeview.models import RouteOrigin
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()