diff options
-rw-r--r-- | myrpki/myirbe.py | 61 | ||||
-rw-r--r-- | myrpki/myrpki.py | 170 | ||||
-rwxr-xr-x | myrpki/xml-parse-test.py | 2 | ||||
-rw-r--r-- | myrpki/yamltest.py | 123 |
4 files changed, 338 insertions, 18 deletions
diff --git a/myrpki/myirbe.py b/myrpki/myirbe.py index 6a7bc2d8..ecbf7b23 100644 --- a/myrpki/myirbe.py +++ b/myrpki/myirbe.py @@ -1,5 +1,28 @@ """ -IRBE-side stuff for myrpki testbed. +IRBE-side stuff for myrpki tools. + +The basic model here is that each entity with resources to certify +runs the myrpki tool, but not all of them necessarily run their own +RPKi engines. The entities that do run RPKI engines get data from the +entities they host via the XML files output by the myrpki tool. Those +XML files are the input to this script, which uses them to do all the +work of constructing certificates, populating SQL databases, and so +forth. A few operations (eg, BSC construction) generate data which +has to be shipped back to the resource holder, which we do by updating +the same XML file. + +In essence, the XML files are a sneakernet (or email, or carrier +pigeon) communication channel between the resource holders and the +RPKI engine operators. + +As a convenience, for the normal case where the RPKI engine operator +is itself a resource holder, this script also runs the myrpki script +directly to process the RPKI engine operator's own resources. + +Note that, due to the back and forth nature of some of these +operations, it may take several cycles for data structures to stablize +and everything to reach a steady state. This is normal. + $Id$ @@ -24,9 +47,15 @@ import rpki.exceptions, rpki.left_right, rpki.log, rpki.x509, rpki.async import myrpki, schema def tag(t): + """ + Wrap an element name in the right XML namespace goop. + """ return "{http://www.hactrn.net/uris/rpki/myrpki/}" + t def findbase64(tree, name, b64type = rpki.x509.X509): + """ + Find and extract a base64-encoded XML element, if present. + """ x = tree.findtext(tag(name)) return b64type(Base64 = x) if x else None @@ -112,17 +141,19 @@ if bpki_modified: print "BPKI (re)initialized. You need to (re)start daemons before continuing." sys.exit() +# Default values for CRL parameters are very low, for testing. + self_crl_interval = cfg.get("self_crl_interval", 300) self_regen_margin = cfg.get("self_regen_margin", 120) rsync_base = cfg.get("rsync_base") pubd_base = cfg.get("pubd_base") rpkid_base = cfg.get("rpkid_base") -# Nasty regexp for parsing rpkid's up-down service URLs +# Nasty regexp for parsing rpkid's up-down service URLs. updown_regexp = re.compile(re.escape(rpkid_base) + "up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I) -# Wrappers to simplify calling rpkid and pubd +# Wrappers to simplify calling rpkid and pubd. call_rpkid = rpki.async.sync_wrapper(caller( proto = rpki.left_right, @@ -156,18 +187,26 @@ cur = db.cursor() xmlfiles = [] +# If [myrpki] section is present in config file, run myrpki.py +# internally, as a convenience, and include its output at the head of +# our list of XML files to process. + if cfg.has_section("myrpki"): myrpki.main() my_xmlfile = cfg.get("xml_filename", None, "myrpki") assert my_xmlfile is not None xmlfiles.append(my_xmlfile) +# Add any other XML files specified on the command line + xmlfiles.extend(argv) my_handle = None for xmlfile in xmlfiles: + # Parse XML file and validate it against our scheme + tree = lxml.etree.parse(xmlfile).getroot() schema.myrpki.assertValid(tree) @@ -176,6 +215,8 @@ for xmlfile in xmlfiles: if xmlfile == my_xmlfile: my_handle = handle + # Update IRDB with parsed resource and roa-request data. + cur.execute( """ DELETE @@ -231,14 +272,18 @@ for xmlfile in xmlfiles: db.commit() + # Check for certificates before attempting anything else + hosted_cacert = findbase64(tree, "bpki_ca_certificate") if not hosted_cacert: print "Nothing else I can do without a trust anchor for the entity I'm hosting." - sys.exit() + continue rpkid_xcert = rpki.x509.X509(PEM_file = bpki_rpkid.fxcert(handle + ".cacert.cer", hosted_cacert.get_PEM(), path_restriction = 1)) pubd_xcert = rpki.x509.X509(PEM_file = bpki_pubd.fxcert(handle + ".cacert.cer", hosted_cacert.get_PEM())) + # See what rpkid and pubd already have on file for this entity. + pubd_reply = call_pubd(( rpki.publication.client_elt.make_pdu(action = "get", tag = "client", client_handle = handle),)) @@ -411,6 +456,10 @@ for xmlfile in xmlfiles: rpkid_query.extend(rpki.left_right.parent_elt.make_pdu( action = "destroy", self_handle = handle, parent_handle = p) for p in parent_pdus) + # Children are simpler than parents, because they call us, so no URL + # to construct and figuring out what certificate to use is their + # problem, not ours. + for child in tree.getiterator(tag("child")): child_handle = child.get("handle") @@ -431,6 +480,8 @@ for xmlfile in xmlfiles: rpkid_query.extend(rpki.left_right.child_elt.make_pdu( action = "destroy", self_handle = handle, child_handle = c) for c in child_pdus) + # If we changed anything, ship updates off to daemons + if rpkid_query: rpkid_reply = call_rpkid(rpkid_query) bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) @@ -441,6 +492,8 @@ for xmlfile in xmlfiles: pubd_reply = call_pubd(pubd_query) assert len(pubd_reply) == 1 and isinstance(pubd_reply[0], rpki.publication.client_elt) and pubd_reply[0].client_handle == handle + # Rewrite XML. + e = tree.find(tag("bpki_bsc_pkcs10")) if e is None and bsc_req is not None: e = lxml.etree.SubElement(tree, "bpki_bsc_pkcs10") diff --git a/myrpki/myrpki.py b/myrpki/myrpki.py index 8435257d..c45a59ca 100644 --- a/myrpki/myrpki.py +++ b/myrpki/myrpki.py @@ -1,10 +1,34 @@ """ -Basic plan here is to read in csv files for tabular data (ROA -requests, child ASN assignments, child prefix assignments), read -command line or magic file for my own handle, and read or generate PEM -for BPKI CA certificate and BPKI EE certificate (cannot do latter -without corresponding BPKI EE PKCS #10). Whack all this together and -generate some XML thing (format still in flux, see schema). +Read an OpenSSL-style config file and a bunch of .csv files to find +out about parents and children and resources and ROA requests, oh my. +Run OpenSSL command line tool to construct BPKI certificates, +including cross-certification of other entities' BPKI certificates. + +Package up all of the above as a single XML file which user can then +ship off to the IRBE. If an XML file already exists, check it for +data coming back from the IRBE (principally PKCS #10 requests for our +BSC) and update it with current data. + +The general idea here is that this one XML file contains all of the +data that needs to be exchanged as part of ordinary update operations; +each party updates it as necessary, then ships it to the other via +some secure channel: carrier pigeon, USB stick, gpg-protected email, +we don't really care. + +This one program is written a little differently from all the other +Python RPKI programs. This one program is intended to run as a +stand-alone script, without the other programs present. It does +require a reasonably up-to-date version of the OpenSSL command line +tool (the one built as a side effect of building rcynic will do), but +it does -not- require POW or any Python libraries beyond what ships +with Python 2.5. So this script uses xml.etree from the Python +standard libraries instead of lxml.etree, which sacrifices XML schema +validation support in favor of portability, and so forth. + +To make things a little weirder, as a convenience to IRBE operators, +this script can itself be loaded as a Python module and invoked as +part of another program. This requires a few minor contortions, but +avoids duplicating common code. $Id$ @@ -23,18 +47,28 @@ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ +# Only standard Python libraries for this program, please. + import subprocess, csv, re, os, getopt, sys, ConfigParser, base64 from xml.etree.ElementTree import Element, SubElement, ElementTree +# our XML namespace. + namespace = "http://www.hactrn.net/uris/rpki/myrpki/" class comma_set(set): + """ + Minor customization of set(), to provide a print syntax. + """ def __str__(self): return ",".join(self) class roa_request(object): + """ + Representation of a ROA request. + """ v4re = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+(-[0-9]+)?$", re.I) v6re = re.compile("^([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+(-[0-9]+)?$", re.I) @@ -48,6 +82,9 @@ class roa_request(object): return "<%s asn %s v4 %s v6 %s>" % (self.__class__.__name__, self.asn, self.v4, self.v6) def add(self, prefix): + """ + Add one prefix to this ROA request. + """ if self.v4re.match(prefix): self.v4.add(prefix) elif self.v6re.match(prefix): @@ -56,24 +93,39 @@ class roa_request(object): raise RuntimeError, "Bad prefix syntax: %r" % (prefix,) def xml(self, e): + """ + Generate XML element represeting representing this ROA request. + """ return SubElement(e, "roa_request", asn = self.asn, v4 = str(self.v4), v6 = str(self.v6)) class roa_requests(dict): + """ + Database of ROA requests. + """ def add(self, asn, prefix): + """ + Add one <ASN, prefix> pair to ROA request database. + """ if asn not in self: self[asn] = roa_request(asn) self[asn].add(prefix) def xml(self, e): + """ + Render ROA requests as XML elements. + """ for r in self.itervalues(): r.xml(e) @classmethod def from_csv(cls, roa_csv_file): + """ + Parse ROA requests from CSV file. + """ self = cls() # format: p/n-m asn for pnm, asn in csv_open(roa_csv_file): @@ -81,6 +133,9 @@ class roa_requests(dict): return self class child(object): + """ + Representation of one child entity. + """ v4re = re.compile("^(([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+)|(([0-9]{1,3}\.){3}[0-9]{1,3}-([0-9]{1,3}\.){3}[0-9]{1,3})$", re.I) v6re = re.compile("^(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+)|(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}-([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4})$", re.I) @@ -97,6 +152,10 @@ class child(object): return "<%s v4 %s v6 %s asns %s validity %s cert %s>" % (self.__class__.__name__, self.v4, self.v6, self.asns, self.validity, self.bpki_certificate) def add(self, prefix = None, asn = None, validity = None, bpki_certificate = None): + """ + Add prefix, autonomous system number, validity date, or BPKI + certificate for this child. + """ if prefix is not None: if self.v4re.match(prefix): self.v4.add(prefix) @@ -112,6 +171,9 @@ class child(object): self.bpki_certificate = bpki_certificate def xml(self, e): + """ + Render this child as an XML element. + """ e2 = SubElement(e, "child", handle = self.handle, valid_until = self.validity, @@ -123,18 +185,30 @@ class child(object): return e2 class children(dict): + """ + Database of children. + """ def add(self, handle, prefix = None, asn = None, validity = None, bpki_certificate = None): + """ + Add resources to a child, creating the child object if necessary. + """ if handle not in self: self[handle] = child(handle) self[handle].add(prefix = prefix, asn = asn, validity = validity, bpki_certificate = bpki_certificate) def xml(self, e): + """ + Render children database to XML. + """ for c in self.itervalues(): c.xml(e) @classmethod def from_csv(cls, children_csv_file, prefix_csv_file, asn_csv_file, xcert): + """ + Parse child resources, certificates, and validity dates from CSV files. + """ self = cls() # childname date pemfile for handle, date, pemfile in csv_open(children_csv_file): @@ -148,6 +222,9 @@ class children(dict): return self class parent(object): + """ + Representation of one parent entity. + """ def __init__(self, handle): self.handle = handle @@ -160,6 +237,9 @@ class parent(object): self.bpki_cms_certificate, self.bpki_https_certificate) def add(self, service_uri = None, bpki_cms_certificate = None, bpki_https_certificate = None): + """ + Add service URI or BPKI certificates to this parent object. + """ if service_uri is not None: self.service_uri = service_uri if bpki_cms_certificate is not None: @@ -168,6 +248,9 @@ class parent(object): self.bpki_https_certificate = bpki_https_certificate def xml(self, e): + """ + Render this parent object to XML. + """ e2 = SubElement(e, "parent", handle = self.handle, service_uri = self.service_uri) @@ -178,8 +261,14 @@ class parent(object): return e2 class parents(dict): + """ + Database of parent objects. + """ def add(self, handle, service_uri = None, bpki_cms_certificate = None, bpki_https_certificate = None): + """ + Add service URI or certificates to parent object, creating it if necessary. + """ if handle not in self: self[handle] = parent(handle) self[handle].add(service_uri = service_uri, bpki_cms_certificate = bpki_cms_certificate, bpki_https_certificate = bpki_https_certificate) @@ -190,6 +279,9 @@ class parents(dict): @classmethod def from_csv(cls, parents_csv_file, xcert): + """ + Parse parent data from CSV file. + """ self = cls() # parentname service_uri parent_bpki_cms_pemfile parent_bpki_https_pemfile for handle, service_uri, parent_cms_pemfile, parent_https_pemfile in csv_open(parents_csv_file): @@ -198,13 +290,28 @@ class parents(dict): return self def csv_open(filename, delimiter = "\t", dialect = None): + """ + Open a CSV file, with settings that make it a tab-delimited file. + You may need to tweak this function for your environment, see the + csv module in the Python standard libraries for details. + """ return csv.reader(open(filename, "rb"), dialect = dialect, delimiter = delimiter) def PEMElement(e, tag, filename): + """ + Create an XML element containing Base64 encoded data taken from a + PEM file. + """ e = SubElement(e, tag) e.text = "".join(p.strip() for p in open(filename).readlines()[1:-1]) class CA(object): + """ + Representation of one certification authority. + """ + + # Mapping of path restriction values we use to OpenSSL config file + # section names. path_restriction = { 0 : "ca_x509_ext_xcert0", 1 : "ca_x509_ext_xcert1" } @@ -225,10 +332,17 @@ class CA(object): "RANDFILE" : ".OpenSSL.whines.unless.I.set.this" } def run_ca(self, *args): + """ + Run OpenSSL "ca" command with tailored environment variables and common initial + arguments. + """ cmd = ("openssl", "ca", "-notext", "-batch", "-config", self.cfg) + args subprocess.check_call(cmd, env = self.env) def run_req(self, key_file, req_file): + """ + Run OpenSSL "req" command with tailored environment variables and common arguments. + """ if not os.path.exists(key_file) or not os.path.exists(req_file): subprocess.check_call(("openssl", "req", "-new", "-sha256", "-newkey", "rsa:2048", "-config", self.cfg, "-keyout", key_file, "-out", req_file), @@ -236,13 +350,20 @@ class CA(object): @staticmethod def touch_file(filename, content = None): + """ + Create dumb little text files expected by OpenSSL "ca" utility. + """ if not os.path.exists(filename): f = open(filename, "w") if content is not None: f.write(content) f.close() - def setup(self, ta_name): + def setup(self, ca_name): + """ + Set up this CA. ca_name is an X.509 distinguished name in + /tag=val/tag=val format. + """ modified = False @@ -256,7 +377,7 @@ class CA(object): if not os.path.exists(self.cer): modified = True - self.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-subj", ta_name, "-in", self.req, "-out", self.cer) + self.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-subj", ca_name, "-in", self.req, "-out", self.cer) if not os.path.exists(self.crl): modified = True @@ -265,6 +386,9 @@ class CA(object): return modified def ee(self, ee_name, base_name): + """ + Issue an end-enity certificate. + """ key_file = "%s/%s.key" % (self.dir, base_name) req_file = "%s/%s.req" % (self.dir, base_name) cer_file = "%s/%s.cer" % (self.dir, base_name) @@ -276,6 +400,9 @@ class CA(object): return False def bsc(self, pkcs10): + """ + Issue BSC certificiate, if we have a PKCS #10 request for it. + """ if pkcs10 is None: return None, None @@ -304,6 +431,9 @@ class CA(object): return req_file, cer_file def fxcert(self, filename, cert, path_restriction = 0): + """ + Write PEM certificate to file, then cross-certify. + """ fn = os.path.join(self.dir, filename) f = open(fn, "w") f.write(cert) @@ -311,6 +441,9 @@ class CA(object): return self.xcert(fn, path_restriction) def xcert(self, cert, path_restriction = 0): + """ + Cross-certify a certificate represented as a PEM file. + """ if not cert: return None @@ -332,7 +465,7 @@ class CA(object): # Cross-certify the cert we were given, if we haven't already. # This only works for self-signed certs, due to limitations of the - # OpenSSL command line tool. + # OpenSSL command line tool, but that suffices for our purposes. if not os.path.exists(xcert): self.run_ca("-ss_cert", cert, "-out", xcert, "-extensions", self.path_restriction[path_restriction]) @@ -340,9 +473,18 @@ class CA(object): return xcert def extract_resources(): - pass + """ + Extract RFC 3779 resources from a certificate. Not written yet. + + """ + raise NotImplementedError + def main(argv = ()): + """ + Main program. Must be callable from other programs as well as being + invoked directly when this module is run as a script. + """ cfg_file = "myrpki.conf" myrpki_section = "myrpki" @@ -358,7 +500,7 @@ def main(argv = ()): raise RuntimeError, "Unexpected arguments %r" % (argv,) cfg = ConfigParser.RawConfigParser() - cfg.read(cfg_file) + cfg.readfp(open(cfg_file, "r"), cfg_file) my_handle = cfg.get(myrpki_section, "handle") roa_csv_file = cfg.get(myrpki_section, "roa_csv") @@ -405,8 +547,14 @@ def main(argv = ()): if bsc_req: PEMElement(e, "bpki_bsc_pkcs10", bsc_req) + # I still miss SYSCAL(RENMWO) + ElementTree(e).write(xml_filename + ".tmp") os.rename(xml_filename + ".tmp", xml_filename) +# When this file is run as a script, run main() with command line +# arguments. main() can't use sys.argv directly as that might be the +# command line for some other program that loads this module. + if __name__ == "__main__": main(sys.argv[1:]) diff --git a/myrpki/xml-parse-test.py b/myrpki/xml-parse-test.py index 8c9e0327..d5f8e007 100755 --- a/myrpki/xml-parse-test.py +++ b/myrpki/xml-parse-test.py @@ -1,5 +1,5 @@ """ -Test parser for myrpki testbed stuff. +Test parser and display tool for myrpki.xml files. $Id$ diff --git a/myrpki/yamltest.py b/myrpki/yamltest.py index de7ecc0b..5e03ecf7 100644 --- a/myrpki/yamltest.py +++ b/myrpki/yamltest.py @@ -1,6 +1,22 @@ """ -Convert testbed.py YAML configuration format to myrpki .conf and .csv -format. Much of the YAML handling code lifted from testbed.py. +Test framework, using the same YAML test description format as +testbed.py, but using the myrpki.py and myirbe.py tools to do all the +back-end work. Reads YAML file, generates .csv and .conf files, runs +daemons and waits for one of them to exit. + +Much of the YAML handling code lifted from testbed.py. + +Still to do: + +- Generate rsyncd.conf and run rsync so that tests can include rcynic + runs aganist generated data. Not particularly difficult, just + tedious, and likely to require fildding with publication paths + (again). + +- Implement testebd.py-style delta actions, that is, modify the + allocation database under control of the YAML file, dump out new + .csv files, and run myrpki.py and myirbe.py again to feed resulting + changes into running daemons. $Id$ @@ -41,6 +57,9 @@ section_regexp = re.compile("\s*\[\s*(.+?)\s*\]\s*$") variable_regexp = re.compile("\s*([-a-zA-Z0-9_]+)\s*=\s*(.+?)\s*$") def cleanpath(*names): + """ + Construct normalized pathnames. + """ return os.path.normpath(os.path.join(*names)) this_dir = os.getcwd() @@ -57,6 +76,9 @@ prog_rootd = cleanpath(rpkid_dir, "rootd.py") prog_openssl = cleanpath(this_dir, "../openssl/openssl/apps/openssl") class roa_request(object): + """ + Representation of a ROA request. + """ def __init__(self, asn, ipv4, ipv6): self.asn = asn @@ -79,9 +101,15 @@ class roa_request(object): @classmethod def parse(cls, yaml): + """ + Parse a ROA request from YAML format. + """ return cls(yaml.get("asn"), yaml.get("ipv4"), yaml.get("ipv6")) class allocation_db(list): + """ + Our allocation database. + """ def __init__(self, yaml): list.__init__(self) @@ -109,10 +137,19 @@ class allocation_db(list): assert not a.is_root() and not a.hosted_by.is_hosted() def dump(self): + """ + Show contents of allocatino database. + """ for a in self: a.dump() def make_rootd_openssl(self): + """ + Factory for a function to run the OpenSSL comand line tool on the + root node of our allocation database. Could easily be generalized + if there were a need, but as it happens we only ever need to do + this for the root node. + """ env = { "PATH" : os.environ["PATH"], "BPKI_DIRECTORY" : self.root.path("bpki.rootd"), "RANDFILE" : ".OpenSSL.whines.unless.I.set.this" } @@ -120,6 +157,12 @@ class allocation_db(list): return lambda *args: subprocess.check_call((prog_openssl,) + args, cwd = cwd, env = env) class allocation(object): + """ + One entity in our allocation database. Every entity in the database + is assumed to hold resources, so needs at least myrpki services. + Entities that don't have the hosted_by property run their own copies + of rpkid, irdbd, and pubd, so they also need myirbe services. + """ parent = None crl_interval = None @@ -129,6 +172,9 @@ class allocation(object): @classmethod def allocate_port(cls): + """ + Allocate a TCP port. + """ cls.base_port += 1 return cls.base_port @@ -136,6 +182,10 @@ class allocation(object): @classmethod def allocate_engine(cls): + """ + Allocate an engine number, mostly used to construct MySQL database + names. + """ cls.base_engine += 1 return cls.base_engine @@ -177,6 +227,10 @@ class allocation(object): self.rootd_port = self.allocate_port() def closure(self): + """ + Compute resource closure of this node and its children, to avoid a + lot of tedious (and error-prone) duplication in the YAML file. + """ resources = self.base for kid in self.kids: resources = resources.union(kid.closure()) @@ -184,6 +238,9 @@ class allocation(object): return resources def dump(self): + """ + Show content of this allocation node. + """ print str(self) def __str__(self): @@ -205,24 +262,42 @@ class allocation(object): return s + " Until: %s\n" % self.resources.valid_until def is_root(self): + """ + Is this the root node? + """ return self.parent is None def is_hosted(self): + """ + Is this entity hosted? + """ return self.hosted_by is not None def path(self, *names): + """ + Construct pathnames in this entity's test directory. + """ return cleanpath(test_dir, self.name, *names) def outfile(self, filename): + """ + Open and log an output file. + """ path = self.path(filename) print "Writing", path return open(path, "w") def up_down_url(self): + """ + Construct service URL for this node's parent. + """ parent_port = self.parent.hosted_by.rpkid_port if self.parent.is_hosted() else self.parent.rpkid_port return "https://localhost:%d/up-down/%s/%s" % (parent_port, self.parent.name, self.name) def dump_asns(self, fn): + """ + Write Autonomous System Numbers CSV file. + """ f = self.outfile(fn) for k in self.kids: for a in k.resources.asn: @@ -230,12 +305,18 @@ class allocation(object): f.close() def dump_children(self, fn): + """ + Write children CSV file. + """ f = self.outfile(fn) for k in self.kids: f.write("%s\t%s\t%s\n" % (k.name, k.resources.valid_until, k.path("bpki.myrpki/ca.cer"))) f.close() def dump_parents(self, fn): + """ + Write parents CSV file. + """ f = self.outfile(fn) if self.is_root(): f.write("%s\t%s\t%s\t%s\n" % ("rootd", "https://localhost:%d/" % self.rootd_port, self.path("bpki.rootd/ca.cer"), self.path("bpki.rootd/ca.cer"))) @@ -245,6 +326,9 @@ class allocation(object): f.close() def dump_prefixes(self, fn): + """ + Write prefixes CSV file. + """ f = self.outfile(fn) for k in self.kids: for p in k.resources.v4 + k.resources.v6: @@ -252,6 +336,9 @@ class allocation(object): f.close() def dump_roas(self, fn): + """ + Write ROA CSV file. + """ f = self.outfile(fn) for r in self.roa_requests: for p in r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or (): @@ -259,6 +346,9 @@ class allocation(object): f.close() def dump_conf(self, fn): + """ + Write configuration file for OpenSSL and RPKI tools. + """ host = self.hosted_by if self.is_hosted() else self @@ -315,6 +405,9 @@ class allocation(object): f.close() def run_myirbe(self): + """ + Run myirbe.py if this entity is not hosted by another engine. + """ if not self.is_hosted(): print "Running myirbe.py for", self.name cmd = ["python", prog_myirbe] @@ -322,10 +415,17 @@ class allocation(object): subprocess.check_call(cmd, cwd = self.path()) def run_myrpki(self): + """ + Run myrpki.py for this entity. + """ print "Running myrpki.py for", self.name subprocess.check_call(("python", prog_myrpki), cwd = self.path()) def run_python_daemon(self, prog): + """ + Start a Python daemon and return a subprocess.Popen object + representing the running daemon. + """ basename = os.path.basename(prog) p = subprocess.Popen(("python", prog, "-c", self.path("myrpki.conf")), cwd = self.path(), @@ -335,15 +435,27 @@ class allocation(object): return p def run_rpkid(self): + """ + Run rpkid. + """ return self.run_python_daemon(prog_rpkid) def run_irdbd(self): + """ + Run irdbd. + """ return self.run_python_daemon(prog_irdbd) def run_pubd(self): + """ + Run pubd. + """ return self.run_python_daemon(prog_pubd) def run_rootd(self): + """ + Run rootd. + """ return self.run_python_daemon(prog_rootd) os.environ["TZ"] = "UTC" @@ -359,11 +471,18 @@ for o, a in opts: if o in ("-c", "--config"): cfg_file = a +# We can't usefully process more than one YAMl file at a time, so +# whine if there's more than one argument left. + if len(argv) > 1: raise RuntimeError, "Unexpected arguments %r" % argv yaml_file = argv[0] if argv else "../rpkid/testbed.1.yaml" +# Allow optional config file for this tool to override default +# passwords: this is mostly so that I can show a complete working +# example without publishing my own server's passwords. + try: cfg = rpki.config.parser(cfg_file, "yamltest") rpkid_password = cfg.get("rpkid_db_pass") |