diff options
-rw-r--r-- | rpkid/irdbd.sql | 8 | ||||
-rw-r--r-- | rpkid/left-right-schema.rnc | 10 | ||||
-rw-r--r-- | rpkid/left-right-schema.rng | 14 | ||||
-rw-r--r-- | rpkid/publication-schema.rnc | 11 | ||||
-rw-r--r-- | rpkid/publication-schema.rng | 48 | ||||
-rw-r--r-- | rpkid/rpki/irdbd.py | 13 | ||||
-rw-r--r-- | rpkid/rpki/left_right.py | 159 | ||||
-rw-r--r-- | rpkid/rpki/publication.py | 16 | ||||
-rw-r--r-- | rpkid/rpki/relaxng.py | 62 | ||||
-rw-r--r-- | rpkid/rpki/rpkid.py | 219 | ||||
-rw-r--r-- | rpkid/rpki/up_down.py | 4 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 8 | ||||
-rw-r--r-- | rpkid/rpkid.sql | 3 |
13 files changed, 453 insertions, 122 deletions
diff --git a/rpkid/irdbd.sql b/rpkid/irdbd.sql index fd461b42..bf324cd8 100644 --- a/rpkid/irdbd.sql +++ b/rpkid/irdbd.sql @@ -41,7 +41,7 @@ DROP TABLE IF EXISTS roa_request; DROP TABLE IF EXISTS registrant_net; DROP TABLE IF EXISTS registrant_asn; DROP TABLE IF EXISTS registrant; -DROP TABLE IF EXISTS gbr_request; +DROP TABLE IF EXISTS ghostbuster_request; CREATE TABLE registrant ( registrant_id SERIAL NOT NULL, @@ -92,12 +92,12 @@ CREATE TABLE roa_request_prefix ( FOREIGN KEY (roa_request_id) REFERENCES roa_request (roa_request_id) ON DELETE CASCADE ) ENGINE=InnoDB; -CREATE TABLE gbr_request ( - gbr_request_id SERIAL NOT NULL, +CREATE TABLE ghostbuster_request ( + ghostbuster_request_id SERIAL NOT NULL, self_handle VARCHAR(40) NOT NULL, parent_handle VARCHAR(40), vcard LONGBLOB NOT NULL, - PRIMARY KEY (gbr_request_id) + PRIMARY KEY (ghostbuster_request_id) ) ENGINE=InnoDB; -- Local Variables: diff --git a/rpkid/left-right-schema.rnc b/rpkid/left-right-schema.rnc index 85f2348a..3b058502 100644 --- a/rpkid/left-right-schema.rnc +++ b/rpkid/left-right-schema.rnc @@ -50,7 +50,7 @@ query_elt |= parent_query query_elt |= child_query query_elt |= repository_query query_elt |= list_roa_requests_query -query_elt |= list_gbr_requests_query +query_elt |= list_ghostbuster_requests_query query_elt |= list_resources_query query_elt |= list_published_objects_query query_elt |= list_received_resources_query @@ -63,7 +63,7 @@ reply_elt |= child_reply reply_elt |= repository_reply reply_elt |= list_resources_reply reply_elt |= list_roa_requests_reply -reply_elt |= list_gbr_requests_reply +reply_elt |= list_ghostbuster_requests_reply reply_elt |= list_published_objects_reply reply_elt |= list_received_resources_reply reply_elt |= report_error_reply @@ -252,13 +252,13 @@ list_roa_requests_reply = element list_roa_requests { attribute ipv6 { ipv6_list }? } -# <list_gbr_requests/> element +# <list_ghostbuster_requests/> element -list_gbr_requests_query = element list_gbr_requests { +list_ghostbuster_requests_query = element list_ghostbuster_requests { tag, self_handle, parent_handle } -list_gbr_requests_reply = element list_gbr_requests { +list_ghostbuster_requests_reply = element list_ghostbuster_requests { tag, self_handle, parent_handle, xsd:string } diff --git a/rpkid/left-right-schema.rng b/rpkid/left-right-schema.rng index fe441006..2af25a10 100644 --- a/rpkid/left-right-schema.rng +++ b/rpkid/left-right-schema.rng @@ -84,7 +84,7 @@ <ref name="list_roa_requests_query"/> </define> <define name="query_elt" combine="choice"> - <ref name="list_gbr_requests_query"/> + <ref name="list_ghostbuster_requests_query"/> </define> <define name="query_elt" combine="choice"> <ref name="list_resources_query"/> @@ -118,7 +118,7 @@ <ref name="list_roa_requests_reply"/> </define> <define name="reply_elt" combine="choice"> - <ref name="list_gbr_requests_reply"/> + <ref name="list_ghostbuster_requests_reply"/> </define> <define name="reply_elt" combine="choice"> <ref name="list_published_objects_reply"/> @@ -889,16 +889,16 @@ </optional> </element> </define> - <!-- <list_gbr_requests/> element --> - <define name="list_gbr_requests_query"> - <element name="list_gbr_requests"> + <!-- <list_ghostbuster_requests/> element --> + <define name="list_ghostbuster_requests_query"> + <element name="list_ghostbuster_requests"> <ref name="tag"/> <ref name="self_handle"/> <ref name="parent_handle"/> </element> </define> - <define name="list_gbr_requests_reply"> - <element name="list_gbr_requests"> + <define name="list_ghostbuster_requests_reply"> + <element name="list_ghostbuster_requests"> <ref name="tag"/> <ref name="self_handle"/> <ref name="parent_handle"/> diff --git a/rpkid/publication-schema.rnc b/rpkid/publication-schema.rnc index 5df646d1..18dd400e 100644 --- a/rpkid/publication-schema.rnc +++ b/rpkid/publication-schema.rnc @@ -44,10 +44,10 @@ start = element msg { } # PDUs allowed in a query -query_elt = ( config_query | client_query | certificate_query | crl_query | manifest_query | roa_query ) +query_elt = ( config_query | client_query | certificate_query | crl_query | manifest_query | roa_query | ghostbuster_query ) # PDUs allowed in a reply -reply_elt = ( config_reply | client_reply | certificate_reply | crl_reply | manifest_reply | roa_reply | report_error_reply ) +reply_elt = ( config_reply | client_reply | certificate_reply | crl_reply | manifest_reply | roa_reply | ghostbuster_reply | report_error_reply ) # Tag attributes for bulk operations tag = attribute tag { xsd:token {maxLength="1024" } } @@ -123,6 +123,13 @@ roa_reply |= element roa { attribute action { "publish" }, tag?, uri } roa_query |= element roa { attribute action { "withdraw" }, tag?, uri } roa_reply |= element roa { attribute action { "withdraw" }, tag?, uri } +# <ghostbuster/> element + +ghostbuster_query |= element ghostbuster { attribute action { "publish" }, tag?, uri, base64 } +ghostbuster_reply |= element ghostbuster { attribute action { "publish" }, tag?, uri } +ghostbuster_query |= element ghostbuster { attribute action { "withdraw" }, tag?, uri } +ghostbuster_reply |= element ghostbuster { attribute action { "withdraw" }, tag?, uri } + # <report_error/> element error = xsd:token { maxLength="1024" } diff --git a/rpkid/publication-schema.rng b/rpkid/publication-schema.rng index 9dc8b751..7544d5eb 100644 --- a/rpkid/publication-schema.rng +++ b/rpkid/publication-schema.rng @@ -73,6 +73,7 @@ <ref name="crl_query"/> <ref name="manifest_query"/> <ref name="roa_query"/> + <ref name="ghostbuster_query"/> </choice> </define> <!-- PDUs allowed in a reply --> @@ -84,6 +85,7 @@ <ref name="crl_reply"/> <ref name="manifest_reply"/> <ref name="roa_reply"/> + <ref name="ghostbuster_reply"/> <ref name="report_error_reply"/> </choice> </define> @@ -500,6 +502,52 @@ <ref name="uri"/> </element> </define> + <!-- <ghostbuster/> element --> + <define name="ghostbuster_query" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>publish</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + <ref name="base64"/> + </element> + </define> + <define name="ghostbuster_reply" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>publish</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <define name="ghostbuster_query" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>withdraw</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <define name="ghostbuster_reply" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>withdraw</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> <!-- <report_error/> element --> <define name="error"> <data type="token"> diff --git a/rpkid/rpki/irdbd.py b/rpkid/rpki/irdbd.py index bb05fee3..a5bf2663 100644 --- a/rpkid/rpki/irdbd.py +++ b/rpkid/rpki/irdbd.py @@ -115,10 +115,10 @@ class main(object): r_msg.append(r_pdu) - def handle_list_gbr_requests(self, q_pdu, r_msg): + def handle_list_ghostbuster_requests(self, q_pdu, r_msg): self.cur.execute( - "SELECT vcard FROM gbr_request self_handle = %s and parent_handle = %s", + "SELECT vcard FROM ghostbuster_request WHERE self_handle = %s AND parent_handle = %s", (q_pdu.self_handle, q_pdu.parent_handle)) vcards = [result[0] for result in self.cur.fetchall()] @@ -126,13 +126,13 @@ class main(object): if not vcards: self.cur.execute( - "SELECT vcard FROM gbr_request self_handle = %s and parent_handle IS NULL", + "SELECT vcard FROM ghostbuster_request WHERE self_handle = %s AND parent_handle IS NULL", (q_pdu.self_handle,)) vcards = [result[0] for result in self.cur.fetchall()] for vcard in vcards: - r_pdu = rpki.left_right.list_gbr_requests_elt() + r_pdu = rpki.left_right.list_ghostbuster_requests_elt() r_pdu.tag = q_pdu.tag r_pdu.self_handle = q_pdu.self_handle r_pdu.parent_handle = q_pdu.parent_handle @@ -141,8 +141,9 @@ class main(object): handle_dispatch = { - rpki.left_right.list_resources_elt : handle_list_resources, - rpki.left_right.list_roa_requests_elt : handle_list_roa_requests } + rpki.left_right.list_resources_elt : handle_list_resources, + rpki.left_right.list_roa_requests_elt : handle_list_roa_requests, + rpki.left_right.list_ghostbuster_requests_elt : handle_list_ghostbuster_requests} def handler(self, query, path, cb): diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index f46e6dde..2c61b5ac 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -183,6 +183,13 @@ class self_elt(data_elt): """ return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) + @property + def ghostbusters(self): + """ + Fetch all Ghostbuster record objects that link to this self object. + """ + return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) + def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ Extra server actions for self_elt. @@ -263,12 +270,13 @@ class self_elt(data_elt): def loop(iterator, parent): q_msg = rpki.publication.msg.query() for ca in parent.cas: - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is not None: q_msg.append(rpki.publication.crl_elt.make_publish(ca_detail.crl_uri, ca_detail.latest_crl)) q_msg.append(rpki.publication.manifest_elt.make_publish(ca_detail.manifest_uri, ca_detail.latest_manifest)) q_msg.extend(rpki.publication.certificate_elt.make_publish(c.uri, c.cert) for c in ca_detail.child_certs) q_msg.extend(rpki.publication.roa_elt.make_publish(r.uri, r.roa) for r in ca_detail.roas if r.roa is not None) + q_msg.extend(rpki.publication.ghostbusters_elt.make_publish(g.uri, g.ghostbuster) for g in ca_detail.ghostbusters) parent.repository.call_pubd(iterator, eb, q_msg) rpki.async.iterator(self.parents, loop, cb) @@ -324,6 +332,11 @@ class self_elt(data_elt): def four(): self.gctx.checkpoint() + rpki.log.debug("Self %s[%d] updating Ghostbuster records" % (self.self_handle, self.self_id)) + self.update_ghostbusters(five) + + def five(): + self.gctx.checkpoint() rpki.log.debug("Self %s[%d] regenerating CRLs and manifests" % (self.self_handle, self.self_id)) self.regenerate_crls_and_manifests(cb) @@ -485,10 +498,10 @@ class self_elt(data_elt): for parent in self.parents: for ca in parent.cas: try: - for ca_detail in ca.fetch_revoked(): + for ca_detail in ca.revoked_ca_details: if now > ca_detail.latest_crl.getNextUpdate(): ca_detail.delete(ca = ca, publisher = publisher) - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is not None and now + regen_margin > ca_detail.latest_crl.getNextUpdate(): ca_detail.generate_crl(publisher = publisher) ca_detail.generate_manifest(publisher = publisher) @@ -510,86 +523,91 @@ class self_elt(data_elt): def update_ghostbusters(self, cb): """ - Generate or update Ghostbusters records for this self. + Generate or update Ghostbuster records for this self. This is heavily based on .update_roas(), and probably both of them need refactoring. """ - raise rpki.exceptions.NotImplementedYet - parents = dict((p.parent_handle, p) for p in self.parents) - def got_gbr_requests(gbr_requests): - - self.gctx.checkpoint() + def got_ghostbuster_requests(ghostbuster_requests): - if self.gctx.sql.dirty: - rpki.log.warn("Unexpected dirty SQL cache, flushing") - self.gctx.sql.sweep() + try: + self.gctx.checkpoint() + if self.gctx.sql.dirty: + rpki.log.warn("Unexpected dirty SQL cache, flushing") + self.gctx.sql.sweep() + + ghostbusters = {} + orphans = [] + for ghostbuster in self.ghostbusters: + k = (ghostbuster.ca_detail_id, ghostbuster.vcard) + if ghostbuster.ca_detail.state != "active" or k in ghostbusters: + orphans.append(ghostbuster) + else: + ghostbusters[k] = ghostbuster - ghostbusters = {} - orphans = [] - for ghostbuster in self.ghostbusters: - k = (ghostbuster.ca_detail.ca.parent.parent_handle, ghostbuster.vcard) - if k not in ghostbusters: - ghostbusters[k] = ghostbuster - elif ghostbuster.ca_detail.state == "active" and ghostbusters[k].ca_detail.state != "active": - orphans.append(ghostbusters[k]) - ghostbusters[k] = ghostbuster - else: - orphans.append(ghostbusters[k]) + publisher = rpki.rpkid.publication_queue() + ca_details = set() - publisher = rpki.rpkid.publication_queue() - ca_details = set() - - seen = set() - for gbr_request in gbr_requests: - if gbr_request.parent_handle not in parents: - rpki.log.warn("Unknown parent_handle %r in Ghostbuster request, skipping" % gbr_request.parent_handle) - continue - k = (gbr_request.parent_handle, gbr_request.vcard) - if k in seen: - rpki.log.warn("Skipping duplicate Ghostbuster request %r" % gbr_request) - continue - see.add(k) - ghostbuster = ghostbusters.pop(k, None) - if ghostbuster is None: - ghostbuster = rpki.rpkid.ghostbuster_obj(self.gctx, self.self_id, parents[gbr_request.parent_handle], vcard) - rpki.log.debug("Created new Ghostbuster request for %r" % gbr_request.parent_handle) - else: - rpki.log.debug("Found existing Ghostbuster request for %r" % gbr_request.parent_handle) - ghostbuster.update(publisher = publisher, fast = True) - ca_details.add(ghostbuster.ca_detail) + seen = set() + for ghostbuster_request in ghostbuster_requests: + if ghostbuster_request.parent_handle not in parents: + rpki.log.warn("Unknown parent_handle %r in Ghostbuster request, skipping" % ghostbuster_request.parent_handle) + continue + k = (ghostbuster_request.parent_handle, ghostbuster_request.vcard) + if k in seen: + rpki.log.warn("Skipping duplicate Ghostbuster request %r" % ghostbuster_request) + continue + seen.add(k) + for ca in parents[ghostbuster_request.parent_handle].cas: + ca_detail = ca.active_ca_detail + if ca_detail is not None: + ghostbuster = ghostbusters.pop((ca_detail.ca_detail_id, ghostbuster_request.vcard), None) + if ghostbuster is None: + ghostbuster = rpki.rpkid.ghostbuster_obj(self.gctx, self.self_id, ca_detail.ca_detail_id, ghostbuster_request.vcard) + rpki.log.debug("Created new Ghostbuster request for %r" % ghostbuster_request.parent_handle) + else: + rpki.log.debug("Found existing Ghostbuster request for %r" % ghostbuster_request.parent_handle) + ghostbuster.update(publisher = publisher, fast = True) + ca_details.add(ca_detail) + + orphans.extend(ghostbusters.itervalues()) + for ghostbuster in orphans: + ca_details.add(ghostbuster.ca_detail) + ghostbuster.revoke(publisher = publisher, fast = True) + + for ca_detail in ca_details: + ca_detail.generate_crl(publisher = publisher) + ca_detail.generate_manifest(publisher = publisher) - orphans.extend(ghostbusters.itervalues()) - for ghostbuster in orphans: - ca_details.add(ghostbuster.ca_detail) - ghostbuster.revoke(publisher = publisher, fast = True) + self.gctx.sql.sweep() - for ca_detail in ca_details: - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) + def publication_failed(e): + rpki.log.traceback() + rpki.log.warn("Couldn't publish Ghostbuster updates for %s, skipping: %s" % (self.self_handle, e)) + self.gctx.checkpoint() + cb() - self.gctx.sql.sweep() + self.gctx.checkpoint() + publisher.call_pubd(cb, publication_failed) - def publication_failed(e): + except (SystemExit, rpki.async.ExitNow): + raise + except Exception, e: rpki.log.traceback() - rpki.log.warn("Couldn't publish Ghostbuster updates for %s, skipping: %s" % (self.self_handle, e)) - self.gctx.checkpoint() + rpki.log.warn("Could not update Ghostbuster records for %s, skipping: %s" % (self.self_handle, e)) cb() - self.gctx.checkpoint() - publisher.call_pubd(cb, publication_failed) - - def gbr_requests_failed(e): + def ghostbuster_requests_failed(e): rpki.log.traceback() - rpki.log.warn("Could not fetch Ghostbusters record requests for %s, skipping: %s" % (self.self_handle, e)) + rpki.log.warn("Could not fetch Ghostbuster record requests for %s, skipping: %s" % (self.self_handle, e)) cb() self.gctx.checkpoint() - self.gctx.irdb_query_gbr_requests(self.self_handle, parents.iterkeys(), - got_gbr_requests, gbr_requests_failed) + self.gctx.irdb_query_ghostbuster_requests(self.self_handle, parents.iterkeys(), + got_ghostbuster_requests, ghostbuster_requests_failed) def update_roas(self, cb): @@ -933,7 +951,7 @@ class parent_elt(data_elt): ca = ca_map[rc.class_name] skis_parent_knows_about = set(c.cert.gSKI() for c in rc.certs) - skis_ca_knows_about = set(ca_detail.latest_ca_cert.gSKI() for ca_detail in ca.fetch_issue_response_candidates()) + skis_ca_knows_about = set(ca_detail.latest_ca_cert.gSKI() for ca_detail in ca.issue_response_candidate_ca_details) skis_only_parent_knows_about = skis_parent_knows_about - skis_ca_knows_about rpki.async.iterator(skis_only_parent_knows_about, ski_loop, rc_iterator) @@ -1166,12 +1184,12 @@ class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): if self.ipv6 is not None: self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6) -class list_gbr_requests_elt(rpki.xml_utils.text_elt, left_right_namespace): +class list_ghostbuster_requests_elt(rpki.xml_utils.text_elt, left_right_namespace): """ - <list_gbr_requests/> element. + <list_ghostbuster_requests/> element. """ - element_name = "list_gbr_requests" + element_name = "list_ghostbuster_requests" attributes = ("self_handle", "tag", "parent_handle") text_attribute = "vcard" @@ -1195,14 +1213,15 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): misnomer here, there's no action attribute and no dispatch, we just dump every published object for the specified <self/> and return. """ - for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: + for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: for ca in parent.cas: - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is not None: r_msg.append(self.make_reply(ca_detail.crl_uri, ca_detail.latest_crl)) r_msg.append(self.make_reply(ca_detail.manifest_uri, ca_detail.latest_manifest)) r_msg.extend(self.make_reply(c.uri, c.cert) for c in ca_detail.child_certs) r_msg.extend(self.make_reply(r.uri, r.roa) for r in ca_detail.roas if r.roa is not None) + r_msg.extend(self.make_reply(g.uri, g.ghostbuster) for g in ca_detail.ghostbusters) cb() def make_reply(self, uri, obj): @@ -1231,7 +1250,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace) """ for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: for ca in parent.cas: - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is not None and ca_detail.latest_ca_cert is not None: r_msg.append(self.make_reply(parent.parent_handle, ca_detail.ca_cert_uri, ca_detail.latest_ca_cert)) cb() @@ -1291,7 +1310,7 @@ class msg(rpki.xml_utils.msg, left_right_namespace): pdus = dict((x.element_name, x) for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt, list_resources_elt, - list_roa_requests_elt, list_gbr_requests_elt, + list_roa_requests_elt, list_ghostbuster_requests_elt, list_published_objects_elt, list_received_resources_elt, report_error_elt)) diff --git a/rpkid/rpki/publication.py b/rpkid/rpki/publication.py index 4c6df14b..51baf505 100644 --- a/rpkid/rpki/publication.py +++ b/rpkid/rpki/publication.py @@ -300,7 +300,17 @@ class roa_elt(publication_object_elt): element_name = "roa" payload_type = rpki.x509.ROA -publication_object_elt.obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt)) +class ghostbuster_elt(publication_object_elt): + """ + <ghostbuster/> element. + """ + + element_name = "ghostbuster" + payload_type = rpki.x509.Ghostbuster + +publication_object_elt.obj2elt = dict( + (e.payload_type, e) for e in + (certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt)) class report_error_elt(rpki.xml_utils.text_elt, publication_namespace): """ @@ -354,8 +364,8 @@ class msg(rpki.xml_utils.msg, publication_namespace): ## @var pdus # Dispatch table of PDUs for this protocol. - pdus = dict((x.element_name, x) - for x in (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt)) + pdus = dict((x.element_name, x) for x in + (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt, report_error_elt)) def serve_top_level(self, gctx, client, cb): """ diff --git a/rpkid/rpki/relaxng.py b/rpkid/rpki/relaxng.py index 4a357246..cb6654b6 100644 --- a/rpkid/rpki/relaxng.py +++ b/rpkid/rpki/relaxng.py @@ -90,7 +90,7 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" enc <ref name="list_roa_requests_query"/> </define> <define name="query_elt" combine="choice"> - <ref name="list_gbr_requests_query"/> + <ref name="list_ghostbuster_requests_query"/> </define> <define name="query_elt" combine="choice"> <ref name="list_resources_query"/> @@ -124,7 +124,7 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" enc <ref name="list_roa_requests_reply"/> </define> <define name="reply_elt" combine="choice"> - <ref name="list_gbr_requests_reply"/> + <ref name="list_ghostbuster_requests_reply"/> </define> <define name="reply_elt" combine="choice"> <ref name="list_published_objects_reply"/> @@ -895,16 +895,16 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" enc </optional> </element> </define> - <!-- <list_gbr_requests/> element --> - <define name="list_gbr_requests_query"> - <element name="list_gbr_requests"> + <!-- <list_ghostbuster_requests/> element --> + <define name="list_ghostbuster_requests_query"> + <element name="list_ghostbuster_requests"> <ref name="tag"/> <ref name="self_handle"/> <ref name="parent_handle"/> </element> </define> - <define name="list_gbr_requests_reply"> - <element name="list_gbr_requests"> + <define name="list_ghostbuster_requests_reply"> + <element name="list_ghostbuster_requests"> <ref name="tag"/> <ref name="self_handle"/> <ref name="parent_handle"/> @@ -1337,6 +1337,7 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <ref name="crl_query"/> <ref name="manifest_query"/> <ref name="roa_query"/> + <ref name="ghostbuster_query"/> </choice> </define> <!-- PDUs allowed in a reply --> @@ -1348,6 +1349,7 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <ref name="crl_reply"/> <ref name="manifest_reply"/> <ref name="roa_reply"/> + <ref name="ghostbuster_reply"/> <ref name="report_error_reply"/> </choice> </define> @@ -1764,6 +1766,52 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <ref name="uri"/> </element> </define> + <!-- <ghostbuster/> element --> + <define name="ghostbuster_query" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>publish</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + <ref name="base64"/> + </element> + </define> + <define name="ghostbuster_reply" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>publish</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <define name="ghostbuster_query" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>withdraw</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <define name="ghostbuster_reply" combine="choice"> + <element name="ghostbuster"> + <attribute name="action"> + <value>withdraw</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> <!-- <report_error/> element --> <define name="error"> <data type="token"> diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py index 5a9da606..f5fe6b7b 100644 --- a/rpkid/rpki/rpkid.py +++ b/rpkid/rpki/rpkid.py @@ -203,7 +203,7 @@ class main(object): self.irdb_query(callback, errback, q_pdu) - def irdb_query_gbr_requests(self, self_handle, parent_handles, callback, errback): + def irdb_query_ghostbuster_requests(self, self_handle, parent_handles, callback, errback): """ Ask IRDB about self's ghostbuster record requests. """ @@ -213,7 +213,7 @@ class main(object): q_pdus = [] for parent_handle in parent_handles: - q_pdu = rpki.left_right.list_gbr_requests_elt() + q_pdu = rpki.left_right.list_ghostbuster_requests_elt() q_pdu.self_handle = self_handle q_pdu.parent_handle = parent_handle q_pdus.append(q_pdu) @@ -381,31 +381,36 @@ class ca_obj(rpki.sql.sql_persistent): """ return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,)) - def fetch_pending(self): + @property + def pending_ca_details(self): """ Fetch the pending ca_details for this CA, if any. """ return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,)) - def fetch_active(self): + @property + def active_ca_detail(self): """ Fetch the active ca_detail for this CA, if any. """ return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,)) - def fetch_deprecated(self): + @property + def deprecated_ca_details(self): """ Fetch deprecated ca_details for this CA, if any. """ return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,)) - def fetch_revoked(self): + @property + def revoked_ca_details(self): """ Fetch revoked ca_details for this CA, if any. """ return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,)) - def fetch_issue_response_candidates(self): + @property + def issue_response_candidate_ca_details(self): """ Fetch ca_details which are candidates for consideration when processing an up-down issue_response PDU. @@ -489,7 +494,7 @@ class ca_obj(rpki.sql.sql_persistent): self.gctx.checkpoint() cb() - ca_details = self.fetch_issue_response_candidates() + ca_details = self.issue_response_candidate_ca_details if True: for x in cert_map.itervalues(): @@ -591,7 +596,7 @@ class ca_obj(rpki.sql.sql_persistent): rpki.log.trace() parent = self.parent - old_detail = self.fetch_active() + old_detail = self.active_ca_detail new_detail = ca_detail_obj.create(self) def done(issue_response): @@ -615,14 +620,14 @@ class ca_obj(rpki.sql.sql_persistent): def loop(iterator, ca_detail): ca_detail.revoke(cb = iterator, eb = eb) - rpki.async.iterator(self.fetch_deprecated(), loop, cb) + rpki.async.iterator(self.deprecated_ca_details, loop, cb) def reissue(self, cb, eb): """ Reissue all current certificates issued by this CA. """ - ca_detail = self.fetch_active() + ca_detail = self.active_ca_detail if ca_detail: ca_detail.reissue(cb, eb) else: @@ -697,6 +702,13 @@ class ca_detail_obj(rpki.sql.sql_persistent): return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) @property + def ghostbusters(self): + """ + Fetch all Ghostbuster objects that link to this ca_detail. + """ + return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) + + @property def crl_uri(self): """ Return publication URI for this ca_detail's CRL. @@ -740,6 +752,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): for roa in predecessor.roas: roa.regenerate(publisher = publisher) + # Need to do something to regenerate ghostbusters here? + publisher.call_pubd(callback, errback) def delete(self, ca, publisher, allow_failure = False): @@ -756,6 +770,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): handler = False if allow_failure else None) for roa in self.roas: roa.revoke(publisher = publisher, allow_failure = allow_failure) + for ghostbuster in self.ghostbusters: + ghostbuster.revoke(publisher = publisher, allow_failure = allow_failure) try: latest_manifest = self.latest_manifest except AttributeError: @@ -825,6 +841,14 @@ class ca_detail_obj(rpki.sql.sql_persistent): nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter()) child_cert.revoke(publisher = publisher) + for roa in self.roas: + nextUpdate = nextUpdate.later(roa.cert.getNotAfter()) + roa.revoke(publisher = publisher) + + for ghostbuster in self.ghostbusters: + nextUpdate = nextUpdate.later(ghostbuster.cert.getNotAfter()) + ghostbuster.revoke(publisher = publisher) + nextUpdate += crl_interval self.generate_crl(publisher = publisher, nextUpdate = nextUpdate) self.generate_manifest(publisher = publisher, nextUpdate = nextUpdate) @@ -1013,6 +1037,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): objs = [(self.crl_uri_tail, self.latest_crl)] objs.extend((c.uri_tail, c.cert) for c in self.child_certs) objs.extend((r.uri_tail, r.roa) for r in self.roas if r.roa is not None) + objs.extend((g.uri_tail, g.ghostbuster) for g in self.ghostbusters) self.latest_manifest = rpki.x509.SignedManifest.build( serial = ca.next_manifest_number(), @@ -1044,6 +1069,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): publisher = publication_queue() for roa in self.roas: roa.regenerate(publisher, fast = True) + for ghostbuster in self.ghostbusters: + ghostbuster.regenerate(publisher, fast = True) for child_cert in self.child_certs: child_cert.reissue(self, publisher, force = True) publisher.call_pubd(cb, eb) @@ -1444,7 +1471,7 @@ class roa_obj(rpki.sql.sql_persistent): ca_detail = None for parent in self.self.parents: for ca in parent.cas: - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is not None: resources = ca_detail.latest_ca_cert.get_3779resources() if v4.issubset(resources.v4) and v6.issubset(resources.v6): @@ -1551,6 +1578,174 @@ class roa_obj(rpki.sql.sql_persistent): return self.cert.gSKI() + ".roa" +class ghostbuster_obj(rpki.sql.sql_persistent): + """ + Ghostbusters record. + """ + + sql_template = rpki.sql.template( + "ghostbuster", + "ghostbuster_id", + "ca_detail_id", + "self_id", + "vcard", + ("ghostbuster", rpki.x509.Ghostbuster), + ("cert", rpki.x509.X509), + ("published", rpki.sundial.datetime)) + + ca_detail_id = None + cert = None + ghostbuster = None + published = None + vcard = None + + @property + def self(self): + """ + Fetch self object to which this ghostbuster_obj links. + """ + return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id) + + @property + def ca_detail(self): + """ + Fetch ca_detail object to which this ghostbuster_obj links. + """ + return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) + + def __init__(self, gctx = None, self_id = None, ca_detail_id = None, vcard = None): + rpki.sql.sql_persistent.__init__(self) + self.gctx = gctx + self.self_id = self_id + self.ca_detail_id = ca_detail_id + self.vcard = vcard + + # Defer marking new ghostbuster as dirty until .generate() has a chance to + # finish setup, otherwise we get SQL consistency errors. + + def update(self, publisher, fast = False): + """ + Bring this ghostbuster_obj up to date if necesssary. + """ + + if self.ghostbuster is None: + rpki.log.debug("Ghostbuster record doesn't exist, generating") + return self.generate(publisher = publisher, fast = fast) + + regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.self.regen_margin) + + if rpki.sundial.now() > regen_time: + rpki.log.debug("Ghostbuster record past threshold %s, regenerating" % (regen_time,)) + return self.regenerate(publisher = publisher, fast = fast) + + def generate(self, publisher, fast = False): + """ + Generate a Ghostbuster record + + Once we have the right covering certificate, we generate the + ghostbuster payload, generate a new EE certificate, use the EE + certificate to sign the ghostbuster payload, publish the result, + then throw away the private key for the EE cert. This is modeled + after the way we handle ROAs. + + If fast is set, we leave generating the new manifest for our + caller to handle, presumably at the end of a bulk operation. + """ + + ca_detail = self.ca_detail + ca = ca_detail.ca + + resources = rpki.resource_set.resource_bag.from_inheritance() + keypair = rpki.x509.RSA.generate() + + self.cert = ca_detail.issue_ee( + ca = ca, + resources = resources, + subject_key = keypair.get_RSApublic(), + sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.uri_from_key(keypair))),)) + self.ghostbuster = rpki.x509.Ghostbuster.build(self.vcard, keypair, (self.cert,)) + self.published = rpki.sundial.now() + self.sql_store() + + rpki.log.debug("Generating Ghostbuster record %r" % self.uri) + publisher.publish(cls = rpki.publication.ghostbuster_elt, uri = self.uri, obj = self.ghostbuster, repository = ca.parent.repository, handler = self.published_callback) + if not fast: + ca_detail.generate_manifest(publisher = publisher) + + def published_callback(self, pdu): + """ + Check publication result. + """ + pdu.raise_if_error() + self.published = None + self.sql_mark_dirty() + + def revoke(self, publisher, regenerate = False, allow_failure = False, fast = False): + """ + Withdraw Ghostbuster associated with this ghostbuster_obj. + + In order to preserve make-before-break properties without + duplicating code, this method also handles generating a + replacement ghostbuster when requested. + + If allow_failure is set, failing to withdraw the ghostbuster will not be + considered an error. + + If fast is set, SQL actions will be deferred, on the assumption + that our caller will handle regenerating CRL and manifest and + flushing the SQL cache. + """ + + ca_detail = self.ca_detail + cert = self.cert + ghostbuster = self.ghostbuster + uri = self.uri + + if regenerate: + assert ca_detail.state == 'active' + self.generate(publisher = publisher, fast = fast) + + rpki.log.debug("Withdrawing Ghostbuster record %r and revoking its EE cert" % uri) + rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) + publisher.withdraw(cls = rpki.publication.ghostbuster_elt, uri = uri, obj = ghostbuster, repository = ca_detail.ca.parent.repository, + handler = False if allow_failure else None) + self.sql_mark_deleted() + if not fast: + ca_detail.generate_crl(publisher = publisher) + ca_detail.generate_manifest(publisher = publisher) + self.gctx.sql.sweep() + + def regenerate(self, publisher, fast = False): + """ + Reissue Ghostbuster associated with this ghostbuster_obj. + """ + if self.ghostbuster is None: + self.generate(publisher = publisher, fast = fast) + else: + self.revoke(publisher = publisher, regenerate = True, fast = fast) + + def uri_from_key(self, key): + """ + Return publication URI for a public key. + """ + return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr" + + @property + def uri(self): + """ + Return the publication URI for this ghostbuster_obj's ghostbuster. + """ + return self.ca_detail.ca.sia_uri + self.uri_tail + + @property + def uri_tail(self): + """ + Return the tail (filename portion) of the publication URI for this + ghostbuster_obj's ghostbuster. + """ + return self.cert.gSKI() + ".gbr" + + class publication_queue(object): """ Utility to simplify publication from within rpkid. diff --git a/rpkid/rpki/up_down.py b/rpkid/rpki/up_down.py index ae374532..37c27983 100644 --- a/rpkid/rpki/up_down.py +++ b/rpkid/rpki/up_down.py @@ -253,7 +253,7 @@ class list_pdu(base_elt): for parent in child.parents: for ca in parent.cas: - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if not ca_detail: continue resources = ca_detail.latest_ca_cert.get_3779resources().intersection(irdb_resources) @@ -367,7 +367,7 @@ class issue_pdu(base_elt): # Check the request self.pkcs10.check_valid_rpki() ca = child.ca_from_class_name(self.class_name) - ca_detail = ca.fetch_active() + ca_detail = ca.active_ca_detail if ca_detail is None: raise rpki.exceptions.NoActiveCA, "No active CA for class %r" % self.class_name diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index 7fda419d..63939690 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -1218,9 +1218,11 @@ class XML_CMS_object(CMS_object): self.schema_check() return self.saxify(self.get_content()) -class GBR_object(CMS_object): +class Ghostbuster(CMS_object): """ - Class to hold CMS-wrapped VCard (Ghostbuster record). + Class to hold Ghostbusters record (CMS-wrapped VCard). This is + quite minimal because we treat the VCard as an opaque byte string + managed by the back-end. """ pem_converter = PEM_converter("GHOSTBUSTERS RECORD") @@ -1243,7 +1245,7 @@ class GBR_object(CMS_object): @classmethod def build(cls, vcard, keypair, certs): """ - Build a Ghostbusters record. + Build a Ghostbuster record. """ self = cls() self.set_content(vcard) diff --git a/rpkid/rpkid.sql b/rpkid/rpkid.sql index 2813c1dc..93712fe0 100644 --- a/rpkid/rpkid.sql +++ b/rpkid/rpkid.sql @@ -33,6 +33,7 @@ -- DROP TABLE commands must be in correct (reverse dependency) order -- to satisfy FOREIGN KEY constraints. +DROP TABLE IF EXISTS gbr; DROP TABLE IF EXISTS ghostbuster; DROP TABLE IF EXISTS roa_prefix; DROP TABLE IF EXISTS roa; @@ -219,7 +220,7 @@ CREATE TABLE ghostbuster ( ghostbuster_id SERIAL NOT NULL, vcard LONGBLOB NOT NULL, cert LONGBLOB NOT NULL, - gbr LONGBLOB NOT NULL, + ghostbuster LONGBLOB NOT NULL, published DATETIME, self_id BIGINT UNSIGNED NOT NULL, ca_detail_id BIGINT UNSIGNED NOT NULL, |