diff options
Diffstat (limited to 'rpkid')
-rw-r--r-- | rpkid/rpki/irdb/__init__.py | 3 | ||||
-rw-r--r-- | rpkid/rpki/irdb/zookeeper.py | 1063 | ||||
-rw-r--r-- | rpkid/rpki/rpkic.py | 965 | ||||
-rw-r--r-- | rpkid/tests/yamltest.py | 6 |
4 files changed, 1133 insertions, 904 deletions
diff --git a/rpkid/rpki/irdb/__init__.py b/rpkid/rpki/irdb/__init__.py index efd75a03..3eb6fab7 100644 --- a/rpkid/rpki/irdb/__init__.py +++ b/rpkid/rpki/irdb/__init__.py @@ -1,6 +1,6 @@ """ Django really wants its models packaged as a models module within a -Python package. Humor it. +Python package, so humor it. $Id$ @@ -20,3 +20,4 @@ PERFORMANCE OF THIS SOFTWARE. """ from rpki.irdb.models import * +from rpki.irdb.zookeeper import Zookeeper diff --git a/rpkid/rpki/irdb/zookeeper.py b/rpkid/rpki/irdb/zookeeper.py new file mode 100644 index 00000000..50a8b6fb --- /dev/null +++ b/rpkid/rpki/irdb/zookeeper.py @@ -0,0 +1,1063 @@ +""" +Management code for the IRDB. + +$Id$ + +Copyright (C) 2009--2012 Internet Systems Consortium ("ISC") + +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 ISC DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ISC 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. +""" + +import subprocess, csv, re, os, getopt, sys, base64, time, glob, copy, warnings +import rpki.config, rpki.cli, rpki.sundial, rpki.log, rpki.oids +import rpki.http, rpki.resource_set, rpki.relaxng, rpki.exceptions +import rpki.left_right, rpki.x509, rpki.async, rpki.irdb +import django.db.transaction + +from lxml.etree import (Element, SubElement, ElementTree, + fromstring as ElementFromString, + tostring as ElementToString) + +from rpki.csv_utils import (csv_reader, csv_writer, BadCSVSyntax) + + + +# XML namespace and protocol version for OOB setup protocol. The name +# is historical and may change before we propose this as the basis for +# a standard. + +myrpki_namespace = "http://www.hactrn.net/uris/rpki/myrpki/" +myrpki_version = "2" +myrpki_namespaceQName = "{" + myrpki_namespace + "}" + +myrpki_section = "myrpki" +irdbd_section = "irdbd" + +# A whole lot of exceptions + +class MissingHandle(Exception): "Missing handle" +class BadCommandSyntax(Exception): "Bad command line syntax." +class BadPrefixSyntax(Exception): "Bad prefix syntax." +class CouldntTalkToDaemon(Exception): "Couldn't talk to daemon." +class BadXMLMessage(Exception): "Bad XML message." +class PastExpiration(Exception): "Expiration date has already passed." +class CantRunRootd(Exception): "Can't run rootd." + + + +def B64Element(e, tag, obj, **kwargs): + """ + Create an XML element containing Base64 encoded data taken from a + DER object. + """ + + if e is None: + se = Element(tag, **kwargs) + else: + se = SubElement(e, tag, **kwargs) + if e is not None and e.text is None: + e.text = "\n" + se.text = "\n" + obj.get_Base64() + se.tail = "\n" + return se + +class PEM_writer(object): + """ + Write PEM files to disk, keeping track of which ones we've already + written and setting the file mode appropriately. + """ + + def __init__(self, verbose = False): + self.wrote = set() + self.verbose = verbose + + def __call__(self, filename, obj): + filename = os.path.realpath(filename) + if filename in self.wrote: + return + tempname = filename + if not filename.startswith("/dev/"): + tempname += ".%s.tmp" % os.getpid() + mode = 0400 if filename.endswith(".key") else 0444 + if self.verbose: + print "Writing", filename + f = os.fdopen(os.open(tempname, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode), "w") + f.write(obj.get_PEM()) + f.close() + if tempname != filename: + os.rename(tempname, filename) + self.wrote.add(filename) + + + + +def etree_read(filename): + """ + Read an etree from a file, verifying then stripping XML namespace + cruft. + """ + + e = ElementTree(file = filename).getroot() + rpki.relaxng.myrpki.assertValid(e) + for i in e.getiterator(): + if i.tag.startswith(myrpki_namespaceQName): + i.tag = i.tag[len(myrpki_namespaceQName):] + else: + raise BadXMLMessage, "XML tag %r is not in namespace %r" % (i.tag, myrpki_namespace) + return e + + +class etree_wrapper(object): + """ + Wrapper for ETree objects so we can return them as function results + without requiring the caller to understand much about them. + + """ + + def __init__(self, e, msg = None): + self.msg = msg + e = copy.deepcopy(e) + e.set("version", myrpki_version) + for i in e.getiterator(): + if i.tag[0] != "{": + i.tag = myrpki_namespaceQName + i.tag + assert i.tag.startswith(myrpki_namespaceQName) + rpki.relaxng.myrpki.assertValid(e) + self.etree = e + + def __str__(self): + return ElementToString(self.etree) + + def save(self, filename, blather = False): + filename = os.path.realpath(filename) + tempname = filename + if not filename.startswith("/dev/"): + tempname += ".%s.tmp" % os.getpid() + ElementTree(self.etree).write(tempname) + if tempname != filename: + os.rename(tempname, filename) + if blather: + print "Wrote", filename + if self.msg is not None: + print self.msg + + + +class Zookeeper(object): + + ## @var show_xml + # Whether to show XML for debugging + + show_xml = False + + def __init__(self, cfg = None, handle = None): + + if cfg is None: + cfg = rpki.config.parser() + + if handle is None: + handle = cfg.get("handle", section = myrpki_section) + + self.cfg = cfg + + self.run_rpkid = cfg.getboolean("run_rpkid", section = myrpki_section) + self.run_pubd = cfg.getboolean("run_pubd", section = myrpki_section) + self.run_rootd = cfg.getboolean("run_rootd", section = myrpki_section) + + if self.run_rootd and (not self.run_pubd or not self.run_rpkid): + raise CantRunRootd, "Can't run rootd unless also running rpkid and pubd" + + self.default_repository = cfg.get("default_repository", "", section = myrpki_section) + self.pubd_contact_info = cfg.get("pubd_contact_info", "", section = myrpki_section) + + self.rsync_module = cfg.get("publication_rsync_module", section = myrpki_section) + self.rsync_server = cfg.get("publication_rsync_server", section = myrpki_section) + + self.reset_identity(handle) + + + def reset_identity(self, handle): + """ + Select handle of current resource holding entity. + """ + + if handle is None: + raise MissingHandle + self.handle= handle + try: + self.resource_ca = rpki.irdb.ResourceHolderCA.objects.get(handle = handle) + except rpki.irdb.ResourceHolderCA.DoesNotExist: + self.resource_ca = None + try: + self.server_ca = rpki.irdb.ServerCA.objects.get() + except rpki.irdb.ServerCA.DoesNotExist: + self.server_ca = None + + + @django.db.transaction.commit_on_success + def initialize(self): + """ + Initialize an RPKI installation. Reads the configuration file, + creates the BPKI and EntityDB directories, generates the initial + BPKI certificates, and creates an XML file describing the + resource-holding aspect of this RPKI installation. + """ + + self.resource_ca, created = rpki.irdb.ResourceHolderCA.objects.get_or_certify(handle = self.handle) + if created: + print "Created new BPKI resource CA for identity %s" % self.handle + + if self.run_rpkid or self.run_pubd: + self.server_ca, created = rpki.irdb.ServerCA.objects.get_or_certify() + if created: + print "Created new BPKI server CA" + rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "irbe") + + if self.run_rpkid: + rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "rpkid") + rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "irdbd") + + if self.run_pubd: + rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "pubd") + + e = Element("identity", handle = self.handle) + B64Element(e, "bpki_ta", self.resource_ca.certificate) + return etree_wrapper(e, msg = 'This is the "identity" file you will need to send to your parent') + + @django.db.transaction.commit_on_success + def configure_rootd(self): + + assert self.run_rpkid and self.run_pubd and self.run_rootd + + rpki.irdb.Rootd.objects.get_or_certify( + issuer = self.resource_ca, + service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port")) + + # The following assumes we'll set up the respository manually. + # Not sure this is a reasonable assumption, particularly if we + # ever fix rootd to use the publication protocol. + + try: + self.resource_ca.repositories.get(handle = self.handle) + return None + + except rpki.irdb.Repository.DoesNotExist: + e = Element("repository", type = "offer", handle = self.handle, parent_handle = self.handle) + B64Element(e, "bpki_client_ta", self.resource_ca.certificate) + return etree_wrapper(e, msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository') + + + def write_bpki_files(self): + """ + Write out BPKI certificate, key, and CRL files for daemons that + need them. + """ + + writer = PEM_writer() + + if self.run_rpkid: + rpkid = self.server_ca.ee_certificates.get(purpose = "rpkid") + writer(self.cfg.get("bpki-ta", section = "rpkid"), self.server_ca.certificate) + writer(self.cfg.get("rpkid-key", section = "rpkid"), rpkid.private_key) + writer(self.cfg.get("rpkid-cert", section = "rpkid"), rpkid.certificate) + writer(self.cfg.get("irdb-cert", section = "rpkid"), + self.server_ca.ee_certificates.get(purpose = "irdbd").certificate) + writer(self.cfg.get("irbe-cert", section = "rpkid"), + self.server_ca.ee_certificates.get(purpose = "irbe").certificate) + + if self.run_pubd: + pubd = self.server_ca.ee_certificates.get(purpose = "pubd") + writer(self.cfg.get("bpki-ta", section = "pubd"), self.server_ca.certificate) + writer(self.cfg.get("pubd-key", section = "pubd"), pubd.private_key) + writer(self.cfg.get("pubd-cert", section = "pubd"), pubd.certificate) + writer(self.cfg.get("irbe-cert", section = "pubd"), + self.server_ca.ee_certificates.get(purpose = "irbe").certificate) + + if self.run_rootd: + rootd = rpki.irdb.ResourceHolderCA.objects.get(handle = self.cfg.get("handle", section = "myrpki")).rootd + writer(self.cfg.get("bpki-ta", section = "rootd"), self.server_ca.certificate) + writer(self.cfg.get("rootd-bpki-crl", section = "rootd"), self.server_ca.latest_crl) + writer(self.cfg.get("rootd-bpki-key", section = "rootd"), rootd.private_key) + writer(self.cfg.get("rootd-bpki-cert", section = "rootd"), rootd.certificate) + writer(self.cfg.get("child-bpki-cert", section = "rootd"), rootd.issuer.certificate) + + + @django.db.transaction.commit_on_success + def update_bpki(self): + """ + Update BPKI certificates. Assumes an existing RPKI installation. + + Basic plan here is to reissue all BPKI certificates we can, right + now. In the long run we might want to be more clever about only + touching ones that need maintenance, but this will do for a start. + + We also reissue CRLs for all CAs. + + Most likely this should be run under cron. + """ + + for model in (rpki.irdb.ServerCA, + rpki.irdb.ResourceHolderCA, + rpki.irdb.ServerEE, + rpki.irdb.Referral, + rpki.irdb.Rootd, + rpki.irdb.HostedCA, + rpki.irdb.BSC, + rpki.irdb.Child, + rpki.irdb.Parent, + rpki.irdb.Client, + rpki.irdb.Repository): + for obj in model.objects.all(): + print "Regenerating certificate", obj.certificate.getSubject() + obj.avow() + + print "Regenerating Server CRL" + self.server_ca.generate_crl() + + for ca in rpki.irdb.ResourceHolderCA.objects.all(): + print "Regenerating CRL for", ca.handle + ca.generate_crl() + + + @django.db.transaction.commit_on_success + def configure_child(self, filename, child_handle = None): + """ + Configure a new child of this RPKI entity, given the child's XML + identity file as an input. Extracts the child's data from the + XML, cross-certifies the child's resource-holding BPKI + certificate, and generates an XML file describing the relationship + between the child and this parent, including this parent's BPKI + data and up-down protocol service URI. + """ + + c = etree_read(filename) + + if child_handle is None: + child_handle = c.get("handle") + + service_uri = "http://%s:%s/up-down/%s/%s" % (self.cfg.get("rpkid_server_host"), + self.cfg.get("rpkid_server_port"), + self.handle, child_handle) + + valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) + + print "Child calls itself %r, we call it %r" % (c.get("handle"), child_handle) + + child, created = rpki.irdb.Child.objects.get_or_certify( + issuer = self.resource_ca, + handle = child_handle, + ta = rpki.x509.X509(Base64 = c.findtext("bpki_ta")), + valid_until = valid_until) + + e = Element("parent", parent_handle = self.handle, child_handle = child_handle, + service_uri = service_uri, valid_until = str(valid_until)) + B64Element(e, "bpki_resource_ta", self.resource_ca.certificate) + B64Element(e, "bpki_child_ta", child.ta) + + try: + if self.default_repository: + repo = self.resource_ca.repositories.get(handle = self.default_repository) + else: + repo = self.resource_ca.repositories.get() + except rpki.irdb.Repository.DoesNotExist: + repo = None + + if repo is None: + print "Couldn't find any usable repositories, not giving referral" + + elif repo.handle == self.handle: + SubElement(e, "repository", type = "offer") + + else: + proposed_sia_base = repo.sia_base + child_handle + "/" + referral_cert, created = rpki.irdb.Referral.objects.get_or_certify(issuer = self.resource_ca) + auth = rpki.x509.SignedReferral() + auth.set_content(B64Element(None, myrpki_namespaceQName + "referral", child.ta, + version = myrpki_version, + authorized_sia_base = proposed_sia_base)) + auth.schema_check() + auth.sign(referral_cert.private_key, referral_cert.certificate, self.resource_ca.latest_crl) + + r = SubElement(e, "repository", type = "referral") + B64Element(r, "authorization", auth, referrer = repo.client_handle) + SubElement(r, "contact_info") + + return etree_wrapper(e, msg = "Send this file back to the child you just configured"), child_handle + + + @django.db.transaction.commit_on_success + def delete_child(self, child_handle): + """ + Delete a child of this RPKI entity. + """ + + assert child_handle is not None + try: + self.resource_ca.children.get(handle = child_handle).delete() + except rpki.irdb.Child.DoesNotExist: + print "No such child \"%s\"" % arg + + + @django.db.transaction.commit_on_success + def configure_parent(self, filename, parent_handle = None): + """ + Configure a new parent of this RPKI entity, given the output of + the parent's configure_child command as input. Reads the parent's + response XML, extracts the parent's BPKI and service URI + information, cross-certifies the parent's BPKI data into this + entity's BPKI, and checks for offers or referrals of publication + service. If a publication offer or referral is present, we + generate a request-for-service message to that repository, in case + the user wants to avail herself of the referral or offer. + """ + + p = etree_read(filename) + + if parent_handle is None: + parent_handle = p.get("parent_handle") + + r = p.find("repository") + + repository_type = "none" + referrer = None + referral_authorization = None + + if r is not None: + repository_type = r.get("type") + + if repository_type == "referral": + a = r.find("authorization") + referrer = a.get("referrer") + referral_authorization = rpki.x509.SignedReferral(Base64 = a.text) + + print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle) + print "Parent calls us %r" % p.get("child_handle") + + rpki.irdb.Parent.objects.get_or_certify( + issuer = self.resource_ca, + handle = parent_handle, + child_handle = p.get("child_handle"), + parent_handle = p.get("parent_handle"), + service_uri = p.get("service_uri"), + ta = rpki.x509.X509(Base64 = p.findtext("bpki_resource_ta")), + repository_type = repository_type, + referrer = referrer, + referral_authorization = referral_authorization) + + if repository_type == "none": + r = Element("repository", type = "none") + r.set("handle", self.handle) + r.set("parent_handle", parent_handle) + B64Element(r, "bpki_client_ta", self.resource_ca.certificate) + return etree_wrapper(r, msg = "This is the file to send to the repository operator"), parent_handle + + + @django.db.transaction.commit_on_success + def delete_parent(self, parent_handle): + """ + Delete a parent of this RPKI entity. + """ + + assert parent_handle is not None + try: + self.resource_ca.parents.get(handle = parent_handle).delete() + except rpki.irdb.Parent.DoesNotExist: + print "No such parent \"%s\"" % arg + + + @django.db.transaction.commit_on_success + def configure_publication_client(self, filename, sia_base = None): + """ + Configure publication server to know about a new client, given the + client's request-for-service message as input. Reads the client's + request for service, cross-certifies the client's BPKI data, and + generates a response message containing the repository's BPKI data + and service URI. + """ + + client = etree_read(filename) + + client_ta = rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")) + + if sia_base is None and client.get("handle") == self.handle and self.resource_ca.certificate == client_ta: + print "This looks like self-hosted publication" + sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, self.handle) + + if sia_base is None and client.get("type") == "referral": + print "This looks like a referral, checking" + try: + auth = client.find("authorization") + referrer = self.server_ca.clients.get(handle = auth.get("referrer")) + referral_cms = rpki.x509.SignedReferral(Base64 = auth.text) + referral_xml = referral_cms.unwrap(ta = (referrer.certificate, self.server_ca.certificate)) + if rpki.x509.X509(Base64 = referral_xml.text) != client_ta: + raise BadXMLMessage, "Referral trust anchor does not match" + sia_base = referral_xml.get("authorized_sia_base") + except rpki.irdb.Client.DoesNotExist: + print "We have no record of the client (%s) alleged to have made this referral" % auth.get("referrer") + + if sia_base is None and client.get("type") == "offer" and client.get("parent_handle") == self.handle: + print "This looks like an offer, client claims to be our child, checking" + try: + child = self.resource_ca.children.get(ta = client_ta) + except rpki.irdb.Child.DoesNotExist: + print "Can't find a child matching this client" + else: + sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module, + self.handle, client.get("handle")) + + # If we still haven't figured out what to do with this client, it + # gets a top-level tree of its own, no attempt at nesting. + + if sia_base is None: + print "Don't know where to nest this client, defaulting to top-level" + sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client.get("handle")) + + if not sia_base.startswith("rsync://"): + raise BadXMLMessage, "Malformed sia_base parameter %r, should start with 'rsync://'" % sia_base + + client_handle = "/".join(sia_base.rstrip("/").split("/")[4:]) + + parent_handle = client.get("parent_handle") + + print "Client calls itself %r, we call it %r" % (client.get("handle"), client_handle) + print "Client says its parent handle is %r" % parent_handle + + rpki.irdb.Client.objects.get_or_certify( + issuer = self.server_ca, + handle = client_handle, + ta = client_ta, + sia_base = sia_base) + + e = Element("repository", type = "confirmed", + client_handle = client_handle, + parent_handle = parent_handle, + sia_base = sia_base, + service_uri = "http://%s:%s/client/%s" % (self.cfg.get("pubd_server_host"), + self.cfg.get("pubd_server_port"), + client_handle)) + + B64Element(e, "bpki_server_ta", self.server_ca.certificate) + B64Element(e, "bpki_client_ta", client_ta) + SubElement(e, "contact_info").text = self.pubd_contact_info + return (etree_wrapper(e, msg = "Send this file back to the publication client you just configured"), + client_handle) + + + @django.db.transaction.commit_on_success + def delete_publication_client(self, client_handle): + """ + Delete a publication client of this RPKI entity. + """ + + assert client_handle is not None + try: + self.resource_ca.clients.get(handle = client_handle).delete() + except rpki.irdb.Client.DoesNotExist: + print "No such client \"%s\"" % arg + + + @django.db.transaction.commit_on_success + def configure_repository(self, filename, parent_handle = None): + """ + Configure a publication repository for this RPKI entity, given the + repository's response to our request-for-service message as input. + Reads the repository's response, extracts and cross-certifies the + BPKI data and service URI, and links the repository data with the + corresponding parent data in our local database. + """ + + r = etree_read(filename) + + if parent_handle is None: + parent_handle = r.get("parent_handle") + + print "Repository calls us %r" % (r.get("client_handle")) + print "Repository response associated with parent_handle %r" % parent_handle + + try: + if parent_handle == self.handle: + turtle = self.resource_ca.rootd + else: + turtle = self.resource_ca.parents.get(handle = parent_handle) + + except (rpki.irdb.Parent.DoesNotExist, rpki.irdb.Rootd.DoesNotExist): + print "Could not find parent %r in our database" % parent_handle + + else: + rpki.irdb.Repository.objects.get_or_certify( + issuer = self.resource_ca, + handle = parent_handle, + client_handle = r.get("client_handle"), + service_uri = r.get("service_uri"), + sia_base = r.get("sia_base"), + ta = rpki.x509.X509(Base64 = r.findtext("bpki_server_ta")), + turtle = turtle) + + + @django.db.transaction.commit_on_success + def delete_repository(self, repository_handle): + """ + Delete a repository of this RPKI entity. + """ + + assert repository_handle is not None + try: + self.resource_ca.repositories.get(handle = arg).delete() + except rpki.irdb.Repository.DoesNotExist: + print "No such repository \"%s\"" % arg + + + @django.db.transaction.commit_on_success + def renew_children(self, child_handle, valid_until = None): + """ + Update validity period for one child entity or, if child_handle is + None, for all child entities. + """ + + if child_handle is None: + children = self.resource_ca.children + else: + children = self.resource_ca.children.filter(handle = child_handle) + + if valid_until is None: + valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) + else: + valid_until = rpki.sundial.fromXMLtime(valid_until) + if valid_until < rpki.sundial.now(): + raise PastExpiration, "Specified new expiration time %s has passed" % valid_until + + print "New validity date", valid_until + + for child in children: + child.valid_until = valid_until + child.save() + + + @django.db.transaction.commit_on_success + def load_prefixes(self, filename): + """ + Whack IRDB to match prefixes.csv. + """ + + grouped4 = {} + grouped6 = {} + + for handle, prefix in csv_reader(filename, columns = 2): + grouped = grouped6 if ":" in prefix else grouped4 + if handle not in grouped: + grouped[handle] = [] + grouped[handle].append(prefix) + + primary_keys = [] + + for version, grouped, rset in ((4, grouped4, rpki.resource_set.resource_set_ipv4), + (6, grouped6, rpki.resource_set.resource_set_ipv6)): + for handle, prefixes in grouped.iteritems(): + child = self.resource_ca.children.get(handle = handle) + for prefix in rset(",".join(prefixes)): + obj, created = rpki.irdb.ChildNet.objects.get_or_create( + child = child, + start_ip = str(prefix.min), + end_ip = str(prefix.max), + version = version) + primary_keys.append(obj.pk) + + q = rpki.irdb.ChildNet.objects + q = q.filter(child__issuer__exact = self.resource_ca) + q = q.exclude(pk__in = primary_keys) + q.delete() + + + @django.db.transaction.commit_on_success + def load_asns(self, filename): + """ + Whack IRDB to match asns.csv. + """ + + grouped = {} + + for handle, asn in csv_reader(filename, columns = 2): + if handle not in grouped: + grouped[handle] = [] + grouped[handle].append(asn) + + primary_keys = [] + + for handle, asns in grouped.iteritems(): + child = self.resource_ca.children.get(handle = handle) + for asn in rpki.resource_set.resource_set_as(",".join(asns)): + obj, created = rpki.irdb.ChildASN.objects.get_or_create( + child = child, + start_as = str(asn.min), + end_as = str(asn.max)) + primary_keys.append(obj.pk) + + q = rpki.irdb.ChildASN.objects + q = q.filter(child__issuer__exact = self.resource_ca) + q = q.exclude(pk__in = primary_keys) + q.delete() + + + @django.db.transaction.commit_on_success + def load_roa_requests(self, filename): + """ + Whack IRDB to match roa.csv. + """ + + grouped = {} + + # format: p/n-m asn group + for pnm, asn, group in csv_reader(filename, columns = 3): + key = (asn, group) + if key not in grouped: + grouped[key] = [] + grouped[key].append(pnm) + + # Deleting and recreating all the ROA requests is inefficient, + # but rpkid's current representation of ROA requests is wrong + # (see #32), so it's not worth a lot of effort here as we're + # just going to have to rewrite this soon anyway. + + self.resource_ca.roa_requests.all().delete() + + for key, pnms in grouped.iteritems(): + asn, group = key + + roa_request = self.resource_ca.roa_requests.create(asn = asn) + + for pnm in pnms: + if ":" in pnm: + p = rpki.resource_set.roa_prefix_ipv6.parse_str(pnm) + v = 6 + else: + p = rpki.resource_set.roa_prefix_ipv4.parse_str(pnm) + v = 4 + roa_request.prefixes.create( + version = v, + prefix = str(p.prefix), + prefixlen = int(p.prefixlen), + max_prefixlen = int(p.max_prefixlen)) + + + @django.db.transaction.commit_on_success + def synchronize(self): + """ + Configure RPKI daemons with the data built up by the other + commands in this program. Most commands which modify the IRDB + should call this when they're done. + """ + + # We can use a single BSC for everything -- except BSC key + # rollovers. Drive off that bridge when we get to it. + + bsc_handle = "bsc" + + # Default values for CRL parameters are low, for testing. Not + # quite as low as they once were, too much expired CRL whining. + + self_crl_interval = self.cfg.getint("self_crl_interval", 2 * 60 * 60) + self_regen_margin = self.cfg.getint("self_regen_margin", self_crl_interval / 4) + pubd_base = "http://%s:%s/" % (self.cfg.get("pubd_server_host"), self.cfg.get("pubd_server_port")) + rpkid_base = "http://%s:%s/" % (self.cfg.get("rpkid_server_host"), self.cfg.get("rpkid_server_port")) + + # Wrappers to simplify calling rpkid and pubd. + + irbe = self.server_ca.ee_certificates.get(purpose = "irbe") + + call_rpkid = rpki.async.sync_wrapper(rpki.http.caller( + proto = rpki.left_right, + client_key = irbe.private_key, + client_cert = irbe.certificate, + server_ta = self.server_ca.certificate, + server_cert = self.server_ca.ee_certificates.get(purpose = "rpkid").certificate, + url = rpkid_base + "left-right", + debug = self.show_xml)) + + if self.run_pubd: + + call_pubd = rpki.async.sync_wrapper(rpki.http.caller( + proto = rpki.publication, + client_key = irbe.private_key, + client_cert = irbe.certificate, + server_ta = self.server_ca.certificate, + server_cert = self.server_ca.ee_certificates.get(purpose = "pubd").certificate, + url = pubd_base + "control", + debug = self.show_xml)) + + # Make sure that pubd's BPKI CRL is up to date. + + call_pubd(rpki.publication.config_elt.make_pdu( + action = "set", + bpki_crl = self.server_ca.latest_crl)) + + for ca in rpki.irdb.ResourceHolderCA.objects.all(): + + # See what rpkid and pubd already have on file for this entity. + + if self.run_pubd: + pubd_reply = call_pubd(rpki.publication.client_elt.make_pdu(action = "list")) + client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication.client_elt)) + + rpkid_reply = call_rpkid( + rpki.left_right.self_elt.make_pdu( action = "get", tag = "self", self_handle = ca.handle), + rpki.left_right.bsc_elt.make_pdu( action = "list", tag = "bsc", self_handle = ca.handle), + rpki.left_right.repository_elt.make_pdu(action = "list", tag = "repository", self_handle = ca.handle), + rpki.left_right.parent_elt.make_pdu( action = "list", tag = "parent", self_handle = ca.handle), + rpki.left_right.child_elt.make_pdu( action = "list", tag = "child", self_handle = ca.handle)) + + self_pdu = rpkid_reply[0] + bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) + repository_pdus = dict((x.repository_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.repository_elt)) + parent_pdus = dict((x.parent_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.parent_elt)) + child_pdus = dict((x.child_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.child_elt)) + + pubd_query = [] + rpkid_query = [] + + self_cert, created = rpki.irdb.HostedCA.objects.get_or_certify( + issuer = self.server_ca, + hosted = ca) + + # There should be exactly one <self/> object per hosted entity, by definition + + if (isinstance(self_pdu, rpki.left_right.report_error_elt) or + self_pdu.crl_interval != self_crl_interval or + self_pdu.regen_margin != self_regen_margin or + self_pdu.bpki_cert != self_cert.certificate): + rpkid_query.append(rpki.left_right.self_elt.make_pdu( + action = "create" if isinstance(self_pdu, rpki.left_right.report_error_elt) else "set", + tag = "self", + self_handle = ca.handle, + bpki_cert = ca.certificate, + crl_interval = self_crl_interval, + regen_margin = self_regen_margin)) + + # In general we only need one <bsc/> per <self/>. BSC objects + # are a little unusual in that the keypair and PKCS #10 + # subelement is generated by rpkid, so complete setup requires + # two round trips. + + bsc_pdu = bsc_pdus.pop(bsc_handle, None) + + if bsc_pdu is None: + rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( + action = "create", + tag = "bsc", + self_handle = ca.handle, + bsc_handle = bsc_handle, + generate_keypair = "yes")) + + elif bsc_pdu.pkcs10_request is None: + rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( + action = "set", + tag = "bsc", + self_handle = ca.handle, + bsc_handle = bsc_handle, + generate_keypair = "yes")) + + rpkid_query.extend(rpki.left_right.bsc_elt.make_pdu( + action = "destroy", self_handle = ca.handle, bsc_handle = b) for b in bsc_pdus) + + # If we've already got actions queued up, run them now, so we + # can finish setting up the BSC before anything tries to use it. + + if rpkid_query: + rpkid_query.append(rpki.left_right.bsc_elt.make_pdu(action = "list", tag = "bsc", self_handle = ca.handle)) + rpkid_reply = call_rpkid(*rpkid_query) + bsc_pdus = dict((x.bsc_handle, x) + for x in rpkid_reply + if isinstance(x, rpki.left_right.bsc_elt) and x.action == "list") + bsc_pdu = bsc_pdus.pop(bsc_handle, None) + for r in rpkid_reply: + if isinstance(r, rpki.left_right.report_error_elt): + print "rpkid reported failure:", r.error_code + if r.error_text: + print r.error_text + if any(isinstance(r, rpki.left_right.report_error_elt) for r in rpkid_reply): + raise CouldntTalkToDaemon + + rpkid_query = [] + + assert bsc_pdu.pkcs10_request is not None + + bsc, created = rpki.irdb.BSC.objects.get_or_certify( + issuer = ca, + handle = bsc_handle, + pkcs10 = bsc_pdu.pkcs10_request) + + if bsc_pdu.signing_cert != bsc.certificate or bsc_pdu.signing_cert_crl != ca.latest_crl: + rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( + action = "set", + tag = "bsc", + self_handle = ca.handle, + bsc_handle = bsc_handle, + signing_cert = bsc.certificate, + signing_cert_crl = ca.latest_crl)) + + # At present we need one <repository/> per <parent/>, not because + # rpkid requires that, but because pubd does. pubd probably should + # be fixed to support a single client allowed to update multiple + # trees, but for the moment the easiest way forward is just to + # enforce a 1:1 mapping between <parent/> and <repository/> objects + + for repository in ca.repositories.all(): + + repository_pdu = repository_pdus.pop(repository.handle, None) + + if (repository_pdu is None or + repository_pdu.bsc_handle != bsc_handle or + repository_pdu.peer_contact_uri != repository.service_uri or + repository_pdu.bpki_cert != repository.certificate): + rpkid_query.append(rpki.left_right.repository_elt.make_pdu( + action = "create" if repository_pdu is None else "set", + tag = repository.handle, + self_handle = ca.handle, + repository_handle = repository.handle, + bsc_handle = bsc_handle, + peer_contact_uri = repository.service_uri, + bpki_cert = repository.certificate)) + + rpkid_query.extend(rpki.left_right.repository_elt.make_pdu( + action = "destroy", self_handle = ca.handle, repository_handle = r) for r in repository_pdus) + + # <parent/> setup code currently assumes 1:1 mapping between + # <repository/> and <parent/>, and further assumes that the handles + # for an associated pair are the identical (that is: + # parent.repository_handle == parent.parent_handle). + + for parent in ca.parents.all(): + + parent_pdu = parent_pdus.pop(parent.handle, None) + + if (parent_pdu is None or + parent_pdu.bsc_handle != bsc_handle or + parent_pdu.repository_handle != parent.handle or + parent_pdu.peer_contact_uri != parent.service_uri or + parent_pdu.sia_base != parent.repository.sia_base or + parent_pdu.sender_name != parent.child_handle or + parent_pdu.recipient_name != parent.parent_handle or + parent_pdu.bpki_cms_cert != parent.certificate): + rpkid_query.append(rpki.left_right.parent_elt.make_pdu( + action = "create" if parent_pdu is None else "set", + tag = parent.handle, + self_handle = ca.handle, + parent_handle = parent.handle, + bsc_handle = bsc_handle, + repository_handle = parent.handle, + peer_contact_uri = parent.service_uri, + sia_base = parent.repository.sia_base, + sender_name = parent.child_handle, + recipient_name = parent.parent_handle, + bpki_cms_cert = parent.certificate)) + + try: + + parent_pdu = parent_pdus.pop(ca.handle, None) + + if (parent_pdu is None or + parent_pdu.bsc_handle != bsc_handle or + parent_pdu.repository_handle != ca.handle or + parent_pdu.peer_contact_uri != ca.rootd.service_uri or + parent_pdu.sia_base != ca.rootd.repository.sia_base or + parent_pdu.sender_name != ca.handle or + parent_pdu.recipient_name != ca.handle or + parent_pdu.bpki_cms_cert != ca.rootd.certificate): + rpkid_query.append(rpki.left_right.parent_elt.make_pdu( + action = "create" if parent_pdu is None else "set", + tag = ca.handle, + self_handle = ca.handle, + parent_handle = ca.handle, + bsc_handle = bsc_handle, + repository_handle = ca.handle, + peer_contact_uri = ca.rootd.service_uri, + sia_base = ca.rootd.repository.sia_base, + sender_name = ca.handle, + recipient_name = ca.handle, + bpki_cms_cert = ca.rootd.certificate)) + + except rpki.irdb.Rootd.DoesNotExist: + pass + + rpkid_query.extend(rpki.left_right.parent_elt.make_pdu( + action = "destroy", self_handle = ca.handle, parent_handle = p) for p in parent_pdus) + + # Children are simpler than parents, because they call us, so no URL + # to construct and figuring out what certificate to use is their + # problem, not ours. + + for child in ca.children.all(): + + child_pdu = child_pdus.pop(child.handle, None) + + if (child_pdu is None or + child_pdu.bsc_handle != bsc_handle or + child_pdu.bpki_cert != child.certificate): + rpkid_query.append(rpki.left_right.child_elt.make_pdu( + action = "create" if child_pdu is None else "set", + tag = child.handle, + self_handle = ca.handle, + child_handle = child.handle, + bsc_handle = bsc_handle, + bpki_cert = child.certificate)) + + rpkid_query.extend(rpki.left_right.child_elt.make_pdu( + action = "destroy", self_handle = ca.handle, child_handle = c) for c in child_pdus) + + # Publication setup. + + # Um, why are we doing this per resource holder? + + if self.run_pubd: + + for client in self.server_ca.clients.all(): + + client_pdu = client_pdus.pop(client.handle, None) + + if (client_pdu is None or + client_pdu.base_uri != client.sia_base or + client_pdu.bpki_cert != client.certificate): + pubd_query.append(rpki.publication.client_elt.make_pdu( + action = "create" if client_pdu is None else "set", + client_handle = client.handle, + bpki_cert = client.certificate, + base_uri = client.sia_base)) + + pubd_query.extend(rpki.publication.client_elt.make_pdu( + action = "destroy", client_handle = p) for p in client_pdus) + + # If we changed anything, ship updates off to daemons + + if rpkid_query: + rpkid_reply = call_rpkid(*rpkid_query) + bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) + if bsc_handle in bsc_pdus and bsc_pdus[bsc_handle].pkcs10_request: + bsc_req = bsc_pdus[bsc_handle].pkcs10_request + for r in rpkid_reply: + if isinstance(r, rpki.left_right.report_error_elt): + print "rpkid reported failure:", r.error_code + if r.error_text: + print r.error_text + if any(isinstance(r, rpki.left_right.report_error_elt) for r in rpkid_reply): + raise CouldntTalkToDaemon + + if pubd_query: + assert self.run_pubd + pubd_reply = call_pubd(*pubd_query) + for r in pubd_reply: + if isinstance(r, rpki.publication.report_error_elt): + print "pubd reported failure:", r.error_code + if r.error_text: + print r.error_text + if any(isinstance(r, rpki.publication.report_error_elt) for r in pubd_reply): + raise CouldntTalkToDaemon diff --git a/rpkid/rpki/rpkic.py b/rpkid/rpki/rpkic.py index 0b249f0a..96431114 100644 --- a/rpkid/rpki/rpkic.py +++ b/rpkid/rpki/rpkic.py @@ -46,27 +46,11 @@ PERFORMANCE OF THIS SOFTWARE. from __future__ import with_statement -import subprocess, csv, re, os, getopt, sys, base64, time, glob, copy, warnings +import csv, re, os, getopt, sys, base64, time, glob, copy, warnings import rpki.config, rpki.cli, rpki.sundial, rpki.log, rpki.oids import rpki.http, rpki.resource_set, rpki.relaxng, rpki.exceptions import rpki.left_right, rpki.x509, rpki.async -from lxml.etree import (Element, SubElement, ElementTree, - fromstring as ElementFromString, - tostring as ElementToString) - -from rpki.csv_utils import (csv_reader, csv_writer, BadCSVSyntax) - - - -# Our XML namespace and protocol version. - -namespace = "http://www.hactrn.net/uris/rpki/myrpki/" -version = "2" -namespaceQName = "{" + namespace + "}" - -# A whole lot of exceptions - class BadCommandSyntax(Exception): "Bad command line syntax." class BadPrefixSyntax(Exception): "Bad prefix syntax." class CouldntTalkToDaemon(Exception): "Couldn't talk to daemon." @@ -74,160 +58,50 @@ class BadXMLMessage(Exception): "Bad XML message." class PastExpiration(Exception): "Expiration date has already passed." class CantRunRootd(Exception): "Can't run rootd." - - -def B64Element(e, tag, obj, **kwargs): - """ - Create an XML element containing Base64 encoded data taken from a - DER object. - """ - - if e is None: - se = Element(tag, **kwargs) - else: - se = SubElement(e, tag, **kwargs) - if e is not None and e.text is None: - e.text = "\n" - se.text = "\n" + obj.get_Base64() - se.tail = "\n" - return se - -class PEM_writer(object): - """ - Write PEM files to disk, keeping track of which ones we've already - written and setting the file mode appropriately. - """ - - def __init__(self, verbose = False): - self.wrote = set() - self.verbose = verbose - - def __call__(self, filename, obj): - filename = os.path.realpath(filename) - if filename in self.wrote: - return - tempname = filename - if not filename.startswith("/dev/"): - tempname += ".%s.tmp" % os.getpid() - mode = 0400 if filename.endswith(".key") else 0444 - if self.verbose: - print "Writing", filename - f = os.fdopen(os.open(tempname, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode), "w") - f.write(obj.get_PEM()) - f.close() - if tempname != filename: - os.rename(tempname, filename) - self.wrote.add(filename) - - - -def etree_write(e, filename, verbose = False, msg = None): - """ - Write out an etree to a file, safely. - - I still miss SYSCAL(RENMWO). - """ - - filename = os.path.realpath(filename) - tempname = filename - if not filename.startswith("/dev/"): - tempname += ".%s.tmp" % os.getpid() - if verbose or msg: - print "Writing", filename - if msg: - print msg - e = copy.deepcopy(e) - e.set("version", version) - for i in e.getiterator(): - if i.tag[0] != "{": - i.tag = namespaceQName + i.tag - assert i.tag.startswith(namespaceQName) - rpki.relaxng.myrpki.assertValid(e) - ElementTree(e).write(tempname) - if tempname != filename: - os.rename(tempname, filename) - -def etree_read(filename, verbose = False): - """ - Read an etree from a file, verifying then stripping XML namespace - cruft. - """ - - if verbose: - print "Reading", filename - e = ElementTree(file = filename).getroot() - rpki.relaxng.myrpki.assertValid(e) - for i in e.getiterator(): - if i.tag.startswith(namespaceQName): - i.tag = i.tag[len(namespaceQName):] - else: - raise BadXMLMessage, "XML tag %r is not in namespace %r" % (i.tag, namespace) - return e - - - class main(rpki.cli.Cmd): prompt = "rpkic> " completedefault = rpki.cli.Cmd.filename_complete - show_xml = False - def __init__(self): os.environ["TZ"] = "UTC" time.tzset() rpki.log.use_syslog = False - self.cfg_file = None - self.handle = None + cfg_file = None + handle = None opts, argv = getopt.getopt(sys.argv[1:], "c:hi:?", ["config=", "help", "identity="]) for o, a in opts: if o in ("-c", "--config"): - self.cfg_file = a + cfg_file = a elif o in ("-h", "--help", "-?"): argv = ["help"] elif o in ("-i", "--identity"): - self.handle = a + handle = a if not argv or argv[0] != "help": rpki.log.init("rpkic") - self.read_config() + self.read_config(cfg_file, handle) rpki.cli.Cmd.__init__(self, argv) - def read_config(self): - - # For reasons I don't understand, importing this at the global - # level isn't working properly today. Importing it here works - # fine. WTF? - - import rpki.config - - self.cfg = rpki.config.parser(self.cfg_file, "myrpki") - - self.cfg.set_global_flags() - - if self.handle is None: - self.handle = self.cfg.get("handle") - - self.histfile = self.cfg.get("history_file", ".rpkic_history") - self.run_rpkid = self.cfg.getboolean("run_rpkid") - self.run_pubd = self.cfg.getboolean("run_pubd") - self.run_rootd = self.cfg.getboolean("run_rootd") + def read_config(self, cfg_file, handle): + global rpki - irdbd_section = "irdbd" + cfg = rpki.config.parser(cfg_file, "myrpki") + cfg.set_global_flags() from django.conf import settings settings.configure( DATABASES = { "default" : { "ENGINE" : "django.db.backends.mysql", - "NAME" : self.cfg.get("sql-database", section = irdbd_section), - "USER" : self.cfg.get("sql-username", section = irdbd_section), - "PASSWORD" : self.cfg.get("sql-password", section = irdbd_section), + "NAME" : cfg.get("sql-database", section = "irdbd"), + "USER" : cfg.get("sql-username", section = "irdbd"), + "PASSWORD" : cfg.get("sql-password", section = "irdbd"), "HOST" : "", "PORT" : "", "OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}}, @@ -239,26 +113,7 @@ class main(rpki.cli.Cmd): import django.core.management django.core.management.call_command("syncdb", verbosity = 0, load_initial_data = False) - if self.run_rootd and (not self.run_pubd or not self.run_rpkid): - raise CantRunRootd, "Can't run rootd unless also running rpkid and pubd" - - self.default_repository = self.cfg.get("default_repository", "") - self.pubd_contact_info = self.cfg.get("pubd_contact_info", "") - - self.rsync_module = self.cfg.get("publication_rsync_module") - self.rsync_server = self.cfg.get("publication_rsync_server") - - self.reset_identity() - - def reset_identity(self): - try: - self.resource_ca = rpki.irdb.ResourceHolderCA.objects.get(handle = self.handle) - except rpki.irdb.ResourceHolderCA.DoesNotExist: - self.resource_ca = None - try: - self.server_ca = rpki.irdb.ServerCA.objects.get() - except rpki.irdb.ServerCA.DoesNotExist: - self.server_ca = None + self.zoo = rpki.irdb.Zookeeper(cfg = cfg, handle = handle) def help_overview(self): """ @@ -282,8 +137,7 @@ class main(rpki.cli.Cmd): argv = arg.split() if len(argv) != 1: raise BadCommandSyntax("This command expexcts one argument, not %r" % arg) - self.handle = argv[0] - self.reset_identity() + self.zoo.reset_identity(argv[0]) def complete_select_identity(self, *args): return self.irdb_handle_complete(rpki.irdb.ResourceHolderCA, *args) @@ -300,90 +154,15 @@ class main(rpki.cli.Cmd): if arg: raise BadCommandSyntax, "This command takes no arguments" - self.resource_ca, created = rpki.irdb.ResourceHolderCA.objects.get_or_certify(handle = self.handle) - if created: - print "Created new BPKI resource CA for identity %s" % self.handle - - if self.run_rpkid or self.run_pubd: - self.server_ca, created = rpki.irdb.ServerCA.objects.get_or_certify() - if created: - print "Created new BPKI server CA" - rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "irbe") - - if self.run_rpkid: - rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "rpkid") - rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "irdbd") - - if self.run_pubd: - rpki.irdb.ServerEE.objects.get_or_certify(issuer = self.server_ca, purpose = "pubd") - - # Build the identity.xml file. Need to check for existing file so we don't - # overwrite? Worry about that later. - - run_rootd = self.run_rootd and self.handle == self.cfg.get("handle") - - e = Element("identity", handle = self.handle) - B64Element(e, "bpki_ta", self.resource_ca.certificate) - etree_write(e, "%s.identity.xml" % self.handle, - msg = None if run_rootd else 'This is the "identity" file you will need to send to your parent') - - if run_rootd: - assert self.run_rpkid and self.run_pubd + r = self.zoo.initialize() + r.save("%s.identity.xml" % self.zoo.handle, not self.zoo.run_pubd) - rpki.irdb.Rootd.objects.get_or_certify( - issuer = self.resource_ca, - service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port")) + if self.zoo.run_rootd and self.zoo.handle == self.zoo.cfg.get("handle"): + r = self.zoo.configure_rootd() + if r is not None: + r.save("%s.%s.repository-request.xml" % (self.zoo.handle, self.zoo.handle), True) - # The following assumes we'll set up the respository manually. - # Not sure this is a reasonable assumption, particularly if we - # ever fix rootd to use the publication protocol. - - try: - self.resource_ca.repositories.get(handle = self.handle) - - except rpki.irdb.Repository.DoesNotExist: - e = Element("repository", type = "offer", handle = self.handle, parent_handle = self.handle) - B64Element(e, "bpki_client_ta", self.resource_ca.certificate) - etree_write(e, "%s.%s.repository-request.xml" % (self.handle, self.handle), - msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository') - - # Not (yet) sure whether we should be calling this here, try it for now - self.write_bpki_files() - - - def write_bpki_files(self): - """ - Write out BPKI certificate, key, and CRL files for daemons that - need them. - """ - - writer = PEM_writer() - - if self.run_rpkid: - rpkid = self.server_ca.ee_certificates.get(purpose = "rpkid") - writer(self.cfg.get("bpki-ta", section = "rpkid"), self.server_ca.certificate) - writer(self.cfg.get("rpkid-key", section = "rpkid"), rpkid.private_key) - writer(self.cfg.get("rpkid-cert", section = "rpkid"), rpkid.certificate) - writer(self.cfg.get("irdb-cert", section = "rpkid"), - self.server_ca.ee_certificates.get(purpose = "irdbd").certificate) - writer(self.cfg.get("irbe-cert", section = "rpkid"), - self.server_ca.ee_certificates.get(purpose = "irbe").certificate) - - if self.run_pubd: - pubd = self.server_ca.ee_certificates.get(purpose = "pubd") - writer(self.cfg.get("bpki-ta", section = "pubd"), self.server_ca.certificate) - writer(self.cfg.get("pubd-key", section = "pubd"), pubd.private_key) - writer(self.cfg.get("pubd-cert", section = "pubd"), pubd.certificate) - writer(self.cfg.get("irbe-cert", section = "pubd"), - self.server_ca.ee_certificates.get(purpose = "irbe").certificate) - - if self.run_rootd: - rootd = rpki.irdb.ResourceHolderCA.objects.get(handle = self.cfg.get("handle", section = "myrpki")).rootd - writer(self.cfg.get("bpki-ta", section = "rootd"), self.server_ca.certificate) - writer(self.cfg.get("rootd-bpki-crl", section = "rootd"), self.server_ca.latest_crl) - writer(self.cfg.get("rootd-bpki-key", section = "rootd"), rootd.private_key) - writer(self.cfg.get("rootd-bpki-cert", section = "rootd"), rootd.certificate) - writer(self.cfg.get("child-bpki-cert", section = "rootd"), rootd.issuer.certificate) + self.zoo.write_bpki_files() def do_update_bpki(self, arg): @@ -399,29 +178,7 @@ class main(rpki.cli.Cmd): Most likely this should be run under cron. """ - for model in (rpki.irdb.ServerCA, - rpki.irdb.ResourceHolderCA, - rpki.irdb.ServerEE, - rpki.irdb.Referral, - rpki.irdb.Rootd, - rpki.irdb.HostedCA, - rpki.irdb.BSC, - rpki.irdb.Child, - rpki.irdb.Parent, - rpki.irdb.Client, - rpki.irdb.Repository): - for obj in model.objects.all(): - print "Regenerating certificate", obj.certificate.getSubject() - obj.avow() - - print "Regenerating Server CRL" - self.server_ca.generate_crl() - - for ca in rpki.irdb.ResourceHolderCA.objects.all(): - print "Regenerating CRL for", ca.handle - ca.generate_crl() - - self.write_bpki_files() + self.zoo.update_bpki() def do_configure_child(self, arg): @@ -444,60 +201,8 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax, "Need to specify filename for child.xml" - c = etree_read(argv[0]) - - if child_handle is None: - child_handle = c.get("handle") - - service_uri = "http://%s:%s/up-down/%s/%s" % (self.cfg.get("rpkid_server_host"), - self.cfg.get("rpkid_server_port"), - self.handle, child_handle) - - valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) - - print "Child calls itself %r, we call it %r" % (c.get("handle"), child_handle) - - child, created = rpki.irdb.Child.objects.get_or_certify( - issuer = self.resource_ca, - handle = child_handle, - ta = rpki.x509.X509(Base64 = c.findtext("bpki_ta")), - valid_until = valid_until) - - e = Element("parent", parent_handle = self.handle, child_handle = child_handle, - service_uri = service_uri, valid_until = str(valid_until)) - B64Element(e, "bpki_resource_ta", self.resource_ca.certificate) - B64Element(e, "bpki_child_ta", child.ta) - - try: - if self.default_repository: - repo = self.resource_ca.repositories.get(handle = self.default_repository) - else: - repo = self.resource_ca.repositories.get() - except rpki.irdb.Repository.DoesNotExist: - repo = None - - if repo is None: - print "Couldn't find any usable repositories, not giving referral" - - elif repo.handle == self.handle: - SubElement(e, "repository", type = "offer") - - else: - proposed_sia_base = repo.sia_base + child_handle + "/" - referral_cert, created = rpki.irdb.Referral.objects.get_or_certify(issuer = self.resource_ca) - auth = rpki.x509.SignedReferral() - auth.set_content(B64Element(None, namespaceQName + "referral", child.ta, - version = version, - authorized_sia_base = proposed_sia_base)) - auth.schema_check() - auth.sign(referral_cert.private_key, referral_cert.certificate, self.resource_ca.latest_crl) - - r = SubElement(e, "repository", type = "referral") - B64Element(r, "authorization", auth, referrer = repo.client_handle) - SubElement(r, "contact_info") - - etree_write(e, "%s.%s.parent-response.xml" % (self.handle, child_handle), - msg = "Send this file back to the child you just configured") + r, child_handle = self.zoo.configure_child(argv[0], child_handle) + r.save("%s.%s.parent-response.xml" % (self.zoo.handle, child_handle), True) def do_delete_child(self, arg): @@ -506,7 +211,7 @@ class main(rpki.cli.Cmd): """ try: - self.resource_ca.children.get(handle = arg).delete() + self.zoo.delete_child(arg) except rpki.irdb.Child.DoesNotExist: print "No such child \"%s\"" % arg @@ -536,46 +241,8 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax, "Need to specify filename for parent.xml on command line" - p = etree_read(argv[0]) - - if parent_handle is None: - parent_handle = p.get("parent_handle") - - r = p.find("repository") - - repository_type = "none" - referrer = None - referral_authorization = None - - if r is not None: - repository_type = r.get("type") - - if repository_type == "referral": - a = r.find("authorization") - referrer = a.get("referrer") - referral_authorization = rpki.x509.SignedReferral(Base64 = a.text) - - print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle) - print "Parent calls us %r" % p.get("child_handle") - - rpki.irdb.Parent.objects.get_or_certify( - issuer = self.resource_ca, - handle = parent_handle, - child_handle = p.get("child_handle"), - parent_handle = p.get("parent_handle"), - service_uri = p.get("service_uri"), - ta = rpki.x509.X509(Base64 = p.findtext("bpki_resource_ta")), - repository_type = repository_type, - referrer = referrer, - referral_authorization = referral_authorization) - - if repository_type == "none": - r = Element("repository", type = "none") - r.set("handle", self.handle) - r.set("parent_handle", parent_handle) - B64Element(r, "bpki_client_ta", self.resource_ca.certificate) - etree_write(r, "%s.%s.repository-request.xml" % (self.handle, parent_handle), - msg = "This is the file to send to the repository operator") + r, parent_handle = self.zoo.configure_parent(argv[0], parent_handle) + r.save("%s.%s.repository-request.xml" % (self.zoo.handle, parent_handle), True) def do_delete_parent(self, arg): @@ -584,7 +251,7 @@ class main(rpki.cli.Cmd): """ try: - self.resource_ca.parents.get(handle = arg).delete() + self.zoo.delete_parent(arg) except rpki.irdb.Parent.DoesNotExist: print "No such parent \"%s\"" % arg @@ -611,73 +278,8 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax, "Need to specify filename for client.xml" - client = etree_read(argv[0]) - - client_ta = rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")) - - if sia_base is None and client.get("handle") == self.handle and self.resource_ca.certificate == client_ta: - print "This looks like self-hosted publication" - sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, self.handle) - - if sia_base is None and client.get("type") == "referral": - print "This looks like a referral, checking" - try: - auth = client.find("authorization") - referrer = self.server_ca.clients.get(handle = auth.get("referrer")) - referral_cms = rpki.x509.SignedReferral(Base64 = auth.text) - referral_xml = referral_cms.unwrap(ta = (referrer.certificate, self.server_ca.certificate)) - if rpki.x509.X509(Base64 = referral_xml.text) != client_ta: - raise BadXMLMessage, "Referral trust anchor does not match" - sia_base = referral_xml.get("authorized_sia_base") - except rpki.irdb.Client.DoesNotExist: - print "We have no record of the client (%s) alleged to have made this referral" % auth.get("referrer") - - if sia_base is None and client.get("type") == "offer" and client.get("parent_handle") == self.handle: - print "This looks like an offer, client claims to be our child, checking" - try: - child = self.resource_ca.children.get(ta = client_ta) - except rpki.irdb.Child.DoesNotExist: - print "Can't find a child matching this client" - else: - sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module, - self.handle, client.get("handle")) - - # If we still haven't figured out what to do with this client, it - # gets a top-level tree of its own, no attempt at nesting. - - if sia_base is None: - print "Don't know where to nest this client, defaulting to top-level" - sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client.get("handle")) - - if not sia_base.startswith("rsync://"): - raise BadXMLMessage, "Malformed sia_base parameter %r, should start with 'rsync://'" % sia_base - - client_handle = "/".join(sia_base.rstrip("/").split("/")[4:]) - - parent_handle = client.get("parent_handle") - - print "Client calls itself %r, we call it %r" % (client.get("handle"), client_handle) - print "Client says its parent handle is %r" % parent_handle - - rpki.irdb.Client.objects.get_or_certify( - issuer = self.server_ca, - handle = client_handle, - ta = client_ta, - sia_base = sia_base) - - e = Element("repository", type = "confirmed", - client_handle = client_handle, - parent_handle = parent_handle, - sia_base = sia_base, - service_uri = "http://%s:%s/client/%s" % (self.cfg.get("pubd_server_host"), - self.cfg.get("pubd_server_port"), - client_handle)) - - B64Element(e, "bpki_server_ta", self.server_ca.certificate) - B64Element(e, "bpki_client_ta", client_ta) - SubElement(e, "contact_info").text = self.pubd_contact_info - etree_write(e, "%s.repository-response.xml" % client_handle.replace("/", "."), - msg = "Send this file back to the publication client you just configured") + r, client_handle = self.zoo.configure_publication_client(argv[0], sia_base) + r.save("%s.repository-response.xml" % client_handle.replace("/", "."), True) def do_delete_publication_client(self, arg): @@ -686,7 +288,7 @@ class main(rpki.cli.Cmd): """ try: - self.resource_ca.clients.get(handle = arg).delete() + self.zoo.delete_publication_client(arg).delete() except rpki.irdb.Client.DoesNotExist: print "No such client \"%s\"" % arg @@ -714,32 +316,7 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax, "Need to specify filename for repository.xml on command line" - r = etree_read(argv[0]) - - if parent_handle is None: - parent_handle = r.get("parent_handle") - - print "Repository calls us %r" % (r.get("client_handle")) - print "Repository response associated with parent_handle %r" % parent_handle - - try: - if parent_handle == self.handle: - turtle = self.resource_ca.rootd - else: - turtle = self.resource_ca.parents.get(handle = parent_handle) - - except (rpki.irdb.Parent.DoesNotExist, rpki.irdb.Rootd.DoesNotExist): - print "Could not find parent %r in our database" % parent_handle - - else: - rpki.irdb.Repository.objects.get_or_certify( - issuer = self.resource_ca, - handle = parent_handle, - client_handle = r.get("client_handle"), - service_uri = r.get("service_uri"), - sia_base = r.get("sia_base"), - ta = rpki.x509.X509(Base64 = r.findtext("bpki_server_ta")), - turtle = turtle) + self.zoo.configure_repository(argv[0], parent_handle) def do_delete_repository(self, arg): """ @@ -750,7 +327,7 @@ class main(rpki.cli.Cmd): """ try: - self.resource_ca.repositories.get(handle = arg).delete() + self.zoo.delete_repository(arg) except rpki.irdb.Repository.DoesNotExist: print "No such repository \"%s\"" % arg @@ -758,9 +335,9 @@ class main(rpki.cli.Cmd): return self.irdb_handle_complete(rpki.irdb.Repository, *args) - def renew_children_common(self, arg, plural): + def do_renew_child(self, arg): """ - Common code for renew_child and renew_all_children commands. + Update validity period for one child entity. """ valid_until = None @@ -770,49 +347,36 @@ class main(rpki.cli.Cmd): if o == "--valid_until": valid_until = a - if plural: - if len(argv) != 0: - raise BadCommandSyntax, "Unexpected arguments" - children = self.resource_ca.children - else: - if len(argv) != 1: - raise BadCommandSyntax, "Need to specify child handle" - children = self.resource_ca.children.filter(handle = argv[0]) - - if valid_until is None: - valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) - else: - valid_until = rpki.sundial.fromXMLtime(valid_until) - if valid_until < rpki.sundial.now(): - raise PastExpiration, "Specified new expiration time %s has passed" % valid_until - - print "New validity date", valid_until - - for child in children: - child.valid_until = valid_until - child.save() - - def do_renew_child(self, arg): - """ - Update validity period for one child entity. - """ + if len(argv) != 1: + raise BadCommandSyntax, "Need to specify child handle" - return self.renew_children_common(arg, False) + self.zoo.renew_children(argv[0], valid_until) def complete_renew_child(self, *args): return self.irdb_handle_complete(rpki.irdb.Child, *args) + def do_renew_all_children(self, arg): """ Update validity period for all child entities. """ - return self.renew_children_common(arg, True) + valid_until = None + + opts, argv = getopt.getopt(arg.split(), "", ["valid_until"]) + for o, a in opts: + if o == "--valid_until": + valid_until = a + if len(argv) != 0: + raise BadCommandSyntax, "Unexpected arguments" - def do_synchronize_prefixes(self, arg): + self.zoo.renew_children(None, valid_until) + + + def do_load_prefixes(self, arg): """ - Synchronize IRDB against prefixes.csv. + Load prefixes into IRDB from CSV file. """ argv = arg.split() @@ -820,37 +384,7 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax("Need to specify prefixes.csv filename") - grouped4 = {} - grouped6 = {} - - for handle, prefix in csv_reader(argv[0], columns = 2): - grouped = grouped6 if ":" in prefix else grouped4 - if handle not in grouped: - grouped[handle] = [] - grouped[handle].append(prefix) - - import django.db.transaction - - with django.db.transaction.commit_on_success(): - - primary_keys = [] - - for version, grouped, rset in ((4, grouped4, rpki.resource_set.resource_set_ipv4), - (6, grouped6, rpki.resource_set.resource_set_ipv6)): - for handle, prefixes in grouped.iteritems(): - child = self.resource_ca.children.get(handle = handle) - for prefix in rset(",".join(prefixes)): - obj, created = rpki.irdb.ChildNet.objects.get_or_create( - child = child, - start_ip = str(prefix.min), - end_ip = str(prefix.max), - version = version) - primary_keys.append(obj.pk) - - q = rpki.irdb.ChildNet.objects - q = q.filter(child__issuer__exact = self.resource_ca) - q = q.exclude(pk__in = primary_keys) - q.delete() + self.zoo.load_prefixes(argv[0]) def do_show_child_resources(self, arg): @@ -879,9 +413,9 @@ class main(rpki.cli.Cmd): print " IPv6:", ipv6 - def do_synchronize_asns(self, arg): + def do_load_asns(self, arg): """ - Synchronize IRDB against asns.csv. + Load ASNs into IRDB from CSV file. """ argv = arg.split() @@ -889,37 +423,12 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax("Need to specify asns.csv filename") - grouped = {} - - for handle, asn in csv_reader(argv[0], columns = 2): - if handle not in grouped: - grouped[handle] = [] - grouped[handle].append(asn) - - import django.db.transaction - - with django.db.transaction.commit_on_success(): - - primary_keys = [] - - for handle, asns in grouped.iteritems(): - child = self.resource_ca.children.get(handle = handle) - for asn in rpki.resource_set.resource_set_as(",".join(asns)): - obj, created = rpki.irdb.ChildASN.objects.get_or_create( - child = child, - start_as = str(asn.min), - end_as = str(asn.max)) - primary_keys.append(obj.pk) + self.zoo.load_asns(argv[0]) - q = rpki.irdb.ChildASN.objects - q = q.filter(child__issuer__exact = self.resource_ca) - q = q.exclude(pk__in = primary_keys) - q.delete() - - def do_synchronize_roa_requests(self, arg): + def do_load_roa_requests(self, arg): """ - Synchronize IRDB against roa.csv. + Load ROA requests into IRDB from CSV file. """ argv = arg.split() @@ -927,364 +436,20 @@ class main(rpki.cli.Cmd): if len(argv) != 1: raise BadCommandSyntax("Need to specify roa.csv filename") - grouped = {} - - # format: p/n-m asn group - for pnm, asn, group in csv_reader(argv[0], columns = 3): - key = (asn, group) - if key not in grouped: - grouped[key] = [] - grouped[key].append(pnm) - - import django.db.transaction - - with django.db.transaction.commit_on_success(): - - # Deleting and recreating all the ROA requests is inefficient, - # but rpkid's current representation of ROA requests is wrong - # (see #32), so it's not worth a lot of effort here as we're - # just going to have to rewrite this soon anyway. - - self.resource_ca.roa_requests.all().delete() - - for key, pnms in grouped.iteritems(): - asn, group = key + self.zoo.load_roa_requests(argv[0]) - roa_request = self.resource_ca.roa_requests.create(asn = asn) - for pnm in pnms: - if ":" in pnm: - p = rpki.resource_set.roa_prefix_ipv6.parse_str(pnm) - v = 6 - else: - p = rpki.resource_set.roa_prefix_ipv4.parse_str(pnm) - v = 4 - roa_request.prefixes.create( - version = v, - prefix = str(p.prefix), - prefixlen = int(p.prefixlen), - max_prefixlen = int(p.max_prefixlen)) def do_synchronize(self, arg): """ - Temporary testing hack (probably) to let me run .synchronize() - manually. + Whack daemons to match IRDB. + + This command may be replaced by implicit synchronization embedded + in of other commands, haven't decided yet. """ if arg: raise BadCommandSyntax("Unexpected argument(s): %r" % arg) - self.synchronize() - - - def synchronize(self): - """ - Configure RPKI daemons with the data built up by the other - commands in this program. Most commands which modify the IRDB - should call this when they're done. - """ - - # We can use a single BSC for everything -- except BSC key - # rollovers. Drive off that bridge when we get to it. - - bsc_handle = "bsc" - - # Default values for CRL parameters are low, for testing. Not - # quite as low as they once were, too much expired CRL whining. - - self_crl_interval = self.cfg.getint("self_crl_interval", 2 * 60 * 60) - self_regen_margin = self.cfg.getint("self_regen_margin", self_crl_interval / 4) - pubd_base = "http://%s:%s/" % (self.cfg.get("pubd_server_host"), self.cfg.get("pubd_server_port")) - rpkid_base = "http://%s:%s/" % (self.cfg.get("rpkid_server_host"), self.cfg.get("rpkid_server_port")) - - # Wrappers to simplify calling rpkid and pubd. - - irbe = self.server_ca.ee_certificates.get(purpose = "irbe") - - call_rpkid = rpki.async.sync_wrapper(rpki.http.caller( - proto = rpki.left_right, - client_key = irbe.private_key, - client_cert = irbe.certificate, - server_ta = self.server_ca.certificate, - server_cert = self.server_ca.ee_certificates.get(purpose = "rpkid").certificate, - url = rpkid_base + "left-right", - debug = self.show_xml)) - - if self.run_pubd: - - call_pubd = rpki.async.sync_wrapper(rpki.http.caller( - proto = rpki.publication, - client_key = irbe.private_key, - client_cert = irbe.certificate, - server_ta = self.server_ca.certificate, - server_cert = self.server_ca.ee_certificates.get(purpose = "pubd").certificate, - url = pubd_base + "control", - debug = self.show_xml)) - - # Make sure that pubd's BPKI CRL is up to date. - - call_pubd(rpki.publication.config_elt.make_pdu( - action = "set", - bpki_crl = self.server_ca.latest_crl)) - - for ca in rpki.irdb.ResourceHolderCA.objects.all(): - - # See what rpkid and pubd already have on file for this entity. - - if self.run_pubd: - pubd_reply = call_pubd(rpki.publication.client_elt.make_pdu(action = "list")) - client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication.client_elt)) - - rpkid_reply = call_rpkid( - rpki.left_right.self_elt.make_pdu( action = "get", tag = "self", self_handle = ca.handle), - rpki.left_right.bsc_elt.make_pdu( action = "list", tag = "bsc", self_handle = ca.handle), - rpki.left_right.repository_elt.make_pdu(action = "list", tag = "repository", self_handle = ca.handle), - rpki.left_right.parent_elt.make_pdu( action = "list", tag = "parent", self_handle = ca.handle), - rpki.left_right.child_elt.make_pdu( action = "list", tag = "child", self_handle = ca.handle)) - - self_pdu = rpkid_reply[0] - bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) - repository_pdus = dict((x.repository_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.repository_elt)) - parent_pdus = dict((x.parent_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.parent_elt)) - child_pdus = dict((x.child_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.child_elt)) - - pubd_query = [] - rpkid_query = [] - - self_cert, created = rpki.irdb.HostedCA.objects.get_or_certify( - issuer = self.server_ca, - hosted = ca) - - # There should be exactly one <self/> object per hosted entity, by definition - - if (isinstance(self_pdu, rpki.left_right.report_error_elt) or - self_pdu.crl_interval != self_crl_interval or - self_pdu.regen_margin != self_regen_margin or - self_pdu.bpki_cert != self_cert.certificate): - rpkid_query.append(rpki.left_right.self_elt.make_pdu( - action = "create" if isinstance(self_pdu, rpki.left_right.report_error_elt) else "set", - tag = "self", - self_handle = ca.handle, - bpki_cert = ca.certificate, - crl_interval = self_crl_interval, - regen_margin = self_regen_margin)) - - # In general we only need one <bsc/> per <self/>. BSC objects - # are a little unusual in that the keypair and PKCS #10 - # subelement is generated by rpkid, so complete setup requires - # two round trips. - - bsc_pdu = bsc_pdus.pop(bsc_handle, None) - - if bsc_pdu is None: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "create", - tag = "bsc", - self_handle = ca.handle, - bsc_handle = bsc_handle, - generate_keypair = "yes")) - - elif bsc_pdu.pkcs10_request is None: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "set", - tag = "bsc", - self_handle = ca.handle, - bsc_handle = bsc_handle, - generate_keypair = "yes")) - - rpkid_query.extend(rpki.left_right.bsc_elt.make_pdu( - action = "destroy", self_handle = ca.handle, bsc_handle = b) for b in bsc_pdus) - - # If we've already got actions queued up, run them now, so we - # can finish setting up the BSC before anything tries to use it. - - if rpkid_query: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu(action = "list", tag = "bsc", self_handle = ca.handle)) - rpkid_reply = call_rpkid(*rpkid_query) - bsc_pdus = dict((x.bsc_handle, x) - for x in rpkid_reply - if isinstance(x, rpki.left_right.bsc_elt) and x.action == "list") - bsc_pdu = bsc_pdus.pop(bsc_handle, None) - for r in rpkid_reply: - if isinstance(r, rpki.left_right.report_error_elt): - print "rpkid reported failure:", r.error_code - if r.error_text: - print r.error_text - if any(isinstance(r, rpki.left_right.report_error_elt) for r in rpkid_reply): - raise CouldntTalkToDaemon - - rpkid_query = [] - - assert bsc_pdu.pkcs10_request is not None - - bsc, created = rpki.irdb.BSC.objects.get_or_certify( - issuer = ca, - handle = bsc_handle, - pkcs10 = bsc_pdu.pkcs10_request) - - if bsc_pdu.signing_cert != bsc.certificate or bsc_pdu.signing_cert_crl != ca.latest_crl: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "set", - tag = "bsc", - self_handle = ca.handle, - bsc_handle = bsc_handle, - signing_cert = bsc.certificate, - signing_cert_crl = ca.latest_crl)) - - # At present we need one <repository/> per <parent/>, not because - # rpkid requires that, but because pubd does. pubd probably should - # be fixed to support a single client allowed to update multiple - # trees, but for the moment the easiest way forward is just to - # enforce a 1:1 mapping between <parent/> and <repository/> objects - - for repository in ca.repositories.all(): - - repository_pdu = repository_pdus.pop(repository.handle, None) - - if (repository_pdu is None or - repository_pdu.bsc_handle != bsc_handle or - repository_pdu.peer_contact_uri != repository.service_uri or - repository_pdu.bpki_cert != repository.certificate): - rpkid_query.append(rpki.left_right.repository_elt.make_pdu( - action = "create" if repository_pdu is None else "set", - tag = repository.handle, - self_handle = ca.handle, - repository_handle = repository.handle, - bsc_handle = bsc_handle, - peer_contact_uri = repository.service_uri, - bpki_cert = repository.certificate)) - - rpkid_query.extend(rpki.left_right.repository_elt.make_pdu( - action = "destroy", self_handle = ca.handle, repository_handle = r) for r in repository_pdus) - - # <parent/> setup code currently assumes 1:1 mapping between - # <repository/> and <parent/>, and further assumes that the handles - # for an associated pair are the identical (that is: - # parent.repository_handle == parent.parent_handle). - - for parent in ca.parents.all(): - - parent_pdu = parent_pdus.pop(parent.handle, None) - - if (parent_pdu is None or - parent_pdu.bsc_handle != bsc_handle or - parent_pdu.repository_handle != parent.handle or - parent_pdu.peer_contact_uri != parent.service_uri or - parent_pdu.sia_base != parent.repository.sia_base or - parent_pdu.sender_name != parent.child_handle or - parent_pdu.recipient_name != parent.parent_handle or - parent_pdu.bpki_cms_cert != parent.certificate): - rpkid_query.append(rpki.left_right.parent_elt.make_pdu( - action = "create" if parent_pdu is None else "set", - tag = parent.handle, - self_handle = ca.handle, - parent_handle = parent.handle, - bsc_handle = bsc_handle, - repository_handle = parent.handle, - peer_contact_uri = parent.service_uri, - sia_base = parent.repository.sia_base, - sender_name = parent.child_handle, - recipient_name = parent.parent_handle, - bpki_cms_cert = parent.certificate)) - - try: - - parent_pdu = parent_pdus.pop(ca.handle, None) - - if (parent_pdu is None or - parent_pdu.bsc_handle != bsc_handle or - parent_pdu.repository_handle != ca.handle or - parent_pdu.peer_contact_uri != ca.rootd.service_uri or - parent_pdu.sia_base != ca.rootd.repository.sia_base or - parent_pdu.sender_name != ca.handle or - parent_pdu.recipient_name != ca.handle or - parent_pdu.bpki_cms_cert != ca.rootd.certificate): - rpkid_query.append(rpki.left_right.parent_elt.make_pdu( - action = "create" if parent_pdu is None else "set", - tag = ca.handle, - self_handle = ca.handle, - parent_handle = ca.handle, - bsc_handle = bsc_handle, - repository_handle = ca.handle, - peer_contact_uri = ca.rootd.service_uri, - sia_base = ca.rootd.repository.sia_base, - sender_name = ca.handle, - recipient_name = ca.handle, - bpki_cms_cert = ca.rootd.certificate)) - - except rpki.irdb.Rootd.DoesNotExist: - pass - - rpkid_query.extend(rpki.left_right.parent_elt.make_pdu( - action = "destroy", self_handle = ca.handle, parent_handle = p) for p in parent_pdus) - - # Children are simpler than parents, because they call us, so no URL - # to construct and figuring out what certificate to use is their - # problem, not ours. - - for child in ca.children.all(): - - child_pdu = child_pdus.pop(child.handle, None) - - if (child_pdu is None or - child_pdu.bsc_handle != bsc_handle or - child_pdu.bpki_cert != child.certificate): - rpkid_query.append(rpki.left_right.child_elt.make_pdu( - action = "create" if child_pdu is None else "set", - tag = child.handle, - self_handle = ca.handle, - child_handle = child.handle, - bsc_handle = bsc_handle, - bpki_cert = child.certificate)) - - rpkid_query.extend(rpki.left_right.child_elt.make_pdu( - action = "destroy", self_handle = ca.handle, child_handle = c) for c in child_pdus) - - # Publication setup. - - # Um, why are we doing this per resource holder? - - if self.run_pubd: - - for client in self.server_ca.clients.all(): - - client_pdu = client_pdus.pop(client.handle, None) - - if (client_pdu is None or - client_pdu.base_uri != client.sia_base or - client_pdu.bpki_cert != client.certificate): - pubd_query.append(rpki.publication.client_elt.make_pdu( - action = "create" if client_pdu is None else "set", - client_handle = client.handle, - bpki_cert = client.certificate, - base_uri = client.sia_base)) - - pubd_query.extend(rpki.publication.client_elt.make_pdu( - action = "destroy", client_handle = p) for p in client_pdus) - - # If we changed anything, ship updates off to daemons - - if rpkid_query: - rpkid_reply = call_rpkid(*rpkid_query) - bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) - if bsc_handle in bsc_pdus and bsc_pdus[bsc_handle].pkcs10_request: - bsc_req = bsc_pdus[bsc_handle].pkcs10_request - for r in rpkid_reply: - if isinstance(r, rpki.left_right.report_error_elt): - print "rpkid reported failure:", r.error_code - if r.error_text: - print r.error_text - if any(isinstance(r, rpki.left_right.report_error_elt) for r in rpkid_reply): - raise CouldntTalkToDaemon - - if pubd_query: - assert self.run_pubd - pubd_reply = call_pubd(*pubd_query) - for r in pubd_reply: - if isinstance(r, rpki.publication.report_error_elt): - print "pubd reported failure:", r.error_code - if r.error_text: - print r.error_text - if any(isinstance(r, rpki.publication.report_error_elt) for r in pubd_reply): - raise CouldntTalkToDaemon + self.zoo.synchronize() diff --git a/rpkid/tests/yamltest.py b/rpkid/tests/yamltest.py index 40dd0e5f..290120f7 100644 --- a/rpkid/tests/yamltest.py +++ b/rpkid/tests/yamltest.py @@ -313,7 +313,7 @@ class allocation(object): for k in self.kids: f.writerows((k.name, a) for a in k.resources.asn) f.close() - self.run_rpkic("synchronize_asns", fn) + self.run_rpkic("load_asns", fn) def dump_prefixes(self, fn): """ @@ -323,7 +323,7 @@ class allocation(object): for k in self.kids: f.writerows((k.name, p) for p in (k.resources.v4 + k.resources.v6)) f.close() - self.run_rpkic("synchronize_prefixes", fn) + self.run_rpkic("load_prefixes", fn) def dump_roas(self, fn): """ @@ -335,7 +335,7 @@ class allocation(object): f.writerows((p, r.asn, group) for p in (r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ())) f.close() - self.run_rpkic("synchronize_roa_requests", fn) + self.run_rpkic("load_roa_requests", fn) @property def pubd(self): |