aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--myrpki/myirbe.py61
-rw-r--r--myrpki/myrpki.py170
-rwxr-xr-xmyrpki/xml-parse-test.py2
-rw-r--r--myrpki/yamltest.py123
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")