diff options
Diffstat (limited to 'rpki/gui/gui_rpki_cache')
-rw-r--r-- | rpki/gui/gui_rpki_cache/__init__.py | 0 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/migrations/0001_initial.py | 136 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/migrations/0002_auto_20160411_2311.py | 41 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/migrations/0003_auto_20160420_2146.py | 24 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/migrations/__init__.py | 0 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/models.py | 174 | ||||
-rw-r--r-- | rpki/gui/gui_rpki_cache/util.py | 301 |
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() |