diff options
Diffstat (limited to 'myrpki/myirbe.py')
-rw-r--r-- | myrpki/myirbe.py | 512 |
1 files changed, 0 insertions, 512 deletions
diff --git a/myrpki/myirbe.py b/myrpki/myirbe.py deleted file mode 100644 index c2c92279..00000000 --- a/myrpki/myirbe.py +++ /dev/null @@ -1,512 +0,0 @@ -""" -IRBE-side stuff for myrpki tools. - -The basic model here is that each entity with resources to certify -runs the myrpki tool, but not all of them necessarily run their own -RPKi engines. The entities that do run RPKI engines get data from the -entities they host via the XML files output by the myrpki tool. Those -XML files are the input to this script, which uses them to do all the -work of constructing certificates, populating SQL databases, and so -forth. A few operations (eg, BSC construction) generate data which -has to be shipped back to the resource holder, which we do by updating -the same XML file. - -In essence, the XML files are a sneakernet (or email, or carrier -pigeon) communication channel between the resource holders and the -RPKI engine operators. - -As a convenience, for the normal case where the RPKI engine operator -is itself a resource holder, this script also runs the myrpki script -directly to process the RPKI engine operator's own resources. - -Note that, due to the back and forth nature of some of these -operations, it may take several cycles for data structures to stablize -and everything to reach a steady state. This is normal. - - -$Id$ - -Copyright (C) 2009 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. -""" - -from __future__ import with_statement - -import lxml.etree, base64, subprocess, sys, os, time, re, getopt, warnings -import rpki.https, rpki.config, rpki.resource_set, rpki.relaxng -import rpki.exceptions, rpki.left_right, rpki.log, rpki.x509, rpki.async -import myrpki, schema - -# Silence warning while loading MySQLdb in Python 2.6, sigh -if hasattr(warnings, "catch_warnings"): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - import MySQLdb -else: - import MySQLdb - -def tag(t): - """ - Wrap an element name in the right XML namespace goop. - """ - return "{http://www.hactrn.net/uris/rpki/myrpki/}" + t - -def findbase64(tree, name, b64type = rpki.x509.X509): - """ - Find and extract a base64-encoded XML element, if present. - """ - x = tree.findtext(tag(name)) - return b64type(Base64 = x) if x else None - -# For simple cases we don't really care what these value are, so long -# as we're consistant about them, so wiring them in is fine. - -bsc_handle = "bsc" -repository_handle = "repository" - -os.environ["TZ"] = "UTC" -time.tzset() - -rpki.log.use_syslog = False -rpki.log.init("myirbe") - -cfg_file = "myrpki.conf" - -bpki_only = False - -opts, argv = getopt.getopt(sys.argv[1:], "bc:h?", ["bpki_only", "config=", "help"]) -for o, a in opts: - if o in ("-b", "--bpki_only"): - bpki_only = True - elif o in ("-c", "--config"): - cfg_file = a - elif o in ("-h", "--help", "-?"): - print __doc__ - sys.exit(0) - -cfg = rpki.config.parser(cfg_file, "myirbe") - -cfg.set_global_flags() - -myrpki.openssl = cfg.get("openssl", "openssl", "myrpki") - -handle = cfg.get("handle", cfg.get("handle", "Amnesiac", "myrpki")) - -want_pubd = cfg.getboolean("want_pubd", False) -want_rootd = cfg.getboolean("want_rootd", False) - -bpki_modified = False - -bpki = myrpki.CA(cfg_file, cfg.get("bpki_directory")) -bpki_modified |= bpki.setup(cfg.get("bpki_ta_dn", "/CN=%s BPKI TA" % handle)) -bpki_modified |= bpki.ee( cfg.get("bpki_rpkid_ee_dn", "/CN=%s rpkid EE" % handle), "rpkid") -bpki_modified |= bpki.ee( cfg.get("bpki_irdbd_ee_dn", "/CN=%s irdbd EE" % handle), "irdbd") -bpki_modified |= bpki.ee( cfg.get("bpki_irbe_ee_dn", "/CN=%s irbe EE" % handle), "irbe") -if want_pubd: - bpki_modified |= bpki.ee( cfg.get("bpki_pubd_ee_dn", "/CN=%s pubd EE" % handle), "pubd") -if want_rootd: - bpki_modified |= bpki.ee( cfg.get("bpki_rootd_ee_dn", "/CN=%s rootd EE" % handle), "rootd") - -if bpki_modified: - print "BPKI (re)initialized. You need to (re)start daemons before continuing." - -if bpki_modified or bpki_only: - sys.exit() - -# Default values for CRL parameters are very low, for testing. - -self_crl_interval = cfg.getint("self_crl_interval", 900) -self_regen_margin = cfg.getint("self_regen_margin", 300) -pubd_base = cfg.get("pubd_base").rstrip("/") + "/" -rpkid_base = cfg.get("rpkid_base").rstrip("/") + "/" - -# Nasty regexp for parsing rpkid's up-down service URLs. - -updown_regexp = re.compile(re.escape(rpkid_base) + "up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I) - -# Wrappers to simplify calling rpkid and pubd. - -call_rpkid = rpki.async.sync_wrapper(rpki.https.caller( - proto = rpki.left_right, - client_key = rpki.x509.RSA( PEM_file = bpki.dir + "/irbe.key"), - client_cert = rpki.x509.X509(PEM_file = bpki.dir + "/irbe.cer"), - server_ta = rpki.x509.X509(PEM_file = bpki.cer), - server_cert = rpki.x509.X509(PEM_file = bpki.dir + "/rpkid.cer"), - url = rpkid_base + "left-right")) - -if want_pubd: - - call_pubd = rpki.async.sync_wrapper(rpki.https.caller( - proto = rpki.publication, - client_key = rpki.x509.RSA( PEM_file = bpki.dir + "/irbe.key"), - client_cert = rpki.x509.X509(PEM_file = bpki.dir + "/irbe.cer"), - server_ta = rpki.x509.X509(PEM_file = bpki.cer), - server_cert = rpki.x509.X509(PEM_file = bpki.dir + "/pubd.cer"), - url = pubd_base + "control")) - - # Make sure that pubd's BPKI CRL is up to date. - - call_pubd(rpki.publication.config_elt.make_pdu( - action = "set", - bpki_crl = rpki.x509.CRL(PEM_file = bpki.crl))) - -irdbd_cfg = rpki.config.parser(cfg.get("irdbd_conf", cfg_file), "irdbd") - -db = MySQLdb.connect(user = irdbd_cfg.get("sql-username"), - db = irdbd_cfg.get("sql-database"), - passwd = irdbd_cfg.get("sql-password")) - -cur = db.cursor() - -xmlfiles = [] - -# If [myrpki] section is present in config file, run myrpki.py -# internally, as a convenience, and include its output at the head of -# our list of XML files to process. - -if cfg.has_section("myrpki"): - myrpki.main(("-c", cfg_file)) - my_xmlfile = cfg.get("xml_filename", None, "myrpki") - assert my_xmlfile is not None - xmlfiles.append(my_xmlfile) - -# Add any other XML files specified on the command line - -xmlfiles.extend(argv) - -my_handle = None - -for xmlfile in xmlfiles: - - # Parse XML file and validate it against our scheme - - tree = lxml.etree.parse(xmlfile).getroot() - try: - schema.myrpki.assertValid(tree) - except lxml.etree.DocumentInvalid: - print lxml.etree.tostring(tree, pretty_print = True) - raise - - handle = tree.get("handle") - - if xmlfile == my_xmlfile: - my_handle = handle - - # Update IRDB with parsed resource and roa-request data. - - cur.execute( - """ - DELETE - FROM roa_request_prefix - USING roa_request, roa_request_prefix - WHERE roa_request.roa_request_id = roa_request_prefix.roa_request_id AND roa_request.roa_request_handle = %s - """, (handle,)) - - cur.execute("DELETE FROM roa_request WHERE roa_request.roa_request_handle = %s", (handle,)) - - for x in tree.getiterator(tag("roa_request")): - cur.execute("INSERT roa_request (roa_request_handle, asn) VALUES (%s, %s)", (handle, x.get("asn"))) - roa_request_id = cur.lastrowid - for version, prefix_set in ((4, rpki.resource_set.roa_prefix_set_ipv4(x.get("v4"))), (6, rpki.resource_set.roa_prefix_set_ipv6(x.get("v6")))): - if prefix_set: - cur.executemany("INSERT roa_request_prefix (roa_request_id, prefix, prefixlen, max_prefixlen, version) VALUES (%s, %s, %s, %s, %s)", - ((roa_request_id, p.prefix, p.prefixlen, p.max_prefixlen, version) for p in prefix_set)) - - cur.execute( - """ - DELETE - FROM registrant_asn - USING registrant, registrant_asn - WHERE registrant.registrant_id = registrant_asn.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute( - """ - DELETE FROM registrant_net USING registrant, registrant_net - WHERE registrant.registrant_id = registrant_net.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute("DELETE FROM registrant WHERE registrant.registry_handle = %s" , (handle,)) - - for x in tree.getiterator(tag("child")): - child_handle = x.get("handle") - asns = rpki.resource_set.resource_set_as(x.get("asns")) - ipv4 = rpki.resource_set.resource_set_ipv4(x.get("v4")) - ipv6 = rpki.resource_set.resource_set_ipv6(x.get("v6")) - - cur.execute("INSERT registrant (registrant_handle, registry_handle, registrant_name, valid_until) VALUES (%s, %s, %s, %s)", - (child_handle, handle, child_handle, rpki.sundial.datetime.fromXMLtime(x.get("valid_until")).to_sql())) - child_id = cur.lastrowid - if asns: - cur.executemany("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)", - ((a.min, a.max, child_id) for a in asns)) - if ipv4: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)", - ((a.min, a.max, child_id) for a in ipv4)) - if ipv6: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)", - ((a.min, a.max, child_id) for a in ipv6)) - - db.commit() - - # Check for certificates before attempting anything else - - hosted_cacert = findbase64(tree, "bpki_ca_certificate") - if not hosted_cacert: - print "Nothing else I can do without a trust anchor for the entity I'm hosting." - continue - - rpkid_xcert = rpki.x509.X509(PEM_file = bpki.fxcert(handle + ".cacert.cer", - hosted_cacert.get_PEM(), - path_restriction = 1)) - - # See what rpkid and pubd already have on file for this entity. - - if want_pubd: - client_pdus = dict((x.client_handle, x) - for x in call_pubd(rpki.publication.client_elt.make_pdu(action = "list")) - if isinstance(x, rpki.publication.client_elt)) - - rpkid_reply = call_rpkid( - rpki.left_right.self_elt.make_pdu( action = "get", tag = "self", self_handle = handle), - rpki.left_right.bsc_elt.make_pdu( action = "list", tag = "bsc", self_handle = handle), - rpki.left_right.repository_elt.make_pdu(action = "list", tag = "repository", self_handle = handle), - rpki.left_right.parent_elt.make_pdu( action = "list", tag = "parent", self_handle = handle), - rpki.left_right.child_elt.make_pdu( action = "list", tag = "child", self_handle = 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 = [] - - # 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 != rpkid_xcert): - 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 = handle, - bpki_cert = rpkid_xcert, - 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 PKCS #10 subelement is generated by rpkid - # in response to generate_keypair, so there's more of a separation - # between create and set than with other objects. - - bsc_cert = findbase64(tree, "bpki_bsc_certificate") - bsc_crl = findbase64(tree, "bpki_crl", rpki.x509.CRL) - - 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 = handle, - bsc_handle = bsc_handle, - generate_keypair = "yes")) - elif bsc_pdu.signing_cert != bsc_cert or bsc_pdu.signing_cert_crl != bsc_crl: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "set", - tag = "bsc", - self_handle = handle, - bsc_handle = bsc_handle, - signing_cert = bsc_cert, - signing_cert_crl = bsc_crl)) - - rpkid_query.extend(rpki.left_right.bsc_elt.make_pdu( - action = "destroy", self_handle = handle, bsc_handle = b) for b in bsc_pdus) - - bsc_req = None - - if bsc_pdu and bsc_pdu.pkcs10_request: - bsc_req = bsc_pdu.pkcs10_request - - # In general we need one <repository/> per publication daemon with - # whom this <self/> has a relationship. In practice there is rarely - # (never?) a good reason for a single <self/> to use multiple - # publication services, so in normal use we only need one - # <repository/> object. If for some reason you really need more - # than this, you'll have to hack. - - repository_cert = findbase64(tree, "bpki_repository_certificate") - if repository_cert: - - repository_pdu = repository_pdus.pop(repository_handle, None) - repository_uri = pubd_base + "client/" + tree.get("repository_handle") - - if (repository_pdu is None or - repository_pdu.bsc_handle != bsc_handle or - repository_pdu.peer_contact_uri != repository_uri or - repository_pdu.bpki_cert != repository_cert): - rpkid_query.append(rpki.left_right.repository_elt.make_pdu( - action = "create" if repository_pdu is None else "set", - tag = repository_handle, - self_handle = handle, - repository_handle = repository_handle, - bsc_handle = bsc_handle, - peer_contact_uri = repository_uri, - bpki_cert = repository_cert)) - - rpkid_query.extend(rpki.left_right.repository_elt.make_pdu( - action = "destroy", self_handle = handle, repository_handle = r) for r in repository_pdus) - - # <parent/> setup code here used to be ridiculously complex. Most - # of the insanity was due to a misguided attempt to deduce pubd - # setup from other data; now that pubd setup is driven by - # pubclients.csv, parent setup should be relatively straightforward, - # but beware of lingering excessive cleverness in anything dealing - # with parent objects in this script. - - for parent in tree.getiterator(tag("parent")): - - parent_handle = parent.get("handle") - parent_pdu = parent_pdus.pop(parent_handle, None) - parent_uri = parent.get("service_uri") - parent_myhandle = parent.get("myhandle") - parent_sia_base = parent.get("sia_base") - parent_cms_cert = findbase64(parent, "bpki_cms_certificate") - parent_https_cert = findbase64(parent, "bpki_https_certificate") - - if (parent_pdu is None or - parent_pdu.bsc_handle != bsc_handle or - parent_pdu.repository_handle != repository_handle or - parent_pdu.peer_contact_uri != parent_uri or - parent_pdu.sia_base != parent_sia_base or - parent_pdu.sender_name != parent_myhandle or - parent_pdu.recipient_name != parent_handle or - parent_pdu.bpki_cms_cert != parent_cms_cert or - parent_pdu.bpki_https_cert != parent_https_cert): - rpkid_query.append(rpki.left_right.parent_elt.make_pdu( - action = "create" if parent_pdu is None else "set", - tag = parent_handle, - self_handle = handle, - parent_handle = parent_handle, - bsc_handle = bsc_handle, - repository_handle = repository_handle, - peer_contact_uri = parent_uri, - sia_base = parent_sia_base, - sender_name = parent_myhandle, - recipient_name = parent_handle, - bpki_cms_cert = parent_cms_cert, - bpki_https_cert = parent_https_cert)) - - rpkid_query.extend(rpki.left_right.parent_elt.make_pdu( - action = "destroy", self_handle = 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 tree.getiterator(tag("child")): - - child_handle = child.get("handle") - child_pdu = child_pdus.pop(child_handle, None) - child_cert = findbase64(child, "bpki_certificate") - - if (child_pdu is None or - child_pdu.bsc_handle != bsc_handle or - child_pdu.bpki_cert != child_cert): - rpkid_query.append(rpki.left_right.child_elt.make_pdu( - action = "create" if child_pdu is None else "set", - tag = child_handle, - self_handle = handle, - child_handle = child_handle, - bsc_handle = bsc_handle, - bpki_cert = child_cert)) - - rpkid_query.extend(rpki.left_right.child_elt.make_pdu( - action = "destroy", self_handle = handle, child_handle = c) for c in child_pdus) - - # Publication setup, used to be inferred (badly) from parent setup, - # now handled explictly via yet another freaking .csv file. - - if want_pubd: - - for client_handle, client_bpki_cert, client_base_uri in myrpki.csv_open(cfg.get("pubclients_csv", "pubclients.csv")): - - if os.path.exists(client_bpki_cert): - - client_pdu = client_pdus.pop(client_handle, None) - - client_bpki_cert = rpki.x509.X509(PEM_file = bpki.xcert(client_bpki_cert)) - - if (client_pdu is None or - client_pdu.base_uri != client_base_uri or - client_pdu.bpki_cert != client_bpki_cert): - 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_bpki_cert, - base_uri = client_base_uri)) - - 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: - assert not isinstance(r, rpki.left_right.report_error_elt) - - if pubd_query: - assert want_pubd - pubd_reply = call_pubd(*pubd_query) - for r in pubd_reply: - assert not isinstance(r, rpki.publication.report_error_elt) - - # Rewrite XML. - - e = tree.find(tag("bpki_bsc_pkcs10")) - if e is None and bsc_req is not None: - e = lxml.etree.SubElement(tree, "bpki_bsc_pkcs10") - elif bsc_req is None: - tree.remove(e) - - if bsc_req is not None: - assert e is not None - e.text = bsc_req.get_Base64() - - # Something weird going on here with lxml linked against recent - # versions of libxml2. Looks like modifying the tree above somehow - # produces validation errors, but it works fine if we convert it to - # a string and parse it again. I'm not seeing any problems with any - # of the other code that uses lxml to do validation, just this one - # place. Weird. Kludge around it for now. - - tree = lxml.etree.fromstring(lxml.etree.tostring(tree)) - - try: - schema.myrpki.assertValid(tree) - except lxml.etree.DocumentInvalid: - print lxml.etree.tostring(tree, pretty_print = True) - raise - - lxml.etree.ElementTree(tree).write(xmlfile + ".tmp", pretty_print = True) - os.rename(xmlfile + ".tmp", xmlfile) - -db.close() |