diff options
Diffstat (limited to 'rpkid/rpki/rpkid.py')
-rw-r--r-- | rpkid/rpki/rpkid.py | 219 |
1 files changed, 207 insertions, 12 deletions
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. |