diff options
-rw-r--r-- | rpkid/Makefile | 2 | ||||
-rw-r--r-- | rpkid/pubd.pdf | bin | 0 -> 2992 bytes | |||
-rwxr-xr-x | rpkid/pubd.py | 126 | ||||
-rw-r--r-- | rpkid/pubd.sql | 31 | ||||
-rw-r--r-- | rpkid/rpki/gctx.py | 34 | ||||
-rw-r--r-- | rpkid/rpki/https.py | 24 | ||||
-rw-r--r-- | rpkid/rpki/publication.py | 42 | ||||
-rw-r--r-- | rpkid/rpkid.pdf | bin | 7493 -> 6931 bytes | |||
-rwxr-xr-x | rpkid/rpkid.py | 16 |
9 files changed, 232 insertions, 43 deletions
diff --git a/rpkid/Makefile b/rpkid/Makefile index f4c83091..b8abdb81 100644 --- a/rpkid/Makefile +++ b/rpkid/Makefile @@ -93,7 +93,7 @@ rpki/relaxng.py: make-relaxng.py python make-relaxng.py >$@.tmp mv $@.tmp $@ -pdf: rpkid.pdf irdbd.pdf +pdf: rpkid.pdf irdbd.pdf pubd.pdf doc:: pdf diff --git a/rpkid/pubd.pdf b/rpkid/pubd.pdf Binary files differnew file mode 100644 index 00000000..4cc50842 --- /dev/null +++ b/rpkid/pubd.pdf diff --git a/rpkid/pubd.py b/rpkid/pubd.py new file mode 100755 index 00000000..7100dc7b --- /dev/null +++ b/rpkid/pubd.py @@ -0,0 +1,126 @@ +# $Id$ + +# 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. + +""" +RPKI publication engine. + +Usage: python pubd.py [ { -c | --config } configfile ] [ { -h | --help } ] + +Default configuration file is pubd.conf, override with --config option. +""" + +import traceback, os, time, getopt, sys, MySQLdb, lxml.etree +import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql +import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log +import rpki.gctx + +class pubd_context(rpki.gctx.global_context): + """A container for various pubd parameters.""" + + def __init__(self, cfg): + + self.db = rpki.sql.connect(cfg) + self.cur = self.db.cursor() + + self.bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta")) + self.irbe_cert = rpki.x509.X509(Auto_file = cfg.get("irbe-cert")) + self.pubd_cert = rpki.x509.X509(Auto_file = cfg.get("pubd-cert")) + self.pubd_key = rpki.x509.RSA( Auto_file = cfg.get("pubd-key")) + + self.https_server_host = cfg.get("server-host", "") + self.https_server_port = int(cfg.get("server-port", "4434")) + + self.publication_base = cfg.get("publication-base", "publication/") + + self.sql_cache = {} + self.sql_dirty = set() + + def handler_common(self, query, client_id, peer_certs): + """Common code for publication PDU processing.""" + q_msg = rpki.publication.cms_msg.unwrap(query, peer_certs) + if q_msg.type != "query": + raise rpki.exceptions.BadQuery, "Message type is not query" + r_msg = q_msg.serve_top_level(self, client_id) + reply = rpki.publication.cms_msg.wrap(r_msg, self.pubd_key, self.pubd_cert) + self.sql_sweep() + return reply + + def control_handler(self, query, path): + """Process one PDU from the IRBE.""" + rpki.log.trace() + try: + return 200, self.handler_common(query, None, (self.bpki_ta, self.irbe_cert)) + except Exception, data: + rpki.log.error(traceback.format_exc()) + return 500, "Unhandled exception %s" % data + + def client_handler(self, query, path): + """Process one PDU from a client.""" + rpki.log.trace() + try: + client_id = path.partition("/client/")[2] + if not client_id.isdigit(): + raise rpki.exceptions.BadContactURL, "Bad path: %s" % path + client = rpki.publication.client_elt.sql_fetch(self, long(client_id)) + if client is None: + raise rpki.exceptions.ClientNotFound, "Could not find client %s" % client_id + return 200, self.handler_common(query, client_id, (client.bpki_ta, client.irbe_cert)) + except Exception, data: + rpki.log.error(traceback.format_exc()) + return 500, "Could not process PDU: %s" % data + + def build_https_ta_cache(self): + """Build dynamic TLS trust anchors.""" + if self.https_ta_cache is None: + clients = rpki.publication.client_elt.sql_fetch_all(self) + self.https_ta_cache = rpki.https.build_https_ta_cache( + [c.bpki_cert for c in clients if c.bpki_cert is not None] + + [c.bpki_glue for c in clients if c.bpki_glue is not None] + + [self.irbe_cert, self.bpki_ta]) + return self.https_ta_cache + +os.environ["TZ"] = "UTC" +time.tzset() + +rpki.log.init("pubd") + +cfg_file = "pubd.conf" + +opts,argv = getopt.getopt(sys.argv[1:], "c:h?", ["config=", "help"]) +for o,a in opts: + if o in ("-h", "--help", "-?"): + print __doc__ + sys.exit(0) + if o in ("-c", "--config"): + cfg_file = a +if argv: + raise RuntimeError, "Unexpected arguments %s" % argv + +cfg = rpki.config.parser(cfg_file, "pubd") + +startup_msg = cfg.get("startup-message", "") +if startup_msg: + rpki.log.info(startup_msg) + +pctx = pubd_context(cfg) + +rpki.https.server(host = pctx.https_server_host, + port = pctx.https_server_port, + server_key = pctx.pubd_key, + server_cert = pctx.pubd_cert, + dynamic_x509store = pctx.build_x509store, + handlers = (("/control", pctx.control_handler), + ("/client/", pctx.client_handler))) diff --git a/rpkid/pubd.sql b/rpkid/pubd.sql new file mode 100644 index 00000000..d8a9938c --- /dev/null +++ b/rpkid/pubd.sql @@ -0,0 +1,31 @@ +-- $Id$ + +-- Copyright (C) 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. + +-- SQL objects needed by pubd.py. + +DROP TABLE IF EXISTS client; + +CREATE TABLE client ( + client_id SERIAL NOT NULL, + base_uri TEXT, + bpki_cert LONGBLOB, + bpki_glue LONGBLOB, + PRIMARY KEY (client_id) +); + +-- Local Variables: +-- indent-tabs-mode: nil +-- End: diff --git a/rpkid/rpki/gctx.py b/rpkid/rpki/gctx.py index 9ae896c5..3ecd46aa 100644 --- a/rpkid/rpki/gctx.py +++ b/rpkid/rpki/gctx.py @@ -23,9 +23,6 @@ import traceback, os, time, getopt, sys, MySQLdb, lxml.etree import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log -# This should be wrapped somewhere in rpki.x509 eventually -import POW - class global_context(object): """A container for various global parameters.""" @@ -157,34 +154,25 @@ class global_context(object): https_ta_cache = None def clear_https_ta_cache(self): - """Clear cached HTTPS trust anchor X509Store.""" + """Clear dynamic TLS trust anchors.""" if self.https_ta_cache is not None: rpki.log.debug("Clearing HTTPS trusted cert cache") self.https_ta_cache = None - def build_x509store(self): - """Build a dynamic x509store object. - - This probably should be refactored to do the real work in the - rpki.https module so that this module can treat the x509store as a - black box. This method's jobs would then be just to identify - certs that need to be added and to cache an opaque object. - """ + def build_https_ta_cache(self): + """Build dynamic TLS trust anchors.""" if self.https_ta_cache is None: - store = POW.X509Store() + selves = rpki.left_right.self_elt.sql_fetch_all(self) children = rpki.left_right.child_elt.sql_fetch_all(self) - certs = [c.bpki_cert for c in children if c.bpki_cert is not None] + \ - [c.bpki_glue for c in children if c.bpki_glue is not None] + \ - [s.bpki_cert for s in selves if s.bpki_cert is not None] + \ - [s.bpki_glue for s in selves if s.bpki_glue is not None] + \ - [self.irbe_cert, self.irdb_cert, self.bpki_ta] - for x in certs: - if rpki.https.debug_tls_certs: - rpki.log.debug("HTTPS dynamic trusted cert issuer %s [%s] subject %s [%s]" % (x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) - store.addTrust(x.get_POW()) - self.https_ta_cache = store + + self.https_ta_cache = rpki.https.build_https_ta_cache( + [c.bpki_cert for c in children if c.bpki_cert is not None] + + [c.bpki_glue for c in children if c.bpki_glue is not None] + + [s.bpki_cert for s in selves if s.bpki_cert is not None] + + [s.bpki_glue for s in selves if s.bpki_glue is not None] + + [self.irbe_cert, self.irdb_cert, self.bpki_ta]) return self.https_ta_cache diff --git a/rpkid/rpki/https.py b/rpkid/rpki/https.py index 45f0954b..c47d22a5 100644 --- a/rpkid/rpki/https.py +++ b/rpkid/rpki/https.py @@ -42,6 +42,16 @@ def tlslite_certChain(x509): else: return tlslite.api.X509CertChain([x.get_tlslite() for x in x509]) +def build_https_ta_cache(certs): + """Build a dynamic TLS trust anchor cache.""" + + store = POW.X509Store() + for x in certs: + if rpki.https.debug_tls_certs: + rpki.log.debug("HTTPS dynamic trusted cert issuer %s [%s] subject %s [%s]" % (x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) + store.addTrust(x.get_POW()) + return store + class Checker(tlslite.api.Checker): """Derived class to handle X.509 client certificate checking.""" @@ -56,12 +66,12 @@ class Checker(tlslite.api.Checker): pem_dump_tls_certs = False - def __init__(self, trust_anchor = None, dynamic_x509store = None): + def __init__(self, trust_anchor = None, dynamic_https_trust_anchor = None): """Initialize our modified certificate checker.""" - self.dynamic_x509store = dynamic_x509store + self.dynamic_https_trust_anchor = dynamic_https_trust_anchor - if dynamic_x509store is not None: + if dynamic_https_trust_anchor is not None: return self.x509store = POW.X509Store() @@ -77,8 +87,8 @@ class Checker(tlslite.api.Checker): print x.get_PEM() def x509store_thunk(self): - if self.dynamic_x509store is not None: - return self.dynamic_x509store() + if self.dynamic_https_trust_anchor is not None: + return self.dynamic_https_trust_anchor() else: return self.x509store @@ -253,7 +263,7 @@ class httpsServer(tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): rpki.log.warn("TLS handshake failure: " + str(error)) return False -def server(handlers, server_key, server_cert, port = 4433, host = "", client_ta = None, dynamic_x509store = None): +def server(handlers, server_key, server_cert, port = 4433, host = "", client_ta = None, dynamic_https_trust_anchor = None): """Run an HTTPS server and wait (forever) for connections.""" if not isinstance(handlers, (tuple, list)): @@ -267,6 +277,6 @@ def server(handlers, server_key, server_cert, port = 4433, host = "", client_ta httpd.rpki_server_key = server_key.get_tlslite() httpd.rpki_server_cert = tlslite_certChain(server_cert) httpd.rpki_sessionCache = tlslite.api.SessionCache() - httpd.rpki_checker = Checker(trust_anchor = client_ta, dynamic_x509store = dynamic_x509store) + httpd.rpki_checker = Checker(trust_anchor = client_ta, dynamic_https_trust_anchor = dynamic_https_trust_anchor) httpd.serve_forever() diff --git a/rpkid/rpki/publication.py b/rpkid/rpki/publication.py index fa5b4874..43235d63 100644 --- a/rpkid/rpki/publication.py +++ b/rpkid/rpki/publication.py @@ -34,21 +34,31 @@ publication_nsmap = { None : publication_xmlns } class data_elt(rpki.left_right.base_elt): """Virtual class for top-level publication protocol data elements. - This is a placeholder. It will probably end up being a mixin that - uses rpki.sql.sql_persistant, just like its counterpart in + This is a placeholder. It may end up being a mixin that uses + rpki.sql.sql_persistant, just like its counterpart in rpki.left_right, but wait and see. """ xmlns = publication_xmlns nsmap = publication_nsmap -class client_elt(data_elt): - """<client/> element.""" +class client_elt(rpki.left_right.data_elt): + """<client/> element. + + This reuses the rpki.left-right.data_elt class because its structure + is identical to that used in the left-right protocol. + """ + + xmlns = publication_xmlns + nsmap = publication_nsmap element_name = "client" attributes = ("action", "tag", "client_id", "base_uri") elements = ("bpki_cert", "bpki_glue") + sql_template = rpki.sql.template("client", "client_id", "base_uri", ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) + + base_uri = None bpki_cert = None bpki_glue = None @@ -79,6 +89,30 @@ class client_elt(data_elt): self.make_b64elt(elt, "bpki_glue", self.bpki_glue.get_DER()) return elt + def serve_fetch_one(self): + """Find the client object on which a get, set, or destroy method + should operate. + """ + r = self.sql_fetch(self.gctx, self.client_id) + if r is None: + raise rpki.exceptions.NotFound + return r + + def serve_list(self, r_msg): + """Handle a list action for client objects.""" + for r_pdu in self.sql_fetch_all(self.gctx): + self.make_reply(r_pdu) + r_msg.append(r_pdu) + + def make_reply(self, r_pdu = None): + """Construct a reply PDU.""" + if r_pdu is None: + r_pdu = client_elt() + r_pdu.client_id = self.client_id + r_pdu.action = self.action + r_pdu.tag = self.tag + return r_pdu + class publication_object_elt(data_elt): """Virtual class for publishable objects. These have very similar syntax, differences lie in underlying datatype and methods. diff --git a/rpkid/rpkid.pdf b/rpkid/rpkid.pdf Binary files differindex 3a2dd280..707dbb63 100644 --- a/rpkid/rpkid.pdf +++ b/rpkid/rpkid.pdf diff --git a/rpkid/rpkid.py b/rpkid/rpkid.py index 0668a24f..4b8fb998 100755 --- a/rpkid/rpkid.py +++ b/rpkid/rpkid.py @@ -52,11 +52,11 @@ if startup_msg: gctx = rpki.gctx.global_context(cfg) -rpki.https.server(host = gctx.https_server_host, - port = gctx.https_server_port, - server_key = gctx.rpkid_key, - server_cert = gctx.rpkid_cert, - dynamic_x509store = gctx.build_x509store, - handlers = (("/left-right", gctx.left_right_handler), - ("/up-down/", gctx.up_down_handler), - ("/cronjob", gctx.cronjob_handler))) +rpki.https.server(host = gctx.https_server_host, + port = gctx.https_server_port, + server_key = gctx.rpkid_key, + server_cert = gctx.rpkid_cert, + dynamic_https_trust_anchor = gctx.build_https_ta_cache, + handlers = (("/left-right", gctx.left_right_handler), + ("/up-down/", gctx.up_down_handler), + ("/cronjob", gctx.cronjob_handler))) |