diff options
author | Rob Austein <sra@hactrn.net> | 2010-12-14 20:35:28 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2010-12-14 20:35:28 +0000 |
commit | d97690e09b1485269e5af6a1445a14df3802c531 (patch) | |
tree | 7a133ff2d9a80dc479e738508065fdceef99d239 /rpkid/rpki | |
parent | cc9aca313010413d28ffb913245cbeda182b2d9b (diff) |
Move all daemons to modules
svn path=/rpkid/irdbd.py; revision=3569
Diffstat (limited to 'rpkid/rpki')
-rw-r--r-- | rpkid/rpki/irdbd.py | 225 | ||||
-rw-r--r-- | rpkid/rpki/pubd.py | 169 | ||||
-rw-r--r-- | rpkid/rpki/rootd.py | 320 | ||||
-rw-r--r-- | rpkid/rpki/rpkid.py | 106 |
4 files changed, 820 insertions, 0 deletions
diff --git a/rpkid/rpki/irdbd.py b/rpkid/rpki/irdbd.py new file mode 100644 index 00000000..a90f71ce --- /dev/null +++ b/rpkid/rpki/irdbd.py @@ -0,0 +1,225 @@ +""" +IR database daemon. + +Usage: python irdbd.py [ { -c | --config } configfile ] [ { -h | --help } ] + +Default configuration file is irdbd.conf, override with --config option. + +$Id$ + +Copyright (C) 2009--2010 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. + +Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") + +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 ARIN DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ARIN 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 sys, os, time, getopt, urlparse, warnings +import rpki.http, rpki.config, rpki.resource_set, rpki.relaxng +import rpki.exceptions, rpki.left_right, rpki.log, rpki.x509 + +# 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 + +class main(object): + + def handle_list_resources(self, q_pdu, r_msg): + + r_pdu = rpki.left_right.list_resources_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.child_handle = q_pdu.child_handle + + self.cur.execute( + "SELECT registrant_id, valid_until FROM registrant WHERE registry_handle = %s AND registrant_handle = %s", + (q_pdu.self_handle, q_pdu.child_handle)) + + if self.cur.rowcount != 1: + raise rpki.exceptions.NotInDatabase, \ + "This query should have produced a single exact match, something's messed up (rowcount = %d, self_handle = %s, child_handle = %s)" \ + % (self.cur.rowcount, q_pdu.self_handle, q_pdu.child_handle) + + registrant_id, valid_until = self.cur.fetchone() + + r_pdu.valid_until = valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") + + r_pdu.asn = rpki.resource_set.resource_set_as.from_sql( + self.cur, + "SELECT start_as, end_as FROM registrant_asn WHERE registrant_id = %s", + (registrant_id,)) + + r_pdu.ipv4 = rpki.resource_set.resource_set_ipv4.from_sql( + self.cur, + "SELECT start_ip, end_ip FROM registrant_net WHERE registrant_id = %s AND version = 4", + (registrant_id,)) + + r_pdu.ipv6 = rpki.resource_set.resource_set_ipv6.from_sql( + self.cur, + "SELECT start_ip, end_ip FROM registrant_net WHERE registrant_id = %s AND version = 6", + (registrant_id,)) + + r_msg.append(r_pdu) + + def handle_list_roa_requests(self, q_pdu, r_msg): + + self.cur.execute( + "SELECT roa_request_id, asn FROM roa_request WHERE roa_request_handle = %s", + (q_pdu.self_handle,)) + + for roa_request_id, asn in self.cur.fetchall(): + + r_pdu = rpki.left_right.list_roa_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.asn = asn + + r_pdu.ipv4 = rpki.resource_set.roa_prefix_set_ipv4.from_sql( + self.cur, + "SELECT prefix, prefixlen, max_prefixlen FROM roa_request_prefix WHERE roa_request_id = %s AND version = 4", + (roa_request_id,)) + + r_pdu.ipv6 = rpki.resource_set.roa_prefix_set_ipv6.from_sql( + self.cur, + "SELECT prefix, prefixlen, max_prefixlen FROM roa_request_prefix WHERE roa_request_id = %s AND version = 6", + (roa_request_id,)) + + r_msg.append(r_pdu) + + handle_dispatch = { + rpki.left_right.list_resources_elt : handle_list_resources, + rpki.left_right.list_roa_requests_elt : handle_list_roa_requests } + + def handler(self, query, path, cb): + try: + + self.db.ping(True) + + r_msg = rpki.left_right.msg.reply() + + try: + + q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.rpkid_cert)) + + if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query(): + raise rpki.exceptions.BadQuery, "Unexpected %r PDU" % q_msg + + for q_pdu in q_msg: + + try: + + try: + h = self.handle_dispatch[type(q_pdu)] + except KeyError: + raise rpki.exceptions.BadQuery, "Unexpected %r PDU" % q_pdu + else: + h(self, q_pdu, r_msg) + + except (rpki.async.ExitNow, SystemExit): + raise + + except Exception, data: + rpki.log.traceback() + r_msg.append(rpki.left_right.report_error_elt.from_exception(data, q_pdu.self_handle, q_pdu.tag)) + + except (rpki.async.ExitNow, SystemExit): + raise + + except Exception, data: + rpki.log.traceback() + r_msg.append(rpki.left_right.report_error_elt.from_exception(data)) + + cb(200, rpki.left_right.cms_msg().wrap(r_msg, self.irdbd_key, self.irdbd_cert)) + + except (rpki.async.ExitNow, SystemExit): + raise + + except Exception, data: + rpki.log.traceback() + + # We only get here in cases where we couldn't or wouldn't generate + # <report_error/>, so just return HTTP failure. + + cb(500, "Unhandled exception %s: %s" % (data.__class__.__name__, data)) + + def __init__(self): + + os.environ["TZ"] = "UTC" + time.tzset() + + cfg_file = "irdbd.conf" + + opts, argv = getopt.getopt(sys.argv[1:], "c:dh?", ["config=", "debug", "help"]) + for o, a in opts: + if o in ("-h", "--help", "-?"): + print __doc__ + sys.exit(0) + if o in ("-c", "--config"): + cfg_file = a + elif o in ("-d", "--debug"): + rpki.log.use_syslog = False + if argv: + raise rpki.exceptions.CommandParseFailure, "Unexpected arguments %s" % argv + + rpki.log.init("irdbd") + + self.cfg = rpki.config.parser(cfg_file, "irdbd") + + startup_msg = self.cfg.get("startup-message", "") + if startup_msg: + rpki.log.info(startup_msg) + + self.cfg.set_global_flags() + + self.db = MySQLdb.connect(user = self.cfg.get("sql-username"), + db = self.cfg.get("sql-database"), + passwd = self.cfg.get("sql-password")) + + self.cur = self.db.cursor() + self.db.autocommit(True) + + self.bpki_ta = rpki.x509.X509(Auto_update = self.cfg.get("bpki-ta")) + self.rpkid_cert = rpki.x509.X509(Auto_update = self.cfg.get("rpkid-cert")) + self.irdbd_cert = rpki.x509.X509(Auto_update = self.cfg.get("irdbd-cert")) + self.irdbd_key = rpki.x509.RSA( Auto_update = self.cfg.get("irdbd-key")) + + u = urlparse.urlparse(self.cfg.get("http-url")) + + assert u.scheme in ("", "http") and \ + u.username is None and \ + u.password is None and \ + u.params == "" and \ + u.query == "" and \ + u.fragment == "" + + rpki.http.server(host = u.hostname or "localhost", + port = u.port or 443, + handlers = ((u.path, self.handler),)) diff --git a/rpkid/rpki/pubd.py b/rpkid/rpki/pubd.py new file mode 100644 index 00000000..c842b6c6 --- /dev/null +++ b/rpkid/rpki/pubd.py @@ -0,0 +1,169 @@ +""" +RPKI publication engine. + +Usage: python pubd.py [ { -c | --config } configfile ] + [ { -h | --help } ] + [ { -p | --profile } outputfile ] + +Default configuration file is pubd.conf, override with --config option. + +$Id$ + +Copyright (C) 2009--2010 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. + +Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") + +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 ARIN DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ARIN 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 os, time, getopt, sys, re +import rpki.resource_set, rpki.up_down, rpki.x509, rpki.sql +import rpki.http, rpki.config, rpki.exceptions, rpki.relaxng +import rpki.log, rpki.publication + +class main(object): + """ + Main program for pubd. + """ + + def __init__(self): + + os.environ["TZ"] = "UTC" + time.tzset() + + self.cfg_file = "pubd.conf" + self.profile = False + + opts, argv = getopt.getopt(sys.argv[1:], "c:dhp:?", ["config=", "debug", "help"]) + for o, a in opts: + if o in ("-h", "--help", "-?"): + print __doc__ + sys.exit(0) + elif o in ("-c", "--config"): + self.cfg_file = a + elif o in ("-d", "--debug"): + rpki.log.use_syslog = False + elif o in ("-p", "--profile"): + self.profile = a + if argv: + raise rpki.exceptions.CommandParseFailure, "Unexpected arguments %s" % argv + + rpki.log.init("pubd") + + if self.profile: + import cProfile + cProfile.run("self.main()", self.profile) + else: + self.main() + + def main(self): + + self.cfg = rpki.config.parser(self.cfg_file, "pubd") + + if self.profile: + rpki.log.info("Running in profile mode with output to %s" % self.profile) + + self.cfg.set_global_flags() + + self.sql = rpki.sql.session(self.cfg) + + self.bpki_ta = rpki.x509.X509(Auto_update = self.cfg.get("bpki-ta")) + self.irbe_cert = rpki.x509.X509(Auto_update = self.cfg.get("irbe-cert")) + self.pubd_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-cert")) + self.pubd_key = rpki.x509.RSA( Auto_update = self.cfg.get("pubd-key")) + + self.http_server_host = self.cfg.get("server-host", "") + self.http_server_port = int(self.cfg.get("server-port", "4434")) + + self.publication_base = self.cfg.get("publication-base", "publication/") + + self.publication_multimodule = self.cfg.getboolean("publication-multimodule", False) + + rpki.http.server( + host = self.http_server_host, + port = self.http_server_port, + handlers = (("/control", self.control_handler), + ("/client/", self.client_handler))) + + def handler_common(self, query, client, cb, certs, crl = None): + """ + Common PDU handler code. + """ + + def done(r_msg): + reply = rpki.publication.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert, crl) + self.sql.sweep() + cb(reply) + + q_msg = rpki.publication.cms_msg(DER = query).unwrap(certs) + q_msg.serve_top_level(self, client, done) + + def control_handler(self, query, path, cb): + """ + Process one PDU from the IRBE. + """ + + def done(x): + cb(200, x) + + rpki.log.trace() + try: + self.sql.ping() + self.handler_common(query, None, done, (self.bpki_ta, self.irbe_cert)) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, data: + rpki.log.traceback() + cb(500, "Unhandled exception %s" % data) + + client_url_regexp = re.compile("/client/([-A-Z0-9_/]+)$", re.I) + + def client_handler(self, query, path, cb): + """ + Process one PDU from a client. + """ + + def done(x): + cb(200, x) + + rpki.log.trace() + try: + self.sql.ping() + match = self.client_url_regexp.search(path) + if match is None: + raise rpki.exceptions.BadContactURL, "Bad path: %s" % path + client_handle = match.group(1) + client = rpki.publication.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,)) + if client is None: + raise rpki.exceptions.ClientNotFound, "Could not find client %s" % client_handle + config = rpki.publication.config_elt.fetch(self) + if config is None or config.bpki_crl is None: + raise rpki.exceptions.CMSCRLNotSet + self.handler_common(query, client, done, (self.bpki_ta, client.bpki_cert, client.bpki_glue), config.bpki_crl) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, data: + rpki.log.traceback() + cb(500, "Could not process PDU: %s" % data) diff --git a/rpkid/rpki/rootd.py b/rpkid/rpki/rootd.py new file mode 100644 index 00000000..74674b57 --- /dev/null +++ b/rpkid/rpki/rootd.py @@ -0,0 +1,320 @@ +""" +Trivial RPKI up-down protocol root server, for testing. Not suitable +for production use. Overrides a bunch of method definitions from the +rpki.* classes in order to reuse as much code as possible. + +Usage: python rootd.py [ { -c | --config } configfile ] [ { -h | --help } ] + +Default configuration file is rootd.conf, override with --config option. + +$Id$ + +Copyright (C) 2009--2010 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. + +Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") + +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 ARIN DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ARIN 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 os, time, getopt, sys +import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509 +import rpki.http, rpki.config, rpki.exceptions, rpki.relaxng +import rpki.sundial, rpki.log + +rootd = None + +class list_pdu(rpki.up_down.list_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + r_msg.payload = rpki.up_down.list_response_pdu() + rootd.compose_response(r_msg) + callback() + +class issue_pdu(rpki.up_down.issue_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + self.pkcs10.check_valid_rpki() + r_msg.payload = rpki.up_down.issue_response_pdu() + rootd.compose_response(r_msg, self.pkcs10) + callback() + +class revoke_pdu(rpki.up_down.revoke_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + rootd.subject_cert = get_subject_cert() + if subject_cert is None or subject_cert.gSKI() != self.ski: + raise rpki.exceptions.NotInDatabase + rootd.del_subject_cert() + rootd.del_subject_pkcs10() + r_msg.payload = rpki.up_down.revoke_response_pdu() + r_msg.payload.class_name = self.class_name + r_msg.payload.ski = self.ski + callback() + +class message_pdu(rpki.up_down.message_pdu): + + name2type = { + "list" : list_pdu, + "list_response" : rpki.up_down.list_response_pdu, + "issue" : issue_pdu, + "issue_response" : rpki.up_down.issue_response_pdu, + "revoke" : revoke_pdu, + "revoke_response" : rpki.up_down.revoke_response_pdu, + "error_response" : rpki.up_down.error_response_pdu } + + type2name = dict((v, k) for k, v in name2type.items()) + + def log_query(self, child): + """ + Log query we're handling. + """ + rpki.log.info("Serving %s query" % self.type) + +class sax_handler(rpki.up_down.sax_handler): + pdu = message_pdu + +class cms_msg(rpki.up_down.cms_msg): + saxify = sax_handler.saxify + +class main(object): + + rpki_root_cert = None + + def get_root_cert(self): + rpki.log.debug("Read root cert %s" % self.rpki_root_cert_file) + self.rpki_root_cert = rpki.x509.X509(Auto_file = self.rpki_root_cert_file) + + def root_newer_than_subject(self): + return os.stat(self.rpki_root_cert_file).st_mtime > os.stat(self.rpki_root_dir + self.rpki_subject_cert).st_mtime + + def get_subject_cert(self): + filename = self.rpki_root_dir + self.rpki_subject_cert + try: + x = rpki.x509.X509(Auto_file = filename) + rpki.log.debug("Read subject cert %s" % filename) + return x + except IOError: + return None + + def set_subject_cert(self, cert): + filename = self.rpki_root_dir + self.rpki_subject_cert + rpki.log.debug("Writing subject cert %s, SKI %s" % (filename, cert.hSKI())) + f = open(filename, "wb") + f.write(cert.get_DER()) + f.close() + + def del_subject_cert(self): + filename = self.rpki_root_dir + self.rpki_subject_cert + rpki.log.debug("Deleting subject cert %s" % filename) + os.remove(filename) + + def get_subject_pkcs10(self): + try: + x = rpki.x509.PKCS10(Auto_file = self.rpki_subject_pkcs10) + rpki.log.debug("Read subject PKCS #10 %s" % self.rpki_subject_pkcs10) + return x + except IOError: + return None + + def set_subject_pkcs10(self, pkcs10): + rpki.log.debug("Writing subject PKCS #10 %s" % self.rpki_subject_pkcs10) + f = open(self.rpki_subject_pkcs10, "wb") + f.write(pkcs10.get_DER()) + f.close() + + def del_subject_pkcs10(self): + rpki.log.debug("Deleting subject PKCS #10 %s" % self.rpki_subject_pkcs10) + try: + os.remove(self.rpki_subject_pkcs10) + except OSError: + pass + + def issue_subject_cert_maybe(self, new_pkcs10): + now = rpki.sundial.now() + subject_cert = self.get_subject_cert() + old_pkcs10 = self.get_subject_pkcs10() + if new_pkcs10 is not None and new_pkcs10 != old_pkcs10: + self.set_subject_pkcs10(new_pkcs10) + if subject_cert is not None: + rpki.log.debug("PKCS #10 changed, regenerating subject certificate") + subject_cert = None + if subject_cert is not None and subject_cert.getNotAfter() <= now + self.rpki_subject_regen: + rpki.log.debug("Subject certificate has reached expiration threshold, regenerating") + subject_cert = None + if subject_cert is not None and self.root_newer_than_subject(): + rpki.log.debug("Root certificate has changed, regenerating subject") + subject_cert = None + self.get_root_cert() + if subject_cert is not None: + return subject_cert + pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10 + if pkcs10 is None: + rpki.log.debug("No PKCS #10 request, can't generate subject certificate yet") + return None + resources = self.rpki_root_cert.get_3779resources() + rpki.log.info("Generating subject cert with resources " + str(resources)) + req_key = pkcs10.getPublicKey() + req_sia = pkcs10.get_SIA() + crldp = self.rpki_base_uri + self.rpki_root_crl + serial = now.totimestamp() + subject_cert = self.rpki_root_cert.issue( + keypair = self.rpki_root_key, + subject_key = req_key, + serial = serial, + sia = req_sia, + aia = self.rpki_root_cert_uri, + crldp = crldp, + resources = resources, + notAfter = now + self.rpki_subject_lifetime) + crl = rpki.x509.CRL.generate( + keypair = self.rpki_root_key, + issuer = self.rpki_root_cert, + serial = serial, + thisUpdate = now, + nextUpdate = now + self.rpki_subject_lifetime, + revokedCertificates = ()) + rpki.log.debug("Writing CRL %s" % (self.rpki_root_dir + self.rpki_root_crl)) + f = open(self.rpki_root_dir + self.rpki_root_crl, "wb") + f.write(crl.get_DER()) + f.close() + manifest_resources = rpki.resource_set.resource_bag.from_inheritance() + manifest_keypair = rpki.x509.RSA.generate() + manifest_cert = self.rpki_root_cert.issue( + keypair = self.rpki_root_key, + subject_key = manifest_keypair.get_RSApublic(), + serial = serial + 1, + sia = None, + aia = self.rpki_root_cert_uri, + crldp = crldp, + resources = manifest_resources, + notAfter = now + self.rpki_subject_lifetime, + is_ca = False) + manifest = rpki.x509.SignedManifest.build( + serial = serial, + thisUpdate = now, + nextUpdate = now + self.rpki_subject_lifetime, + names_and_objs = [(self.rpki_subject_cert, subject_cert), (self.rpki_root_crl, crl)], + keypair = manifest_keypair, + certs = manifest_cert) + rpki.log.debug("Writing manifest %s" % (self.rpki_root_dir + self.rpki_root_manifest)) + f = open(self.rpki_root_dir + self.rpki_root_manifest, "wb") + f.write(manifest.get_DER()) + f.close() + self.set_subject_cert(subject_cert) + return subject_cert + + def compose_response(self, r_msg, pkcs10 = None): + subject_cert = self.issue_subject_cert_maybe(pkcs10) + rc = rpki.up_down.class_elt() + rc.class_name = self.rpki_class_name + rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri) + rc.from_resource_bag(self.rpki_root_cert.get_3779resources()) + rc.issuer = self.rpki_root_cert + r_msg.payload.classes.append(rc) + if subject_cert is not None: + rc.certs.append(rpki.up_down.certificate_elt()) + rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_base_uri + self.rpki_subject_cert) + rc.certs[0].cert = subject_cert + + def up_down_handler(self, query, path, cb): + try: + q_msg = cms_msg(DER = query).unwrap((self.bpki_ta, self.child_bpki_cert)) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + return cb(400, "Could not process PDU: %s" % e) + + def done(r_msg): + cb(200, cms_msg().wrap(r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl)) + + try: + q_msg.serve_top_level(None, done) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + try: + done(q_msg.serve_error(e)) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + cb(500, "Could not process PDU: %s" % e) + + def __init__(self): + + global rootd + rootd = self # Gross, but simpler than what we'd have to do otherwise + + os.environ["TZ"] = "UTC" + time.tzset() + + self.cfg_file = "rootd.conf" + + opts, argv = getopt.getopt(sys.argv[1:], "c:dh?", ["config=", "debug", "help"]) + for o, a in opts: + if o in ("-h", "--help", "-?"): + print __doc__ + sys.exit(0) + elif o in ("-c", "--config"): + self.cfg_file = a + elif o in ("-d", "--debug"): + rpki.log.use_syslog = False + if argv: + raise rpki.exceptions.CommandParseFailure, "Unexpected arguments %s" % argv + + rpki.log.init("rootd") + + self.cfg = rpki.config.parser(self.cfg_file, "rootd") + + self.cfg.set_global_flags() + + self.bpki_ta = rpki.x509.X509(Auto_file = self.cfg.get("bpki-ta")) + self.rootd_bpki_key = rpki.x509.RSA( Auto_file = self.cfg.get("rootd-bpki-key")) + self.rootd_bpki_cert = rpki.x509.X509(Auto_file = self.cfg.get("rootd-bpki-cert")) + self.rootd_bpki_crl = rpki.x509.CRL( Auto_file = self.cfg.get("rootd-bpki-crl")) + self.child_bpki_cert = rpki.x509.X509(Auto_file = self.cfg.get("child-bpki-cert")) + + self.http_server_host = self.cfg.get("server-host", "") + self.http_server_port = int(self.cfg.get("server-port")) + + self.rpki_class_name = self.cfg.get("rpki-class-name", "wombat") + + self.rpki_root_dir = self.cfg.get("rpki-root-dir") + self.rpki_base_uri = self.cfg.get("rpki-base-uri", "rsync://" + self.rpki_class_name + ".invalid/") + + self.rpki_root_key = rpki.x509.RSA( Auto_file = self.cfg.get("rpki-root-key")) + self.rpki_root_cert_file = self.cfg.get("rpki-root-cert") + self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "Root.cer") + + self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "Root.mnf") + self.rpki_root_crl = self.cfg.get("rpki-root-crl", "Root.crl") + self.rpki_subject_cert = self.cfg.get("rpki-subject-cert", "Child.cer") + self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10", "Child.pkcs10") + + self.rpki_subject_lifetime = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-lifetime", "30d")) + self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen", self.rpki_subject_lifetime.convert_to_seconds() / 2)) + + rpki.http.server(host = self.http_server_host, + port = self.http_server_port, + handlers = self.up_down_handler) diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py new file mode 100644 index 00000000..927e1456 --- /dev/null +++ b/rpkid/rpki/rpkid.py @@ -0,0 +1,106 @@ +""" +RPKI engine daemon. + +Usage: python rpkid.py [ { -c | --config } configfile ] + [ { -h | --help } ] + [ { -p | --profile } outputfile ] + +Default configuration file is rpkid.conf, override with --config option. + +$Id$ + +Copyright (C) 2009--2010 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. + +Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") + +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 ARIN DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ARIN 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 os, time, getopt, sys +import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql +import rpki.http, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log +import rpki.rpki_engine + +class main(object): + """ + Main program for rpkid. This class probably ought to be merged with + rpki.rpki_engine.rpkid_context, but that can wait, for today the + task is to move all the real code out of the scripts, this is the + minimal necessary change to do that. + """ + + def __init__(self): + + os.environ["TZ"] = "UTC" + time.tzset() + + self.cfg_file = "rpkid.conf" + self.profile = None + + opts, argv = getopt.getopt(sys.argv[1:], "c:dhp:?", ["config=", "debug", "help", "profile="]) + for o, a in opts: + if o in ("-h", "--help", "-?"): + print __doc__ + sys.exit(0) + elif o in ("-d", "--debug"): + rpki.log.use_syslog = False + elif o in ("-c", "--config"): + self.cfg_file = a + elif o in ("-p", "--profile"): + self.profile = a + if argv: + raise rpki.exceptions.CommandParseFailure, "Unexpected arguments %s" % argv + + rpki.log.init("rpkid") + + if self.profile: + import cProfile + cProfile.run("self.main()", self.profile) + else: + self.main() + + def main(self): + + self.cfg = rpki.config.parser(self.cfg_file, "rpkid") + + startup_msg = self.cfg.get("startup-message", "") + if startup_msg: + rpki.log.info(startup_msg) + + if self.profile: + rpki.log.info("Running in profile mode with output to %s" % self.profile) + + self.cfg.set_global_flags() + + gctx = rpki.rpki_engine.rpkid_context(self.cfg) + + gctx.start_cron() + + rpki.http.server( + host = gctx.http_server_host, + port = gctx.http_server_port, + handlers = (("/left-right", gctx.left_right_handler), + ("/up-down/", gctx.up_down_handler), + ("/cronjob", gctx.cronjob_handler))) |