aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in36
-rw-r--r--buildtools/build-freebsd-ports.py71
-rw-r--r--buildtools/freebsd-skeleton/rpki-ca/Makefile2
-rw-r--r--buildtools/freebsd-skeleton/rpki-rp/Makefile2
-rwxr-xr-xca/irbe_cli80
-rw-r--r--ca/rpki-confgen.xml190
-rwxr-xr-xca/rpki-manage17
-rwxr-xr-xca/rpki-sql-backup2
-rwxr-xr-xca/rpki-sql-setup4
-rwxr-xr-xca/rpki-start-servers4
-rw-r--r--ca/rpki.wsgi3
-rwxr-xr-xca/rpkigui-apache-conf-gen64
-rw-r--r--ca/tests/Makefile.in41
-rw-r--r--ca/tests/publication-control-protocol-samples.xml155
-rw-r--r--ca/tests/publication-protocol-samples.xml451
-rw-r--r--ca/tests/rrdp-samples.xml88
-rw-r--r--ca/tests/smoketest.py395
-rw-r--r--ca/tests/sql-cleaner.py13
-rw-r--r--ca/tests/sql-dumper.py2
-rwxr-xr-xca/tests/test-rrdp.py121
-rw-r--r--ca/tests/testpoke.py4
-rw-r--r--ca/tests/xml-parse-test.py34
-rw-r--r--ca/tests/yamlconf.py35
-rw-r--r--ca/tests/yamltest.py298
-rw-r--r--h/rpki/sk_roa.h2
-rwxr-xr-xpotpourri/rrdp-fetch.py68
-rwxr-xr-xpotpourri/rrdp-test-tool142
-rw-r--r--potpourri/upgrade-add-ghostbusters.py2
-rw-r--r--rpki/adns.py14
-rw-r--r--rpki/async.py16
-rw-r--r--rpki/config.py92
-rw-r--r--rpki/csv_utils.py2
-rw-r--r--rpki/db_router.py57
-rw-r--r--rpki/django_settings.py245
-rw-r--r--rpki/exceptions.py15
-rw-r--r--rpki/fields.py196
-rw-r--r--rpki/gui/app/check_expired.py6
-rw-r--r--rpki/gui/app/forms.py17
-rw-r--r--rpki/gui/app/glue.py3
-rw-r--r--rpki/gui/app/models.py14
-rwxr-xr-xrpki/gui/app/range_list.py2
-rw-r--r--rpki/gui/app/views.py41
-rw-r--r--rpki/gui/cacheview/models.py8
-rw-r--r--rpki/gui/cacheview/tests.py1
-rw-r--r--rpki/gui/cacheview/util.py3
-rw-r--r--rpki/gui/cacheview/views.py1
-rw-r--r--rpki/gui/decorators.py15
-rw-r--r--rpki/gui/default_settings.py171
-rw-r--r--rpki/gui/models.py4
-rw-r--r--rpki/gui/routeview/api.py2
-rw-r--r--rpki/gui/routeview/util.py2
-rw-r--r--rpki/gui/script_util.py44
-rw-r--r--rpki/http.py47
-rw-r--r--rpki/http_simple.py137
-rw-r--r--rpki/ipaddrs.py9
-rw-r--r--rpki/irdb/migrations/0001_initial.py595
-rw-r--r--rpki/irdb/migrations/__init__.py0
-rw-r--r--rpki/irdb/models.py123
-rw-r--r--rpki/irdb/zookeeper.py105
-rw-r--r--rpki/irdbd.py195
-rw-r--r--rpki/left_right.py422
-rw-r--r--rpki/log.py7
-rw-r--r--rpki/old_irdbd.py27
-rw-r--r--rpki/pubd.py219
-rw-r--r--rpki/pubdb/__init__.py21
-rw-r--r--rpki/pubdb/migrations/0001_initial.py120
-rw-r--r--rpki/pubdb/migrations/__init__.py0
-rw-r--r--rpki/pubdb/models.py310
-rw-r--r--rpki/publication.py466
-rw-r--r--rpki/publication_control.py269
-rw-r--r--rpki/rcynic.py4
-rw-r--r--rpki/relaxng.py583
-rw-r--r--rpki/resource_set.py48
-rw-r--r--rpki/rootd.py403
-rw-r--r--rpki/rpkic.py44
-rw-r--r--rpki/rpkid.py438
-rw-r--r--rpki/rpkid_tasks.py11
-rw-r--r--rpki/rpkidb/__init__.py3
-rwxr-xr-xrpki/rtr/bgpdump.py2
-rw-r--r--rpki/sql.py51
-rw-r--r--rpki/sql_schemas.py129
-rw-r--r--rpki/sundial.py15
-rw-r--r--rpki/up_down.py701
-rw-r--r--rpki/x509.py137
-rw-r--r--rpki/xml_utils.py54
-rw-r--r--schemas/relaxng/left-right.rnc (renamed from schemas/relaxng/left-right-schema.rnc)0
-rw-r--r--schemas/relaxng/left-right.rng (renamed from schemas/relaxng/left-right-schema.rng)2
-rw-r--r--schemas/relaxng/myrpki.rng2
-rw-r--r--schemas/relaxng/publication-control.rnc (renamed from schemas/relaxng/publication-schema.rnc)55
-rw-r--r--schemas/relaxng/publication-control.rng280
-rw-r--r--schemas/relaxng/publication-schema.rng577
-rw-r--r--schemas/relaxng/publication.rnc111
-rw-r--r--schemas/relaxng/publication.rng201
-rw-r--r--schemas/relaxng/router-certificate.rnc (renamed from schemas/relaxng/router-certificate-schema.rnc)0
-rw-r--r--schemas/relaxng/router-certificate.rng (renamed from schemas/relaxng/router-certificate-schema.rng)2
-rw-r--r--schemas/relaxng/rrdp.rnc83
-rw-r--r--schemas/relaxng/rrdp.rng163
-rw-r--r--schemas/relaxng/up-down.rnc (renamed from schemas/relaxng/up-down-schema.rnc)0
-rw-r--r--schemas/relaxng/up-down.rng (renamed from schemas/relaxng/up-down-schema.rng)2
-rw-r--r--schemas/sql/pubd.sql90
-rw-r--r--schemas/sql/rpkid.sql35
101 files changed, 6555 insertions, 4040 deletions
diff --git a/Makefile.in b/Makefile.in
index 8908ae32..3121c7c4 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -45,11 +45,13 @@ SETUP_PY_ROOT = `${PYTHON} -c 'import sys; print "--root " + sys.argv[1] if sys.
POW_SO = rpki/POW/_POW.so
-RNGS = schemas/relaxng/left-right-schema.rng \
- schemas/relaxng/up-down-schema.rng \
- schemas/relaxng/publication-schema.rng \
+RNGS = schemas/relaxng/left-right.rng \
+ schemas/relaxng/up-down.rng \
+ schemas/relaxng/publication.rng \
+ schemas/relaxng/publication-control.rng \
schemas/relaxng/myrpki.rng \
- schemas/relaxng/router-certificate-schema.rng
+ schemas/relaxng/router-certificate.rng \
+ schemas/relaxng/rrdp.rng
SQLS = schemas/sql/rpkid.sql \
schemas/sql/pubd.sql
@@ -187,20 +189,26 @@ ${abs_top_srcdir}/rpki/sql_schemas.py: buildtools/make-sql-schemas.py ${SQLS}
cd schemas/sql; ${PYTHON} ${abs_top_srcdir}/buildtools/make-sql-schemas.py >$@.tmp
mv $@.tmp $@
-schemas/relaxng/left-right-schema.rng: schemas/relaxng/left-right-schema.rnc
- ${TRANG} schemas/relaxng/left-right-schema.rnc schemas/relaxng/left-right-schema.rng
+schemas/relaxng/left-right.rng: schemas/relaxng/left-right.rnc
+ ${TRANG} schemas/relaxng/left-right.rnc schemas/relaxng/left-right.rng
-schemas/relaxng/up-down-schema.rng: schemas/relaxng/up-down-schema.rnc
- ${TRANG} schemas/relaxng/up-down-schema.rnc schemas/relaxng/up-down-schema.rng
+schemas/relaxng/up-down.rng: schemas/relaxng/up-down.rnc
+ ${TRANG} schemas/relaxng/up-down.rnc schemas/relaxng/up-down.rng
-schemas/relaxng/publication-schema.rng: schemas/relaxng/publication-schema.rnc
- ${TRANG} schemas/relaxng/publication-schema.rnc schemas/relaxng/publication-schema.rng
+schemas/relaxng/publication.rng: schemas/relaxng/publication.rnc
+ ${TRANG} schemas/relaxng/publication.rnc schemas/relaxng/publication.rng
+
+schemas/relaxng/publication-control.rng: schemas/relaxng/publication-control.rnc
+ ${TRANG} schemas/relaxng/publication-control.rnc schemas/relaxng/publication-control.rng
schemas/relaxng/myrpki.rng: schemas/relaxng/myrpki.rnc
${TRANG} schemas/relaxng/myrpki.rnc schemas/relaxng/myrpki.rng
-schemas/relaxng/router-certificate-schema.rng: schemas/relaxng/router-certificate-schema.rnc
- ${TRANG} schemas/relaxng/router-certificate-schema.rnc schemas/relaxng/router-certificate-schema.rng
+schemas/relaxng/router-certificate.rng: schemas/relaxng/router-certificate.rnc
+ ${TRANG} schemas/relaxng/router-certificate.rnc schemas/relaxng/router-certificate.rng
+
+schemas/relaxng/rrdp.rng: schemas/relaxng/rrdp.rnc
+ ${TRANG} schemas/relaxng/rrdp.rnc schemas/relaxng/rrdp.rng
# Eg: PYLINT_FLAGS='--disable=W0311'
@@ -208,8 +216,10 @@ lint:
{ find rpki rp ca -name '*.py' -print; find rp ca -type f -perm -1 -print | xargs grep -El '^#!.+python'; } | \
sort -u | xargs pylint --rcfile ${abs_top_srcdir}/buildtools/pylint.rc ${PYLINT_FLAGS}
-tags: Makefile
+tags: Makefile .FORCE
find rpki rp ca schemas -type f \
\( -name '*.[ch]' -o -name '*.py' -o -name '*.sql' -o -name '*.rnc' \) \
! -name relaxng.py ! -name sql_schemas.py -print | \
etags -
+
+.FORCE:
diff --git a/buildtools/build-freebsd-ports.py b/buildtools/build-freebsd-ports.py
index c422f02f..c653e5cf 100644
--- a/buildtools/build-freebsd-ports.py
+++ b/buildtools/build-freebsd-ports.py
@@ -23,36 +23,40 @@ This is a script because we need to generate package lists and update
version numbers in the Makefiles.
"""
-import sys
import os
import re
-import subprocess
-import errno
+import sys
import glob
+import errno
import shutil
import argparse
+import subprocess
def check_dir(s):
if not os.path.isdir(s):
raise argparse.ArgumentTypeError("%r is not a directory" % s)
return s
-parser = argparse.ArgumentParser(description = __doc__)
-parser.add_argument("--allow-dirty", action = "store_true",
- help = "don't insist on pristine subversion checkout")
+parser = argparse.ArgumentParser(description = __doc__,
+ formatter_class = argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("--local-dist", action = "store_true",
+ help = "generate local distribution from subversion working tree (implies --make-package)")
parser.add_argument("--make-package", action = "store_true",
help = "build binary package")
parser.add_argument("--no-clean", action = "store_true",
help = "don't clean port after staging etc (implies --no-tarball)")
parser.add_argument("--no-tarball", action = "store_true",
help = "don't create tarball of generated port")
+parser.add_argument("--portsdir", type = os.path.abspath,
+ default = os.path.abspath("freebsd-ports"),
+ help = "where to build FreeBSD port trees")
parser.add_argument("svndir", metavar = "subversion-working-directory", type = check_dir,
help = "directory containing subversion working tree")
args = parser.parse_args()
svnversion = subprocess.check_output(("svnversion", "-c", args.svndir)).strip().split(":")[-1]
-if args.allow_dirty:
+if args.local_dist:
svnversion = svnversion.translate(None, "M")
if not svnversion.isdigit():
@@ -66,53 +70,56 @@ if branch != "trunk" and (branch[:2] != "tk" or not branch[2:].isdigit()):
version = "0." + svnversion
tarname = "rpki-%s-r%s" % (branch, svnversion)
tarball = tarname + ".tar.xz"
-url = "http://download.rpki.net/" + tarball
-
-portsdir = os.path.abspath("freebsd-ports")
-portsdir_old = portsdir + ".old"
-# Could perhaps use distutils.sysconfig.get_python_lib() instead of
-# this regexp hack, but would be just as complicated in its own way,
-# so just go with this for the moment.
-
-py_lib = re.compile(r"^lib/python\d+\.\d+")
-py_sitelib = re.compile(r"^lib/python\d+\.\d+/site-packages")
+portsdir_old = args.portsdir + ".old"
if os.path.isdir(portsdir_old):
shutil.rmtree(portsdir_old)
-if os.path.isdir(portsdir):
- os.rename(portsdir, portsdir_old)
+if os.path.isdir(args.portsdir):
+ os.rename(args.portsdir, portsdir_old)
-shutil.copytree(os.path.join(args.svndir, "buildtools", "freebsd-skeleton"), portsdir)
+shutil.copytree(os.path.join(args.svndir, "buildtools", "freebsd-skeleton"), args.portsdir)
-if os.path.exists(os.path.join(portsdir_old, tarball)):
- os.link(os.path.join(portsdir_old, tarball), os.path.join(portsdir, tarball))
+if args.local_dist:
+ subprocess.check_call(("svn", "export", args.svndir, os.path.join(args.portsdir, tarname)))
+ subprocess.check_call(("tar", "cJvvf", tarball, tarname), cwd = args.portsdir)
+ shutil.rmtree(os.path.join(args.portsdir, tarname))
+elif os.path.exists(os.path.join(portsdir_old, tarball)):
+ os.link(os.path.join(portsdir_old, tarball), os.path.join(args.portsdir, tarball))
elif os.path.exists(os.path.join("/usr/ports/distfiles", tarball)):
- shutil.copy(os.path.join("/usr/ports/distfiles", tarball), os.path.join(portsdir, tarball))
+ shutil.copy(os.path.join("/usr/ports/distfiles", tarball), os.path.join(args.portsdir, tarball))
if os.path.isdir(portsdir_old):
shutil.rmtree(portsdir_old)
-if args.make_package:
- pkgdir = os.path.join(portsdir, "packages")
+if args.make_package or args.local_dist:
+ pkgdir = os.path.join(args.portsdir, "packages")
os.mkdir(pkgdir)
-formatdict = dict(SVNVERSION = svnversion, SVNBRANCH = branch)
+py_lib = re.compile(r"^lib/python\d+\.\d+")
+py_sitelib = re.compile(r"^lib/python\d+\.\d+/site-packages")
+
+if args.local_dist:
+ master_site = "file://" + args.portsdir + "/"
+else:
+ master_site = "http://download.rpki.net/"
+
+formatdict = dict(SVNVERSION = svnversion, SVNBRANCH = branch, MASTER_SITE = master_site)
keepdirs = ("usr", "etc", "bin", "var", "lib", "libexec", "sbin", "share", "etc/rc.d", "%%PYTHON_SITELIBDIR%%")
for port in ("rpki-rp", "rpki-ca"):
- base = os.path.join(portsdir, port)
+ base = os.path.join(args.portsdir, port)
stage = os.path.join(base, "work", "stage")
- fn = os.path.join(portsdir, port, "Makefile")
+ fn = os.path.join(args.portsdir, port, "Makefile")
with open(fn, "r") as f:
template = f.read()
with open(fn, "w") as f:
f.write(template % formatdict)
- subprocess.check_call(("make", "makesum", "stage", "DISTDIR=" + portsdir), cwd = base)
+ subprocess.check_call(("make", "makesum", "stage", "DISTDIR=" + args.portsdir), cwd = base)
with open(os.path.join(base, "pkg-plist"), "w") as f:
usr_local = None
@@ -135,11 +142,11 @@ for port in ("rpki-rp", "rpki-ca"):
if dn and dn not in keepdirs and not py_lib.match(dn):
f.write("@dirrm %s\n" % dn)
- if args.make_package:
- subprocess.check_call(("make", "clean", "package", "PKGREPOSITORY=" + pkgdir), cwd = base)
+ if args.make_package or args.local_dist:
+ subprocess.check_call(("make", "clean", "package", "DISTDIR=" + args.portsdir, "PKGREPOSITORY=" + pkgdir), cwd = base)
if not args.no_clean:
subprocess.check_call(("make", "clean"), cwd = base)
if not args.no_tarball and not args.no_clean:
- subprocess.check_call(("tar", "czf", "%s-port.tgz" % port, port), cwd = portsdir)
+ subprocess.check_call(("tar", "czf", "%s-port.tgz" % port, port), cwd = args.portsdir)
diff --git a/buildtools/freebsd-skeleton/rpki-ca/Makefile b/buildtools/freebsd-skeleton/rpki-ca/Makefile
index cf095aa1..fbde22fe 100644
--- a/buildtools/freebsd-skeleton/rpki-ca/Makefile
+++ b/buildtools/freebsd-skeleton/rpki-ca/Makefile
@@ -1,7 +1,7 @@
PORTNAME= rpki-ca
PORTVERSION= 0.%(SVNVERSION)s
CATEGORIES= net
-MASTER_SITES= http://download.rpki.net/
+MASTER_SITES= %(MASTER_SITE)s
DISTFILES= rpki-%(SVNBRANCH)s-r%(SVNVERSION)s.tar.xz
WRKSRC= ${WRKDIR}/rpki-%(SVNBRANCH)s-r%(SVNVERSION)s
MAINTAINER= sra@hactrn.net
diff --git a/buildtools/freebsd-skeleton/rpki-rp/Makefile b/buildtools/freebsd-skeleton/rpki-rp/Makefile
index adae6ef0..0c1fdd91 100644
--- a/buildtools/freebsd-skeleton/rpki-rp/Makefile
+++ b/buildtools/freebsd-skeleton/rpki-rp/Makefile
@@ -1,7 +1,7 @@
PORTNAME= rpki-rp
PORTVERSION= 0.%(SVNVERSION)s
CATEGORIES= net
-MASTER_SITES= http://download.rpki.net/
+MASTER_SITES= %(MASTER_SITE)s
DISTFILES= rpki-%(SVNBRANCH)s-r%(SVNVERSION)s.tar.xz
WRKSRC= ${WRKDIR}/rpki-%(SVNBRANCH)s-r%(SVNVERSION)s
MAINTAINER= sra@hactrn.net
diff --git a/ca/irbe_cli b/ca/irbe_cli
index 1becd403..de907ca7 100755
--- a/ca/irbe_cli
+++ b/ca/irbe_cli
@@ -41,12 +41,11 @@ import sys
import getopt
import textwrap
import rpki.left_right
-import rpki.http
+import rpki.http_simple
import rpki.x509
import rpki.config
import rpki.log
import rpki.publication
-import rpki.async
pem_out = None
@@ -305,7 +304,7 @@ for o, a in opts:
if not argv:
usage(1)
-cfg = rpki.config.parser(cfg_file, "irbe_cli")
+cfg = rpki.config.parser(set_filename = cfg_file, section = "irbe_cli")
q_msg_left_right = []
q_msg_publication = []
@@ -322,19 +321,22 @@ while argv:
argv = q_pdu.client_getopt(argv[1:])
q_msg.append(q_pdu)
-from django.conf import settings
-
-settings.configure(
- DATABASES = { "default" : {
- "ENGINE" : "django.db.backends.mysql",
- "NAME" : cfg.get("sql-database", section = "irdbd"),
- "USER" : cfg.get("sql-username", section = "irdbd"),
- "PASSWORD" : cfg.get("sql-password", section = "irdbd"),
- "HOST" : "",
- "PORT" : "",
- "OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}},
- INSTALLED_APPS = ("rpki.irdb",),
-)
+if True:
+ os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings")
+
+else:
+ from django.conf import settings
+ settings.configure(
+ DATABASES = { "default" : {
+ "ENGINE" : "django.db.backends.mysql",
+ "NAME" : cfg.get("sql-database", section = "irdbd"),
+ "USER" : cfg.get("sql-username", section = "irdbd"),
+ "PASSWORD" : cfg.get("sql-password", section = "irdbd"),
+ "HOST" : "",
+ "PORT" : "",
+ "OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}},
+ INSTALLED_APPS = ("rpki.irdb",),
+ )
import rpki.irdb
@@ -343,46 +345,36 @@ irbe = server_ca.ee_certificates.get(purpose = "irbe")
if q_msg_left_right:
- class left_right_proto(object):
- cms_msg = left_right_cms_msg
- msg = left_right_msg
-
rpkid = server_ca.ee_certificates.get(purpose = "rpkid")
rpkid_url = "http://%s:%s/left-right/" % (
cfg.get("server-host", section = "rpkid"),
cfg.get("server-port", section = "rpkid"))
- call_rpkid = rpki.async.sync_wrapper(rpki.http.caller(
- proto = left_right_proto,
- client_key = irbe.private_key,
- client_cert = irbe.certificate,
- server_ta = server_ca.certificate,
- server_cert = rpkid.certificate,
- url = rpkid_url,
- debug = verbose))
-
- call_rpkid(*q_msg_left_right)
+ rpki.http_simple.client(
+ proto_cms_msg = left_right_cms_msg,
+ client_key = irbe.private_key,
+ client_cert = irbe.certificate,
+ server_ta = server_ca.certificate,
+ server_cert = rpkid.certificate,
+ url = rpkid_url,
+ debug = verbose,
+ q_msg = left_right_msg(*q_msg_left_right))
if q_msg_publication:
- class publication_proto(object):
- msg = publication_msg
- cms_msg = publication_cms_msg
-
pubd = server_ca.ee_certificates.get(purpose = "pubd")
pubd_url = "http://%s:%s/control/" % (
cfg.get("server-host", section = "pubd"),
cfg.get("server-port", section = "pubd"))
- call_pubd = rpki.async.sync_wrapper(rpki.http.caller(
- proto = publication_proto,
- client_key = irbe.private_key,
- client_cert = irbe.certificate,
- server_ta = server_ca.certificate,
- server_cert = pubd.certificate,
- url = pubd_url,
- debug = verbose))
-
- call_pubd(*q_msg_publication)
+ rpki.http_simple.client(
+ proto_cms_msg = publication_cms_msg,
+ client_key = irbe.private_key,
+ client_cert = irbe.certificate,
+ server_ta = server_ca.certificate,
+ server_cert = pubd.certificate,
+ url = pubd_url,
+ debug = verbose,
+ q_msg = publication_msg(*q_msg_publication))
diff --git a/ca/rpki-confgen.xml b/ca/rpki-confgen.xml
index e0ed273a..b3e50823 100644
--- a/ca/rpki-confgen.xml
+++ b/ca/rpki-confgen.xml
@@ -186,17 +186,17 @@
</doc>
</option>
- <option name = "publication_root_cert_directory"
- value = "${myrpki::publication_base_directory}.root">
+ <option name = "rrdp_publication_base_directory"
+ value = "${autoconf::datarootdir}/rpki/rrdp-publication">
<doc>
- Root of local directory tree where rootd (sigh) should write out
- published data. This is just like publication_base_directory, but
- rootd is too dumb to use pubd and needs its own directory in
- which to write one certificate, one CRL, and one manifest.
- Neither rootd nor rsyncd much cares //where// you tell them to put
- this stuff, the important thing is that the rsync URIs in
- generated certificates match up with the published objects so that
- relying parties can find and verify rootd's published outputs.
+ Root of local directory tree where pubd should write out RRDP
+ files. You need to configure this, and the configuration
+ should match up with the directory where you point the web
+ server (usually Apache) that serves the RRDP files. Neither
+ pubd nor Apache much cares //where// you tell it to put this
+ stuff, the important thing is that all the URIs match up so
+ that relying parties can find and verify rpkid's published
+ outputs.
</doc>
</option>
@@ -209,15 +209,6 @@
</doc>
</option>
- <option name = "publication_root_module"
- value = "root">
- <doc>
- rsyncd module name corresponding to publication_root_cert_directory.
- This has to match the module you configured into `rsyncd.conf`.
- Leave this alone unless you have some need to change it.
- </doc>
- </option>
-
<option name = "publication_rsync_server"
value = "${myrpki::pubd_server_host}">
<doc>
@@ -577,6 +568,20 @@
</doc>
</option>
+ <option name = "rrdp-publication-base"
+ value = "${myrpki::rrdp_publication_base_directory}">
+ <doc>
+ Root of local directory tree where pubd should write out RRDP
+ files. You need to configure this, and the configuration
+ should match up with the directory where you point the web
+ server (usually Apache) that serves the RRDP files. Neither
+ pubd nor Apache much cares //where// you tell it to put this
+ stuff, the important thing is that all the URIs match up so
+ that relying parties can find and verify rpkid's published
+ outputs.
+ </doc>
+ </option>
+
<option name = "server-host"
value = "${myrpki::pubd_server_host}">
<doc>
@@ -618,6 +623,15 @@
</doc>
</option>
+ <option name = "pubd-crl"
+ value = "${myrpki::bpki_servers_directory}/ca.crl">
+ <doc>
+ Where pubd should look for the CRL covering its own BPKI EE
+ certificate. Don't change this unless you really know what
+ you are doing.
+ </doc>
+ </option>
+
<option name = "irbe-cert"
value = "${myrpki::bpki_servers_directory}/irbe.cer">
<doc>
@@ -638,10 +652,9 @@
</doc>
<doc>
- Ok, if that wasn't enough to scare you off: rootd is a mess, and
- needs to be rewritten, or, better, merged into rpkid. It
- doesn't use the publication protocol, and it requires far too
- many configuration parameters.
+ Ok, if that wasn't enough to scare you off: rootd is a mess,
+ needs to be rewritten, or, better, merged into rpkid, and
+ requires far too many configuration parameters.
</doc>
<doc>
@@ -712,6 +725,13 @@
</doc>
</option>
+ <option name = "pubd-bpki-cert">
+ <doc>
+ BPKI certificate for pubd. Don't set this unless you really
+ know what you are doing.
+ </doc>
+ </option>
+
<option name = "server-host"
value = "${myrpki::rootd_server_host}">
<doc>
@@ -726,72 +746,85 @@
</doc>
</option>
- <option name = "rpki-root-dir"
- value = "${myrpki::publication_base_directory}">
+ <option name = "rpki_data_dir"
+ value = "${myrpki::bpki_servers_directory}">
<doc>
- Where rootd should write its output. Yes, rootd should be using
- pubd instead of publishing directly, but it doesn't. This
- needs to match pubd's configuration.
+ Directory where rootd should store its RPKI data files. This
+ is only used to construct other variables, rootd itself
+ doesn't read it.
</doc>
</option>
- <option name = "rpki-base-uri"
- value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/">
+ <option name = "rpki_base_uri"
+ value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/${myrpki::handle}-root/root">
<doc>
- rsync URI corresponding to directory containing rootd's outputs.
+ rsync URI corresponding to directory containing rootd's
+ outputs. This is only used to construct other variables,
+ rootd itself doesn't read it.
</doc>
</option>
<option name = "rpki-root-cert-uri"
- value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_root_module}/root.cer">
+ value = "${rootd::rpki_base_uri}.cer">
<doc>
rsync URI for rootd's root (self-signed) RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-key"
- value = "${myrpki::bpki_servers_directory}/root.key">
+ <option name = "rpki-root-cert-file"
+ value = "${rootd::rpki_data_dir}/root.cer">
+ <doc>
+ Filename of rootd's root RPKI certificate.
+ </doc>
+ </option>
+
+ <option name = "rpki-root-key-file"
+ value = "${rootd::rpki_data_dir}/root.key">
<doc>
Private key corresponding to rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-cert"
- value = "${myrpki::publication_root_cert_directory}/root.cer">
+ <option name = "rpki-root-crl-uri"
+ value = "${rootd::rpki_base_uri}/root.crl">
<doc>
- Filename (as opposed to rsync URI) of rootd's root RPKI
- certificate.
+ URI of the CRL for rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-subject-pkcs10"
- value = "${myrpki::bpki_servers_directory}/rootd.subject.pkcs10">
+ <option name = "rpki-root-crl-file"
+ value = "${rootd::rpki_data_dir}/root.crl">
<doc>
- Where rootd should stash a copy of the PKCS #10 request it gets
- from its one (and only) child
+ Filename of the CRL for rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-subject-lifetime"
- value = "30d">
+ <option name = "rpki-root-manifest-uri"
+ value = "${rootd::rpki_base_uri}/root.mft">
<doc>
- Lifetime of the one and only RPKI certificate rootd issues.
+ URI of the manifest for rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-crl"
- value = "root.crl">
+ <option name = "rpki-root-manifest-file"
+ value = "${rootd::rpki_data_dir}/root.mft">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the CRL
- for rootd's root RPKI certificate.
+ Filename of the manifest for rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-manifest"
- value = "root.mft">
+ <option name = "rpki-subject-pkcs10-file"
+ value = "${rootd::rpki_data_dir}/subject.pkcs10">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the
- manifest for rootd's root RPKI certificate.
+ Where rootd should stash a copy of the PKCS #10 request it gets
+ from its one (and only) child
+ </doc>
+ </option>
+
+ <option name = "rpki-subject-lifetime"
+ value = "30d">
+ <doc>
+ Lifetime of the one and only RPKI certificate rootd issues.
</doc>
</option>
@@ -803,43 +836,44 @@
</doc>
</option>
- <option name = "rpki-subject-cert"
- value = "${myrpki::handle}.cer">
+ <option name = "rpki-subject-cert-uri"
+ value = "${rootd::rpki_base_uri}/${myrpki::handle}.cer">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the one
- (and only) RPKI certificate rootd issues.
+ URI of the one (and only) RPKI certificate rootd issues.
</doc>
</option>
- </section>
-
- <section name = "web_portal">
-
- <doc>
- Glue to allow the Django application to pull user configuration
- from this file rather than directly editing settings.py.
- </doc>
-
- <option name = "sql-database"
- value = "${myrpki::irdbd_sql_database}">
+ <option name = "rpki-subject-cert-file"
+ value = "${rootd::rpki_data_dir}/${myrpki::handle}.cer">
<doc>
- SQL database name the web portal should use.
+ Filename of the one (and only) RPKI certificate rootd issues.
</doc>
</option>
- <option name = "sql-username"
- value = "${myrpki::irdbd_sql_username}">
+ <option name = "pubd-contact-uri"
+ value = "http://${myrpki::pubd_server_host}:${myrpki::pubd_server_port}/client/${myrpki::handle}-root">
<doc>
- SQL user name the web portal should use.
+ URI at which rootd should contact pubd for service.
</doc>
</option>
- <option name = "sql-password"
- value = "${myrpki::irdbd_sql_password}">
- <doc>
- SQL password the web portal should use.
- </doc>
- </option>
+ </section>
+
+ <section name = "web_portal">
+
+ <doc>
+ Glue to allow Django to pull user configuration from this file
+ rather than requiring the user to edit settings.py.
+ </doc>
+
+ <!--
+ We used to have SQL settings for the GUI here, but since
+ they're pretty much required to be identical to the ones for
+ irdbd at this point, the duplicate entries were just another
+ chance to misconfigure something, so I removed them. Not yet
+ sure whether this was the right approach. Too much historical
+ baggage in this file.
+ -->
<option name = "secret-key">
<doc>
diff --git a/ca/rpki-manage b/ca/rpki-manage
index 0d581ce9..db1e9ce3 100755
--- a/ca/rpki-manage
+++ b/ca/rpki-manage
@@ -5,9 +5,20 @@ from django.core.management import execute_from_command_line
# django-admin seems to have problems creating the superuser account when
# $LANG is unset or is set to something totally incompatible with UTF-8.
-if os.environ.get('LANG') in (None, "", "C"):
- os.environ['LANG'] = 'en_US.UTF-8'
-os.environ['DJANGO_SETTINGS_MODULE'] = 'rpki.gui.default_settings'
+if os.environ.get("LANG") in (None, "", "C"):
+ os.environ["LANG"] = "en_US.UTF-8"
+
+# Where to find the Django settings module
+
+os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings")
+
+# We don't know whether we're being used to configure the GUI or not
+# (well, not without examining the specific command, which we'd like
+# to avoid). Default to enabling the GUI so that such commands will
+# work, but allow the user to override via the environment variable.
+
+if not os.environ.get("RPKI_GUI_ENABLE"):
+ os.environ["RPKI_GUI_ENABLE"] = "yes"
execute_from_command_line()
diff --git a/ca/rpki-sql-backup b/ca/rpki-sql-backup
index e60f9ae3..02835956 100755
--- a/ca/rpki-sql-backup
+++ b/ca/rpki-sql-backup
@@ -41,7 +41,7 @@ parser.add_argument("-o", "--output",
help = "destination for SQL dump (default: stdout)")
args = parser.parse_args()
-cfg = rpki.config.parser(args.config, "myrpki")
+cfg = rpki.config.parser(set_filename = args.config, section = "myrpki")
for name in ("rpkid", "irdbd", "pubd"):
if cfg.getboolean("start_" + name, False):
diff --git a/ca/rpki-sql-setup b/ca/rpki-sql-setup
index edc2c242..848e3d0f 100755
--- a/ca/rpki-sql-setup
+++ b/ca/rpki-sql-setup
@@ -54,7 +54,7 @@ class RootDB(object):
user = "root",
passwd = getpass.getpass("Please enter your MySQL root password: "))
else:
- mysql_cfg = rpki.config.parser(self.mysql_defaults, "client")
+ mysql_cfg = rpki.config.parser(set_filename = self.mysql_defaults, section = "client")
self.db = MySQLdb.connect(db = "mysql",
user = mysql_cfg.get("user"),
passwd = mysql_cfg.get("password"))
@@ -299,7 +299,7 @@ parser.set_defaults(dispatch = do_create_if_missing)
args = parser.parse_args()
try:
- cfg = rpki.config.parser(args.config, "myrpki")
+ cfg = rpki.config.parser(set_filename = args.config, section = "myrpki")
root = RootDB(args.mysql_defaults)
current_version = Version(rpki.version.VERSION)
for program_name in ("irdbd", "rpkid", "pubd"):
diff --git a/ca/rpki-start-servers b/ca/rpki-start-servers
index 8a745896..f1f70aa8 100755
--- a/ca/rpki-start-servers
+++ b/ca/rpki-start-servers
@@ -64,13 +64,13 @@ group.add_argument("--log-syslog", default = "daemon", nargs = "?",
help = "log syslog")
args = parser.parse_args()
-cfg = rpki.config.parser(args.config, "myrpki")
+cfg = rpki.config.parser(set_filename = args.config, section = "myrpki")
def run(name, old_flag = None):
if cfg.getboolean("start_" + name, cfg.getboolean("run_" + name if old_flag is None else old_flag, False)):
# pylint: disable=E1103
log_file = os.path.join(args.log_directory, name + ".log")
- cmd = (os.path.join(rpki.autoconf.libexecdir, name), "--config", cfg.filename, "--log-level", args.log_level)
+ cmd = (os.path.join(rpki.autoconf.libexecdir, name), "--log-level", args.log_level)
if args.log_file:
cmd += ("--log-file", log_file)
elif args.log_rotating_file_kbytes:
diff --git a/ca/rpki.wsgi b/ca/rpki.wsgi
index 72ba75ac..7fa85d73 100644
--- a/ca/rpki.wsgi
+++ b/ca/rpki.wsgi
@@ -21,7 +21,8 @@ import sys
import os
import rpki.autoconf
-os.environ['DJANGO_SETTINGS_MODULE'] = 'rpki.gui.default_settings'
+os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings",
+ RPKI_GUI_ENABLE = "yes")
# Needed for local_settings.py
sys.path.insert(1, rpki.autoconf.sysconfdir + '/rpki')
diff --git a/ca/rpkigui-apache-conf-gen b/ca/rpkigui-apache-conf-gen
index f28bb7b3..1bd29e16 100755
--- a/ca/rpkigui-apache-conf-gen
+++ b/ca/rpkigui-apache-conf-gen
@@ -29,6 +29,50 @@ import rpki.autoconf
fqdn = socket.getfqdn()
vhost_template = """\
+
+#
+# Stuff that should be visible with both HTTP and HTTPS is (now)
+# outside the vhost block (see if this works properly...).
+#
+
+#
+# Allow access to the directory where rcynic-html writes
+# its output files.
+#
+<Directory %(RCYNIC_HTML_DIR)s>
+%(allow)s
+</Directory>
+
+#
+# Add alias pointing to rcynic-html's output files.
+#
+# If for some reason you need to change this, be careful to leave
+# the trailing slash off the URL, otherwise /rcynic will be
+# swallowed by the WSGIScriptAlias
+#
+Alias /rcynic %(RCYNIC_HTML_DIR)s/
+
+#
+# Allow access to the directory where pubd writes RRDP files.
+#
+<Directory %(datarootdir)s/rpki/rrdp-publication/>
+%(allow)s
+</Directory>
+
+#
+# Add alias pointing to pubd's RRD output files.
+#
+Alias /rrdp %(datarootdir)s/rpki/rrdp-publication/
+
+#
+# RRDP "notification" file needs a short expiration: this is
+# a critical part of how RRDP interacts with HTTP caching.
+#
+<LocationMatch ^/rrdp/updates[.]xml$>
+ ExpiresActive on
+ ExpiresDefault "access plus 5 minutes"
+</LocationMatch>
+
#
# By default, this configuration assumes that you use name-based
# virtual hosting. If that's not what you want, you may need
@@ -78,23 +122,6 @@ vhost_template = """\
Alias /site_media/ %(datarootdir)s/rpki/media/
#
- # Allow access to the directory where rcynic-html writes
- # its output files.
- #
- <Directory %(RCYNIC_HTML_DIR)s>
-%(allow)s
- </Directory>
-
- #
- # Add alias pointing to rcynic-html's output files.
- #
- # If for some reason you need to change this, be careful to leave
- # the trailing slash off the URL, otherwise /rcynic will be
- # swallowed by the WSGIScriptAlias
- #
- Alias /rcynic %(RCYNIC_HTML_DIR)s/
-
- #
# Redirect to the GUI dashboard when someone hits the bare vhost.
#
RedirectMatch ^/$ /rpki/
@@ -102,7 +129,7 @@ vhost_template = """\
#
# Enable HTTPS
#
- SSLEngine on
+ SSLEngine on
#
# Specify HTTPS server certificate and key files for this virtual host.
@@ -402,6 +429,7 @@ class Debian(Platform):
def enable(self):
self.run("a2enmod", "ssl")
+ self.run("a2enmod", "expires")
self.run("a2ensite", "rpki")
#
# In light of BREACH and CRIME attacks, mod_deflate is looking
diff --git a/ca/tests/Makefile.in b/ca/tests/Makefile.in
index 9796dd2b..618a741e 100644
--- a/ca/tests/Makefile.in
+++ b/ca/tests/Makefile.in
@@ -3,12 +3,11 @@
PYTHON = @PYTHON@
abs_top_builddir = @abs_top_builddir@
-all: protocol-samples
+all:
+ @true
clean:
- rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples yamltest.dir rcynic.xml rcynic-data
-
-protocol-samples: left-right-protocol-samples/.stamp publication-protocol-samples/.stamp
+ rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples rrdp-samples yamltest.dir rcynic.xml rcynic-data
left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protocol-samples.xsl
rm -rf left-right-protocol-samples
@@ -16,20 +15,44 @@ left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protoc
xsltproc --param verbose 0 --stringparam dir left-right-protocol-samples split-protocol-samples.xsl left-right-protocol-samples.xml
touch $@
+left-right-relaxng: left-right-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/left-right.rng left-right-protocol-samples/*.xml
+
publication-protocol-samples/.stamp: publication-protocol-samples.xml split-protocol-samples.xsl
rm -rf publication-protocol-samples
mkdir publication-protocol-samples
xsltproc --param verbose 0 --stringparam dir publication-protocol-samples split-protocol-samples.xsl publication-protocol-samples.xml
touch $@
-relaxng: protocol-samples
- xmllint --noout --relaxng ../../schemas/relaxng/left-right-schema.rng left-right-protocol-samples/*.xml
- xmllint --noout --relaxng ../../schemas/relaxng/up-down-schema.rng up-down-protocol-samples/*.xml
- xmllint --noout --relaxng ../../schemas/relaxng/publication-schema.rng publication-protocol-samples/*.xml
+publication-relaxng: publication-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/publication.rng publication-protocol-samples/*.xml
+
+publication-control-protocol-samples/.stamp: publication-control-protocol-samples.xml split-protocol-samples.xsl
+ rm -rf publication-control-protocol-samples
+ mkdir publication-control-protocol-samples
+ xsltproc --param verbose 0 --stringparam dir publication-control-protocol-samples split-protocol-samples.xsl publication-control-protocol-samples.xml
+ touch $@
+
+publication-control-relaxng: publication-control-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/publication-control.rng publication-control-protocol-samples/*.xml
+
+rrdp-samples/.stamp: rrdp-samples.xml split-protocol-samples.xsl
+ rm -rf rrdp-samples
+ mkdir rrdp-samples
+ xsltproc --param verbose 0 --stringparam dir rrdp-samples split-protocol-samples.xsl rrdp-samples.xml
+ touch $@
+
+rrdp-relaxng: rrdp-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/rrdp.rng rrdp-samples/*.xml
+
+up-down-relaxng:
+ xmllint --noout --relaxng ../../schemas/relaxng/up-down.rng up-down-protocol-samples/*.xml
+
+relaxng: up-down-relaxng left-right-relaxng publication-relaxng publication-control-relaxng rrdp-relaxng
all-tests:: relaxng
-parse-test: protocol-samples
+parse-test: left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples
${PYTHON} xml-parse-test.py
all-tests:: parse-test
diff --git a/ca/tests/publication-control-protocol-samples.xml b/ca/tests/publication-control-protocol-samples.xml
new file mode 100644
index 00000000..e094f3f6
--- /dev/null
+++ b/ca/tests/publication-control-protocol-samples.xml
@@ -0,0 +1,155 @@
+<!-- -*- SGML -*-
+ - $Id$
+ -
+ - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+ -
+ - Permission to use, copy, modify, and distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+ -
+ -
+ - This is a collection of sample publication protocol PDU samples
+ - to use as test cases for the publication protocol RelaxNG schema.
+ -->
+
+<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="create" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="set" client_handle="3">
+ <bpki_glue>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_glue>
+ </client>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="set" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="get" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="list"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="list" client_handle="3">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="destroy" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="destroy" client_handle="3"/>
+ </msg>
+
+ <!-- === -->
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <report_error error_code="your_hair_is_on_fire">text string</report_error>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <report_error error_code="your_hair_is_on_fire"/>
+ </msg>
+
+</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/publication-protocol-samples.xml b/ca/tests/publication-protocol-samples.xml
index 96b095a7..6d0a99a9 100644
--- a/ca/tests/publication-protocol-samples.xml
+++ b/ca/tests/publication-protocol-samples.xml
@@ -1,370 +1,107 @@
<!-- -*- SGML -*-
- - $Id$
+ - $Id$
-
- - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+ - Sample PDUs for RPKI publication protocol, from current I-D.
-
- - Permission to use, copy, modify, and distribute this software for any
- - purpose with or without fee is hereby granted, provided that the above
- - copyright notice and this permission notice appear in all copies.
+ - Copyright (c) 2014 IETF Trust and the persons identified as authors
+ - of the code. All rights reserved.
-
- - THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
- - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- - AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
- - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- - PERFORMANCE OF THIS SOFTWARE.
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
-
+ - * Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
-
- - This is a collection of sample publication protocol PDU samples
- - to use as test cases for the publication protocol RelaxNG schema.
+ - * Redistributions in binary form must reproduce the above copyright
+ - notice, this list of conditions and the following disclaimer in
+ - the documentation and/or other materials provided with the
+ - distribution.
+ -
+ - * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ - names of specific contributors, may be used to endorse or promote
+ - products derived from this software without specific prior written
+ - permission.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ - POSSIBILITY OF SUCH DAMAGE.
-->
<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1">
- <config action="set">
- <bpki_crl>
- MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm
- aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww
- CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt
- vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO
- cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac
- 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R
- 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA
- vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M=
- </bpki_crl>
- </config>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1">
- <config action="set"/>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1">
- <config action="get"/>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1">
- <config action="get">
- <bpki_crl>
- MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm
- aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww
- CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt
- vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO
- cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac
- 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R
- 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA
- vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M=
- </bpki_crl>
- </config>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="create" client_handle="3"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="set" client_handle="3">
- <bpki_glue>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_glue>
- </client>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="set" client_handle="3"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="get" client_handle="3"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="list"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="list" client_handle="3">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="destroy" client_handle="3"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="destroy" client_handle="3"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer">
- MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2
- NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4MDUyMjE4MDUxMloXDTA4MDUy
- NDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIxOEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4
- MjhGN0YxNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNs
- XIMQYRgNtC6Fr/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3
- F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQluffiNDjzteCCv
- oyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSmUDuZ1HDz1nDuYvIVKjB/MpH9
- aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiw
- x9z0oiDcE96QCz+B0vsjc9mGaA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGP
- BuUKtwKn2W3I8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV
- HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklSLzEvMzBx
- QUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEFBQcBAQQ5MDcwNQYIKwYBBQUH
- MAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB
- /wQOMAwwCgYIKwYBBQUHDgIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsG
- CCsGAQUFBwELBIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
- c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
- c3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56WUtQZnhjLm1uZjAaBggrBgEF
- BQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYBBQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAO
- AwUAwAACAQMFAcAAAiAwDgMFAsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7
- jtI2PJY6+zwv306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh
- M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFURazENztppsolH
- eTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel/SM/UvOArCCOBvf0Gz7kSuup
- DSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxdx28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu
- 6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBauY
- </certificate>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <!-- Zero or more PDUs -->
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <!-- Zero or more PDUs -->
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhE
+ RjRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4
+ MDUyMjE4MDUxMloXDTA4MDUyNDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIx
+ OEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4MjhGN0YxNzCCASIwDQYJKoZI
+ hvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNsXIMQYRgNtC6F
+ r/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3
+ F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQlu
+ ffiNDjzteCCvoyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSm
+ UDuZ1HDz1nDuYvIVKjB/MpH9aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/
+ o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiwx9z0oiDcE96QCz+B0vsjc9mG
+ aA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGPBuUKtwKn2W3I
+ 8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV
+ HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQv
+ UklSLzEvMzBxQUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEF
+ BQcBAQQ5MDcwNQYIKwYBBQUHMAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAv
+ dGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB/wQOMAwwCgYIKwYBBQUHDgIw
+ DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsGCCsGAQUFBwEL
+ BIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
+ c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9z
+ dDo0NDAwL3Rlc3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56
+ WUtQZnhjLm1uZjAaBggrBgEFBQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYB
+ BQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAOAwUAwAACAQMFAcAAAiAwDgMF
+ AsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7jtI2PJY6+zwv
+ 306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh
+ M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFUR
+ azENztppsolHeTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel
+ /SM/UvOArCCOBvf0Gz7kSuupDSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxd
+ x28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx
+ 0iwPYdLiDbdWFbtTdPcXBauY
+ </publish>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <report_error error_code="your_hair_is_on_fire">
+ Shampooing with sterno again, are we?
+ </report_error>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <report_error error_code="your_hair_is_on_fire"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer" hash="deadf00d"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/>
+ </msg>
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl">
- MIIBwzCBrAIBATANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2NkE5RTkx
- NzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzFw0wODA1MjIxODA0MTZaFw0wODA1MjIxODA1MTZa
- MBQwEgIBAhcNMDgwNTIyMTc1ODQwWqAvMC0wHwYDVR0jBBgwFoAU30qAF+ZqnpFy5GEtjPTI
- PyON4qMwCgYDVR0UBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAKkM0Fb/pJpHVHWZyjp4wojH
- W2KkvA/DFtBiz3moxocSnkDVP3QI19uVvqdC6nH3hJyFmsAMwULR0f1XU/V4j+X+FqYEl6Nv
- p8zAEPIB4r8xbEFs7udRwXRAjkJmOQbv9aomF2i+d7jpTFVJxShZWOgsoGEhIy/aktKQrOIR
- c4ZDrXpQwXVj2Y7+cGVfQ4gvnPOdlyLcnNovoegazATvA3EcidBNPWRg7XTCz0LVBEB7JgPd
- nNyXRg35HdMEHBl7U9uUQJXP7S02oaQ1ehNDMfaJPgBBpQtAnM1lIzJfevd9+e4ywGsRpxAV
- 8wxTXSPd1jwuKtS0kwrgsrQ8Ya85xUE=
- </crl>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft">
- MIIHCgYJKoZIhvcNAQcCoIIG+zCCBvcCAQMxDTALBglghkgBZQMEAgEwggEeBgsqhkiG9w0B
- CRABGqCCAQ0EggEJMIIBBQIBEhgPMjAwODA1MjIxODA1MTVaGA8yMDA4MDUyMjE4MDYxNVoG
- CWCGSAFlAwQCATCB0jBEFh9ZbTVUTzRJYnlDb0pNZ3E2R2o4dG41Mng5U0UuY2VyAyEA4L8Z
- WMyuhOx+o6kUfsRR++QjSaRaATy4UOeVtjvZVqYwRBYfWnRxbjB3NEVFbU9hclAzQmd1SUY3
- MDhhNTM4LmNlcgMhAGQI1gYJotxWmwzcmpLNFZJ656uWOjcPYANlbNz80xm8MEQWH2xxa1Vx
- RHEwMDBESW9ZVjlybXdLTGdrN2F6by5jZXIDIQB7jRAEpkPvc4s4PX9vDvnTifj3BIE145FO
- 1ne2kEejVqCCBBEwggQNMIIC9aADAgECAgEFMA0GCSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMT
- KDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBDRENEODI4RjdGMTcwHhcNMDgwNTIyMTc1
- NzQ5WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQDEyhERkRBMjMyMUJENEVCMDNFQTE1RkUy
- N0NGRkRGMEFGRkU1QjBFNjY4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/Gk
- AHW5pDqye0+TvUp7sl0rVgmTfeHpVp18ypxvuovogVJgkjEtBEikfaFU0646wYD6JM6IJFJX
- lWLWd7bVmhkWViKuZL0VmT2wpUToNHCLUGUQUVVX8R7oSHFdTArv2AqH+6yt0LmczDH1y2M6
- 2Tgkz9wZ9ryyuPx3VX4PkHzUMlkGFICj1fvyXkcAu8jBaxR9UME1c413TPaMi6lMh1HUmtVN
- LJMP5+/SnwEAW/Z3dPClCFIgQXK3nAKPVzAIwADEiqhK7cSchhO7ikI1CVt0XzG4n7oaILc3
- Hq/DAxyiutw5GlkUlKPri2YJzJ3+H4P+TveSa/b02fVA5csm/QIDAQABo4IBKjCCASYwHQYD
- VR0OBBYEFN/aIyG9TrA+oV/ifP/fCv/lsOZoMB8GA1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjw
- zc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJl
- ZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBETnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEB
- BFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2NhbGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8x
- L2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5jZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEF
- BQcOAjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADpsE9HfgVTgmX1WeJTE
- fm87CXuOoGH85RFiAngSt5kR4gYCyadklOZ7Eta+ERUZVu4tcKO6sJOTuHPfVrAvR0VpgH+j
- PvXboYWSfwJdi00BC28ScrVM2zarA7B10+J6Oq8tbFlAyVBkrbuPet/axmndBtGWhrBTynGl
- nc/5L371Lxy6CrOYqXO0Qx3SrOKaailAe3zTIpHQeACqnPdL00zIBw/hVy/VNaH1wy+FmhAz
- TsmsQUrMyovJcu/ry5w0KHlP8BTnqfykikCWR+Lw0VQHmpJGAbtrmsOeIbfLY1zl7A81lDAl
- AG/ZH1DUdDOUIXMLHWur+D2rwjp7RL16LHYxggGqMIIBpgIBA4AU39ojIb1OsD6hX+J8/98K
- /+Ww5mgwCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEaMBwGCSqG
- SIb3DQEJBTEPFw0wODA1MjIxODA1MTVaMC8GCSqGSIb3DQEJBDEiBCBj/GjEQw3LgKPf5DTz
- 8eu1fcp6/cQjqqne6ZqFkF42azANBgkqhkiG9w0BAQEFAASCAQBOY0uHNMwy/o1nFANSgha5
- PZxt8fz+wTrbeomCb+lxqQKq1clcSiQORVGc8NmqC8sS5OR3eTw/3qnK9yPHxz2UQ4hn1pBa
- +Zy5veM61qMaXCw6w98EyNcvUfA1AkezAjkabfHQDs3o4Ezh49thXXyRcBoF+O6Lmi+LZbT2
- 4jvfFbaXW9zsb6/DaoDkeHnlk+YYgfSP4wOnkK5uqxtDW8QpMPq3GGdIp0oJDkzEdj7VsWIL
- 9JP2mxxL8fTPVUyAPOmURYwYDXqhke2O9eVDiCYhrEfB8/84Rint4Cj8n5aCujnAtqtwxHpD
- 0NRYO/V1MjhG+ARy1vRH1Dm0r92RBam3
- </manifest>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa">
- MIIGmwYJKoZIhvcNAQcCoIIGjDCCBogCAQMxDTALBglghkgBZQMEAgEwKgYLKoZIhvcNAQkQ
- ARigGwQZMBcCAgKaMBEwDwQCAAEwCTAHAwUACgMALKCCBJgwggSUMIIDfKADAgECAgEJMA0G
- CSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMTKDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBD
- RENEODI4RjdGMTcwHhcNMDgwNTIyMTc1ODI0WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQD
- Eyg5NkE5MTRBODNBQjREMzQwQzhBMTg1N0RBRTZDMEEyRTA5M0I2QjNBMIIBIjANBgkqhkiG
- 9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoK50BjW5bcF4gsdaYhndtVADZvQk3RCsvuqDElF6uLi
- 9BYQq/NHyDOIMyJtvCmzjdv3Y135n1sNO7YvssqHlt7dMfCQTD5ND1GpFnQLdWP7stWM5AbO
- nJV6+PtDITUA/QHOli7Do0YCUgR6G+1QJsMu0DK+TRSzBJ6WP7WIYOBOOg3y/NKc1rkWhS1Q
- dcQepbHgQYZHzzpjNDR6+oYVuhuUEWx1P6O4pv/p+tpE0SDua7jBjMywIYHkPQBecf2IX1RU
- WNojB9dJlnRx5YUUneP2SvF2MrmdDbclgzwhf6alqD2OjiMuoBOG8yeTKcuhzCMnrFAklbst
- 6x3Rnq9BswIDAQABo4IBsTCCAa0wHQYDVR0OBBYEFJapFKg6tNNAyKGFfa5sCi4JO2s6MB8G
- A1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjwzc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5j
- Oi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBE
- TnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEBBFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2Nh
- bGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8xL2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5j
- ZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEFBQcOAjAOBgNVHQ8BAf8EBAMCB4AwYwYIKwYBBQUH
- AQsEVzBVMFMGCCsGAQUFBzALhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklS
- L1IwLzEvbHFrVXFEcTAwMERJb1lWOXJtd0tMZ2s3YXpvLnJvYTAgBggrBgEFBQcBBwEB/wQR
- MA8wDQQCAAEwBwMFAAoDACwwDQYJKoZIhvcNAQELBQADggEBAL8iHwsyGOYhhIf3nVuL361y
- TOJSP8SR0mtQLHULPl+GkYk+5MRNWtL8ucTXFvniYJtOCXEGGEIO9eDXvkQIXQSz/qbF9URQ
- fuf38ghRza257syVhal6UHTgCFYuRIO9CUjcU1vkWUxH05BBIHlYdtlIQbAG/mRsCPCEgSmG
- bbQaomGlUOqmJMlKxLLcoAtz2vDrwVotgHyfS5h2mgINFjnlLcNLTci+sfs7/aQAkDYx7K98
- se/ZlMorvGkFNhHoOTcGIrWkYsfkbTygVwWRm278PaB3o4449Kvsg/gb8BZeHXRs68cr5Mcf
- jP7Q6jeypjTgDBnwb1yzoJIKWszFuSgxggGqMIIBpgIBA4AUlqkUqDq000DIoYV9rmwKLgk7
- azowCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEYMBwGCSqGSIb3
- DQEJBTEPFw0wODA1MjIxNzU4MjRaMC8GCSqGSIb3DQEJBDEiBCDCyf9v9Wed515TRp2WwnyM
- 1rk6dB///X+aqIym2e9jdTANBgkqhkiG9w0BAQEFAASCAQAFvzrHeRPW+wn4WSyoyBEq0zKS
- Cyh5tu1qTR0NHs6Rr/p8Pk81P1HQLND/U+znJZKLWlO2niEHUXPIicPDYchbj8ApH9VxKA+1
- lCWllOzFAsYyZFr3/VNs9pVp2eT4F9eEYBrBVDSNrD72MMTlWm1T5MEXqltTJJOCKzUEX96x
- 91iW6A+4erop7S8hpCnxqkTin4bFVreqYcGc4CC4bh+L9pPqJnURcEk7Qeu/WEHQBm38voB4
- S11qRZNrJMQ99oiJR7hXDIBm66HjGqoUL2gPCfpgJEVVnM9pVv2k889z4eTTck2Qj54gga2W
- Xkvw4Je420aDx88s9T2+PqXcbZ4g
- </roa>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <report_error error_code="your_hair_is_on_fire">text string</report_error>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <report_error error_code="your_hair_is_on_fire"/>
- </msg>
</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/rrdp-samples.xml b/ca/tests/rrdp-samples.xml
new file mode 100644
index 00000000..0318b169
--- /dev/null
+++ b/ca/tests/rrdp-samples.xml
@@ -0,0 +1,88 @@
+<!-- -*- SGML -*-
+ - $Id$
+ -
+ - This is a collection of sample RRDP PDU samples to use as test
+ - cases for the RRDP RelaxNG schema.
+ -
+ - Need to figure out whose copyright should be on these examples.
+ - BSD in any case so makes little practical difference, just need to
+ - be sure we give proper credit. Might be RIPE, might be IETF
+ - Trust, might be us for derivative work. Slap ours on for the
+ - moment, fix when we figure this out.
+ -
+ - Copyright (C) 2014 Dragon Research Labs ("DRL")
+ -
+ - Permission to use, copy, modify, and distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+ -->
+
+<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
+
+ <!-- Notification file: lists current snapshots and deltas -->
+
+ <notification version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="203">
+ <snapshot uri="http://host.example/d9f6dc91-0394-40b9-9663-66aeb623a/snapshot/202.xml" hash="279b79fd8389e20585f26735ee70e0e4d4b8af23bb2e2e611c70e92d2433edea"/>
+ <delta from="156" to="183" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/156/183.xml" hash="a2d56ec180f2dde2a46bf90565932e25829b852a0b43107d5de6e41394c29100"/>
+ <delta from="183" to="184" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/183/184.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29200"/>
+ <delta from="184" to="197" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/184/197.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29201"/>
+ <delta from="197" to="203" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/197/203.xml" hash="a2d56ec180f2dde2a4f92e0565932e25829b852a0b43107d5de6e41394c29300"/>
+ </notification>
+
+ <!-- Snapshot segment: think DNS AXFR -->
+
+ <snapshot version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="1">
+ <publish uri="http://host.example/foo/bar/cer1.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XE
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer2.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer3.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD
+ </publish>
+ </snapshot>
+
+ <!-- Delta segment: think DNS IXFR -->
+
+ <deltas version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" from="0" to="3">
+ <delta serial="1">
+ <publish uri="http://host.example/foo/bar/cer1.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ </publish>
+ </delta>
+ <delta serial="2">
+ <withdraw uri="http://host.example/foo/bar/cer1.cer" hash="deadf00d"/>
+ <publish uri="http://host.example/foo/bar/cer2.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer3.cer" hash="deadf00d">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ </publish>
+ </delta>
+ <delta serial="3">
+ <withdraw uri="http://host.example/foo/bar/cer2.cer" hash="deadf00d"/>
+ </delta>
+ </deltas>
+
+</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/smoketest.py b/ca/tests/smoketest.py
index 32f11cc3..d24fc460 100644
--- a/ca/tests/smoketest.py
+++ b/ca/tests/smoketest.py
@@ -47,7 +47,7 @@ import rpki.http
import rpki.log
import rpki.left_right
import rpki.config
-import rpki.publication
+import rpki.publication_control
import rpki.async
from rpki.mysql_import import MySQLdb
@@ -68,7 +68,7 @@ parser.add_argument("yaml_file", type = argparse.FileType("r"),
help = "YAML description of test network")
args = parser.parse_args()
-cfg = rpki.config.parser(args.config, "smoketest", allow_missing = True)
+cfg = rpki.config.parser(set_filename = args.config, section = "smoketest", allow_missing = True)
# Load the YAML script early, so we can report errors ASAP
@@ -80,6 +80,7 @@ def allocate_port():
"""
Allocate a TCP port number.
"""
+
global base_port
p = base_port
base_port += 1
@@ -219,8 +220,8 @@ def main():
for a in db:
a.setup_bpki_certs()
- setup_publication(pubd_sql)
- setup_rootd(db.root, y.get("rootd", {}))
+ setup_publication(pubd_sql, db.root.irdb_db_name)
+ setup_rootd(db.root, y.get("rootd", {}), db)
setup_rsyncd()
setup_rcynic()
@@ -232,11 +233,13 @@ def main():
try:
logger.info("Starting rootd")
- rootd_process = subprocess.Popen((prog_python, prog_rootd, "--foreground", "--log-stdout", "--log-level", "debug", "--config", rootd_name + ".conf"))
+ rootd_process = subprocess.Popen((prog_python, prog_rootd, "--foreground", "--log-stdout", "--log-level", "debug"),
+ env = dict(os.environ, RPKI_CONF = rootd_name + ".conf"))
logger.info("Starting pubd")
- pubd_process = subprocess.Popen((prog_python, prog_pubd, "--foreground", "--log-stdout", "--log-level", "debug", "--config", pubd_name + ".conf") +
- (("-p", pubd_name + ".prof") if args.profile else ()))
+ pubd_process = subprocess.Popen((prog_python, prog_pubd, "--foreground", "--log-stdout", "--log-level", "debug") +
+ (("-p", pubd_name + ".prof") if args.profile else ()),
+ env = dict(os.environ, RPKI_CONF = pubd_name + ".conf"))
logger.info("Starting rsyncd")
rsyncd_process = subprocess.Popen((prog_rsyncd, "--daemon", "--no-detach", "--config", rsyncd_name + ".conf"))
@@ -249,15 +252,17 @@ def main():
# the code until final exit is all closures.
def start():
- rpki.async.iterator(db.engines, create_rpki_objects, created_rpki_objects)
+ rpki.async.iterator(db.engines, create_rpki_objects, create_pubd_objects)
def create_rpki_objects(iterator, a):
a.create_rpki_objects(iterator)
- def created_rpki_objects():
-
- # Set pubd's BPKI CRL
- set_pubd_crl(yaml_loop)
+ def create_pubd_objects():
+ call_pubd([rpki.publication_control.client_elt.make_pdu(action = "create",
+ client_handle = db.root.client_handle + "-" + rootd_name,
+ base_uri = rootd_sia,
+ bpki_cert = cross_certify(rootd_name + "-TA", pubd_name + "-TA"))],
+ cb = lambda ignored: yaml_loop())
def yaml_loop():
@@ -324,6 +329,7 @@ def cmd_sleep(cb, interval):
"""
Set an alarm, then wait for it to go off.
"""
+
howlong = rpki.sundial.timedelta.parse(interval)
logger.info("Sleeping %r", howlong)
rpki.async.timer(cb).set(howlong)
@@ -332,6 +338,7 @@ def cmd_shell(cb, *cmd):
"""
Run a shell command.
"""
+
cmd = " ".join(cmd)
status = subprocess.call(cmd, shell = True)
logger.info("Shell command returned status %d", status)
@@ -341,6 +348,7 @@ def cmd_echo(cb, *words):
"""
Echo some text to the log.
"""
+
logger.info(" ".join(words))
cb()
@@ -498,6 +506,7 @@ class allocation_db(list):
"""
Print content of the database.
"""
+
for a in self:
print a
@@ -518,6 +527,7 @@ class allocation(object):
"""
Initialize one entity and insert it into the database.
"""
+
db.append(self)
self.name = yaml["name"]
self.parent = parent
@@ -554,6 +564,7 @@ class allocation(object):
"""
Compute the transitive resource closure.
"""
+
resources = self.base
for kid in self.kids:
resources |= kid.closure()
@@ -708,6 +719,7 @@ class allocation(object):
"""
Set the engine number for this entity.
"""
+
self.irdb_db_name = "irdb%d" % n
self.irdb_port = allocate_port()
self.rpki_db_name = "rpki%d" % n
@@ -717,6 +729,7 @@ class allocation(object):
"""
Get rpki port to use for this entity.
"""
+
if self.is_hosted:
assert self.hosted_by.rpki_port is not None
return self.hosted_by.rpki_port
@@ -728,6 +741,7 @@ class allocation(object):
"""
Create BPKI certificates for this entity.
"""
+
logger.info("Constructing BPKI keys and certs for %s", self.name)
setup_bpki_cert_chain(name = self.name,
ee = ("RPKI", "IRDB", "IRBE"),
@@ -741,15 +755,16 @@ class allocation(object):
"""
Write config files for this entity.
"""
+
logger.info("Writing config files for %s", self.name)
assert self.rpki_port is not None
- d = { "my_name" : self.name,
- "irdb_db_name" : self.irdb_db_name,
- "irdb_db_pass" : irdb_db_pass,
- "irdb_port" : self.irdb_port,
- "rpki_db_name" : self.rpki_db_name,
- "rpki_db_pass" : rpki_db_pass,
- "rpki_port" : self.rpki_port }
+ d = dict(my_name = self.name,
+ irdb_db_name = self.irdb_db_name,
+ irdb_db_pass = irdb_db_pass,
+ irdb_port = self.irdb_port,
+ rpki_db_name = self.rpki_db_name,
+ rpki_db_pass = rpki_db_pass,
+ rpki_port = self.rpki_port)
f = open(self.name + ".conf", "w")
f.write(conf_fmt_1 % d)
for line in self.extra_conf:
@@ -760,6 +775,7 @@ class allocation(object):
"""
Set up this entity's IRDB.
"""
+
logger.info("Setting up MySQL for %s", self.name)
db = MySQLdb.connect(user = "rpki", db = self.rpki_db_name, passwd = rpki_db_pass,
conv = sql_conversions)
@@ -794,6 +810,7 @@ class allocation(object):
once during setup, then do it again every time we apply a delta to
this entity.
"""
+
logger.info("Updating MySQL data for IRDB %s", self.name)
db = MySQLdb.connect(user = "irdb", db = self.irdb_db_name, passwd = irdb_db_pass,
conv = sql_conversions)
@@ -847,15 +864,20 @@ class allocation(object):
"""
Run daemons for this entity.
"""
+
logger.info("Running daemons for %s", self.name)
- self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "--foreground", "--log-stdout", "--log-level", "debug", "--config", self.name + ".conf") +
- (("--profile", self.name + ".prof") if args.profile else ()))
- self.irdbd_process = subprocess.Popen((prog_python, prog_irdbd, "--foreground", "--log-stdout", "--log-level", "debug", "--config", self.name + ".conf"))
+ env = dict(os.environ, RPKI_CONF = self.name + ".conf")
+ self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "--foreground", "--log-stdout", "--log-level", "debug") +
+ (("--profile", self.name + ".prof") if args.profile else ()),
+ env = env)
+ self.irdbd_process = subprocess.Popen((prog_python, prog_irdbd, "--foreground", "--log-stdout", "--log-level", "debug"),
+ env = env)
def kill_daemons(self):
"""
Kill daemons for this entity.
"""
+
# pylint: disable=E1103
for proc, name in ((self.rpkid_process, "rpkid"),
(self.irdbd_process, "irdbd")):
@@ -925,45 +947,7 @@ class allocation(object):
certificant = self.name + "-SELF"
else:
certifier = self.name + "-SELF"
- certfile = certifier + "-" + certificant + ".cer"
-
- logger.info("Cross certifying %s into %s's BPKI (%s)", certificant, certifier, certfile)
-
- child = rpki.x509.X509(Auto_file = certificant + ".cer")
- parent = rpki.x509.X509(Auto_file = certifier + ".cer")
- keypair = rpki.x509.RSA(Auto_file = certifier + ".key")
- serial_file = certifier + ".srl"
-
- now = rpki.sundial.now()
- notAfter = now + rpki.sundial.timedelta(days = 30)
-
- try:
- f = open(serial_file, "r")
- serial = f.read()
- f.close()
- serial = int(serial.splitlines()[0], 16)
- except IOError:
- serial = 1
-
- x = parent.bpki_cross_certify(
- keypair = keypair,
- source_cert = child,
- serial = serial,
- notAfter = notAfter,
- now = now)
-
- f = open(serial_file, "w")
- f.write("%02x\n" % (serial + 1))
- f.close()
-
- f = open(certfile, "w")
- f.write(x.get_PEM())
- f.close()
-
- logger.debug("Cross certified %s:", certfile)
- logger.debug(" Issuer %s [%s]", x.getIssuer(), x.hAKI())
- logger.debug(" Subject %s [%s]", x.getSubject(), x.hSKI())
- return x
+ return cross_certify(certificant, certifier)
def create_rpki_objects(self, cb):
"""
@@ -982,13 +966,11 @@ class allocation(object):
selves = [self] + self.hosts
- for i, s in enumerate(selves):
- logger.info("Creating RPKI objects for [%d] %s", i, s.name)
-
rpkid_pdus = []
pubd_pdus = []
- for s in selves:
+ for i, s in enumerate(selves):
+ logger.info("Creating RPKI objects for [%d] %s", i, s.name)
rpkid_pdus.append(rpki.left_right.self_elt.make_pdu(
action = "create",
@@ -1005,7 +987,7 @@ class allocation(object):
bsc_handle = "b",
generate_keypair = True))
- pubd_pdus.append(rpki.publication.client_elt.make_pdu(
+ pubd_pdus.append(rpki.publication_control.client_elt.make_pdu(
action = "create",
client_handle = s.client_handle,
base_uri = s.sia_base,
@@ -1126,18 +1108,6 @@ class allocation(object):
self.cross_certify(self.parent.name + "-SELF")
self.cross_certify(parent_host + "-TA")
- logger.info("Writing leaf YAML for %s", self.name)
- f = open(self.name + ".yaml", "w")
- f.write(yaml_fmt_1 % {
- "parent_name" : self.parent.name,
- "parent_host" : parent_host,
- "my_name" : self.name,
- "http_port" : self.parent.get_rpki_port(),
- "class_name" : 2 if self.parent.is_hosted else 1,
- "sia" : self.sia_base,
- "ski" : ski })
- f.close()
-
def run_cron(self, cb):
"""
Trigger cron run for this engine.
@@ -1174,20 +1144,22 @@ def setup_bpki_cert_chain(name, ee = (), ca = ()):
"""
Build a set of BPKI certificates.
"""
+
s = "exec >/dev/null 2>&1\n"
#s = "set -x\n"
for kind in ("TA",) + ee + ca:
- d = { "name" : name,
- "kind" : kind,
- "ca" : "false" if kind in ee else "true",
- "openssl" : prog_openssl }
+ d = dict(name = name,
+ kind = kind,
+ ca = "false" if kind in ee else "true",
+ openssl = prog_openssl)
f = open("%(name)s-%(kind)s.conf" % d, "w")
f.write(bpki_cert_fmt_1 % d)
f.close()
if not os.path.exists("%(name)s-%(kind)s.key" % d):
s += bpki_cert_fmt_2 % d
s += bpki_cert_fmt_3 % d
- d = { "name" : name, "openssl" : prog_openssl }
+ d = dict(name = name,
+ openssl = prog_openssl)
s += bpki_cert_fmt_4 % d
for kind in ee + ca:
d["kind"] = kind
@@ -1197,19 +1169,24 @@ def setup_bpki_cert_chain(name, ee = (), ca = ()):
s += bpki_cert_fmt_6 % d
subprocess.check_call(s, shell = True)
-def setup_rootd(rpkid, rootd_yaml):
+def setup_rootd(rpkid, rootd_yaml, db):
"""
Write the config files for rootd.
"""
+
rpkid.cross_certify(rootd_name + "-TA", reverse = True)
+ cross_certify(pubd_name + "-TA", rootd_name + "-TA")
logger.info("Writing config files for %s", rootd_name)
- d = { "rootd_name" : rootd_name,
- "rootd_port" : rootd_port,
- "rpkid_name" : rpkid.name,
- "rootd_sia" : rootd_sia,
- "rsyncd_dir" : rsyncd_dir,
- "openssl" : prog_openssl,
- "lifetime" : rootd_yaml.get("lifetime", "30d") }
+ d = dict(rootd_name = rootd_name,
+ rootd_port = rootd_port,
+ rpkid_name = rpkid.name,
+ pubd_name = pubd_name,
+ rootd_sia = rootd_sia,
+ rsyncd_dir = rsyncd_dir,
+ openssl = prog_openssl,
+ lifetime = rootd_yaml.get("lifetime", "30d"),
+ pubd_port = pubd_port,
+ rootd_handle = db.root.client_handle + "-" + rootd_name)
f = open(rootd_name + ".conf", "w")
f.write(rootd_fmt_1 % d)
f.close()
@@ -1224,10 +1201,11 @@ def setup_rcynic():
"""
Write the config file for rcynic.
"""
+
logger.info("Config file for rcynic")
- d = { "rcynic_name" : rcynic_name,
- "rootd_name" : rootd_name,
- "rootd_sia" : rootd_sia }
+ d = dict(rcynic_name = rcynic_name,
+ rootd_name = rootd_name,
+ rootd_sia = rootd_sia)
f = open(rcynic_name + ".conf", "w")
f.write(rcynic_fmt_1 % d)
f.close()
@@ -1236,19 +1214,21 @@ def setup_rsyncd():
"""
Write the config file for rsyncd.
"""
+
logger.info("Config file for rsyncd")
- d = { "rsyncd_name" : rsyncd_name,
- "rsyncd_port" : rsyncd_port,
- "rsyncd_module" : rsyncd_module,
- "rsyncd_dir" : rsyncd_dir }
+ d = dict(rsyncd_name = rsyncd_name,
+ rsyncd_port = rsyncd_port,
+ rsyncd_module = rsyncd_module,
+ rsyncd_dir = rsyncd_dir)
f = open(rsyncd_name + ".conf", "w")
f.write(rsyncd_fmt_1 % d)
f.close()
-def setup_publication(pubd_sql):
+def setup_publication(pubd_sql, irdb_db_name):
"""
Set up publication daemon.
"""
+
logger.info("Configure publication daemon")
publication_dir = os.getcwd() + "/publication"
assert rootd_sia.startswith("rsync://")
@@ -1268,12 +1248,14 @@ def setup_publication(pubd_sql):
if "DROP TABLE IF EXISTS" not in sql.upper():
raise
db.close()
- d = { "pubd_name" : pubd_name,
- "pubd_port" : pubd_port,
- "pubd_db_name" : pubd_db_name,
- "pubd_db_user" : pubd_db_user,
- "pubd_db_pass" : pubd_db_pass,
- "pubd_dir" : rsyncd_dir }
+ d = dict(pubd_name = pubd_name,
+ pubd_port = pubd_port,
+ pubd_db_name = pubd_db_name,
+ pubd_db_user = pubd_db_user,
+ pubd_db_pass = pubd_db_pass,
+ pubd_dir = rsyncd_dir,
+ irdb_db_name = irdb_db_name,
+ irdb_db_pass = irdb_db_pass)
f = open(pubd_name + ".conf", "w")
f.write(pubd_fmt_1 % d)
f.close()
@@ -1288,12 +1270,13 @@ def setup_publication(pubd_sql):
def call_pubd(pdus, cb):
"""
- Send a publication message to publication daemon and return the
- response.
+ Send a publication control message to publication daemon and return
+ the response.
"""
+
logger.info("Calling pubd")
- q_msg = rpki.publication.msg.query(*pdus)
- q_cms = rpki.publication.cms_msg()
+ q_msg = rpki.publication_control.msg.query(*pdus)
+ q_cms = rpki.publication_control.cms_msg()
q_der = q_cms.wrap(q_msg, pubd_irbe_key, pubd_irbe_cert)
q_url = "http://localhost:%d/control" % pubd_port
@@ -1301,13 +1284,13 @@ def call_pubd(pdus, cb):
def call_pubd_cb(r_der):
global pubd_last_cms_time
- r_cms = rpki.publication.cms_msg(DER = r_der)
+ r_cms = rpki.publication_control.cms_msg(DER = r_der)
r_msg = r_cms.unwrap((pubd_ta, pubd_pubd_cert))
pubd_last_cms_time = r_cms.check_replay(pubd_last_cms_time, q_url)
logger.debug(r_cms.pretty_print_content())
assert r_msg.is_reply
for r_pdu in r_msg:
- assert not isinstance(r_pdu, rpki.publication.report_error_elt)
+ r_pdu.raise_if_error()
cb(r_msg)
def call_pubd_eb(e):
@@ -1319,15 +1302,47 @@ def call_pubd(pdus, cb):
callback = call_pubd_cb,
errback = call_pubd_eb)
-def set_pubd_crl(cb):
+
+def cross_certify(certificant, certifier):
"""
- Whack publication daemon's bpki_crl. This must be configured before
- publication daemon starts talking to its clients, and must be
- updated whenever we update the CRL.
+ Cross-certify and return the resulting certificate.
"""
- logger.info("Setting pubd's BPKI CRL")
- crl = rpki.x509.CRL(Auto_file = pubd_name + "-TA.crl")
- call_pubd([rpki.publication.config_elt.make_pdu(action = "set", bpki_crl = crl)], cb = lambda ignored: cb())
+
+ certfile = certifier + "-" + certificant + ".cer"
+
+ logger.info("Cross certifying %s into %s's BPKI (%s)", certificant, certifier, certfile)
+
+ child = rpki.x509.X509(Auto_file = certificant + ".cer")
+ parent = rpki.x509.X509(Auto_file = certifier + ".cer")
+ keypair = rpki.x509.RSA(Auto_file = certifier + ".key")
+ serial_file = certifier + ".srl"
+
+ now = rpki.sundial.now()
+ notAfter = now + rpki.sundial.timedelta(days = 30)
+
+ try:
+ with open(serial_file, "r") as f:
+ serial = int(f.read().splitlines()[0], 16)
+ except IOError:
+ serial = 1
+
+ x = parent.bpki_cross_certify(
+ keypair = keypair,
+ source_cert = child,
+ serial = serial,
+ notAfter = notAfter,
+ now = now)
+
+ with open(serial_file, "w") as f:
+ f.write("%02x\n" % (serial + 1))
+
+ with open(certfile, "w") as f:
+ f.write(x.get_PEM())
+
+ logger.debug("Cross certified %s:", certfile)
+ logger.debug(" Issuer %s [%s]", x.getIssuer(), x.hAKI())
+ logger.debug(" Subject %s [%s]", x.getSubject(), x.hSKI())
+ return x
last_rcynic_run = None
@@ -1335,6 +1350,7 @@ def run_rcynic():
"""
Run rcynic to see whether what was published makes sense.
"""
+
logger.info("Running rcynic")
env = os.environ.copy()
env["TZ"] = ""
@@ -1350,6 +1366,7 @@ def mangle_sql(filename):
"""
Mangle an SQL file into a sequence of SQL statements.
"""
+
words = []
f = open(filename)
for line in f:
@@ -1432,88 +1449,57 @@ bpki_cert_fmt_6 = ''' && \
-config %(name)s-%(kind)s.conf \
'''
-yaml_fmt_1 = '''---
-version: 1
-posturl: http://localhost:%(http_port)s/up-down/%(parent_name)s/%(my_name)s
-recipient-id: "%(parent_name)s"
-sender-id: "%(my_name)s"
-
-cms-cert-file: %(my_name)s-RPKI.cer
-cms-key-file: %(my_name)s-RPKI.key
-cms-ca-cert-file: %(my_name)s-TA.cer
-cms-crl-file: %(my_name)s-TA.crl
-cms-ca-certs-file:
- - %(my_name)s-TA-%(parent_name)s-SELF.cer
-
-ssl-cert-file: %(my_name)s-RPKI.cer
-ssl-key-file: %(my_name)s-RPKI.key
-ssl-ca-cert-file: %(my_name)s-TA.cer
-ssl-ca-certs-file:
- - %(my_name)s-TA-%(parent_host)s-TA.cer
-
-# We're cheating here by hardwiring the class name
-
-requests:
- list:
- type: list
- issue:
- type: issue
- class: %(class_name)s
- sia:
- - %(sia)s
- cert-request-key-file: %(my_name)s.key
- revoke:
- type: revoke
- class: %(class_name)s
- ski: %(ski)s
-'''
-
conf_fmt_1 = '''\
[irdbd]
-startup-message = This is %(my_name)s irdbd
+startup-message = This is %(my_name)s irdbd
-sql-database = %(irdb_db_name)s
-sql-username = irdb
-sql-password = %(irdb_db_pass)s
-bpki-ta = %(my_name)s-TA.cer
-rpkid-cert = %(my_name)s-RPKI.cer
-irdbd-cert = %(my_name)s-IRDB.cer
-irdbd-key = %(my_name)s-IRDB.key
-http-url = http://localhost:%(irdb_port)d/
-enable_tracebacks = yes
+sql-database = %(irdb_db_name)s
+sql-username = irdb
+sql-password = %(irdb_db_pass)s
+bpki-ta = %(my_name)s-TA.cer
+rpkid-cert = %(my_name)s-RPKI.cer
+irdbd-cert = %(my_name)s-IRDB.cer
+irdbd-key = %(my_name)s-IRDB.key
+http-url = http://localhost:%(irdb_port)d/
+enable_tracebacks = yes
[irbe_cli]
-rpkid-bpki-ta = %(my_name)s-TA.cer
-rpkid-cert = %(my_name)s-RPKI.cer
-rpkid-irbe-cert = %(my_name)s-IRBE.cer
-rpkid-irbe-key = %(my_name)s-IRBE.key
-rpkid-url = http://localhost:%(rpki_port)d/left-right
-enable_tracebacks = yes
+rpkid-bpki-ta = %(my_name)s-TA.cer
+rpkid-cert = %(my_name)s-RPKI.cer
+rpkid-irbe-cert = %(my_name)s-IRBE.cer
+rpkid-irbe-key = %(my_name)s-IRBE.key
+rpkid-url = http://localhost:%(rpki_port)d/left-right
+enable_tracebacks = yes
[rpkid]
-startup-message = This is %(my_name)s rpkid
+startup-message = This is %(my_name)s rpkid
-sql-database = %(rpki_db_name)s
-sql-username = rpki
-sql-password = %(rpki_db_pass)s
+sql-database = %(rpki_db_name)s
+sql-username = rpki
+sql-password = %(rpki_db_pass)s
-bpki-ta = %(my_name)s-TA.cer
-rpkid-key = %(my_name)s-RPKI.key
-rpkid-cert = %(my_name)s-RPKI.cer
-irdb-cert = %(my_name)s-IRDB.cer
-irbe-cert = %(my_name)s-IRBE.cer
+bpki-ta = %(my_name)s-TA.cer
+rpkid-key = %(my_name)s-RPKI.key
+rpkid-cert = %(my_name)s-RPKI.cer
+irdb-cert = %(my_name)s-IRDB.cer
+irbe-cert = %(my_name)s-IRBE.cer
-irdb-url = http://localhost:%(irdb_port)d/
+irdb-url = http://localhost:%(irdb_port)d/
-server-host = localhost
-server-port = %(rpki_port)d
+server-host = localhost
+server-port = %(rpki_port)d
+
+use-internal-cron = false
+enable_tracebacks = yes
-use-internal-cron = false
-enable_tracebacks = yes
+[myrpki]
+start_rpkid = yes
+start_irdbd = yes
+start_pubd = no
'''
rootd_fmt_1 = '''\
@@ -1525,24 +1511,28 @@ rootd-bpki-cert = %(rootd_name)s-RPKI.cer
rootd-bpki-key = %(rootd_name)s-RPKI.key
rootd-bpki-crl = %(rootd_name)s-TA.crl
child-bpki-cert = %(rootd_name)s-TA-%(rpkid_name)s-SELF.cer
+pubd-bpki-cert = %(rootd_name)s-TA-%(pubd_name)s-TA.cer
server-port = %(rootd_port)s
-rpki-root-dir = %(rsyncd_dir)sroot
-rpki-base-uri = %(rootd_sia)sroot/
-rpki-root-cert-uri = %(rootd_sia)sroot.cer
+rpki-class-name = trunk
+
+pubd-contact-uri = http://localhost:%(pubd_port)d/client/%(rootd_handle)s
-rpki-root-key = root.key
-rpki-root-cert = root.cer
+rpki-root-cert-file = root.cer
+rpki-root-cert-uri = %(rootd_sia)sroot.cer
+rpki-root-key-file = root.key
-rpki-subject-pkcs10 = %(rootd_name)s.subject.pkcs10
+rpki-subject-cert-file = trunk.cer
+rpki-subject-cert-uri = %(rootd_sia)sroot/trunk.cer
+rpki-subject-pkcs10-file= trunk.p10
rpki-subject-lifetime = %(lifetime)s
-rpki-root-crl = root.crl
-rpki-root-manifest = root.mft
+rpki-root-crl-file = root.crl
+rpki-root-crl-uri = %(rootd_sia)sroot/root.crl
-rpki-class-name = trunk
-rpki-subject-cert = trunk.cer
+rpki-root-manifest-file = root.mft
+rpki-root-manifest-uri = %(rootd_sia)sroot/root.mft
include-bpki-crl = yes
enable_tracebacks = yes
@@ -1579,7 +1569,7 @@ certificatePolicies = critical, @rpki_certificate_policy
[rpki_certificate_policy]
-policyIdentifier = 1.3.6.1.5.5.7.14.2
+policyIdentifier = 1.3.6.1.5.5.7.14.2
'''
rootd_fmt_2 = '''\
@@ -1602,8 +1592,7 @@ awk '!/-----(BEGIN|END)/' >>%(rootd_name)s.tal &&
-outform DER \
-extfile %(rootd_name)s.conf \
-extensions req_x509_rpki_ext \
- -signkey root.key &&
-ln -f root.cer %(rsyncd_dir)s
+ -signkey root.key
'''
rcynic_fmt_1 = '''\
@@ -1636,6 +1625,7 @@ sql-database = %(pubd_db_name)s
sql-username = %(pubd_db_user)s
sql-password = %(pubd_db_pass)s
bpki-ta = %(pubd_name)s-TA.cer
+pubd-crl = %(pubd_name)s-TA.crl
pubd-cert = %(pubd_name)s-PUBD.cer
pubd-key = %(pubd_name)s-PUBD.key
irbe-cert = %(pubd_name)s-IRBE.cer
@@ -1643,6 +1633,17 @@ server-host = localhost
server-port = %(pubd_port)d
publication-base = %(pubd_dir)s
enable_tracebacks = yes
+
+[irdbd]
+
+sql-database = %(irdb_db_name)s
+sql-username = irdb
+sql-password = %(irdb_db_pass)s
+
+[myrpki]
+start_rpkid = no
+start_irdbd = no
+start_pubd = yes
'''
main()
diff --git a/ca/tests/sql-cleaner.py b/ca/tests/sql-cleaner.py
index ca88d456..369a68ea 100644
--- a/ca/tests/sql-cleaner.py
+++ b/ca/tests/sql-cleaner.py
@@ -22,18 +22,21 @@ import rpki.config
import rpki.sql_schemas
from rpki.mysql_import import MySQLdb
-cfg = rpki.config.parser(None, "yamltest", allow_missing = True)
+cfg = rpki.config.parser(section = "yamltest", allow_missing = True)
for name in ("rpkid", "irdbd", "pubd"):
username = cfg.get("%s_sql_username" % name, name[:4])
password = cfg.get("%s_sql_password" % name, "fnord")
+ # All of this schema creation stuff will go away once we're on Django ORM.
+ # For the moment, a quick kludge for testing.
schema = []
- for line in getattr(rpki.sql_schemas, name, "").splitlines():
- schema.extend(line.partition("--")[0].split())
- schema = " ".join(schema).strip(";").split(";")
- schema = [statement.strip() for statement in schema if statement and "DROP TABLE" not in statement]
+ if name == "rpkid":
+ for line in getattr(rpki.sql_schemas, name, "").splitlines():
+ schema.extend(line.partition("--")[0].split())
+ schema = " ".join(schema).strip(";").split(";")
+ schema = [statement.strip() for statement in schema if statement and "DROP TABLE" not in statement]
db = MySQLdb.connect(user = username, passwd = password)
cur = db.cursor()
diff --git a/ca/tests/sql-dumper.py b/ca/tests/sql-dumper.py
index 19cc1b34..d0fe3489 100644
--- a/ca/tests/sql-dumper.py
+++ b/ca/tests/sql-dumper.py
@@ -22,7 +22,7 @@ import subprocess
import rpki.config
from rpki.mysql_import import MySQLdb
-cfg = rpki.config.parser(None, "yamltest", allow_missing = True)
+cfg = rpki.config.parser(section = "yamltest", allow_missing = True)
for name in ("rpkid", "irdbd", "pubd"):
diff --git a/ca/tests/test-rrdp.py b/ca/tests/test-rrdp.py
new file mode 100755
index 00000000..db626a35
--- /dev/null
+++ b/ca/tests/test-rrdp.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2013 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Proof-of-concept test driver for RRDP code. Still fairly kludgy in places.
+"""
+
+import os
+import sys
+import glob
+import time
+import signal
+import textwrap
+import argparse
+import subprocess
+
+parser = argparse.ArgumentParser(description = __doc__)
+parser.add_argument("--use-smoketest", action = "store_true")
+parser.add_argument("--yaml-file", default = "smoketest.2.yaml")
+parser.add_argument("--delay", type = int, default = 30)
+parser.add_argument("--exhaustive", action = "store_true")
+parser.add_argument("--skip-daemons", action = "store_true")
+args = parser.parse_args()
+
+def log(msg):
+ sys.stdout.write(msg + "\n")
+ sys.stdout.flush()
+
+def run(*argv):
+ log("Running: " + " ".join(argv))
+ subprocess.check_call(argv)
+
+def dataglob(pattern):
+ return glob.iglob(os.path.join(("smoketest.dir" if args.use_smoketest else "yamltest.dir/RIR"), pattern))
+
+def snapshot_to_serial(fn):
+ return int(os.path.splitext(os.path.basename(fn))[0])
+
+def delta_to_serial(fn):
+ return int(os.path.splitext(os.path.basename(fn))[0].split("-")[1])
+
+top = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..", ".."))
+
+rrdp_test_tool = os.path.join(top, "potpourri/rrdp-test-tool")
+rcynic = os.path.join(top, "rp/rcynic/rcynic")
+rcynic_text = os.path.join(top, "rp/rcynic/rcynic-text")
+
+with open("rcynic-rrdp.conf", "w") as f:
+ f.write(textwrap.dedent('''# Automatically generated for RRDP tests, do not edit.
+ [rcynic]
+ xml-summary = rcynic.xml
+ jitter = 0
+ use-links = yes
+ use-syslog = no
+ use-stderr = yes
+ log-level = log_debug
+ run-rsync = no
+ '''))
+ if args.use_smoketest:
+ f.write("trust-anchor = smoketest.dir/root.cer\n")
+ else:
+ f.write("trust-anchor = yamltest.dir/RIR/publication/RIR-root/root.cer\n")
+
+if args.skip_daemons:
+ log("--skip-daemons specified, so running neither smoketest nor yamltest")
+elif args.use_smoketest:
+ run("python", "smoketest.py", args.yaml_file)
+else:
+ run("python", "sql-cleaner.py")
+ class GotSIGUSR1(Exception):
+ pass
+ def handle_sigusr1(signum, frame):
+ raise GotSIGUSR1
+ old_sigusr1 = signal.signal(signal.SIGUSR1, handle_sigusr1)
+ argv = ("python", "yamltest.py", args.yaml_file, "--notify-when-startup-complete", str(os.getpid()))
+ log("Running: " + " ".join(argv))
+ yamltest = subprocess.Popen(argv)
+ log("Waiting for SIGUSR1 from yamltest")
+ try:
+ while True:
+ signal.pause()
+ except GotSIGUSR1:
+ signal.signal(signal.SIGUSR1, old_sigusr1)
+ log("Sleeping %s" % args.delay)
+ time.sleep(args.delay)
+ yamltest.terminate()
+
+snapshots = dict((snapshot_to_serial(fn), fn) for fn in dataglob("rrdp-publication/*/snapshot/*.xml"))
+deltas = dict((delta_to_serial(fn), fn) for fn in dataglob("rrdp-publication/*/deltas/*.xml"))
+
+for snapshot in sorted(snapshots):
+
+ time.sleep(1)
+ run("rm", "-rf", "rcynic-data")
+ run(rrdp_test_tool, snapshots[snapshot])
+ run(rcynic, "-c", "rcynic-rrdp.conf")
+ run(rcynic_text, "rcynic.xml")
+
+ for delta in sorted(deltas):
+ if delta > snapshot:
+ time.sleep(1)
+ run(rrdp_test_tool, deltas[delta])
+ run(rcynic, "-c", "rcynic-rrdp.conf")
+ run(rcynic_text, "rcynic.xml")
+
+ if not args.exhaustive:
+ break
diff --git a/ca/tests/testpoke.py b/ca/tests/testpoke.py
index efa068c9..8a443e0d 100644
--- a/ca/tests/testpoke.py
+++ b/ca/tests/testpoke.py
@@ -74,9 +74,9 @@ def get_PEM_chain(name, cert = None):
if cert is not None:
chain.append(cert)
if name in yaml_data:
- chain.extend([rpki.x509.X509(PEM = x) for x in yaml_data[name]])
+ chain.extend(rpki.x509.X509(PEM = x) for x in yaml_data[name])
elif name + "-file" in yaml_data:
- chain.extend([rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"]])
+ chain.extend(rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"])
return chain
def query_up_down(q_pdu):
diff --git a/ca/tests/xml-parse-test.py b/ca/tests/xml-parse-test.py
index 5ea25492..85f4453e 100644
--- a/ca/tests/xml-parse-test.py
+++ b/ca/tests/xml-parse-test.py
@@ -28,8 +28,14 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-import glob, lxml.etree, lxml.sax
-import rpki.up_down, rpki.left_right, rpki.publication, rpki.relaxng
+import glob
+import lxml.etree
+import lxml.sax
+import rpki.up_down
+import rpki.left_right
+import rpki.publication
+import rpki.publication_control
+import rpki.relaxng
verbose = False
@@ -88,17 +94,17 @@ def lr_tester(elt_in, elt_out, msg):
def pp_tester(elt_in, elt_out, msg):
assert isinstance(msg, rpki.publication.msg)
for obj in msg:
- if isinstance(obj, rpki.publication.client_elt):
+ if isinstance(obj, rpki.publication.publish_elt):
+ pprint(((obj.payload, "Publish object"),))
+ if isinstance(obj, rpki.publication.withdraw_elt):
+ pprint(((None, "Withdraw object"),))
+
+def pc_tester(elt_in, elt_out, msg):
+ assert isinstance(msg, rpki.publication_control.msg)
+ for obj in msg:
+ if isinstance(obj, rpki.publication_control.client_elt):
pprint(((obj.bpki_cert, "BPKI cert"),
(obj.bpki_glue, "BPKI glue")))
- if isinstance(obj, rpki.publication.certificate_elt):
- pprint(((obj.payload, "RPKI cert"),))
- if isinstance(obj, rpki.publication.crl_elt):
- pprint(((obj.payload, "RPKI CRL"),))
- if isinstance(obj, rpki.publication.manifest_elt):
- pprint(((obj.payload, "RPKI manifest"),))
- if isinstance(obj, rpki.publication.roa_elt):
- pprint(((obj.payload, "ROA"),))
test(fileglob = "up-down-protocol-samples/*.xml",
rng = rpki.relaxng.up_down,
@@ -117,3 +123,9 @@ test(fileglob = "publication-protocol-samples/*.xml",
sax_handler = rpki.publication.sax_handler,
encoding = "us-ascii",
tester = pp_tester)
+
+test(fileglob = "publication-control-protocol-samples/*.xml",
+ rng = rpki.relaxng.publication_control,
+ sax_handler = rpki.publication_control.sax_handler,
+ encoding = "us-ascii",
+ tester = pc_tester)
diff --git a/ca/tests/yamlconf.py b/ca/tests/yamlconf.py
index 1b339a89..bb82ef74 100644
--- a/ca/tests/yamlconf.py
+++ b/ca/tests/yamlconf.py
@@ -125,7 +125,7 @@ class router_cert(object):
def __init__(self, asn, router_id):
self.asn = rpki.resource_set.resource_set_as("".join(str(asn).split()))
self.router_id = router_id
- self.keypair = rpki.x509.ECDSA.generate(self.ecparams())
+ self.keypair = rpki.x509.ECDSA.generate(params = self.ecparams(), quiet = True)
self.pkcs10 = rpki.x509.PKCS10.create(keypair = self.keypair)
self.gski = self.pkcs10.gSKI()
@@ -491,16 +491,18 @@ class allocation(object):
def syncdb(self):
import django.core.management
assert not self.is_hosted
- django.core.management.call_command("syncdb",
- database = self.irdb_name,
- load_initial_data = False,
- interactive = False,
- verbosity = 0)
+ django.core.management.call_command(
+ "syncdb",
+ verbosity = 0,
+ database = self.irdb_name,
+ migrate = True,
+ load_initial_data = False,
+ interactive = False)
def hire_zookeeper(self):
assert not self.is_hosted
self._zoo = rpki.irdb.Zookeeper(
- cfg = rpki.config.parser(self.path("rpki.conf")),
+ cfg = rpki.config.parser(filename = self.path("rpki.conf")),
logstream = None if quiet else sys.stdout)
@property
@@ -530,15 +532,15 @@ class allocation(object):
notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365),
resources = root_resources)
- with open(self.path("publication.root", "root.cer"), "wb") as f:
+ with open(self.path("root.cer"), "wb") as f:
f.write(root_cert.get_DER())
with open(self.path("root.key"), "wb") as f:
f.write(root_key.get_DER())
with open(cleanpath(test_dir, "root.tal"), "w") as f:
- f.write("rsync://%s/root/root.cer\n\n%s" % (
- self.rsync_server, root_key.get_public().get_Base64()))
+ f.write("rsync://%s/root/root.cer\n\n" % self.rsync_server)
+ f.write(root_key.get_public().get_Base64())
def mkdir(self, *path):
path = self.path(*path)
@@ -681,7 +683,7 @@ def main():
# passwords: this is mostly so that I can show a complete working
# example without publishing my own server's passwords.
- cfg = rpki.config.parser(args.config, "yamlconf", allow_missing = True)
+ cfg = rpki.config.parser(set_filename = args.config, section = "yamlconf", allow_missing = True)
try:
cfg.set_global_flags()
except:
@@ -755,9 +757,13 @@ def body():
pre_django_sql_setup(set(d.irdb_name for d in db if not d.is_hosted))
# Now ready for fun with multiple databases in Django!
-
+ #
# https://docs.djangoproject.com/en/1.4/topics/db/multi-db/
# https://docs.djangoproject.com/en/1.4/topics/db/sql/
+ #
+ # This program's use of the ORM is sufficiently different that it's
+ # not worth straining to use rpki.django_settings, so we just use
+ # Django's settings API directly.
database_template = {
"ENGINE" : "django.db.backends.mysql",
@@ -767,8 +773,7 @@ def body():
"PORT" : "",
"OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}
- databases = dict((d.irdb_name,
- dict(database_template, NAME = d.irdb_name))
+ databases = dict((d.irdb_name, dict(database_template, NAME = d.irdb_name))
for d in db if not d.is_hosted)
databases["default"] = databases[db.root.irdb_name]
@@ -778,7 +783,7 @@ def body():
settings.configure(
DATABASES = databases,
DATABASE_ROUTERS = ["rpki.irdb.router.DBContextRouter"],
- INSTALLED_APPS = ("rpki.irdb",))
+ INSTALLED_APPS = ("rpki.irdb", "south"))
import rpki.irdb
diff --git a/ca/tests/yamltest.py b/ca/tests/yamltest.py
index 2b65dbd2..6ef63382 100644
--- a/ca/tests/yamltest.py
+++ b/ca/tests/yamltest.py
@@ -43,6 +43,7 @@ import re
import os
import logging
import argparse
+import webbrowser
import sys
import yaml
import signal
@@ -67,19 +68,21 @@ def cleanpath(*names):
"""
Construct normalized pathnames.
"""
+
return os.path.normpath(os.path.join(*names))
# Pathnames for various things we need
this_dir = os.getcwd()
test_dir = cleanpath(this_dir, "yamltest.dir")
-rpkid_dir = cleanpath(this_dir, "..")
+ca_dir = cleanpath(this_dir, "..")
-prog_rpkic = cleanpath(rpkid_dir, "rpkic")
-prog_rpkid = cleanpath(rpkid_dir, "rpkid")
-prog_irdbd = cleanpath(rpkid_dir, "irdbd")
-prog_pubd = cleanpath(rpkid_dir, "pubd")
-prog_rootd = cleanpath(rpkid_dir, "rootd")
+prog_rpkic = cleanpath(ca_dir, "rpkic")
+prog_rpkid = cleanpath(ca_dir, "rpkid")
+prog_irdbd = cleanpath(ca_dir, "irdbd")
+prog_pubd = cleanpath(ca_dir, "pubd")
+prog_rootd = cleanpath(ca_dir, "rootd")
+prog_rpki_manage = cleanpath(ca_dir, "rpki-manage")
class roa_request(object):
"""
@@ -110,6 +113,7 @@ class roa_request(object):
"""
Parse a ROA request from YAML format.
"""
+
return cls(y.get("asn"), y.get("ipv4"), y.get("ipv6"))
@@ -129,7 +133,7 @@ class router_cert(object):
def __init__(self, asn, router_id):
self.asn = rpki.resource_set.resource_set_as("".join(str(asn).split()))
self.router_id = router_id
- self.keypair = rpki.x509.ECDSA.generate(self.ecparams())
+ self.keypair = rpki.x509.ECDSA.generate(params = self.ecparams(), quiet = True)
self.pkcs10 = rpki.x509.PKCS10.create(keypair = self.keypair)
self.gski = self.pkcs10.gSKI()
@@ -154,7 +158,7 @@ class allocation_db(list):
def __init__(self, yaml):
list.__init__(self)
self.root = allocation(yaml, self)
- assert self.root.is_root
+ assert self.root.is_root and not any(a.is_root for a in self if a is not self.root) and self[0] is self.root
if self.root.crl_interval is None:
self.root.crl_interval = 60 * 60
if self.root.regen_margin is None:
@@ -180,6 +184,7 @@ class allocation_db(list):
"""
Show contents of allocation database.
"""
+
for a in self:
a.dump()
@@ -210,6 +215,7 @@ class allocation(object):
"""
Allocate a TCP port.
"""
+
cls.base_port += 1
return cls.base_port
@@ -221,6 +227,7 @@ class allocation(object):
Allocate an engine number, mostly used to construct MySQL database
names.
"""
+
cls.base_engine += 1
return cls.base_engine
@@ -275,6 +282,7 @@ class allocation(object):
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 |= kid.closure()
@@ -285,6 +293,7 @@ class allocation(object):
"""
Show content of this allocation node.
"""
+
print str(self)
def __str__(self):
@@ -309,6 +318,7 @@ class allocation(object):
"""
Is this the root node?
"""
+
return self.parent is None
@property
@@ -316,6 +326,7 @@ class allocation(object):
"""
Is this entity hosted?
"""
+
return self.hosted_by is not None
@property
@@ -323,18 +334,21 @@ class allocation(object):
"""
Does this entity run a pubd?
"""
+
return self.is_root or not (self.is_hosted or only_one_pubd)
def path(self, *names):
"""
Construct pathnames in this entity's test directory.
"""
+
return cleanpath(test_dir, self.host.name, *names)
def csvout(self, fn):
"""
Open and log a CSV output file.
"""
+
path = self.path(fn)
print "Writing", path
return rpki.csv_utils.csv_writer(path)
@@ -343,6 +357,7 @@ class allocation(object):
"""
Construct service URL for this node's parent.
"""
+
return "http://localhost:%d/up-down/%s/%s" % (self.parent.host.rpkid_port,
self.parent.name,
self.name)
@@ -351,12 +366,12 @@ class allocation(object):
"""
Write Autonomous System Numbers CSV file.
"""
+
fn = "%s.asns.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for k in self.kids:
- f.writerows((k.name, a) for a in k.resources.asn)
- f.close()
+ with self.csvout(fn) as f:
+ for k in self.kids:
+ f.writerows((k.name, a) for a in k.resources.asn)
if not args.stop_after_config:
self.run_rpkic("load_asns", fn)
@@ -364,12 +379,12 @@ class allocation(object):
"""
Write prefixes CSV file.
"""
+
fn = "%s.prefixes.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for k in self.kids:
- f.writerows((k.name, p) for p in (k.resources.v4 + k.resources.v6))
- f.close()
+ with self.csvout(fn) as f:
+ for k in self.kids:
+ f.writerows((k.name, p) for p in (k.resources.v4 + k.resources.v6))
if not args.stop_after_config:
self.run_rpkic("load_prefixes", fn)
@@ -377,13 +392,13 @@ class allocation(object):
"""
Write ROA CSV file.
"""
+
fn = "%s.roas.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for g1, r in enumerate(self.roa_requests):
- f.writerows((p, r.asn, "G%08d%08d" % (g1, g2))
- for g2, p in enumerate((r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ())))
- f.close()
+ with self.csvout(fn) as f:
+ for g1, r in enumerate(self.roa_requests):
+ f.writerows((p, r.asn, "G%08d%08d" % (g1, g2))
+ for g2, p in enumerate((r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ())))
if not args.stop_after_config:
self.run_rpkic("load_roa_requests", fn)
@@ -391,17 +406,14 @@ class allocation(object):
"""
Write Ghostbusters vCard file.
"""
+
if self.ghostbusters:
fn = "%s.ghostbusters.vcard" % d.name
if not args.skip_config:
path = self.path(fn)
print "Writing", path
- f = open(path, "w")
- for i, g in enumerate(self.ghostbusters):
- if i:
- f.write("\n")
- f.write(g)
- f.close()
+ with open(path, "w") as f:
+ f.write("\n".join(self.ghostbusters))
if not args.stop_after_config:
self.run_rpkic("load_ghostbuster_requests", fn)
@@ -409,6 +421,7 @@ class allocation(object):
"""
Write EE certificates (router certificates, etc).
"""
+
if self.router_certs:
fn = "%s.routercerts.xml" % d.name
if not args.skip_config:
@@ -434,6 +447,7 @@ class allocation(object):
"""
Walk up tree until we find somebody who runs pubd.
"""
+
s = self
while not s.runs_pubd:
s = s.parent
@@ -444,6 +458,7 @@ class allocation(object):
"""
Work out what pubd configure_publication_client will call us.
"""
+
path = []
s = self
if not args.flat_publication:
@@ -463,46 +478,45 @@ class allocation(object):
"""
r = dict(
- handle = self.name,
- run_rpkid = str(not self.is_hosted),
- run_pubd = str(self.runs_pubd),
- run_rootd = str(self.is_root),
- irdbd_sql_database = "irdb%d" % self.engine,
- irdbd_sql_username = "irdb",
- rpkid_sql_database = "rpki%d" % self.engine,
- rpkid_sql_username = "rpki",
- rpkid_server_host = "localhost",
- rpkid_server_port = str(self.rpkid_port),
- irdbd_server_host = "localhost",
- irdbd_server_port = str(self.irdbd_port),
- rootd_server_port = str(self.rootd_port),
- pubd_sql_database = "pubd%d" % self.engine,
- pubd_sql_username = "pubd",
- pubd_server_host = "localhost",
- pubd_server_port = str(self.pubd.pubd_port),
- publication_rsync_server = "localhost:%s" % self.pubd.rsync_port,
- bpki_servers_directory = self.path(),
- publication_base_directory = self.path("publication"),
- shared_sql_password = "fnord")
+ handle = self.name,
+ run_rpkid = str(not self.is_hosted),
+ run_pubd = str(self.runs_pubd),
+ run_rootd = str(self.is_root),
+ irdbd_sql_database = "irdb%d" % self.engine,
+ irdbd_sql_username = "irdb",
+ rpkid_sql_database = "rpki%d" % self.engine,
+ rpkid_sql_username = "rpki",
+ rpkid_server_host = "localhost",
+ rpkid_server_port = str(self.rpkid_port),
+ irdbd_server_host = "localhost",
+ irdbd_server_port = str(self.irdbd_port),
+ rootd_server_port = str(self.rootd_port),
+ pubd_sql_database = "pubd%d" % self.engine,
+ pubd_sql_username = "pubd",
+ pubd_server_host = "localhost",
+ pubd_server_port = str(self.pubd.pubd_port),
+ publication_rsync_server = "localhost:%s" % self.pubd.rsync_port,
+ bpki_servers_directory = self.path(),
+ publication_base_directory = self.path("publication"),
+ rrdp_publication_base_directory = self.path("rrdp-publication"),
+ shared_sql_password = "fnord")
r.update(config_overrides)
- f = open(self.path("rpki.conf"), "w")
- f.write("# Automatically generated, do not edit\n")
- print "Writing", f.name
-
- section = None
- for line in open(cleanpath(rpkid_dir, "examples/rpki.conf")):
- m = section_regexp.match(line)
- if m:
- section = m.group(1)
- m = variable_regexp.match(line)
- option = m.group(1) if m and section == "myrpki" else None
- if option and option in r:
- line = "%s = %s\n" % (option, r[option])
- f.write(line)
+ with open(self.path("rpki.conf"), "w") as f:
+ f.write("# Automatically generated, do not edit\n")
+ print "Writing", f.name
- f.close()
+ section = None
+ for line in open(cleanpath(ca_dir, "examples/rpki.conf")):
+ m = section_regexp.match(line)
+ if m:
+ section = m.group(1)
+ m = variable_regexp.match(line)
+ option = m.group(1) if m and section == "myrpki" else None
+ if option and option in r:
+ line = "%s = %s\n" % (option, r[option])
+ f.write(line)
def dump_rsyncd(self):
"""
@@ -510,25 +524,24 @@ class allocation(object):
"""
if self.runs_pubd:
- f = open(self.path("rsyncd.conf"), "w")
- print "Writing", f.name
- f.writelines(s + "\n" for s in
- ("# Automatically generated, do not edit",
- "port = %d" % self.rsync_port,
- "address = localhost",
- "[rpki]",
- "log file = rsyncd.log",
- "read only = yes",
- "use chroot = no",
- "path = %s" % self.path("publication"),
- "comment = RPKI test",
- "[root]",
- "log file = rsyncd_root.log",
- "read only = yes",
- "use chroot = no",
- "path = %s" % self.path("publication.root"),
- "comment = RPKI test root"))
- f.close()
+ with open(self.path("rsyncd.conf"), "w") as f:
+ print "Writing", f.name
+ f.writelines(s + "\n" for s in
+ ("# Automatically generated, do not edit",
+ "port = %d" % self.rsync_port,
+ "address = localhost",
+ "[rpki]",
+ "log file = rsyncd.log",
+ "read only = yes",
+ "use chroot = no",
+ "path = %s" % self.path("publication"),
+ "comment = RPKI test",
+ "[root]",
+ "log file = rsyncd_root.log",
+ "read only = yes",
+ "use chroot = no",
+ "path = %s" % self.path("publication.root"),
+ "comment = RPKI test root"))
@classmethod
def next_rpkic_counter(cls):
@@ -539,65 +552,116 @@ class allocation(object):
"""
Run rpkic for this entity.
"""
- cmd = [prog_rpkic, "-i", self.name, "-c", self.path("rpki.conf")]
+
+ cmd = [prog_rpkic, "-i", self.name]
if args.profile:
cmd.append("--profile")
cmd.append(self.path("rpkic.%s.prof" % rpki.sundial.now()))
cmd.extend(str(a) for a in argv if a is not None)
print 'Running "%s"' % " ".join(cmd)
- env = os.environ.copy()
- env["YAMLTEST_RPKIC_COUNTER"] = self.next_rpkic_counter()
+ env = dict(os.environ,
+ YAMLTEST_RPKIC_COUNTER = self.next_rpkic_counter(),
+ RPKI_CONF = self.path("rpki.conf"))
subprocess.check_call(cmd, cwd = self.host.path(), env = env)
+ def syncdb(self):
+ """
+ Run whatever Django ORM commands are necessary to set up the
+ database this week.
+ """
+
+ verbosity = 1
+
+ if verbosity > 0:
+ print "Running Django setup for", self.name
+
+ if not os.fork():
+ os.environ.update(RPKI_CONF = self.path("rpki.conf"),
+ RPKI_GUI_ENABLE = "yes")
+ logging.getLogger().setLevel(logging.WARNING)
+ import django.core.management
+ django.core.management.call_command("syncdb", migrate = True, verbosity = verbosity,
+ load_initial_data = False, interactive = False)
+ from django.contrib.auth.models import User
+ User.objects.create_superuser("root", "root@example.org", "fnord")
+ sys.exit(0)
+
+ if os.wait()[1]:
+ raise RuntimeError("Django setup failed for %s" % self.name)
+
def run_python_daemon(self, prog):
"""
Start a Python daemon and return a subprocess.Popen object
representing the running daemon.
"""
+
basename = os.path.splitext(os.path.basename(prog))[0]
cmd = [prog, "--foreground", "--log-level", "debug",
- "--log-file", self.path(basename + ".log"),
- "--config", self.path("rpki.conf")]
+ "--log-file", self.path(basename + ".log")]
if args.profile and basename != "rootd":
cmd.extend((
"--profile", self.path(basename + ".prof")))
- p = subprocess.Popen(cmd, cwd = self.path())
- print 'Running %s for %s: pid %d process %r' % (" ".join(cmd), self.name, p.pid, p)
+ env = dict(os.environ, RPKI_CONF = self.path("rpki.conf"))
+ p = subprocess.Popen(cmd, cwd = self.path(), env = env)
+ print "Running %s for %s: pid %d process %r" % (" ".join(cmd), self.name, p.pid, p)
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)
def run_rsyncd(self):
"""
Run rsyncd.
"""
+
p = subprocess.Popen(("rsync", "--daemon", "--no-detach", "--config", "rsyncd.conf"),
cwd = self.path())
print "Running rsyncd for %s: pid %d process %r" % (self.name, p.pid, p)
return p
+ def run_gui(self):
+ """
+ Start an instance of the RPKI GUI under the Django test server and
+ return a subprocess.Popen object representing the running daemon.
+ """
+
+ port = 8000 + self.engine
+ cmd = (prog_rpki_manage, "runserver", str(port))
+ env = dict(os.environ,
+ RPKI_CONF = self.path("rpki.conf"),
+ RPKI_DJANGO_DEBUG = "yes",
+ ALLOW_PLAIN_HTTP_FOR_TESTING = "I solemnly swear that I am not running this in production")
+ p = subprocess.Popen(cmd, cwd = self.path(), env = env,
+ stdout = open(self.path("gui.log"), "w"), stderr = subprocess.STDOUT)
+ print "Running %s for %s: pid %d process %r" % (" ".join(cmd), self.name, p.pid, p)
+ return p
+
+
def create_root_certificate(db_root):
print "Creating rootd RPKI root certificate"
@@ -609,9 +673,9 @@ def create_root_certificate(db_root):
root_key = rpki.x509.RSA.generate(quiet = True)
- root_uri = "rsync://localhost:%d/rpki/" % db_root.pubd.rsync_port
+ root_uri = "rsync://localhost:%d/rpki/%s-root/root" % (db_root.pubd.rsync_port, db_root.name)
- root_sia = (root_uri, root_uri + "root.mft", None)
+ root_sia = (root_uri + "/", root_uri + "/root.mft", None)
root_cert = rpki.x509.X509.self_certify(
keypair = root_key,
@@ -621,22 +685,21 @@ def create_root_certificate(db_root):
notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365),
resources = root_resources)
- f = open(db_root.path("publication.root/root.cer"), "wb")
- f.write(root_cert.get_DER())
- f.close()
+ with open(db_root.path("root.cer"), "wb") as f:
+ f.write(root_cert.get_DER())
- f = open(db_root.path("root.key"), "wb")
- f.write(root_key.get_DER())
- f.close()
+ with open(db_root.path("root.key"), "wb") as f:
+ f.write(root_key.get_DER())
- f = open(os.path.join(test_dir, "root.tal"), "w")
- f.write("rsync://localhost:%d/root/root.cer\n\n" % db_root.pubd.rsync_port)
- f.write(root_key.get_public().get_Base64())
- f.close()
+ with open(os.path.join(test_dir, "root.tal"), "w") as f:
+ f.write(root_uri + ".cer\n\n")
+ f.write(root_key.get_public().get_Base64())
+logger = logging.getLogger(__name__)
-os.environ["TZ"] = "UTC"
+os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings",
+ TZ = "UTC")
time.tzset()
parser = argparse.ArgumentParser(description = __doc__)
@@ -656,6 +719,12 @@ parser.add_argument("--synchronize", action = "store_true",
help = "synchronize IRDB with daemons")
parser.add_argument("--profile", action = "store_true",
help = "enable profiling")
+parser.add_argument("-g", "--run_gui", action = "store_true",
+ help = "enable GUI using django-admin runserver")
+parser.add_argument("--browser", action = "store_true",
+ help = "create web browser tabs for GUI")
+parser.add_argument("--notify-when-startup-complete", type = int,
+ help = "send SIGUSR1 to this process when startup is complete")
parser.add_argument("yaml_file", type = argparse.FileType("r"),
help = "YAML description of test network")
args = parser.parse_args()
@@ -672,7 +741,7 @@ try:
# passwords: this is mostly so that I can show a complete working
# example without publishing my own server's passwords.
- cfg = rpki.config.parser(args.config, "yamltest", allow_missing = True)
+ cfg = rpki.config.parser(set_filename = args.config, section = "yamltest", allow_missing = True)
only_one_pubd = cfg.getboolean("only_one_pubd", True)
allocation.base_port = cfg.getint("base_port", 4400)
@@ -713,6 +782,7 @@ try:
for d in db:
if not d.is_hosted:
+ print "Initializing", d.name
os.makedirs(d.path())
d.dump_conf()
if d.runs_pubd:
@@ -720,7 +790,9 @@ try:
d.dump_rsyncd()
if d.is_root:
os.makedirs(d.path("publication.root"))
+ d.syncdb()
d.run_rpkic("initialize_server_bpki")
+ print
# Initialize resource holding BPKI and generate self-descriptor
# for each entity.
@@ -758,6 +830,8 @@ try:
if d.runs_pubd:
progs.append(d.run_pubd())
progs.append(d.run_rsyncd())
+ if args.run_gui:
+ progs.append(d.run_gui())
if args.synchronize or not args.skip_config:
@@ -826,9 +900,27 @@ try:
d.dump_ghostbusters()
d.dump_router_certificates()
+ if args.run_gui:
+ print
+ print 'GUI user "root", password "fnord"'
+ for d in db:
+ if not d.is_hosted:
+ url = "http://127.0.0.1:%d/rpki/" % (8000 + d.engine)
+ print "GUI URL", url, "for", d.name
+ if args.browser:
+ if d is db.root:
+ webbrowser.open_new(url)
+ else:
+ webbrowser.open_new_tab(url)
+ time.sleep(2)
+
# Wait until something terminates.
if not args.stop_after_config or args.keep_going:
+ if args.notify_when_startup_complete:
+ print
+ print "Sending SIGUSR1 to process", args.notify_when_startup_complete
+ os.kill(args.notify_when_startup_complete, signal.SIGUSR1)
print
print "Waiting for daemons to exit"
signal.signal(signal.SIGCHLD, lambda *dont_care: None)
diff --git a/h/rpki/sk_roa.h b/h/rpki/sk_roa.h
index 13036955..7423f8ff 100644
--- a/h/rpki/sk_roa.h
+++ b/h/rpki/sk_roa.h
@@ -1,6 +1,6 @@
/*
* Automatically generated, do not edit.
- * Generator $Id: defstack.py 4878 2012-11-15 22:13:53Z sra $
+ * Generator $Id: defstack.py 5784 2014-04-10 22:56:47Z sra $
*/
#ifndef __RPKI_ROA_H__DEFSTACK_H__
diff --git a/potpourri/rrdp-fetch.py b/potpourri/rrdp-fetch.py
new file mode 100755
index 00000000..aa5b762b
--- /dev/null
+++ b/potpourri/rrdp-fetch.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Fetch an RRDP notifcation file and follow all the links. Should be
+merged into rrdp-test-tool eventually, but one thing at a time.
+"""
+
+from urllib2 import urlopen
+from lxml.etree import ElementTree, XML
+from socket import getfqdn
+from rpki.x509 import sha256
+from rpki.relaxng import rrdp
+from urlparse import urlparse
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+
+class BadHash(Exception):
+ "Calculated hash value doesn't match expected hash value."
+
+def fetch(elt):
+ uri = elt.get("uri")
+ hash = elt.get("hash")
+ print "Fetching", uri
+
+ text = urlopen(uri).read()
+ h = sha256(text).encode("hex")
+ if h != hash:
+ raise BadHash("Bad hash for %s: expected %s got %s" % (uri, hash, h))
+
+ xml = XML(text)
+ rrdp.schema.assertValid(xml)
+
+ u = urlparse(uri)
+ fn = u.netloc + u.path
+
+ return elt, xml, fn
+
+parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter)
+parser.add_argument("uri", nargs = "?",
+ default = "http://" + getfqdn() + "/rrdp/updates.xml",
+ help = "RRDP notification file to fetch")
+args = parser.parse_args()
+
+updates = ElementTree(file = urlopen(args.uri))
+rrdp.schema.assertValid(updates)
+
+snapshot = fetch(updates.find(rrdp.xmlns + "snapshot"))
+
+deltas = [fetch(elt) for elt in updates.findall(rrdp.xmlns + "delta")]
+
+print updates
+print snapshot
+for delta in deltas:
+ print delta
diff --git a/potpourri/rrdp-test-tool b/potpourri/rrdp-test-tool
new file mode 100755
index 00000000..b4dc65da
--- /dev/null
+++ b/potpourri/rrdp-test-tool
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Test tool for prototype RRDP implementation. Eventually some of this
+code will likely be refactored into more user-friendly form, but for
+the moment this just does whatever insane thing I need to do this week
+for testing.
+"""
+
+import rpki.relaxng
+import rpki.x509
+import lxml.etree
+import argparse
+import os
+
+class Tags(object):
+ def __init__(self, *tags):
+ for tag in tags:
+ setattr(self, tag, rpki.relaxng.rrdp.xmlns + tag)
+
+tags = Tags("notification", "deltas", "delta", "snapshot", "publish", "withdraw")
+
+class main(object):
+
+ def __init__(self):
+ parser = argparse.ArgumentParser(description = __doc__)
+ parser.add_argument("--rcynic-tree", default = "rcynic-data/unauthenticated",
+ help = "directory tree in which to write extracted RPKI objects")
+ parser.add_argument("--serial-filename",
+ help = "file name in which to store RRDP serial number")
+ parser.add_argument("rrdp_file", nargs = "+",
+ help = "RRDP snapshot or deltas file")
+ self.args = parser.parse_args()
+ if not os.path.isdir(self.args.rcynic_tree):
+ os.makedirs(self.args.rcynic_tree)
+ for rrdp_file in self.args.rrdp_file:
+ xml = lxml.etree.ElementTree(file = rrdp_file).getroot()
+ rpki.relaxng.rrdp.assertValid(xml)
+ getattr(self, xml.tag[len(rpki.relaxng.rrdp.xmlns):])(xml)
+
+ @property
+ def serial_filename(self):
+ return self.args.serial_filename or os.path.join(self.args.rcynic_tree, "serial")
+
+ def get_serial(self):
+ with open(self.serial_filename, "r") as f:
+ return f.read().strip()
+
+ def set_serial(self, value):
+ with open(self.serial_filename, "w") as f:
+ f.write("%s\n" % value)
+
+ def notification(self, xml):
+ print "Notification version %s session %s serial %s" % (
+ xml.get("version"), xml.get("session_id"), xml.get("serial"))
+ assert xml[0].tag == tags.snapshot
+ print " Snapshot URI %s hash %s" % (
+ xml[0].get("uri"), xml[0].get("hash"))
+ for i, elt in enumerate(xml.iterchildren(tags.delta)):
+ print " Delta %3d from %6s to %6s URI %s hash %s" % (
+ i, elt.get("from"), elt.get("to"), elt.get("uri"), elt.get("hash"))
+
+ def uri_to_filename(self, uri):
+ assert uri.startswith("rsync://")
+ return os.path.join(self.args.rcynic_tree, uri[len("rsync://"):])
+
+ def add_obj(self, uri, obj):
+ fn = self.uri_to_filename(uri)
+ dn = os.path.dirname(fn)
+ if not os.path.isdir(dn):
+ os.makedirs(dn)
+ with open(fn, "wb") as f:
+ f.write(obj)
+
+ def del_obj(self, uri, hash):
+ fn = self.uri_to_filename(uri)
+ with open(fn, "rb") as f:
+ if hash != rpki.x509.sha256(f.read()).encode("hex"):
+ raise RuntimeError("Hash mismatch for URI %s" % uri)
+ os.unlink(fn)
+ dn = os.path.dirname(fn)
+ while True:
+ try:
+ os.rmdir(dn)
+ except OSError:
+ break
+ else:
+ dn = os.path.dirname(dn)
+
+ def snapshot(self, xml):
+ print "Unpacking snapshot version %s session %s serial %6s" % (
+ xml.get("version"), xml.get("session_id"), xml.get("serial"))
+ for elt in xml.iterchildren(tags.publish):
+ print " ", elt.get("uri")
+ self.add_obj(elt.get("uri"), elt.text.decode("base64"))
+ self.set_serial(xml.get("serial"))
+
+ def deltas(self, xml):
+ cur = int(self.get_serial())
+ old = int(xml.get("from"))
+ new = int(xml.get("to"))
+ print "Unpacking deltas version %s session %s from %s to %s" % (
+ xml.get("version"), xml.get("session_id"), old, new)
+ if cur != old:
+ raise RuntimeError("Can't apply deltas: current %s old %s new %s" % (cur, old, new))
+ for i, delta in enumerate(xml.iterchildren(tags.delta)):
+ serial = int(delta.get("serial"))
+ print " Delta %3d serial %d" % (i, serial)
+ if cur != serial - 1:
+ raise RuntimeError("Can't apply delta: current %s delta serial %s" % (cur, serial))
+ for j, elt in enumerate(delta.iterchildren(tags.withdraw)):
+ uri = elt.get("uri")
+ hash = elt.get("hash")
+ print " %3d withdraw URI %s hash %s" % (j, uri, hash)
+ self.del_obj(uri, hash)
+ for j, elt in enumerate(delta.iterchildren(tags.publish)):
+ uri = elt.get("uri")
+ hash = elt.get("hash", None)
+ print " %3d publish URI %s hash %s" % (j, uri, hash)
+ if hash is not None:
+ self.del_obj(uri, hash)
+ self.add_obj(elt.get("uri"), elt.text.decode("base64"))
+ cur += 1
+ self.set_serial(cur)
+
+if __name__ == "__main__":
+ main()
diff --git a/potpourri/upgrade-add-ghostbusters.py b/potpourri/upgrade-add-ghostbusters.py
index a8c8a92b..2548487c 100644
--- a/potpourri/upgrade-add-ghostbusters.py
+++ b/potpourri/upgrade-add-ghostbusters.py
@@ -43,7 +43,7 @@ for o, a in opts:
if o in ("-c", "--config"):
cfg_file = a
-cfg = rpki.config.parser(cfg_file, "myrpki")
+cfg = rpki.config.parser(filename = cfg_file, section = "myrpki")
fix("irdbd", """
CREATE TABLE ghostbuster_request (
diff --git a/rpki/adns.py b/rpki/adns.py
index 968684b5..018bb7cf 100644
--- a/rpki/adns.py
+++ b/rpki/adns.py
@@ -88,6 +88,7 @@ class dispatcher(asyncore.dispatcher):
"""
Receive a packet, hand it off to query class callback.
"""
+
wire, from_address = self.recvfrom(self.bufsize)
self.cb(self.af, from_address[0], from_address[1], wire)
@@ -95,18 +96,21 @@ class dispatcher(asyncore.dispatcher):
"""
Pass errors to query class errback.
"""
+
self.eb(sys.exc_info()[1])
def handle_connect(self):
"""
Quietly ignore UDP "connection" events.
"""
+
pass
def writable(self):
"""
We don't need to hear about UDP socket becoming writable.
"""
+
return False
@@ -138,6 +142,7 @@ class query(object):
query; if we find an answer there, just return it. Otherwise
start the network query.
"""
+
if resolver.cache:
answer = resolver.cache.get((self.qname, self.qtype, self.qclass))
else:
@@ -161,6 +166,7 @@ class query(object):
Outer loop. If we haven't got a response yet and still have
nameservers to check, start inner loop. Otherwise, we're done.
"""
+
self.timer.cancel()
if self.response is None and self.nameservers:
self.iterator = rpki.async.iterator(self.nameservers[:], self.loop2, self.done2)
@@ -172,6 +178,7 @@ class query(object):
Inner loop. Send query to next nameserver in our list, unless
we've hit the overall timeout for this query.
"""
+
self.timer.cancel()
try:
timeout = resolver._compute_timeout(self.start)
@@ -191,6 +198,7 @@ class query(object):
"""
No answer from nameserver, move on to next one (inner loop).
"""
+
self.response = None
self.iterator()
@@ -200,6 +208,7 @@ class query(object):
error, handle as if we've timed out on this nameserver; otherwise,
pass error back to caller.
"""
+
self.timer.cancel()
if isinstance(e, socket.error):
self.response = None
@@ -215,6 +224,7 @@ class query(object):
we're done, otherwise handle error appropriately and move on to
next nameserver.
"""
+
sender = (af, dns.inet.inet_pton(af, from_host))
if from_port != resolver.port or sender not in self.nameservers:
return
@@ -240,6 +250,7 @@ class query(object):
while before starting the cycle again, unless we've hit the
timeout threshold for the whole query.
"""
+
if self.response is None and self.nameservers:
try:
delay = rpki.sundial.timedelta(seconds = min(resolver._compute_timeout(self.start), self.backoff))
@@ -256,6 +267,7 @@ class query(object):
"""
Shut down our timer and sockets.
"""
+
self.timer.cancel()
for s in self.sockets.itervalues():
s.close()
@@ -264,6 +276,7 @@ class query(object):
"""
Something bad happened. Clean up, then pass error back to caller.
"""
+
self.cleanup()
self.eb(self, e)
@@ -273,6 +286,7 @@ class query(object):
pass it back to caller; if we got an error, pass the appropriate
exception back to caller.
"""
+
self.cleanup()
try:
if not self.nameservers:
diff --git a/rpki/async.py b/rpki/async.py
index b17c31ed..f2abd05d 100644
--- a/rpki/async.py
+++ b/rpki/async.py
@@ -131,6 +131,7 @@ class timer(object):
"""
Debug logging.
"""
+
if self.gc_debug:
bt = traceback.extract_stack(limit = 3)
logger.debug("%s from %s:%d", msg, bt[0][0], bt[0][1])
@@ -140,6 +141,7 @@ class timer(object):
Set a timer. Argument can be a datetime, to specify an absolute
time, or a timedelta, to specify an offset time.
"""
+
if self.gc_debug:
self.trace("Setting %r to %r" % (self, when))
if isinstance(when, rpki.sundial.timedelta):
@@ -162,6 +164,7 @@ class timer(object):
"""
Cancel a timer, if it was set.
"""
+
if self.gc_debug:
self.trace("Canceling %r" % self)
try:
@@ -174,6 +177,7 @@ class timer(object):
"""
Test whether this timer is currently set.
"""
+
return self in timer_queue
def set_handler(self, handler):
@@ -184,12 +188,14 @@ class timer(object):
bound method to an object in a class representing a network
connection).
"""
+
self.handler = handler
def set_errback(self, errback):
"""
Set a timer's errback. Like set_handler(), for errbacks.
"""
+
self.errback = errback
@classmethod
@@ -202,6 +208,7 @@ class timer(object):
called, so that even if new events keep getting scheduled, we'll
return to the I/O loop reasonably quickly.
"""
+
now = rpki.sundial.now()
while timer_queue and now >= timer_queue[0].when:
t = timer_queue.pop(0)
@@ -233,6 +240,7 @@ class timer(object):
the same units (argh!), and we're not doing anything that
hair-triggered, so rounding up is simplest.
"""
+
if not timer_queue:
return None
now = rpki.sundial.now()
@@ -251,6 +259,7 @@ class timer(object):
queue content, but this way we can notify subclasses that provide
their own cancel() method.
"""
+
while timer_queue:
timer_queue.pop(0).cancel()
@@ -258,12 +267,14 @@ def _raiseExitNow(signum, frame):
"""
Signal handler for event_loop().
"""
+
raise ExitNow
def exit_event_loop():
"""
Force exit from event_loop().
"""
+
raise ExitNow
def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)):
@@ -271,6 +282,7 @@ def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)):
Use a near-term (default: zero interval) timer to schedule an event
to run after letting the I/O system have a turn.
"""
+
timer(handler).set(delay)
## @var debug_event_timing
@@ -282,6 +294,7 @@ def event_loop(catch_signals = (signal.SIGINT, signal.SIGTERM)):
"""
Replacement for asyncore.loop(), adding timer and signal support.
"""
+
old_signal_handlers = {}
while True:
save_sigs = len(old_signal_handlers) == 0
@@ -347,6 +360,7 @@ class sync_wrapper(object):
Wrapped code has requested normal termination. Store result, and
exit the event loop.
"""
+
self.res = res
self.fin = True
logger.debug("%r callback with result %r", self, self.res)
@@ -357,6 +371,7 @@ class sync_wrapper(object):
Wrapped code raised an exception. Store exception data, then exit
the event loop.
"""
+
exc_info = sys.exc_info()
self.err = exc_info if exc_info[1] is err else err
self.fin = True
@@ -401,6 +416,7 @@ class gc_summary(object):
"""
Collect and log GC state for this period, reset timer.
"""
+
logger.debug("gc_summary: Running gc.collect()")
gc.collect()
logger.debug("gc_summary: Summarizing (threshold %d)", self.threshold)
diff --git a/rpki/config.py b/rpki/config.py
index f38427c4..b8d25896 100644
--- a/rpki/config.py
+++ b/rpki/config.py
@@ -32,23 +32,16 @@ logger = logging.getLogger(__name__)
## @var default_filename
# Default name of config file if caller doesn't specify one explictly.
-default_filename = "rpki.conf"
-
-## @var default_dirname
-# Default name of directory to check for global config file, or None
-# if no global config file. Autoconf-generated code may set this to a
-# non-None value during script startup.
-
try:
import rpki.autoconf
- default_dirname = rpki.autoconf.sysconfdir
+ default_filename = os.path.join(rpki.autoconf.sysconfdir, "rpki.conf")
except ImportError:
- default_dirname = None
+ default_filename = None
-## @var default_envname
+## @var rpki_conf_envname
# Name of environment variable containing config file name.
-default_envname = "RPKI_CONF"
+rpki_conf_envname = "RPKI_CONF"
class parser(object):
"""
@@ -61,44 +54,48 @@ class parser(object):
get-methods with default values and default section name.
- If no filename is given to the constructor (filename = None), we
- check for an environment variable naming the config file, then we
- check for a default filename in the current directory, then finally
- we check for a global config file if autoconf provided a directory
- name to check.
+ If no filename is given to the constructor (filename and
+ set_filename both None), we check for an environment variable naming
+ the config file, then finally we check for a global config file if
+ autoconf provided a directory name to check.
+
+ NB: Programs which accept a configuration filename on the command
+ lines should pass that filename using set_filename so that we can
+ set the magic environment variable. Constraints from some external
+ libraries (principally Django) sometimes require library code to
+ look things up in the configuration file without the knowledge of
+ the controlling program, but setting the environment variable
+ insures that everybody's reading from the same script, as it were.
"""
- def __init__(self, filename = None, section = None, allow_missing = False):
+ # Odd keyword-only calling sequence is a defense against old code
+ # that thinks it knows how __init__() handles positional arguments.
+
+ def __init__(self, **kwargs):
+ section = kwargs.pop("section", None)
+ allow_missing = kwargs.pop("allow_missing", False)
+ set_filename = kwargs.pop("set_filename", None)
+ filename = kwargs.pop("filename", set_filename)
+
+ assert not kwargs, "Unexpected keyword arguments: " + ", ".join("%s = %r" % kv for kv in kwargs.iteritems())
+
+ if set_filename is not None:
+ os.environ[rpki_conf_envname] = set_filename
self.cfg = ConfigParser.RawConfigParser()
self.default_section = section
- filenames = []
- if filename is not None:
- filenames.append(filename)
- else:
- if default_envname in os.environ:
- filenames.append(os.environ[default_envname])
- filenames.append(default_filename)
- if default_dirname is not None:
- filenames.append("%s/%s" % (default_dirname, default_filename))
+ self.filename = filename or os.getenv(rpki_conf_envname) or default_filename
- f = fn = None
+ try:
+ with open(self.filename, "r") as f:
+ self.cfg.readfp(f)
+ except IOError:
+ if allow_missing:
+ self.filename = None
+ else:
+ raise
- for fn in filenames:
- try:
- f = open(fn)
- break
- except IOError:
- f = None
-
- if f is not None:
- self.filename = fn
- self.cfg.readfp(f, fn)
- elif allow_missing:
- self.filename = None
- else:
- raise
def has_section(self, section):
"""
@@ -107,6 +104,7 @@ class parser(object):
return self.cfg.has_section(section)
+
def has_option(self, option, section = None):
"""
Test whether an option exists.
@@ -116,6 +114,7 @@ class parser(object):
section = self.default_section
return self.cfg.has_option(section, option)
+
def multiget(self, option, section = None):
"""
Parse OpenSSL-style foo.0, foo.1, ... subscripted options.
@@ -134,6 +133,7 @@ class parser(object):
for option in matches:
yield self.cfg.get(section, option)
+
_regexp = re.compile("\\${(.*?)::(.*?)}")
def _repl(self, m):
@@ -141,16 +141,19 @@ class parser(object):
Replacement function for indirect variable substitution.
This is intended for use with re.subn().
"""
+
section, option = m.group(1, 2)
if section == "ENV":
return os.getenv(option, "")
else:
return self.cfg.get(section, option)
+
def get(self, option, default = None, section = None):
"""
Get an option, perhaps with a default value.
"""
+
if section is None:
section = self.default_section
if default is not None and not self.cfg.has_option(section, option):
@@ -161,10 +164,12 @@ class parser(object):
if not modified:
return val
+
def getboolean(self, option, default = None, section = None):
"""
Get a boolean option, perhaps with a default value.
"""
+
v = self.get(option, default, section)
if isinstance(v, str):
v = v.lower()
@@ -173,18 +178,23 @@ class parser(object):
v = self.cfg._boolean_states[v]
return v
+
def getint(self, option, default = None, section = None):
"""
Get an integer option, perhaps with a default value.
"""
+
return int(self.get(option, default, section))
+
def getlong(self, option, default = None, section = None):
"""
Get a long integer option, perhaps with a default value.
"""
+
return long(self.get(option, default, section))
+
def set_global_flags(self):
"""
Consolidated control for all the little global control flags
diff --git a/rpki/csv_utils.py b/rpki/csv_utils.py
index 9ba04a02..9034e96b 100644
--- a/rpki/csv_utils.py
+++ b/rpki/csv_utils.py
@@ -99,6 +99,7 @@ class csv_writer(object):
"""
Close this writer.
"""
+
if self.file is not None:
self.file.close()
self.file = None
@@ -109,4 +110,5 @@ class csv_writer(object):
"""
Fake inheritance from whatever object csv.writer deigns to give us.
"""
+
return getattr(self.writer, attr)
diff --git a/rpki/db_router.py b/rpki/db_router.py
new file mode 100644
index 00000000..89ed6e5d
--- /dev/null
+++ b/rpki/db_router.py
@@ -0,0 +1,57 @@
+# $Id$
+
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Global Django ORM database router for the RPKI CA code.
+"""
+
+# Reference:
+# https://docs.djangoproject.com/en/1.6/topics/db/multi-db/
+
+class RPKIDBRouter(object):
+ """
+ Django ORM database router for RPKI code. rpkid and pubd get their
+ own databases, named "rpkidb" and "pubdb", respectively. Everything
+ else goes to the "default" database.
+ """
+
+ dedicated = ("rpkidb", "pubdb")
+
+ def db_for_read(self, model, **hints):
+ if model._meta.app_label in self.dedicated:
+ return model._meta.app_label
+ else:
+ return "default"
+
+ def db_for_write(self, model, **hints):
+ if model._meta.app_label in self.dedicated:
+ return model._meta.app_label
+ else:
+ return "default"
+
+ def allow_relation(self, obj1, obj2, **hints):
+ if obj1._meta.app_label in self.dedicated and obj1._meta.app_label == obj2._meta.app_label:
+ return True
+ elif obj1._meta.app_label not in self.dedicated and obj2._meta.app_label not in self.dedicated:
+ return True
+ else:
+ return None
+
+ def allow_syncdb(self, db, model):
+ if model._meta.app_label in self.dedicated:
+ return db == model._meta.app_label
+ else:
+ return db not in self.dedicated
diff --git a/rpki/django_settings.py b/rpki/django_settings.py
new file mode 100644
index 00000000..d3cadcfc
--- /dev/null
+++ b/rpki/django_settings.py
@@ -0,0 +1,245 @@
+# $Id$
+
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+This module contains configuration settings for Django libraries.
+
+Most of our CA code uses at least the Django ORM; the web interface
+uses a lot more of Django. We also want to handle all normal user
+configuration via rpki.conf, so some of the code here is just pulling
+settings from rpki.conf and stuffing them into the form Django wants.
+"""
+
+__version__ = "$Id$"
+
+import os
+import socket
+
+import rpki.config
+import rpki.autoconf
+
+# Some configuration, including SQL authorization, comes from rpki.conf.
+cfg = rpki.config.parser()
+
+
+# Do -not- turn on DEBUG here except for short-lived tests, otherwise
+# long-running programs like irdbd will eventually run out of memory
+# and crash. This is also why this is controlled by an environment
+# variable rather than by an rpki.conf setting: just because we want
+# debugging enabled in the GUI doesn't mean we want it in irdb.
+#
+# If you must enable debugging, you may need to add code that uses
+# django.db.reset_queries() to clear the query list manually, but it's
+# probably better just to run with debugging disabled, since that's
+# the expectation for production code.
+#
+# https://docs.djangoproject.com/en/dev/faq/models/#why-is-django-leaking-memory
+
+if os.getenv("RPKI_DJANGO_DEBUG") == "yes":
+ DEBUG = True
+
+
+# Database configuration. This is always enabled, and uses a database
+# "router" to handle multiple databases. We may want to add yet
+# another database to hold South's migration tables, to avoid the
+# silliness of requiring an IRDB on, eg, a pubd-only server.
+#
+# We used to set an option to force MySQL to create InnnoDB databases,
+# and we used to set HOST and PORT to the null string, but all of
+# these are the defaults with recent versions of MySQL and Django, so
+# in theory none of them should be necessary.
+
+DATABASES = dict(
+ default = dict(ENGINE = "django.db.backends.mysql",
+ NAME = cfg.get("sql-database", section = "irdbd"),
+ USER = cfg.get("sql-username", section = "irdbd"),
+ PASSWORD = cfg.get("sql-password", section = "irdbd")))
+
+if cfg.getboolean("start_rpkid", section = "myrpki"):
+ DATABASES.update(
+ rpkidb = dict(ENGINE = "django.db.backends.mysql",
+ NAME = cfg.get("sql-database", section = "rpkid"),
+ USER = cfg.get("sql-username", section = "rpkid"),
+ PASSWORD = cfg.get("sql-password", section = "rpkid")))
+
+if cfg.getboolean("start_pubd", section = "myrpki"):
+ DATABASES.update(
+ pubdb = dict(ENGINE = "django.db.backends.mysql",
+ NAME = cfg.get("sql-database", section = "pubd"),
+ USER = cfg.get("sql-username", section = "pubd"),
+ PASSWORD = cfg.get("sql-password", section = "pubd")))
+
+# ORM database "router" to sort out which apps use which databases.
+
+DATABASE_ROUTERS = ["rpki.db_router.RPKIDBRouter"]
+
+# Figure out which apps we're running -- GUI code below adds many more.
+
+INSTALLED_APPS = ["south"]
+
+if cfg.getboolean("start_irdbd", section = "myrpki"):
+ INSTALLED_APPS.append("rpki.irdb")
+
+if cfg.getboolean("start_rpkid", section = "myrpki"):
+ INSTALLED_APPS.append("rpki.rpkidb")
+
+if cfg.getboolean("start_pubd", section = "myrpki"):
+ INSTALLED_APPS.append("rpki.pubdb")
+
+# That's about it if we just need the ORM, but Django throws a hissy
+# fit if SECRET_KEY isn't set, whether we use it for anything or not.
+#
+# Make this unique, and don't share it with anybody.
+if cfg.has_option("secret-key", section = "web_portal"):
+ SECRET_KEY = cfg.get("secret-key", section = "web_portal")
+else:
+ SECRET_KEY = os.urandom(66).encode("hex")
+
+
+# If we're the GUI (or a program like rpki-manage that might be
+# configuring the GUI) we need a lot of other stuff, so check for an
+# environment variable that rpki.wsgi and rpki-manage set for us.
+
+if os.getenv("RPKI_GUI_ENABLE") == "yes":
+
+ # Where to put static files.
+ STATIC_ROOT = rpki.autoconf.datarootdir + "/rpki/media"
+
+ # Must end with a slash!
+ STATIC_URL = "/media/"
+
+ # Where to email server errors.
+ ADMINS = (("Administrator", "root@localhost"),)
+
+ LOGGING = {
+ "version": 1,
+ "formatters": {
+ "verbose": {
+ # see http://docs.python.org/2.7/library/logging.html#logging.LogRecord
+ "format": "%(levelname)s %(asctime)s %(name)s %(message)s"
+ },
+ },
+ "handlers": {
+ "stderr": {
+ "class": "logging.StreamHandler",
+ "level": "DEBUG",
+ "formatter": "verbose",
+ },
+ "mail_admins": {
+ "level": "ERROR",
+ "class": "django.utils.log.AdminEmailHandler",
+ },
+ },
+ "loggers": {
+ "django": {
+ "level": "ERROR",
+ "handlers": ["stderr", "mail_admins"],
+ },
+ "rpki.gui": {
+ "level": "WARNING",
+ "handlers": ["stderr"],
+ },
+ },
+ }
+
+ def select_tz():
+ "Find a supported timezone that looks like UTC"
+ for tz in ("UTC", "GMT", "Etc/UTC", "Etc/GMT"):
+ if os.path.exists("/usr/share/zoneinfo/" + tz):
+ return tz
+ # Can't determine the proper timezone, fall back to UTC and let Django
+ # report the error to the user.
+ return "UTC"
+
+ # Local time zone for this installation. Choices can be found here:
+ # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+ # although not all choices may be available on all operating systems.
+ # If running in a Windows environment this must be set to the same as your
+ # system time zone.
+ TIME_ZONE = select_tz()
+
+ # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+ # for details on why you might need this.
+ def get_allowed_hosts():
+ allowed_hosts = set(cfg.multiget("allowed-hosts", section = "web_portal"))
+ allowed_hosts.add(socket.getfqdn())
+ allowed_hosts.add("127.0.0.1")
+ allowed_hosts.add("::1")
+ try:
+ import netifaces
+ for interface in netifaces.interfaces():
+ addresses = netifaces.ifaddresses(interface)
+ for af in (netifaces.AF_INET, netifaces.AF_INET6):
+ if af in addresses:
+ for address in addresses[af]:
+ if "addr" in address:
+ allowed_hosts.add(address["addr"])
+ except ImportError:
+ pass
+ return list(allowed_hosts)
+
+ ALLOWED_HOSTS = get_allowed_hosts()
+
+ # List of callables that know how to import templates from various sources.
+ TEMPLATE_LOADERS = (
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
+ "django.template.loaders.eggs.Loader"
+ )
+
+ MIDDLEWARE_CLASSES = (
+ "django.middleware.common.CommonMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware"
+ )
+
+ ROOT_URLCONF = "rpki.gui.urls"
+
+ INSTALLED_APPS.extend((
+ "django.contrib.auth",
+ #"django.contrib.admin",
+ #"django.contrib.admindocs",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.staticfiles",
+ "rpki.gui.app",
+ "rpki.gui.cacheview",
+ "rpki.gui.routeview",
+ ))
+
+ TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.contrib.auth.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.media",
+ "django.contrib.messages.context_processors.messages",
+ "django.core.context_processors.request",
+ "django.core.context_processors.static"
+ )
+
+# End of GUI-specific settings.
+
+
+# Allow local site to override any setting above -- but if there's
+# anything that local sites routinely need to modify, please consider
+# putting that configuration into rpki.conf and just adding code here
+# to read that configuration.
+try:
+ from local_settings import *
+except:
+ pass
diff --git a/rpki/exceptions.py b/rpki/exceptions.py
index 504c6f28..3ca8bd81 100644
--- a/rpki/exceptions.py
+++ b/rpki/exceptions.py
@@ -288,6 +288,16 @@ class NoObjectAtURI(RPKI_Exception):
No object published at specified URI.
"""
+class ExistingObjectAtURI(RPKI_Exception):
+ """
+ An object has already been published at specified URI.
+ """
+
+class DifferentObjectAtURI(RPKI_Exception):
+ """
+ An object with a different hash exists at specified URI.
+ """
+
class CMSContentNotSet(RPKI_Exception):
"""
Inner content of a CMS_object has not been set. If object is known
@@ -365,3 +375,8 @@ class WrongEKU(RPKI_Exception):
"""
Extended Key Usage extension does not match profile.
"""
+
+class UnexpectedUpDownResponse(RPKI_Exception):
+ """
+ Up-down message is not of the expected type.
+ """
diff --git a/rpki/fields.py b/rpki/fields.py
new file mode 100644
index 00000000..1ca6c893
--- /dev/null
+++ b/rpki/fields.py
@@ -0,0 +1,196 @@
+# $Id$
+#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2011--2012 Internet Systems Consortium ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notices and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL AND ISC DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL OR
+# ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Common Django ORM field classes.
+
+Many of these are complex ASN.1 DER objects stored as SQL BLOBs, since
+the only sane text representation would just be the Base64 encoding of
+the DER and thus would add no value.
+"""
+
+import logging
+
+from django.db import models
+from south.modelsinspector import add_introspection_rules
+
+import rpki.x509
+import rpki.sundial
+
+logger = logging.getLogger(__name__)
+
+
+class EnumField(models.PositiveSmallIntegerField):
+ """
+ An enumeration type that uses strings in Python and small integers
+ in SQL.
+ """
+
+ description = "An enumeration type"
+
+ __metaclass__ = models.SubfieldBase
+
+ def __init__(self, *args, **kwargs):
+ if isinstance(kwargs.get("choices"), (tuple, list)) and isinstance(kwargs["choices"][0], (str, unicode)):
+ kwargs["choices"] = tuple(enumerate(kwargs["choices"], 1))
+ models.PositiveSmallIntegerField.__init__(self, *args, **kwargs)
+ self.enum_i2s = dict(self.flatchoices)
+ self.enum_s2i = dict((v, k) for k, v in self.flatchoices)
+
+ def to_python(self, value):
+ return self.enum_i2s.get(value, value)
+
+ def get_prep_value(self, value):
+ return self.enum_s2i.get(value, value)
+
+
+class SundialField(models.DateTimeField):
+ """
+ A field type for our customized datetime objects.
+ """
+ __metaclass__ = models.SubfieldBase
+
+ description = "A datetime type using our customized datetime objects"
+
+ def to_python(self, value):
+ if isinstance(value, rpki.sundial.pydatetime.datetime):
+ return rpki.sundial.datetime.from_datetime(
+ models.DateTimeField.to_python(self, value))
+ else:
+ return value
+
+ def get_prep_value(self, value):
+ if isinstance(value, rpki.sundial.datetime):
+ return value.to_datetime()
+ else:
+ return value
+
+
+class BlobField(models.Field):
+ """
+ Basic BLOB field, no type conversion, just an opaque byte string.
+
+ "BLOB" = "Binary Large OBject". Most SQL implementations seem to
+ have such a thing, but support appears to predate standardization,
+ so they all do it slightly differently and we have to cope.
+
+ In PostgreSQL, BLOBs are called "bytea".
+
+ In MySQL, there are different sizes of BLOBs and one must pick the
+ right one to avoid data truncation. RPKI manifests and CRLs can be
+ longer than 65535 octets, so in MySQL the only safe BLOB type for
+ general use is "LONGBLOB".
+
+ SQLite...is not like the other children: data types are more like
+ guidelines than actual rules. But "BLOB" works.
+
+ For anything else, we just use "BLOB" and hope for the best.
+ """
+
+ __metaclass__ = models.SubfieldBase
+ description = "Raw BLOB type without ASN.1 encoding/decoding"
+
+ def __init__(self, *args, **kwargs):
+ self.blob_type = kwargs.pop("blob_type", None)
+ kwargs["serialize"] = False
+ kwargs["blank"] = True
+ kwargs["default"] = None
+ models.Field.__init__(self, *args, **kwargs)
+
+ def db_type(self, connection):
+ if self.blob_type is not None:
+ return self.blob_type
+ elif connection.settings_dict['ENGINE'] == "django.db.backends.mysql":
+ return "LONGBLOB"
+ elif connection.settings_dict['ENGINE'] == "django.db.backends.posgresql":
+ return "bytea"
+ else:
+ return "BLOB"
+
+
+# For reasons which now escape me, I had a few fields in the old
+# hand-coded SQL which used MySQL type BINARY(20) to hold SKIs.
+# Presumably this was so that I could then use those SKIs in indexes
+# and searches, but apparently I never got around to that part.
+#
+# SKIs probably would be better stored as hex strings anyway, so not
+# bothering with a separate binary type model for this. Deal with
+# this if and when it ever becomes an issue.
+
+
+class DERField(BlobField):
+ """
+ Field class for DER objects. These are derived from BLOBs, but with
+ automatic translation between ASN.1 and Python types.
+
+ DERField itself is an abstract class, concrete field classes are
+ derived from it.
+ """
+
+ __metaclass__ = models.SubfieldBase
+
+ def to_python(self, value):
+ assert value is None or isinstance(value, (self.rpki_type, str))
+ if isinstance(value, str):
+ return self.rpki_type(DER = value)
+ else:
+ return value
+
+ def get_prep_value(self, value):
+ assert value is None or isinstance(value, (self.rpki_type, str))
+ if isinstance(value, self.rpki_type):
+ return value.get_DER()
+ else:
+ return value
+
+class CertificateField(DERField):
+ description = "X.509 certificate"
+ rpki_type = rpki.x509.X509
+
+class KeyField(DERField):
+ description = "RSA keypair"
+ rpki_type = rpki.x509.RSA
+
+class CRLField(DERField):
+ description = "Certificate Revocation List"
+ rpki_type = rpki.x509.CRL
+
+class PKCS10Field(DERField):
+ description = "PKCS #10 certificate request"
+ rpki_type = rpki.x509.PKCS10
+
+class ManifestField(DERField):
+ description = "RPKI Manifest"
+ rpki_type = rpki.x509.SignedManifest
+
+class ROAField(DERField):
+ description = "ROA"
+ rpki_type = rpki.x509.ROA
+
+class GhostbusterField(DERField):
+ description = "Ghostbuster Record"
+ rpki_type = rpki.x509.Ghostbuster
+
+
+field_classes = (EnumField, SundialField, BlobField, CertificateField, KeyField,
+ CRLField, PKCS10Field, ManifestField, ROAField, GhostbusterField)
+
+add_introspection_rules([(field_classes, [], {})],
+ [r"^rpki\.fields\." + cls.__name__ for cls in field_classes])
+
+del field_classes
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py
index a084af79..2907f071 100644
--- a/rpki/gui/app/check_expired.py
+++ b/rpki/gui/app/check_expired.py
@@ -41,8 +41,8 @@ def check_cert(handle, p, errs):
The displayed object name defaults to the class name, but can be overridden
using the `object_name` argument.
-
"""
+
t = p.certificate.getNotAfter()
if t <= expire_time:
e = 'expired' if t <= now else 'will expire'
@@ -102,8 +102,8 @@ def check_expire(conf, errs):
def check_child_certs(conf, errs):
"""Fetch the list of published objects from rpkid, and inspect the issued
resource certs (uri ending in .cer).
-
"""
+
z = Zookeeper(handle=conf.handle)
req = list_published_objects_elt.make_pdu(action="list",
tag="list_published_objects",
@@ -139,8 +139,8 @@ def notify_expired(expire_days=14, from_email=None):
expire_days: the number of days ahead of today to warn
from_email: set the From: address for the email
-
"""
+
global expire_time # so i don't have to pass it around
global now
diff --git a/rpki/gui/app/forms.py b/rpki/gui/app/forms.py
index 5394a804..02561303 100644
--- a/rpki/gui/app/forms.py
+++ b/rpki/gui/app/forms.py
@@ -52,6 +52,7 @@ class GhostbusterRequestForm(forms.ModelForm):
Generate a ModelForm with the subset of parents for the current
resource handle.
"""
+
# override default form field
parent = forms.ModelChoiceField(queryset=None, required=False,
help_text='Specify specific parent, or none for all parents')
@@ -86,6 +87,7 @@ class GhostbusterRequestForm(forms.ModelForm):
class ImportForm(forms.Form):
"""Form used for uploading parent/child identity xml files."""
+
handle = forms.CharField(required=False,
widget=forms.TextInput(attrs={'class': 'xlarge'}),
help_text='Optional. Your name for this entity, or blank to accept name in XML')
@@ -101,6 +103,7 @@ class ImportRepositoryForm(forms.Form):
class ImportClientForm(forms.Form):
"""Form used for importing publication client requests."""
+
xml = forms.FileField(label='XML file')
@@ -137,6 +140,7 @@ class UserCreateForm(forms.Form):
class UserEditForm(forms.Form):
"""Form for editing a user."""
+
email = forms.CharField()
pw = forms.CharField(widget=forms.PasswordInput, label='Password',
required=False)
@@ -185,8 +189,8 @@ class ROARequest(forms.Form):
"""Takes an optional `conf` keyword argument specifying the user that
is creating the ROAs. It is used for validating that the prefix the
user entered is currently allocated to that user.
-
"""
+
conf = kwargs.pop('conf', None)
kwargs['auto_id'] = False
super(ROARequest, self).__init__(*args, **kwargs)
@@ -199,8 +203,8 @@ class ROARequest(forms.Form):
rpki.resource_set.resource_range_ip object.
If there is no mask provided, assume the closest classful mask.
-
"""
+
prefix = self.cleaned_data.get('prefix')
if '/' not in prefix:
p = IPAddress(prefix)
@@ -296,7 +300,6 @@ class AddASNForm(forms.Form):
Returns a forms.Form subclass which verifies that the entered ASN range
does not overlap with a previous allocation to the specified child, and
that the ASN range is within the range allocated to the parent.
-
"""
asns = forms.CharField(
@@ -335,8 +338,8 @@ class AddNetForm(forms.Form):
Returns a forms.Form subclass which validates that the entered address
range is within the resources allocated to the parent, and does not overlap
with what is already allocated to the specified child.
-
"""
+
address_range = forms.CharField(
help_text='CIDR or range',
widget=forms.TextInput(attrs={'autofocus': 'true'})
@@ -383,7 +386,6 @@ def ChildForm(instance):
This is roughly based on the equivalent ModelForm, but uses Form as a base
class so that selection boxes for the AS and Prefixes can be edited in a
single form.
-
"""
class _wrapped(forms.Form):
@@ -401,11 +403,13 @@ def ChildForm(instance):
class Empty(forms.Form):
"""Stub form for views requiring confirmation."""
+
pass
class ResourceHolderForm(forms.Form):
"""form for editing ACL on Conf objects."""
+
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
help_text='users allowed to mange this resource holder'
@@ -413,7 +417,8 @@ class ResourceHolderForm(forms.Form):
class ResourceHolderCreateForm(forms.Form):
- """form for creating new resource holdres."""
+ """form for creating new resource holders."""
+
handle = forms.CharField(max_length=30)
parent = forms.ModelChoiceField(
required=False,
diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py
index 0bf5f942..f17ba5ac 100644
--- a/rpki/gui/app/glue.py
+++ b/rpki/gui/app/glue.py
@@ -16,7 +16,6 @@
"""
This file contains code that interfaces between the django views implementing
the portal gui and the rpki.* modules.
-
"""
from __future__ import with_statement
@@ -39,6 +38,7 @@ from django.db.transaction import commit_on_success
def ghostbuster_to_vcard(gbr):
"""Convert a GhostbusterRequest object into a vCard object."""
+
import vobject
vcard = vobject.vCard()
@@ -86,7 +86,6 @@ def list_received_resources(log, conf):
The semantics are to clear the entire table and populate with the list of
certs received. Other models should not reference the table directly with
foreign keys.
-
"""
z = Zookeeper(handle=conf.handle)
diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py
index 32a897c7..d6332796 100644
--- a/rpki/gui/app/models.py
+++ b/rpki/gui/app/models.py
@@ -120,16 +120,16 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def parents(self):
"""Simulates irdb.models.Parent.objects, but returns app.models.Parent
proxy objects.
-
"""
+
return Parent.objects.filter(issuer=self)
@property
def children(self):
"""Simulates irdb.models.Child.objects, but returns app.models.Child
proxy objects.
-
"""
+
return Child.objects.filter(issuer=self)
@property
@@ -148,8 +148,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes(self):
"""Return all IPv4 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
q = models.Q()
@@ -162,8 +162,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes_v6(self):
"""Return all IPv6 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
q = models.Q()
@@ -174,6 +174,7 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def send_alert(self, subject, message, from_email, severity=Alert.INFO):
"""Store an alert for this resource holder."""
+
self.alerts.create(subject=subject, text=message, severity=severity)
send_mail(
@@ -189,8 +190,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
Contact emails are extract from any ghostbuster requests, and any
linked user accounts.
-
"""
+
notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address]
notify_emails.extend(
[acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email]
@@ -209,7 +210,6 @@ class ResourceCert(models.Model):
"""Represents a resource certificate.
This model is used to cache the output of <list_received_resources/>.
-
"""
# Handle to which this cert was issued
@@ -237,6 +237,7 @@ class ResourceCert(models.Model):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert.issuer:
@@ -410,7 +411,6 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6):
class ConfACL(models.Model):
"""Stores access control for which users are allowed to manage a given
resource handle.
-
"""
conf = models.ForeignKey(Conf)
diff --git a/rpki/gui/app/range_list.py b/rpki/gui/app/range_list.py
index 21fd1f29..5cb4f5e4 100755
--- a/rpki/gui/app/range_list.py
+++ b/rpki/gui/app/range_list.py
@@ -70,6 +70,7 @@ class RangeList(list):
def difference(self, other):
"""Return a RangeList object which contains ranges in this object which
are not in "other"."""
+
it = iter(other)
try:
@@ -85,6 +86,7 @@ class RangeList(list):
def V(v):
"""convert the integer value to the appropriate type for this
range"""
+
return x.__class__.datum_type(v)
try:
diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py
index 9a1c4cfe..228d5c6c 100644
--- a/rpki/gui/app/views.py
+++ b/rpki/gui/app/views.py
@@ -71,6 +71,7 @@ def superuser_required(f):
def get_conf(user, handle):
"""return the Conf object for 'handle'.
user is a request.user object to use enforce ACLs."""
+
if user.is_superuser:
qs = models.Conf.objects.all()
else:
@@ -81,8 +82,8 @@ def get_conf(user, handle):
def handle_required(f):
"""Decorator for view functions which require the user to be logged in and
a resource handle selected for the session.
-
"""
+
@login_required
@tls_required
def wrapped_fn(request, *args, **kwargs):
@@ -126,8 +127,8 @@ def generic_import(request, queryset, configure, form_class=None,
if None (default), the user will be redirected to the detail page for
the imported object. Otherwise, the user will be redirected to the
specified URL.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
if form_class is None:
form_class = forms.ImportForm
@@ -251,6 +252,7 @@ def dashboard(request):
@login_required
def conf_list(request, **kwargs):
"""Allow the user to select a handle."""
+
log = request.META['wsgi.errors']
next_url = request.GET.get('next', reverse(dashboard))
if request.user.is_superuser:
@@ -266,6 +268,7 @@ def conf_list(request, **kwargs):
@login_required
def conf_select(request):
"""Change the handle for the current session."""
+
if not 'handle' in request.GET:
return redirect(conf_list)
handle = request.GET['handle']
@@ -288,8 +291,8 @@ def serve_xml(content, basename, ext='xml'):
`basename` is the prefix to specify for the XML filename.
`csv` is the type (default: xml)
-
"""
+
resp = http.HttpResponse(content, mimetype='application/%s' % ext)
resp['Content-Disposition'] = 'attachment; filename=%s.%s' % (basename, ext)
return resp
@@ -298,6 +301,7 @@ def serve_xml(content, basename, ext='xml'):
@handle_required
def conf_export(request):
"""Return the identity.xml for the current handle."""
+
conf = get_conf(request.user, request.session['handle'])
z = Zookeeper(handle=conf.handle)
xml = z.generate_identity()
@@ -307,6 +311,7 @@ def conf_export(request):
@handle_required
def export_asns(request):
"""Export CSV file containing ASN allocations to children."""
+
conf = get_conf(request.user, request.session['handle'])
s = cStringIO.StringIO()
csv_writer = csv.writer(s, delimiter=' ')
@@ -342,6 +347,7 @@ def import_asns(request):
@handle_required
def export_prefixes(request):
"""Export CSV file containing ASN allocations to children."""
+
conf = get_conf(request.user, request.session['handle'])
s = cStringIO.StringIO()
csv_writer = csv.writer(s, delimiter=' ')
@@ -411,6 +417,7 @@ def parent_delete(request, pk):
@handle_required
def parent_export(request, pk):
"""Export XML repository request for a given parent."""
+
conf = get_conf(request.user, request.session['handle'])
parent = get_object_or_404(conf.parents, pk=pk)
z = Zookeeper(handle=conf.handle)
@@ -474,6 +481,7 @@ def child_detail(request, pk):
@handle_required
def child_edit(request, pk):
"""Edit the end validity date for a resource handle's child."""
+
log = request.META['wsgi.errors']
conf = get_conf(request.user, request.session['handle'])
child = get_object_or_404(conf.children.all(), pk=pk)
@@ -505,8 +513,8 @@ def child_response(request, pk):
"""
Export the XML file containing the output of the configure_child
to send back to the client.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
child = get_object_or_404(models.Child, issuer=conf, pk=pk)
z = Zookeeper(handle=conf.handle)
@@ -551,7 +559,6 @@ def get_covered_routes(rng, max_prefixlen, asn):
A "newstatus" attribute is monkey-patched on the RouteOrigin objects which
can be used in the template. "status" remains the current validation
status of the object.
-
"""
# find all routes that match or are completed covered by the proposed new roa
@@ -591,7 +598,6 @@ def roa_create(request):
Doesn't use the generic create_object() form because we need to
create both the ROARequest and ROARequestPrefix objects.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -628,7 +634,6 @@ def roa_create(request):
class ROARequestFormSet(BaseFormSet):
"""There is no way to pass arbitrary keyword arguments to the form
constructor, so we have to override BaseFormSet to allow it.
-
"""
def __init__(self, *args, **kwargs):
self.conf = kwargs.pop('conf')
@@ -666,7 +671,6 @@ def roa_create_multi(request):
?roa=1.1.1.1-2.2.2.2,42
The ASN may optionally be omitted.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -717,8 +721,8 @@ def roa_create_multi(request):
def roa_create_confirm(request):
"""This function is called when the user confirms the creation of a ROA
request. It is responsible for updating the IRDB.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
log = request.META['wsgi.errors']
if request.method == 'POST':
@@ -746,8 +750,8 @@ def roa_create_confirm(request):
def roa_create_multi_confirm(request):
"""This function is called when the user confirms the creation of a ROA
request. It is responsible for updating the IRDB.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
log = request.META['wsgi.errors']
if request.method == 'POST':
@@ -778,7 +782,6 @@ def roa_delete(request, pk):
Uses a form for double confirmation, displaying how the route
validation status may change as a result.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -835,6 +838,7 @@ def roa_clone(request, pk):
@handle_required
def roa_import(request):
"""Import CSV containing ROA declarations."""
+
if request.method == 'POST':
form = forms.ImportCSVForm(request.POST, request.FILES)
if form.is_valid():
@@ -860,6 +864,7 @@ def roa_import(request):
@handle_required
def roa_export(request):
"""Export CSV containing ROA declarations."""
+
# FIXME: remove when Zookeeper can do this
f = cStringIO.StringIO()
csv_writer = csv.writer(f, delimiter=' ')
@@ -941,8 +946,8 @@ def ghostbuster_edit(request, pk):
def refresh(request):
"""
Query rpkid, update the db, and redirect back to the dashboard.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
glue.list_received_resources(request.META['wsgi.errors'], conf)
return http.HttpResponseRedirect(reverse(dashboard))
@@ -953,8 +958,8 @@ def route_view(request):
"""
Display a list of global routing table entries which match resources
listed in received certificates.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
count = request.GET.get('count', 25)
page = request.GET.get('page', 1)
@@ -972,6 +977,7 @@ def route_view(request):
def route_detail(request, pk):
"""Show a list of ROAs that match a given IPv4 route."""
+
route = get_object_or_404(models.RouteOrigin, pk=pk)
# when running rootd, viewing the 0.0.0.0/0 route will cause a fetch of all
# roas, so we paginate here, even though in the general case the number of
@@ -989,8 +995,8 @@ def route_suggest(request):
"""Handles POSTs from the route view and redirects to the ROA creation
page based on selected route objects. The form should contain elements of
the form "pk-NUM" where NUM is the RouteOrigin object id.
-
"""
+
if request.method == 'POST':
routes = []
for pk in request.POST.iterkeys():
@@ -1040,6 +1046,7 @@ def repository_delete(request, pk):
@handle_required
def repository_import(request):
"""Import XML response file from repository operator."""
+
return generic_import(request,
models.Repository.objects,
Zookeeper.configure_repository,
@@ -1094,8 +1101,8 @@ def client_import(request):
def client_export(request, pk):
"""Return the XML file resulting from a configure_publication_client
request.
-
"""
+
client = get_object_or_404(models.Client, pk=pk)
z = Zookeeper()
xml = z.generate_repository_response(client)
@@ -1107,6 +1114,7 @@ def client_export(request, pk):
@superuser_required
def resource_holder_list(request):
"""Display a list of all the RPKI handles managed by this server."""
+
return render(request, 'app/resource_holder_list.html', {
'object_list': models.Conf.objects.all()
})
@@ -1115,6 +1123,7 @@ def resource_holder_list(request):
@superuser_required
def resource_holder_edit(request, pk):
"""Display a list of all the RPKI handles managed by this server."""
+
conf = get_object_or_404(models.Conf, pk=pk)
if request.method == 'POST':
form = forms.ResourceHolderForm(request.POST, request.FILES)
@@ -1221,6 +1230,7 @@ def user_create(request):
@superuser_required
def user_list(request):
"""Display a list of all the RPKI handles managed by this server."""
+
return render(request, 'app/user_list.html', {
'object_list': User.objects.all()
})
@@ -1316,6 +1326,7 @@ class AlertDeleteView(DeleteView):
@handle_required
def alert_clear_all(request):
"""Clear all alerts associated with the current resource holder."""
+
if request.method == 'POST':
form = forms.Empty(request.POST, request.FILES)
if form.is_valid():
diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/cacheview/models.py
index c3ee8421..08acfa2d 100644
--- a/rpki/gui/cacheview/models.py
+++ b/rpki/gui/cacheview/models.py
@@ -58,6 +58,7 @@ class ValidationLabel(models.Model):
Represents a specific error condition defined in the rcynic XML
output file.
"""
+
label = models.CharField(max_length=79, db_index=True, unique=True)
status = models.CharField(max_length=255)
kind = models.PositiveSmallIntegerField(choices=kinds)
@@ -70,6 +71,7 @@ class RepositoryObject(models.Model):
"""
Represents a globally unique RPKI repository object, specified by its URI.
"""
+
uri = models.URLField(unique=True, db_index=True)
generations = list(enumerate(('current', 'backup')))
@@ -89,6 +91,7 @@ class SignedObject(models.Model):
The signing certificate is ommitted here in order to give a proper
value for the 'related_name' attribute.
"""
+
repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True)
# on-disk file modification time
@@ -108,6 +111,7 @@ class SignedObject(models.Model):
"""
convert the local timestamp to UTC and convert to a datetime object
"""
+
return datetime.utcfromtimestamp(self.mtime + time.timezone)
def status_id(self):
@@ -116,6 +120,7 @@ class SignedObject(models.Model):
The selector is chosen based on the current generation only. If there is any bad status,
return bad, else if there are any warn status, return warn, else return good.
"""
+
for x in reversed(kinds):
if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]):
return x[1]
@@ -129,6 +134,7 @@ class Cert(SignedObject):
"""
Object representing a resource certificate.
"""
+
addresses = models.ManyToManyField(AddressRange, related_name='certs')
addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs')
asns = models.ManyToManyField(ASRange, related_name='certs')
@@ -141,6 +147,7 @@ class Cert(SignedObject):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert != cert.issuer:
@@ -180,6 +187,7 @@ class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4):
@property
def routes(self):
"""return all routes covered by this roa prefix"""
+
return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min,
prefix_max__lte=self.prefix_max)
diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py
index 2247054b..daca07bf 100644
--- a/rpki/gui/cacheview/tests.py
+++ b/rpki/gui/cacheview/tests.py
@@ -12,6 +12,7 @@ class SimpleTest(TestCase):
"""
Tests that 1 + 1 always equals 2.
"""
+
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py
index 9e8748bf..31ad8b8b 100644
--- a/rpki/gui/cacheview/util.py
+++ b/rpki/gui/cacheview/util.py
@@ -310,8 +310,8 @@ def fetch_published_objects():
"""Query rpkid for all objects published by local users, and look up the
current validation status of each object. The validation status is used
later to send alerts for objects which have transitioned to invalid.
-
"""
+
logger.info('querying for published objects')
handles = [conf.handle for conf in Conf.objects.all()]
@@ -353,7 +353,6 @@ class Handle(object):
def notify_invalid():
"""Send email alerts to the addresses registered in ghostbuster records for
any invalid objects that were published by users of this system.
-
"""
logger.info('sending notifications for invalid objects')
diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py
index 94870eb2..451c0d1e 100644
--- a/rpki/gui/cacheview/views.py
+++ b/rpki/gui/cacheview/views.py
@@ -29,6 +29,7 @@ def cert_chain(obj):
"""
returns an iterator covering all certs from the root cert down to the EE.
"""
+
chain = [obj]
while obj != obj.issuer:
obj = obj.issuer
diff --git a/rpki/gui/decorators.py b/rpki/gui/decorators.py
index 69d20c46..75efeae0 100644
--- a/rpki/gui/decorators.py
+++ b/rpki/gui/decorators.py
@@ -15,15 +15,22 @@
__version__ = '$Id$'
from django import http
+from os import getenv
-def tls_required(f):
- """Decorator which returns a 500 error if the connection is not secured
- with TLS (https).
+# Don't set this in production, ever. Really. You have been warned.
+#
+_allow_plain_http_for_testing = getenv("ALLOW_PLAIN_HTTP_FOR_TESTING") == "I solemnly swear that I am not running this in production"
+
+def tls_required(f):
+ """
+ Decorator which returns a 500 error if the connection is not
+ secured with TLS (https).
"""
+
def _tls_required(request, *args, **kwargs):
- if not request.is_secure():
+ if not request.is_secure() and not _allow_plain_http_for_testing:
return http.HttpResponseServerError(
'This resource may only be accessed securely via https',
content_type='text/plain')
diff --git a/rpki/gui/default_settings.py b/rpki/gui/default_settings.py
deleted file mode 100644
index 3859247c..00000000
--- a/rpki/gui/default_settings.py
+++ /dev/null
@@ -1,171 +0,0 @@
-"""
-This module contains static configuration settings for the web portal.
-"""
-
-__version__ = '$Id$'
-
-import os
-import random
-import string
-import socket
-
-import rpki.config
-import rpki.autoconf
-
-# Where to put static files.
-STATIC_ROOT = rpki.autoconf.datarootdir + '/rpki/media'
-
-# Must end with a slash!
-STATIC_URL = '/media/'
-
-# Where to email server errors.
-ADMINS = (('Administrator', 'root@localhost'),)
-
-LOGGING = {
- 'version': 1,
- 'formatters': {
- 'verbose': {
- # see http://docs.python.org/2.7/library/logging.html#logging.LogRecord
- 'format': '%(levelname)s %(asctime)s %(name)s %(message)s'
- },
- },
- 'handlers': {
- 'stderr': {
- 'class': 'logging.StreamHandler',
- 'level': 'DEBUG',
- 'formatter': 'verbose',
- },
- 'mail_admins': {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler',
- },
- },
- 'loggers': {
- 'django': {
- 'level': 'ERROR',
- 'handlers': ['stderr', 'mail_admins'],
- },
- 'rpki.gui': {
- 'level': 'WARNING',
- 'handlers': ['stderr'],
- },
- },
-}
-
-# Load the SQL authentication bits from the system rpki.conf.
-rpki_config = rpki.config.parser(section='web_portal')
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': rpki_config.get('sql-database'),
- 'USER': rpki_config.get('sql-username'),
- 'PASSWORD': rpki_config.get('sql-password'),
-
- # Ensure the default storage engine is InnoDB since we need
- # foreign key support. The Django documentation suggests
- # removing this after the syncdb is performed as an optimization,
- # but there isn't an easy way to do this automatically.
-
- 'OPTIONS': {
- 'init_command': 'SET storage_engine=INNODB',
- }
- }
-}
-
-
-def select_tz():
- "Find a supported timezone that looks like UTC"
- for tz in ('UTC', 'GMT', 'Etc/UTC', 'Etc/GMT'):
- if os.path.exists('/usr/share/zoneinfo/' + tz):
- return tz
- # Can't determine the proper timezone, fall back to UTC and let Django
- # report the error to the user.
- return 'UTC'
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = select_tz()
-
-def get_secret_key():
- """Retrieve the secret-key value from rpki.conf or generate a random value
- if it is not present."""
- d = string.letters + string.digits
- val = ''.join([random.choice(d) for _ in range(50)])
- return rpki_config.get('secret-key', val)
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = get_secret_key()
-
-# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
-# for details on why you might need this.
-def get_allowed_hosts():
- allowed_hosts = set(rpki_config.multiget("allowed-hosts"))
- allowed_hosts.add(socket.getfqdn())
- try:
- import netifaces
- for interface in netifaces.interfaces():
- addresses = netifaces.ifaddresses(interface)
- for af in (netifaces.AF_INET, netifaces.AF_INET6):
- if af in addresses:
- for address in addresses[af]:
- if "addr" in address:
- allowed_hosts.add(address["addr"])
- except ImportError:
- pass
- return list(allowed_hosts)
-
-ALLOWED_HOSTS = get_allowed_hosts()
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
- 'django.template.loaders.eggs.Loader'
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware'
-)
-
-ROOT_URLCONF = 'rpki.gui.urls'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- #'django.contrib.admin',
- #'django.contrib.admindocs',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.staticfiles',
- 'rpki.irdb',
- 'rpki.gui.app',
- 'rpki.gui.cacheview',
- 'rpki.gui.routeview',
- 'south',
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- "django.contrib.auth.context_processors.auth",
- "django.core.context_processors.debug",
- "django.core.context_processors.i18n",
- "django.core.context_processors.media",
- "django.contrib.messages.context_processors.messages",
- "django.core.context_processors.request",
- "django.core.context_processors.static"
-)
-
-# Allow local site to override any setting above -- but if there's
-# anything that local sites routinely need to modify, please consider
-# putting that configuration into rpki.conf and just adding code here
-# to read that configuration.
-try:
- from local_settings import *
-except:
- pass
diff --git a/rpki/gui/models.py b/rpki/gui/models.py
index 184383c0..62400d2a 100644
--- a/rpki/gui/models.py
+++ b/rpki/gui/models.py
@@ -42,8 +42,8 @@ class IPv6AddressField(models.Field):
"""
Note that we add a custom conversion to encode long values as hex
strings in SQL statements. See settings.get_conv() for details.
-
"""
+
return value.toBytes()
@@ -82,6 +82,7 @@ class Prefix(models.Model):
"""
Returns the prefix as a rpki.resource_set.resource_range_ip object.
"""
+
return self.range_cls(self.prefix_min, self.prefix_max)
@property
@@ -96,6 +97,7 @@ class Prefix(models.Model):
def __unicode__(self):
"""This method may be overridden by subclasses. The default
implementation calls get_prefix_display(). """
+
return self.get_prefix_display()
class Meta:
diff --git a/rpki/gui/routeview/api.py b/rpki/gui/routeview/api.py
index cf699c9a..b4ff297a 100644
--- a/rpki/gui/routeview/api.py
+++ b/rpki/gui/routeview/api.py
@@ -29,8 +29,8 @@ def route_list(request):
By default, only returns up to 10 matching routes, but the client may
request a different limit with the 'count=' query string parameter.
-
"""
+
hard_limit = 100
if request.method == 'GET' and 'prefix__in' in request.GET:
diff --git a/rpki/gui/routeview/util.py b/rpki/gui/routeview/util.py
index 54d50f24..a2b515c8 100644
--- a/rpki/gui/routeview/util.py
+++ b/rpki/gui/routeview/util.py
@@ -179,8 +179,8 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='auto'):
filename [optional]: the full path to the downloaded file to parse
filetype [optional]: 'text' or 'mrt'
-
"""
+
start_time = time.time()
if filename.startswith('http://'):
diff --git a/rpki/gui/script_util.py b/rpki/gui/script_util.py
index c3a864fd..678ddae6 100644
--- a/rpki/gui/script_util.py
+++ b/rpki/gui/script_util.py
@@ -16,11 +16,6 @@
This module contains utility functions for use in standalone scripts.
"""
-from django.conf import settings
-
-from rpki import config
-from rpki import autoconf
-
__version__ = '$Id$'
@@ -28,16 +23,29 @@ def setup():
"""
Configure Django enough to use the ORM.
"""
- cfg = config.parser(section='web_portal')
- # INSTALLED_APPS doesn't seem necessary so long as you are only accessing
- # existing tables.
- settings.configure(
- DATABASES={
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': cfg.get('sql-database'),
- 'USER': cfg.get('sql-username'),
- 'PASSWORD': cfg.get('sql-password'),
- }
- },
- )
+
+ # In theory we no longer need to call settings.configure, which
+ # probably means this whole module can go away soon, but leave
+ # breadcrumbs for now.
+
+ if True:
+ os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings")
+
+ else:
+ from rpki import config
+ from rpki import autoconf
+ from django.conf import settings
+
+ cfg = config.parser(section='web_portal')
+ # INSTALLED_APPS doesn't seem necessary so long as you are only accessing
+ # existing tables.
+ settings.configure(
+ DATABASES={
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': cfg.get('sql-database'),
+ 'USER': cfg.get('sql-username'),
+ 'PASSWORD': cfg.get('sql-password'),
+ }
+ },
+ )
diff --git a/rpki/http.py b/rpki/http.py
index 546dd310..e41b0080 100644
--- a/rpki/http.py
+++ b/rpki/http.py
@@ -112,6 +112,7 @@ def supported_address_families(enable_ipv6):
IP address families on which servers should listen, and to consider
when selecting addresses for client connections.
"""
+
if enable_ipv6 and have_ipv6:
return (socket.AF_INET, socket.AF_INET6)
else:
@@ -121,6 +122,7 @@ def localhost_addrinfo():
"""
Return pseudo-getaddrinfo results for localhost.
"""
+
result = [(socket.AF_INET, "127.0.0.1")]
if enable_ipv6_clients and have_ipv6:
result.append((socket.AF_INET6, "::1"))
@@ -144,6 +146,7 @@ class http_message(object):
Clean up (some of) the horrible messes that HTTP allows in its
headers.
"""
+
if headers is None:
headers = () if self.headers is None else self.headers.items()
translate_underscore = True
@@ -166,6 +169,7 @@ class http_message(object):
"""
Parse and normalize an incoming HTTP message.
"""
+
self = cls()
headers = headers.split("\r\n")
self.parse_first_line(*headers.pop(0).split(None, 2))
@@ -180,6 +184,7 @@ class http_message(object):
"""
Format an outgoing HTTP message.
"""
+
s = self.format_first_line()
if self.body is not None:
assert isinstance(self.body, str)
@@ -198,6 +203,7 @@ class http_message(object):
"""
Parse HTTP version, raise an exception if we can't.
"""
+
if version[:5] != "HTTP/":
raise rpki.exceptions.HTTPBadVersion("Couldn't parse version %s" % version)
self.version = tuple(int(i) for i in version[5:].split("."))
@@ -207,6 +213,7 @@ class http_message(object):
"""
Figure out whether this HTTP message encourages a persistent connection.
"""
+
c = self.headers.get("Connection")
if self.version == (1, 1):
return c is None or "close" not in c.lower()
@@ -233,6 +240,7 @@ class http_request(http_message):
"""
Parse first line of HTTP request message.
"""
+
self.parse_version(version)
self.cmd = cmd
self.path = path
@@ -242,6 +250,7 @@ class http_request(http_message):
Format first line of HTTP request message, and set up the
User-Agent header.
"""
+
self.headers.setdefault("User-Agent", self.software_name)
return "%s %s HTTP/%d.%d\r\n" % (self.cmd, self.path, self.version[0], self.version[1])
@@ -262,6 +271,7 @@ class http_response(http_message):
"""
Parse first line of HTTP response message.
"""
+
self.parse_version(version)
self.code = int(code)
self.reason = reason
@@ -271,6 +281,7 @@ class http_response(http_message):
Format first line of HTTP response message, and set up Date and
Server headers.
"""
+
self.headers.setdefault("Date", time.strftime("%a, %d %b %Y %T GMT"))
self.headers.setdefault("Server", self.software_name)
return "HTTP/%d.%d %s %s\r\n" % (self.version[0], self.version[1], self.code, self.reason)
@@ -319,6 +330,7 @@ class http_stream(asynchat.async_chat):
"""
(Re)start HTTP message parser, reset timer.
"""
+
assert not self.buffer
self.chunk_handler = None
self.set_terminator("\r\n\r\n")
@@ -330,6 +342,7 @@ class http_stream(asynchat.async_chat):
stream's timeout value if we're doing timeouts, otherwise clear
it.
"""
+
if self.timeout is not None:
self.logger.debug("Setting timeout %s", self.timeout)
self.timer.set(self.timeout)
@@ -341,6 +354,7 @@ class http_stream(asynchat.async_chat):
"""
Buffer incoming data from asynchat.
"""
+
self.buffer.append(data)
self.update_timeout()
@@ -348,6 +362,7 @@ class http_stream(asynchat.async_chat):
"""
Consume data buffered from asynchat.
"""
+
val = "".join(self.buffer)
self.buffer = []
return val
@@ -369,6 +384,7 @@ class http_stream(asynchat.async_chat):
separate mechanisms (chunked, content-length, TCP close) is going
to tell us how to find the end of the message body.
"""
+
self.update_timeout()
if self.chunk_handler:
self.chunk_handler()
@@ -392,6 +408,7 @@ class http_stream(asynchat.async_chat):
stream up to read it; otherwise, this is the last chunk, so start
the process of exiting the chunk decoder.
"""
+
n = int(self.get_buffer().partition(";")[0], 16)
self.logger.debug("Chunk length %s", n)
if n:
@@ -407,6 +424,7 @@ class http_stream(asynchat.async_chat):
body of a chunked message (sic). Save it, and prepare to move on
to the next chunk.
"""
+
self.logger.debug("Chunk body")
self.msg.body += self.buffer
self.buffer = []
@@ -418,6 +436,7 @@ class http_stream(asynchat.async_chat):
Consume the CRLF that terminates a chunk, reinitialize chunk
decoder to be ready for the next chunk.
"""
+
self.logger.debug("Chunk CRLF")
s = self.get_buffer()
assert s == "", "%r: Expected chunk CRLF, got '%s'" % (self, s)
@@ -428,6 +447,7 @@ class http_stream(asynchat.async_chat):
Consume chunk trailer, which should be empty, then (finally!) exit
the chunk decoder and hand complete message off to the application.
"""
+
self.logger.debug("Chunk trailer")
s = self.get_buffer()
assert s == "", "%r: Expected end of chunk trailers, got '%s'" % (self, s)
@@ -438,6 +458,7 @@ class http_stream(asynchat.async_chat):
"""
Hand normal (not chunked) message off to the application.
"""
+
self.msg.body = self.get_buffer()
self.handle_message()
@@ -447,6 +468,7 @@ class http_stream(asynchat.async_chat):
whether it's one we should just pass along, otherwise log a stack
trace and close the stream.
"""
+
self.timer.cancel()
etype = sys.exc_info()[0]
if etype in (SystemExit, rpki.async.ExitNow):
@@ -459,6 +481,7 @@ class http_stream(asynchat.async_chat):
"""
Inactivity timer expired, close connection with prejudice.
"""
+
self.logger.debug("Timeout, closing")
self.close()
@@ -467,6 +490,7 @@ class http_stream(asynchat.async_chat):
Wrapper around asynchat connection close handler, so that we can
log the event, cancel timer, and so forth.
"""
+
self.logger.debug("Close event in HTTP stream handler")
self.timer.cancel()
asynchat.async_chat.handle_close(self)
@@ -497,12 +521,14 @@ class http_server(http_stream):
Content-Length header (that is: this message will be the last one
in this server stream). No special action required.
"""
+
self.handle_message()
def find_handler(self, path):
"""
Helper method to search self.handlers.
"""
+
for s, h in self.handlers:
if path.startswith(s):
return h
@@ -515,6 +541,7 @@ class http_server(http_stream):
Content-Type, look for a handler, and if everything looks right,
pass the message body, path, and a reply callback to the handler.
"""
+
self.logger.debug("Received request %r", self.msg)
if not self.msg.persistent:
self.expect_close = True
@@ -541,12 +568,14 @@ class http_server(http_stream):
"""
Send an error response to this request.
"""
+
self.send_message(code = code, reason = reason)
def send_reply(self, code, body = None, reason = "OK"):
"""
Send a reply to this request.
"""
+
self.send_message(code = code, body = body, reason = reason)
def send_message(self, code, reason = "OK", body = None):
@@ -556,6 +585,7 @@ class http_server(http_stream):
listen for next message; otherwise, queue up a close event for
this stream so it will shut down once the reply has been sent.
"""
+
self.logger.debug("Sending response %s %s", code, reason)
if code >= 400:
self.expect_close = True
@@ -611,6 +641,7 @@ class http_listener(asyncore.dispatcher):
Asyncore says we have an incoming connection, spawn an http_server
stream for it and pass along all of our handler data.
"""
+
try:
res = self.accept()
if res is None:
@@ -627,6 +658,7 @@ class http_listener(asyncore.dispatcher):
"""
Asyncore signaled an error, pass it along or log it.
"""
+
if sys.exc_info()[0] in (SystemExit, rpki.async.ExitNow):
raise
self.logger.exception("Error in HTTP listener")
@@ -662,6 +694,7 @@ class http_client(http_stream):
"""
Create socket and request a connection.
"""
+
if not use_adns:
self.logger.debug("Not using ADNS")
self.gotaddrinfo([(socket.AF_INET, self.host)])
@@ -678,12 +711,14 @@ class http_client(http_stream):
Handle DNS lookup errors. For now, just whack the connection.
Undoubtedly we should do something better with diagnostics here.
"""
+
self.handle_error()
def gotaddrinfo(self, addrinfo):
"""
Got address data from DNS, create socket and request connection.
"""
+
try:
self.af, self.address = random.choice(addrinfo)
self.logger.debug("Connecting to AF %s host %s port %s addr %s", self.af, self.host, self.port, self.address)
@@ -701,6 +736,7 @@ class http_client(http_stream):
"""
Asyncore says socket has connected.
"""
+
self.logger.debug("Socket connected")
self.set_state("idle")
assert self.queue.client is self
@@ -710,6 +746,7 @@ class http_client(http_stream):
"""
Set HTTP client connection state.
"""
+
self.logger.debug("State transition %s => %s", self.state, state)
self.state = state
@@ -720,12 +757,14 @@ class http_client(http_stream):
in this server stream). In this case we want to read until we
reach the end of the data stream.
"""
+
self.set_terminator(None)
def send_request(self, msg):
"""
Queue up request message and kickstart connection.
"""
+
self.logger.debug("Sending request %r", msg)
assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state)
self.set_state("request-sent")
@@ -782,6 +821,7 @@ class http_client(http_stream):
message now; if we were waiting for the response to a request we
sent, signal the error.
"""
+
http_stream.handle_close(self)
self.logger.debug("State %s", self.state)
if self.get_terminator() is None:
@@ -796,6 +836,7 @@ class http_client(http_stream):
Connection idle timer has expired. Shut down connection in any
case, noisily if we weren't idle.
"""
+
bad = self.state not in ("idle", "closing")
if bad:
self.logger.warning("Timeout while in state %s", self.state)
@@ -813,6 +854,7 @@ class http_client(http_stream):
Asyncore says something threw an exception. Log it, then shut
down the connection and pass back the exception.
"""
+
eclass, edata = sys.exc_info()[0:2]
self.logger.warning("Error on HTTP client connection %s:%s %s %s", self.host, self.port, eclass, edata)
http_stream.handle_error(self)
@@ -840,6 +882,7 @@ class http_queue(object):
"""
Append http_request object(s) to this queue.
"""
+
self.logger.debug("Adding requests %r", requests)
self.queue.extend(requests)
@@ -852,6 +895,7 @@ class http_queue(object):
exception, or timeout) for the query currently in progress will
call this method when it's time to kick out the next query.
"""
+
try:
if self.client is None:
self.client = http_client(self, self.hostport)
@@ -871,6 +915,7 @@ class http_queue(object):
"""
Kick out the next query in this queue, if any.
"""
+
if self.queue:
self.client.send_request(self.queue[0])
@@ -881,6 +926,7 @@ class http_queue(object):
handling of what otherwise would be a nasty set of race
conditions.
"""
+
if client_ is self.client:
self.logger.debug("Detaching client %r", client_)
self.client = None
@@ -1032,6 +1078,7 @@ class caller(object):
"""
Handle CMS-wrapped XML response message.
"""
+
try:
r_cms = self.proto.cms_msg(DER = r_der)
r_msg = r_cms.unwrap((self.server_ta, self.server_cert))
diff --git a/rpki/http_simple.py b/rpki/http_simple.py
new file mode 100644
index 00000000..2a00ff9c
--- /dev/null
+++ b/rpki/http_simple.py
@@ -0,0 +1,137 @@
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+HTTP using Python standard libraries, for RPKI programs that don't
+need the full-blown rpki.http asynchronous code.
+"""
+
+import logging
+import httplib
+import urlparse
+import BaseHTTPServer
+
+logger = logging.getLogger(__name__)
+
+
+rpki_content_type = "application/x-rpki"
+
+
+class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """
+ HTTP request handler simple RPKI servers.
+ """
+
+ def do_POST(self):
+ try:
+ content_type = self.headers.get("Content-Type")
+ content_length = self.headers.get("Content-Length")
+ if content_type != rpki_content_type:
+ self.send_error(415, "No handler for Content-Type %s" % content_type)
+ else:
+ for prefix, handler in self.rpki_handlers:
+ if self.path.startswith(prefix):
+ handler(self, (self.rfile.read()
+ if content_length is None else
+ self.rfile.read(int(content_length))))
+ break
+ else:
+ self.send_error(404, "No handler for path %s" % self.path)
+ except Exception, e:
+ logger.exception("Unhandled exception")
+ self.send_error(501, "Unhandled exception")
+
+ def send_cms_response(self, der):
+ self.send_response(200)
+ self.send_header("Content-Type", rpki_content_type)
+ self.send_header("Content-Length", str(len(der)))
+ self.end_headers()
+ self.wfile.write(der)
+
+ def log_message(self, *args):
+ logger.info(*args, extra = dict(context = "%s:%s" % self.client_address))
+
+ def send_error(self, code, message = None):
+ # BaseHTTPRequestHandler.send_error() generates HTML error messages,
+ # which we don't want, so we override the method to suppress this.
+ self.send_response(code, message)
+ self.send_header("Content-Type", rpki_content_type)
+ self.send_header("Connection", "close")
+ self.end_headers()
+
+
+def server(handlers, port, host = ""):
+ """
+ Run an HTTP server and wait (forever) for connections.
+ """
+
+ if not isinstance(handlers, (tuple, list)):
+ handlers = (("/", handlers),)
+
+ class RequestHandler(HTTPRequestHandler):
+ rpki_handlers = handlers
+
+ BaseHTTPServer.HTTPServer((host, port), RequestHandler).serve_forever()
+
+
+class BadURL(Exception):
+ "Bad contact URL"
+
+class RequestFailed(Exception):
+ "HTTP returned failure"
+
+class BadContentType(Exception):
+ "Wrong HTTP Content-Type"
+
+
+def client(proto_cms_msg, client_key, client_cert, server_ta, server_cert, url, q_msg,
+ debug = False, replay_track = None, client_crl = None):
+ """
+ Issue single a query and return the response, handling all the CMS and XML goo.
+ """
+
+ u = urlparse.urlparse(url)
+
+ if u.scheme not in ("", "http") or u.username or u.password or u.params or u.query or u.fragment:
+ raise BadURL("Unusable URL %s", url)
+
+ q_cms = proto_cms_msg()
+ q_der = q_cms.wrap(q_msg, client_key, client_cert, client_crl)
+
+ if debug:
+ debug.write("<!-- Query -->\n" + q_cms.pretty_print_content() + "\n")
+
+ http = httplib.HTTPConnection(u.hostname, u.port or httplib.HTTP_PORT)
+ http.request("POST", u.path, q_der, {"Content-Type" : rpki_content_type})
+ r = http.getresponse()
+
+ if r.status != 200:
+ raise RequestFailed("HTTP request failed with status %r reason %r" % (r.status, r.reason))
+
+ if r.getheader("Content-Type") != rpki_content_type:
+ raise BadContentType("HTTP Content-Type %r, expected %r" % (r.getheader("Content-Type"), rpki_content_type))
+
+ r_der = r.read()
+ r_cms = proto_cms_msg(DER = r_der)
+ r_msg = r_cms.unwrap((server_ta, server_cert))
+
+ if replay_track is not None:
+ replay_track.cms_timestamp = r_cms.check_replay(replay_track.cms_timestamp, url)
+
+ if debug:
+ debug.write("<!-- Reply -->\n" + r_cms.pretty_print_content() + "\n")
+
+ return r_msg
diff --git a/rpki/ipaddrs.py b/rpki/ipaddrs.py
index 68b2d27d..25eefd0d 100644
--- a/rpki/ipaddrs.py
+++ b/rpki/ipaddrs.py
@@ -61,6 +61,7 @@ class v4addr(long):
"""
Construct a v4addr object.
"""
+
if isinstance(x, unicode):
x = x.encode("ascii")
if isinstance(x, str):
@@ -72,6 +73,7 @@ class v4addr(long):
"""
Convert a v4addr object to a raw byte string.
"""
+
return struct.pack("!I", long(self))
@classmethod
@@ -79,12 +81,14 @@ class v4addr(long):
"""
Convert from a raw byte string to a v4addr object.
"""
+
return cls(struct.unpack("!I", x)[0])
def __str__(self):
"""
Convert a v4addr object to string format.
"""
+
return socket.inet_ntop(socket.AF_INET, self.to_bytes())
class v6addr(long):
@@ -101,6 +105,7 @@ class v6addr(long):
"""
Construct a v6addr object.
"""
+
if isinstance(x, unicode):
x = x.encode("ascii")
if isinstance(x, str):
@@ -112,6 +117,7 @@ class v6addr(long):
"""
Convert a v6addr object to a raw byte string.
"""
+
return struct.pack("!QQ", long(self) >> 64, long(self) & 0xFFFFFFFFFFFFFFFF)
@classmethod
@@ -119,6 +125,7 @@ class v6addr(long):
"""
Convert from a raw byte string to a v6addr object.
"""
+
x = struct.unpack("!QQ", x)
return cls((x[0] << 64) | x[1])
@@ -126,12 +133,14 @@ class v6addr(long):
"""
Convert a v6addr object to string format.
"""
+
return socket.inet_ntop(socket.AF_INET6, self.to_bytes())
def parse(s):
"""
Parse a string as either an IPv4 or IPv6 address, and return object of appropriate class.
"""
+
if isinstance(s, unicode):
s = s.encode("ascii")
return v6addr(s) if ":" in s else v4addr(s)
diff --git a/rpki/irdb/migrations/0001_initial.py b/rpki/irdb/migrations/0001_initial.py
new file mode 100644
index 00000000..bc0b9743
--- /dev/null
+++ b/rpki/irdb/migrations/0001_initial.py
@@ -0,0 +1,595 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'ServerCA'
+ db.create_table(u'irdb_serverca', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('private_key', self.gf('rpki.fields.KeyField')(default=None, blank=True)),
+ ('latest_crl', self.gf('rpki.fields.CRLField')(default=None, blank=True)),
+ ('next_serial', self.gf('django.db.models.fields.BigIntegerField')(default=1)),
+ ('next_crl_number', self.gf('django.db.models.fields.BigIntegerField')(default=1)),
+ ('last_crl_update', self.gf('rpki.fields.SundialField')()),
+ ('next_crl_update', self.gf('rpki.fields.SundialField')()),
+ ))
+ db.send_create_signal(u'irdb', ['ServerCA'])
+
+ # Adding model 'ResourceHolderCA'
+ db.create_table(u'irdb_resourceholderca', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('private_key', self.gf('rpki.fields.KeyField')(default=None, blank=True)),
+ ('latest_crl', self.gf('rpki.fields.CRLField')(default=None, blank=True)),
+ ('next_serial', self.gf('django.db.models.fields.BigIntegerField')(default=1)),
+ ('next_crl_number', self.gf('django.db.models.fields.BigIntegerField')(default=1)),
+ ('last_crl_update', self.gf('rpki.fields.SundialField')()),
+ ('next_crl_update', self.gf('rpki.fields.SundialField')()),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(unique=True, max_length=120)),
+ ))
+ db.send_create_signal(u'irdb', ['ResourceHolderCA'])
+
+ # Adding model 'HostedCA'
+ db.create_table(u'irdb_hostedca', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['irdb.ServerCA'])),
+ ('hosted', self.gf('django.db.models.fields.related.OneToOneField')(related_name='hosted_by', unique=True, to=orm['irdb.ResourceHolderCA'])),
+ ))
+ db.send_create_signal(u'irdb', ['HostedCA'])
+
+ # Adding unique constraint on 'HostedCA', fields ['issuer', 'hosted']
+ db.create_unique(u'irdb_hostedca', ['issuer_id', 'hosted_id'])
+
+ # Adding model 'ServerRevocation'
+ db.create_table(u'irdb_serverrevocation', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('serial', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('revoked', self.gf('rpki.fields.SundialField')()),
+ ('expires', self.gf('rpki.fields.SundialField')()),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='revocations', to=orm['irdb.ServerCA'])),
+ ))
+ db.send_create_signal(u'irdb', ['ServerRevocation'])
+
+ # Adding unique constraint on 'ServerRevocation', fields ['issuer', 'serial']
+ db.create_unique(u'irdb_serverrevocation', ['issuer_id', 'serial'])
+
+ # Adding model 'ResourceHolderRevocation'
+ db.create_table(u'irdb_resourceholderrevocation', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('serial', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('revoked', self.gf('rpki.fields.SundialField')()),
+ ('expires', self.gf('rpki.fields.SundialField')()),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='revocations', to=orm['irdb.ResourceHolderCA'])),
+ ))
+ db.send_create_signal(u'irdb', ['ResourceHolderRevocation'])
+
+ # Adding unique constraint on 'ResourceHolderRevocation', fields ['issuer', 'serial']
+ db.create_unique(u'irdb_resourceholderrevocation', ['issuer_id', 'serial'])
+
+ # Adding model 'ServerEE'
+ db.create_table(u'irdb_serveree', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('private_key', self.gf('rpki.fields.KeyField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ee_certificates', to=orm['irdb.ServerCA'])),
+ ('purpose', self.gf('rpki.fields.EnumField')()),
+ ))
+ db.send_create_signal(u'irdb', ['ServerEE'])
+
+ # Adding unique constraint on 'ServerEE', fields ['issuer', 'purpose']
+ db.create_unique(u'irdb_serveree', ['issuer_id', 'purpose'])
+
+ # Adding model 'Referral'
+ db.create_table(u'irdb_referral', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('private_key', self.gf('rpki.fields.KeyField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.OneToOneField')(related_name='referral_certificate', unique=True, to=orm['irdb.ResourceHolderCA'])),
+ ))
+ db.send_create_signal(u'irdb', ['Referral'])
+
+ # Adding model 'Turtle'
+ db.create_table(u'irdb_turtle', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('service_uri', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ))
+ db.send_create_signal(u'irdb', ['Turtle'])
+
+ # Adding model 'Rootd'
+ db.create_table(u'irdb_rootd', (
+ (u'turtle_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['irdb.Turtle'], unique=True, primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('private_key', self.gf('rpki.fields.KeyField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.OneToOneField')(related_name='rootd', unique=True, to=orm['irdb.ResourceHolderCA'])),
+ ))
+ db.send_create_signal(u'irdb', ['Rootd'])
+
+ # Adding model 'BSC'
+ db.create_table(u'irdb_bsc', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='bscs', to=orm['irdb.ResourceHolderCA'])),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('pkcs10', self.gf('rpki.fields.PKCS10Field')(default=None, blank=True)),
+ ))
+ db.send_create_signal(u'irdb', ['BSC'])
+
+ # Adding unique constraint on 'BSC', fields ['issuer', 'handle']
+ db.create_unique(u'irdb_bsc', ['issuer_id', 'handle'])
+
+ # Adding model 'Child'
+ db.create_table(u'irdb_child', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('ta', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('valid_until', self.gf('rpki.fields.SundialField')()),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', to=orm['irdb.ResourceHolderCA'])),
+ ('name', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ))
+ db.send_create_signal(u'irdb', ['Child'])
+
+ # Adding unique constraint on 'Child', fields ['issuer', 'handle']
+ db.create_unique(u'irdb_child', ['issuer_id', 'handle'])
+
+ # Adding model 'ChildASN'
+ db.create_table(u'irdb_childasn', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('start_as', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('end_as', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('child', self.gf('django.db.models.fields.related.ForeignKey')(related_name='asns', to=orm['irdb.Child'])),
+ ))
+ db.send_create_signal(u'irdb', ['ChildASN'])
+
+ # Adding unique constraint on 'ChildASN', fields ['child', 'start_as', 'end_as']
+ db.create_unique(u'irdb_childasn', ['child_id', 'start_as', 'end_as'])
+
+ # Adding model 'ChildNet'
+ db.create_table(u'irdb_childnet', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('start_ip', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('end_ip', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('version', self.gf('rpki.fields.EnumField')()),
+ ('child', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges', to=orm['irdb.Child'])),
+ ))
+ db.send_create_signal(u'irdb', ['ChildNet'])
+
+ # Adding unique constraint on 'ChildNet', fields ['child', 'start_ip', 'end_ip', 'version']
+ db.create_unique(u'irdb_childnet', ['child_id', 'start_ip', 'end_ip', 'version'])
+
+ # Adding model 'Parent'
+ db.create_table(u'irdb_parent', (
+ (u'turtle_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['irdb.Turtle'], unique=True, primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('ta', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='parents', to=orm['irdb.ResourceHolderCA'])),
+ ('parent_handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('child_handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('repository_type', self.gf('rpki.fields.EnumField')()),
+ ('referrer', self.gf('rpki.irdb.models.HandleField')(max_length=120, null=True, blank=True)),
+ ('referral_authorization', self.gf('rpki.irdb.models.SignedReferralField')(default=None, null=True, blank=True)),
+ ))
+ db.send_create_signal(u'irdb', ['Parent'])
+
+ # Adding unique constraint on 'Parent', fields ['issuer', 'handle']
+ db.create_unique(u'irdb_parent', ['issuer_id', 'handle'])
+
+ # Adding model 'ROARequest'
+ db.create_table(u'irdb_roarequest', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='roa_requests', to=orm['irdb.ResourceHolderCA'])),
+ ('asn', self.gf('django.db.models.fields.BigIntegerField')()),
+ ))
+ db.send_create_signal(u'irdb', ['ROARequest'])
+
+ # Adding model 'ROARequestPrefix'
+ db.create_table(u'irdb_roarequestprefix', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('roa_request', self.gf('django.db.models.fields.related.ForeignKey')(related_name='prefixes', to=orm['irdb.ROARequest'])),
+ ('version', self.gf('rpki.fields.EnumField')()),
+ ('prefix', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('prefixlen', self.gf('django.db.models.fields.PositiveSmallIntegerField')()),
+ ('max_prefixlen', self.gf('django.db.models.fields.PositiveSmallIntegerField')()),
+ ))
+ db.send_create_signal(u'irdb', ['ROARequestPrefix'])
+
+ # Adding unique constraint on 'ROARequestPrefix', fields ['roa_request', 'version', 'prefix', 'prefixlen', 'max_prefixlen']
+ db.create_unique(u'irdb_roarequestprefix', ['roa_request_id', 'version', 'prefix', 'prefixlen', 'max_prefixlen'])
+
+ # Adding model 'GhostbusterRequest'
+ db.create_table(u'irdb_ghostbusterrequest', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ghostbuster_requests', to=orm['irdb.ResourceHolderCA'])),
+ ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ghostbuster_requests', null=True, to=orm['irdb.Parent'])),
+ ('vcard', self.gf('django.db.models.fields.TextField')()),
+ ))
+ db.send_create_signal(u'irdb', ['GhostbusterRequest'])
+
+ # Adding model 'EECertificateRequest'
+ db.create_table(u'irdb_eecertificaterequest', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('valid_until', self.gf('rpki.fields.SundialField')()),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ee_certificate_requests', to=orm['irdb.ResourceHolderCA'])),
+ ('pkcs10', self.gf('rpki.fields.PKCS10Field')(default=None, blank=True)),
+ ('gski', self.gf('django.db.models.fields.CharField')(max_length=27)),
+ ('cn', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('sn', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('eku', self.gf('django.db.models.fields.TextField')(null=True)),
+ ))
+ db.send_create_signal(u'irdb', ['EECertificateRequest'])
+
+ # Adding unique constraint on 'EECertificateRequest', fields ['issuer', 'gski']
+ db.create_unique(u'irdb_eecertificaterequest', ['issuer_id', 'gski'])
+
+ # Adding model 'EECertificateRequestASN'
+ db.create_table(u'irdb_eecertificaterequestasn', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('start_as', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('end_as', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('ee_certificate_request', self.gf('django.db.models.fields.related.ForeignKey')(related_name='asns', to=orm['irdb.EECertificateRequest'])),
+ ))
+ db.send_create_signal(u'irdb', ['EECertificateRequestASN'])
+
+ # Adding unique constraint on 'EECertificateRequestASN', fields ['ee_certificate_request', 'start_as', 'end_as']
+ db.create_unique(u'irdb_eecertificaterequestasn', ['ee_certificate_request_id', 'start_as', 'end_as'])
+
+ # Adding model 'EECertificateRequestNet'
+ db.create_table(u'irdb_eecertificaterequestnet', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('start_ip', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('end_ip', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('version', self.gf('rpki.fields.EnumField')()),
+ ('ee_certificate_request', self.gf('django.db.models.fields.related.ForeignKey')(related_name='address_ranges', to=orm['irdb.EECertificateRequest'])),
+ ))
+ db.send_create_signal(u'irdb', ['EECertificateRequestNet'])
+
+ # Adding unique constraint on 'EECertificateRequestNet', fields ['ee_certificate_request', 'start_ip', 'end_ip', 'version']
+ db.create_unique(u'irdb_eecertificaterequestnet', ['ee_certificate_request_id', 'start_ip', 'end_ip', 'version'])
+
+ # Adding model 'Repository'
+ db.create_table(u'irdb_repository', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('ta', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='repositories', to=orm['irdb.ResourceHolderCA'])),
+ ('client_handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('service_uri', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('sia_base', self.gf('django.db.models.fields.TextField')()),
+ ('turtle', self.gf('django.db.models.fields.related.OneToOneField')(related_name='repository', unique=True, to=orm['irdb.Turtle'])),
+ ))
+ db.send_create_signal(u'irdb', ['Repository'])
+
+ # Adding unique constraint on 'Repository', fields ['issuer', 'handle']
+ db.create_unique(u'irdb_repository', ['issuer_id', 'handle'])
+
+ # Adding model 'Client'
+ db.create_table(u'irdb_client', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('certificate', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ('ta', self.gf('rpki.fields.CertificateField')(default=None, blank=True)),
+ ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='clients', to=orm['irdb.ServerCA'])),
+ ('sia_base', self.gf('django.db.models.fields.TextField')()),
+ ('parent_handle', self.gf('rpki.irdb.models.HandleField')(max_length=120)),
+ ))
+ db.send_create_signal(u'irdb', ['Client'])
+
+ # Adding unique constraint on 'Client', fields ['issuer', 'handle']
+ db.create_unique(u'irdb_client', ['issuer_id', 'handle'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'Client', fields ['issuer', 'handle']
+ db.delete_unique(u'irdb_client', ['issuer_id', 'handle'])
+
+ # Removing unique constraint on 'Repository', fields ['issuer', 'handle']
+ db.delete_unique(u'irdb_repository', ['issuer_id', 'handle'])
+
+ # Removing unique constraint on 'EECertificateRequestNet', fields ['ee_certificate_request', 'start_ip', 'end_ip', 'version']
+ db.delete_unique(u'irdb_eecertificaterequestnet', ['ee_certificate_request_id', 'start_ip', 'end_ip', 'version'])
+
+ # Removing unique constraint on 'EECertificateRequestASN', fields ['ee_certificate_request', 'start_as', 'end_as']
+ db.delete_unique(u'irdb_eecertificaterequestasn', ['ee_certificate_request_id', 'start_as', 'end_as'])
+
+ # Removing unique constraint on 'EECertificateRequest', fields ['issuer', 'gski']
+ db.delete_unique(u'irdb_eecertificaterequest', ['issuer_id', 'gski'])
+
+ # Removing unique constraint on 'ROARequestPrefix', fields ['roa_request', 'version', 'prefix', 'prefixlen', 'max_prefixlen']
+ db.delete_unique(u'irdb_roarequestprefix', ['roa_request_id', 'version', 'prefix', 'prefixlen', 'max_prefixlen'])
+
+ # Removing unique constraint on 'Parent', fields ['issuer', 'handle']
+ db.delete_unique(u'irdb_parent', ['issuer_id', 'handle'])
+
+ # Removing unique constraint on 'ChildNet', fields ['child', 'start_ip', 'end_ip', 'version']
+ db.delete_unique(u'irdb_childnet', ['child_id', 'start_ip', 'end_ip', 'version'])
+
+ # Removing unique constraint on 'ChildASN', fields ['child', 'start_as', 'end_as']
+ db.delete_unique(u'irdb_childasn', ['child_id', 'start_as', 'end_as'])
+
+ # Removing unique constraint on 'Child', fields ['issuer', 'handle']
+ db.delete_unique(u'irdb_child', ['issuer_id', 'handle'])
+
+ # Removing unique constraint on 'BSC', fields ['issuer', 'handle']
+ db.delete_unique(u'irdb_bsc', ['issuer_id', 'handle'])
+
+ # Removing unique constraint on 'ServerEE', fields ['issuer', 'purpose']
+ db.delete_unique(u'irdb_serveree', ['issuer_id', 'purpose'])
+
+ # Removing unique constraint on 'ResourceHolderRevocation', fields ['issuer', 'serial']
+ db.delete_unique(u'irdb_resourceholderrevocation', ['issuer_id', 'serial'])
+
+ # Removing unique constraint on 'ServerRevocation', fields ['issuer', 'serial']
+ db.delete_unique(u'irdb_serverrevocation', ['issuer_id', 'serial'])
+
+ # Removing unique constraint on 'HostedCA', fields ['issuer', 'hosted']
+ db.delete_unique(u'irdb_hostedca', ['issuer_id', 'hosted_id'])
+
+ # Deleting model 'ServerCA'
+ db.delete_table(u'irdb_serverca')
+
+ # Deleting model 'ResourceHolderCA'
+ db.delete_table(u'irdb_resourceholderca')
+
+ # Deleting model 'HostedCA'
+ db.delete_table(u'irdb_hostedca')
+
+ # Deleting model 'ServerRevocation'
+ db.delete_table(u'irdb_serverrevocation')
+
+ # Deleting model 'ResourceHolderRevocation'
+ db.delete_table(u'irdb_resourceholderrevocation')
+
+ # Deleting model 'ServerEE'
+ db.delete_table(u'irdb_serveree')
+
+ # Deleting model 'Referral'
+ db.delete_table(u'irdb_referral')
+
+ # Deleting model 'Turtle'
+ db.delete_table(u'irdb_turtle')
+
+ # Deleting model 'Rootd'
+ db.delete_table(u'irdb_rootd')
+
+ # Deleting model 'BSC'
+ db.delete_table(u'irdb_bsc')
+
+ # Deleting model 'Child'
+ db.delete_table(u'irdb_child')
+
+ # Deleting model 'ChildASN'
+ db.delete_table(u'irdb_childasn')
+
+ # Deleting model 'ChildNet'
+ db.delete_table(u'irdb_childnet')
+
+ # Deleting model 'Parent'
+ db.delete_table(u'irdb_parent')
+
+ # Deleting model 'ROARequest'
+ db.delete_table(u'irdb_roarequest')
+
+ # Deleting model 'ROARequestPrefix'
+ db.delete_table(u'irdb_roarequestprefix')
+
+ # Deleting model 'GhostbusterRequest'
+ db.delete_table(u'irdb_ghostbusterrequest')
+
+ # Deleting model 'EECertificateRequest'
+ db.delete_table(u'irdb_eecertificaterequest')
+
+ # Deleting model 'EECertificateRequestASN'
+ db.delete_table(u'irdb_eecertificaterequestasn')
+
+ # Deleting model 'EECertificateRequestNet'
+ db.delete_table(u'irdb_eecertificaterequestnet')
+
+ # Deleting model 'Repository'
+ db.delete_table(u'irdb_repository')
+
+ # Deleting model 'Client'
+ db.delete_table(u'irdb_client')
+
+
+ models = {
+ u'irdb.bsc': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'BSC'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bscs'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'pkcs10': ('rpki.fields.PKCS10Field', [], {'default': 'None', 'blank': 'True'})
+ },
+ u'irdb.child': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Child'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'name': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'ta': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'valid_until': ('rpki.fields.SundialField', [], {})
+ },
+ u'irdb.childasn': {
+ 'Meta': {'unique_together': "(('child', 'start_as', 'end_as'),)", 'object_name': 'ChildASN'},
+ 'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asns'", 'to': u"orm['irdb.Child']"}),
+ 'end_as': ('django.db.models.fields.BigIntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'start_as': ('django.db.models.fields.BigIntegerField', [], {})
+ },
+ u'irdb.childnet': {
+ 'Meta': {'unique_together': "(('child', 'start_ip', 'end_ip', 'version'),)", 'object_name': 'ChildNet'},
+ 'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': u"orm['irdb.Child']"}),
+ 'end_ip': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'start_ip': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'version': ('rpki.fields.EnumField', [], {})
+ },
+ u'irdb.client': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Client'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'clients'", 'to': u"orm['irdb.ServerCA']"}),
+ 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'sia_base': ('django.db.models.fields.TextField', [], {}),
+ 'ta': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'})
+ },
+ u'irdb.eecertificaterequest': {
+ 'Meta': {'unique_together': "(('issuer', 'gski'),)", 'object_name': 'EECertificateRequest'},
+ 'cn': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'eku': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'gski': ('django.db.models.fields.CharField', [], {'max_length': '27'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ee_certificate_requests'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'pkcs10': ('rpki.fields.PKCS10Field', [], {'default': 'None', 'blank': 'True'}),
+ 'sn': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'valid_until': ('rpki.fields.SundialField', [], {})
+ },
+ u'irdb.eecertificaterequestasn': {
+ 'Meta': {'unique_together': "(('ee_certificate_request', 'start_as', 'end_as'),)", 'object_name': 'EECertificateRequestASN'},
+ 'ee_certificate_request': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asns'", 'to': u"orm['irdb.EECertificateRequest']"}),
+ 'end_as': ('django.db.models.fields.BigIntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'start_as': ('django.db.models.fields.BigIntegerField', [], {})
+ },
+ u'irdb.eecertificaterequestnet': {
+ 'Meta': {'unique_together': "(('ee_certificate_request', 'start_ip', 'end_ip', 'version'),)", 'object_name': 'EECertificateRequestNet'},
+ 'ee_certificate_request': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': u"orm['irdb.EECertificateRequest']"}),
+ 'end_ip': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'start_ip': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'version': ('rpki.fields.EnumField', [], {})
+ },
+ u'irdb.ghostbusterrequest': {
+ 'Meta': {'object_name': 'GhostbusterRequest'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'null': 'True', 'to': u"orm['irdb.Parent']"}),
+ 'vcard': ('django.db.models.fields.TextField', [], {})
+ },
+ u'irdb.hostedca': {
+ 'Meta': {'unique_together': "(('issuer', 'hosted'),)", 'object_name': 'HostedCA'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'hosted': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'hosted_by'", 'unique': 'True', 'to': u"orm['irdb.ResourceHolderCA']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['irdb.ServerCA']"})
+ },
+ u'irdb.parent': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Parent', '_ormbases': [u'irdb.Turtle']},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'child_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parents'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'referral_authorization': ('rpki.irdb.models.SignedReferralField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+ 'referrer': ('rpki.irdb.models.HandleField', [], {'max_length': '120', 'null': 'True', 'blank': 'True'}),
+ 'repository_type': ('rpki.fields.EnumField', [], {}),
+ 'ta': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ u'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ u'irdb.referral': {
+ 'Meta': {'object_name': 'Referral'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'referral_certificate'", 'unique': 'True', 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'private_key': ('rpki.fields.KeyField', [], {'default': 'None', 'blank': 'True'})
+ },
+ u'irdb.repository': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Repository'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'client_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'repositories'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'sia_base': ('django.db.models.fields.TextField', [], {}),
+ 'ta': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'turtle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'repository'", 'unique': 'True', 'to': u"orm['irdb.Turtle']"})
+ },
+ u'irdb.resourceholderca': {
+ 'Meta': {'object_name': 'ResourceHolderCA'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'unique': 'True', 'max_length': '120'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_crl_update': ('rpki.fields.SundialField', [], {}),
+ 'latest_crl': ('rpki.fields.CRLField', [], {'default': 'None', 'blank': 'True'}),
+ 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'next_crl_update': ('rpki.fields.SundialField', [], {}),
+ 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'private_key': ('rpki.fields.KeyField', [], {'default': 'None', 'blank': 'True'})
+ },
+ u'irdb.resourceholderrevocation': {
+ 'Meta': {'unique_together': "(('issuer', 'serial'),)", 'object_name': 'ResourceHolderRevocation'},
+ 'expires': ('rpki.fields.SundialField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revocations'", 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'revoked': ('rpki.fields.SundialField', [], {}),
+ 'serial': ('django.db.models.fields.BigIntegerField', [], {})
+ },
+ u'irdb.roarequest': {
+ 'Meta': {'object_name': 'ROARequest'},
+ 'asn': ('django.db.models.fields.BigIntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'roa_requests'", 'to': u"orm['irdb.ResourceHolderCA']"})
+ },
+ u'irdb.roarequestprefix': {
+ 'Meta': {'unique_together': "(('roa_request', 'version', 'prefix', 'prefixlen', 'max_prefixlen'),)", 'object_name': 'ROARequestPrefix'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'max_prefixlen': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'prefixlen': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+ 'roa_request': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'prefixes'", 'to': u"orm['irdb.ROARequest']"}),
+ 'version': ('rpki.fields.EnumField', [], {})
+ },
+ u'irdb.rootd': {
+ 'Meta': {'object_name': 'Rootd', '_ormbases': [u'irdb.Turtle']},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'issuer': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'rootd'", 'unique': 'True', 'to': u"orm['irdb.ResourceHolderCA']"}),
+ 'private_key': ('rpki.fields.KeyField', [], {'default': 'None', 'blank': 'True'}),
+ u'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ u'irdb.serverca': {
+ 'Meta': {'object_name': 'ServerCA'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_crl_update': ('rpki.fields.SundialField', [], {}),
+ 'latest_crl': ('rpki.fields.CRLField', [], {'default': 'None', 'blank': 'True'}),
+ 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'next_crl_update': ('rpki.fields.SundialField', [], {}),
+ 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'private_key': ('rpki.fields.KeyField', [], {'default': 'None', 'blank': 'True'})
+ },
+ u'irdb.serveree': {
+ 'Meta': {'unique_together': "(('issuer', 'purpose'),)", 'object_name': 'ServerEE'},
+ 'certificate': ('rpki.fields.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ee_certificates'", 'to': u"orm['irdb.ServerCA']"}),
+ 'private_key': ('rpki.fields.KeyField', [], {'default': 'None', 'blank': 'True'}),
+ 'purpose': ('rpki.fields.EnumField', [], {})
+ },
+ u'irdb.serverrevocation': {
+ 'Meta': {'unique_together': "(('issuer', 'serial'),)", 'object_name': 'ServerRevocation'},
+ 'expires': ('rpki.fields.SundialField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revocations'", 'to': u"orm['irdb.ServerCA']"}),
+ 'revoked': ('rpki.fields.SundialField', [], {}),
+ 'serial': ('django.db.models.fields.BigIntegerField', [], {})
+ },
+ u'irdb.turtle': {
+ 'Meta': {'object_name': 'Turtle'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['irdb'] \ No newline at end of file
diff --git a/rpki/irdb/migrations/__init__.py b/rpki/irdb/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rpki/irdb/migrations/__init__.py
diff --git a/rpki/irdb/models.py b/rpki/irdb/models.py
index 6fa48c59..f89cebd4 100644
--- a/rpki/irdb/models.py
+++ b/rpki/irdb/models.py
@@ -34,6 +34,8 @@ import socket
import rpki.POW
from south.modelsinspector import add_introspection_rules
+from rpki.fields import EnumField, SundialField, CertificateField, DERField, KeyField, CRLField, PKCS10Field
+
## @var ip_version_choices
# Choice argument for fields implementing IP version numbers.
@@ -61,11 +63,11 @@ ee_certificate_lifetime = rpki.sundial.timedelta(days = 60)
###
-# Field types
+# Field classes
class HandleField(django.db.models.CharField):
"""
- A handle field type.
+ A handle field class. Replace this with SlugField?
"""
description = 'A "handle" in one of the RPKI protocols'
@@ -74,103 +76,26 @@ class HandleField(django.db.models.CharField):
kwargs["max_length"] = 120
django.db.models.CharField.__init__(self, *args, **kwargs)
-class EnumField(django.db.models.PositiveSmallIntegerField):
- """
- An enumeration type that uses strings in Python and small integers
- in SQL.
- """
-
- description = "An enumeration type"
-
- __metaclass__ = django.db.models.SubfieldBase
-
- def __init__(self, *args, **kwargs):
- if isinstance(kwargs.get("choices"), (tuple, list)) and isinstance(kwargs["choices"][0], str):
- kwargs["choices"] = tuple(enumerate(kwargs["choices"], 1))
- django.db.models.PositiveSmallIntegerField.__init__(self, *args, **kwargs)
- self.enum_i2s = dict(self.flatchoices)
- self.enum_s2i = dict((v, k) for k, v in self.flatchoices)
-
- def to_python(self, value):
- return self.enum_i2s.get(value, value)
-
- def get_prep_value(self, value):
- return self.enum_s2i.get(value, value)
-
-class SundialField(django.db.models.DateTimeField):
- """
- A field type for our customized datetime objects.
- """
- __metaclass__ = django.db.models.SubfieldBase
-
- description = "A datetime type using our customized datetime objects"
-
- def to_python(self, value):
- if isinstance(value, rpki.sundial.pydatetime.datetime):
- return rpki.sundial.datetime.from_datetime(
- django.db.models.DateTimeField.to_python(self, value))
- else:
- return value
-
- def get_prep_value(self, value):
- if isinstance(value, rpki.sundial.datetime):
- return value.to_datetime()
- else:
- return value
-
-
-class DERField(django.db.models.Field):
- """
- Field types for DER objects.
- """
-
- __metaclass__ = django.db.models.SubfieldBase
- def __init__(self, *args, **kwargs):
- kwargs["serialize"] = False
- kwargs["blank"] = True
- kwargs["default"] = None
- django.db.models.Field.__init__(self, *args, **kwargs)
-
- def db_type(self, connection):
- if connection.settings_dict['ENGINE'] == "django.db.backends.posgresql":
- return "bytea"
- else:
- return "BLOB"
-
- def to_python(self, value):
- assert value is None or isinstance(value, (self.rpki_type, str))
- if isinstance(value, str):
- return self.rpki_type(DER = value)
- else:
- return value
+class SignedReferralField(DERField):
+ description = "CMS signed object containing XML"
+ rpki_type = rpki.x509.SignedReferral
- def get_prep_value(self, value):
- assert value is None or isinstance(value, (self.rpki_type, str))
- if isinstance(value, self.rpki_type):
- return value.get_DER()
- else:
- return value
+# Alias to keep old rpki.gui migrations happy. Would generating a new
+# schema migration for rpki.gui remove the need, or do we have to
+# preserve every old field class we've ever used forever? Dunno.
-class CertificateField(DERField):
- description = "X.509 certificate"
- rpki_type = rpki.x509.X509
+RSAKeyField = KeyField
-class RSAKeyField(DERField):
- description = "RSA keypair"
- rpki_type = rpki.x509.RSA
+# Introspection rules for Django South
-class CRLField(DERField):
- description = "Certificate Revocation List"
- rpki_type = rpki.x509.CRL
+field_classes = [HandleField, SignedReferralField]
-class PKCS10Field(DERField):
- description = "PKCS #10 certificate request"
- rpki_type = rpki.x509.PKCS10
+add_introspection_rules([(field_classes, [], {})],
+ [r"^rpki\.irdb\.models\." + cls.__name__
+ for cls in field_classes])
-class SignedReferralField(DERField):
- description = "CMS signed object containing XML"
- rpki_type = rpki.x509.SignedReferral
+del field_classes
# Custom managers
@@ -231,7 +156,7 @@ class ResourceHolderEEManager(CertificateManager):
class CA(django.db.models.Model):
certificate = CertificateField()
- private_key = RSAKeyField()
+ private_key = KeyField()
latest_crl = CRLField()
# Might want to bring these into line with what rpkid does. Current
@@ -391,7 +316,7 @@ class ResourceHolderRevocation(Revocation):
issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "revocations")
class EECertificate(Certificate):
- private_key = RSAKeyField()
+ private_key = KeyField()
class Meta:
abstract = True
@@ -634,13 +559,3 @@ class Client(CrossCertification):
# This shouldn't be necessary
class Meta:
unique_together = ("issuer", "handle")
-
-# for Django South -- these are just simple subclasses
-add_introspection_rules([],
- (r'^rpki\.irdb\.models\.CertificateField',
- r'^rpki\.irdb\.models\.CRLField',
- r'^rpki\.irdb\.models\.EnumField',
- r'^rpki\.irdb\.models\.HandleField',
- r'^rpki\.irdb\.models\.RSAKeyField',
- r'^rpki\.irdb\.models\.SignedReferralField',
- r'^rpki\.irdb\.models\.SundialField'))
diff --git a/rpki/irdb/zookeeper.py b/rpki/irdb/zookeeper.py
index 1e163a4d..41060f9e 100644
--- a/rpki/irdb/zookeeper.py
+++ b/rpki/irdb/zookeeper.py
@@ -28,13 +28,13 @@ import types
import rpki.config
import rpki.sundial
import rpki.oids
-import rpki.http
+import rpki.http_simple
import rpki.resource_set
import rpki.relaxng
import rpki.left_right
import rpki.x509
-import rpki.async
import rpki.irdb
+import rpki.publication_control
import django.db.transaction
from lxml.etree import (Element, SubElement, ElementTree,
@@ -148,7 +148,6 @@ class etree_wrapper(object):
"""
Wrapper for ETree objects so we can return them as function results
without requiring the caller to understand much about them.
-
"""
def __init__(self, e, msg = None, debug = False):
@@ -189,9 +188,9 @@ class etree_wrapper(object):
class Zookeeper(object):
## @var show_xml
- # Whether to show XML for debugging
+ # If not None, a file-like object to which to prettyprint XML, for debugging.
- show_xml = False
+ show_xml = None
def __init__(self, cfg = None, handle = None, logstream = None):
@@ -533,13 +532,8 @@ class Zookeeper(object):
if self.run_pubd:
updates = []
- updates.append(
- rpki.publication.config_elt.make_pdu(
- action = "set",
- bpki_crl = self.server_ca.latest_crl))
-
updates.extend(
- rpki.publication.client_elt.make_pdu(
+ rpki.publication_control.client_elt.make_pdu(
action = "set",
client_handle = client.handle,
bpki_cert = client.certificate)
@@ -1040,11 +1034,6 @@ class Zookeeper(object):
def call_rpkid(self, *pdus):
"""
Issue a call to rpkid, return result.
-
- Implementation is a little silly, constructs a wrapper object,
- invokes it once, then throws it away. Hard to do better without
- rewriting a bit of the HTTP code, as we want to be sure we're
- using the current BPKI certificate and key objects.
"""
url = "http://%s:%s/left-right" % (
@@ -1059,16 +1048,15 @@ class Zookeeper(object):
elif len(pdus) == 1 and isinstance(pdus[0], (tuple, list)):
pdus = pdus[0]
- call_rpkid = rpki.async.sync_wrapper(rpki.http.caller(
- proto = rpki.left_right,
- client_key = irbe.private_key,
- client_cert = irbe.certificate,
- server_ta = self.server_ca.certificate,
- server_cert = rpkid.certificate,
- url = url,
- debug = self.show_xml))
-
- return call_rpkid(*pdus)
+ return rpki.http_simple.client(
+ proto_cms_msg = rpki.left_right.cms_msg,
+ client_key = irbe.private_key,
+ client_cert = irbe.certificate,
+ server_ta = self.server_ca.certificate,
+ server_cert = rpkid.certificate,
+ url = url,
+ q_msg = rpki.left_right.msg.query(*pdus),
+ debug = self.show_xml)
def run_rpkid_now(self):
@@ -1141,20 +1129,15 @@ class Zookeeper(object):
clear_replay_protection = "yes")
for ca in rpki.irdb.ResourceHolderCA.objects.all())
if self.run_pubd:
- self.call_pubd(rpki.publication.client_elt.make_pdu(action = "set",
- client_handle = client.handle,
- clear_replay_protection = "yes")
+ self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "set",
+ client_handle = client.handle,
+ clear_replay_protection = "yes")
for client in self.server_ca.clients.all())
def call_pubd(self, *pdus):
"""
Issue a call to pubd, return result.
-
- Implementation is a little silly, constructs a wrapper object,
- invokes it once, then throws it away. Hard to do better without
- rewriting a bit of the HTTP code, as we want to be sure we're
- using the current BPKI certificate and key objects.
"""
url = "http://%s:%s/control" % (
@@ -1169,16 +1152,15 @@ class Zookeeper(object):
elif len(pdus) == 1 and isinstance(pdus[0], (tuple, list)):
pdus = pdus[0]
- call_pubd = rpki.async.sync_wrapper(rpki.http.caller(
- proto = rpki.publication,
- client_key = irbe.private_key,
- client_cert = irbe.certificate,
- server_ta = self.server_ca.certificate,
- server_cert = pubd.certificate,
- url = url,
- debug = self.show_xml))
-
- return call_pubd(*pdus)
+ return rpki.http_simple.client(
+ proto_cms_msg = rpki.publication_control.cms_msg,
+ client_key = irbe.private_key,
+ client_cert = irbe.certificate,
+ server_ta = self.server_ca.certificate,
+ server_cert = pubd.certificate,
+ url = url,
+ q_msg = rpki.publication_control.msg.query(*pdus),
+ debug = self.show_xml)
def check_error_report(self, pdus):
@@ -1187,11 +1169,11 @@ class Zookeeper(object):
throw exceptions as needed.
"""
- if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication.report_error_elt)) for pdu in pdus):
+ if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication_control.report_error_elt)) for pdu in pdus):
for pdu in pdus:
if isinstance(pdu, rpki.left_right.report_error_elt):
self.log("rpkid reported failure: %s" % pdu.error_code)
- elif isinstance(pdu, rpki.publication.report_error_elt):
+ elif isinstance(pdu, rpki.publication_control.report_error_elt):
self.log("pubd reported failure: %s" % pdu.error_code)
else:
continue
@@ -1527,16 +1509,10 @@ class Zookeeper(object):
if not self.run_pubd:
return
- # Make sure that pubd's BPKI CRL is up to date.
-
- self.call_pubd(rpki.publication.config_elt.make_pdu(
- action = "set",
- bpki_crl = self.server_ca.latest_crl))
-
# See what pubd already has on file
- pubd_reply = self.call_pubd(rpki.publication.client_elt.make_pdu(action = "list"))
- client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication.client_elt))
+ pubd_reply = self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "list"))
+ client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication_control.client_elt))
pubd_query = []
# Check all clients
@@ -1548,15 +1524,32 @@ class Zookeeper(object):
if (client_pdu is None or
client_pdu.base_uri != client.sia_base or
client_pdu.bpki_cert != client.certificate):
- pubd_query.append(rpki.publication.client_elt.make_pdu(
+ pubd_query.append(rpki.publication_control.client_elt.make_pdu(
action = "create" if client_pdu is None else "set",
client_handle = client.handle,
bpki_cert = client.certificate,
base_uri = client.sia_base))
+ # rootd instances are also a weird sort of client
+
+ for rootd in rpki.irdb.Rootd.objects.all():
+
+ client_handle = rootd.issuer.handle + "-root"
+ client_pdu = client_pdus.pop(client_handle, None)
+ sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client_handle)
+
+ if (client_pdu is None or
+ client_pdu.base_uri != sia_base or
+ client_pdu.bpki_cert != rootd.issuer.certificate):
+ pubd_query.append(rpki.publication_control.client_elt.make_pdu(
+ action = "create" if client_pdu is None else "set",
+ client_handle = client_handle,
+ bpki_cert = rootd.issuer.certificate,
+ base_uri = sia_base))
+
# Delete any unknown clients
- pubd_query.extend(rpki.publication.client_elt.make_pdu(
+ pubd_query.extend(rpki.publication_control.client_elt.make_pdu(
action = "destroy", client_handle = p) for p in client_pdus)
# If we changed anything, ship updates off to pubd
diff --git a/rpki/irdbd.py b/rpki/irdbd.py
index d53ae67c..805280d7 100644
--- a/rpki/irdbd.py
+++ b/rpki/irdbd.py
@@ -26,7 +26,7 @@ import time
import logging
import argparse
import urlparse
-import rpki.http
+import rpki.http_simple
import rpki.config
import rpki.resource_set
import rpki.relaxng
@@ -36,120 +36,114 @@ import rpki.log
import rpki.x509
import rpki.daemonize
+from lxml.etree import Element, SubElement
+
logger = logging.getLogger(__name__)
class main(object):
def handle_list_resources(self, q_pdu, r_msg):
+ self_handle = q_pdu.get("self_handle")
+ child_handle = q_pdu.get("child_handle")
child = rpki.irdb.Child.objects.get(
- issuer__handle__exact = q_pdu.self_handle,
- handle = q_pdu.child_handle)
+ issuer__handle__exact = self_handle,
+ handle = child_handle)
resources = child.resource_bag
- r_pdu = rpki.left_right.list_resources_elt()
- r_pdu.tag = q_pdu.tag
- r_pdu.self_handle = q_pdu.self_handle
- r_pdu.child_handle = q_pdu.child_handle
- r_pdu.valid_until = child.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ")
- r_pdu.asn = resources.asn
- r_pdu.ipv4 = resources.v4
- r_pdu.ipv6 = resources.v6
- r_msg.append(r_pdu)
+ r_pdu = SubElement(r_msg, rpki.left_right.tag_list_resources, self_handle = self_handle, child_handle = child_handle,
+ valid_until = child.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ"))
+ for k, v in (("asn", resources.asn),
+ ("ipv4", resources.v4),
+ ("ipv6", resources.v6),
+ ("tag", q_pdu.get("tag"))):
+ if v:
+ r_pdu.set(k, str(v))
def handle_list_roa_requests(self, q_pdu, r_msg):
+ self_handle = q_pdu.get("self_handle")
for request in rpki.irdb.ROARequest.objects.raw("""
SELECT irdb_roarequest.*
FROM irdb_roarequest, irdb_resourceholderca
WHERE irdb_roarequest.issuer_id = irdb_resourceholderca.id
AND irdb_resourceholderca.handle = %s
- """, [q_pdu.self_handle]):
+ """, [self_handle]):
prefix_bag = request.roa_prefix_bag
- r_pdu = rpki.left_right.list_roa_requests_elt()
- r_pdu.tag = q_pdu.tag
- r_pdu.self_handle = q_pdu.self_handle
- r_pdu.asn = request.asn
- r_pdu.ipv4 = prefix_bag.v4
- r_pdu.ipv6 = prefix_bag.v6
- r_msg.append(r_pdu)
+ r_pdu = SubElement(r_msg, rpki.left_right.tag_list_roa_requests, self_handle = self_handle, asn = str(request.asn))
+ for k, v in (("ipv4", prefix_bag.v4),
+ ("ipv6", prefix_bag.v6),
+ ("tag", q_pdu.get("tag"))):
+ if v:
+ r_pdu.set(k, str(v))
def handle_list_ghostbuster_requests(self, q_pdu, r_msg):
+ self_handle = q_pdu.get("self_handle")
+ parent_handle = q_pdu.get("parent_handle")
ghostbusters = rpki.irdb.GhostbusterRequest.objects.filter(
- issuer__handle__exact = q_pdu.self_handle,
- parent__handle__exact = q_pdu.parent_handle)
+ issuer__handle__exact = self_handle,
+ parent__handle__exact = parent_handle)
if ghostbusters.count() == 0:
ghostbusters = rpki.irdb.GhostbusterRequest.objects.filter(
- issuer__handle__exact = q_pdu.self_handle,
+ issuer__handle__exact = self_handle,
parent = None)
for ghostbuster in ghostbusters:
- 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
- r_pdu.vcard = ghostbuster.vcard
- r_msg.append(r_pdu)
+ r_pdu = SubElement(r_msg, q_pdu.tag, self_handle = self_handle, parent_handle = parent_handle)
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+ r_pdu.text = ghostbuster.vcard
def handle_list_ee_certificate_requests(self, q_pdu, r_msg):
- for ee_req in rpki.irdb.EECertificateRequest.objects.filter(issuer__handle__exact = q_pdu.self_handle):
+ self_handle = q_pdu.get("self_handle")
+ for ee_req in rpki.irdb.EECertificateRequest.objects.filter(issuer__handle__exact = self_handle):
resources = ee_req.resource_bag
- r_pdu = rpki.left_right.list_ee_certificate_requests_elt()
- r_pdu.tag = q_pdu.tag
- r_pdu.self_handle = q_pdu.self_handle
- r_pdu.gski = ee_req.gski
- r_pdu.valid_until = ee_req.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ")
- r_pdu.asn = resources.asn
- r_pdu.ipv4 = resources.v4
- r_pdu.ipv6 = resources.v6
- r_pdu.cn = ee_req.cn
- r_pdu.sn = ee_req.sn
- r_pdu.eku = ee_req.eku
- r_pdu.pkcs10 = ee_req.pkcs10
- r_msg.append(r_pdu)
-
- def handler(self, query, path, cb):
+ r_pdu = SubElement(r_msg, q_pdu.tag, self_handle = self_handle, gski = ee_req.gski,
+ valid_until = ee_req.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ"),
+ cn = ee_req.cn, sn = ee_req.sn)
+ for k, v in (("asn", resources.asn),
+ ("ipv4", resources.v4),
+ ("ipv6", resources.v6),
+ ("eku", ee_req.eku),
+ ("tag", q_pdu.get("tag"))):
+ if v:
+ r_pdu.set(k, str(v))
+ SubElement(r_pdu, rpki.left_right.tag_pkcs10).text = ee_req.pkcs10.get_Base64()
+
+ def handler(self, request, q_der):
try:
- q_pdu = None
- r_msg = rpki.left_right.msg.reply()
from django.db import connection
connection.cursor() # Reconnect to mysqld if necessary
self.start_new_transaction()
serverCA = rpki.irdb.ServerCA.objects.get()
rpkid = serverCA.ee_certificates.get(purpose = "rpkid")
+ irdbd = serverCA.ee_certificates.get(purpose = "irdbd")
+ q_cms = rpki.left_right.cms_msg_no_sax(DER = q_der)
+ q_msg = q_cms.unwrap((serverCA.certificate, rpkid.certificate))
+ self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, request.path)
+ if q_msg.get("type") != "query":
+ raise rpki.exceptions.BadQuery("Message type is %s, expected query" % q_msg.get("type"))
+ r_msg = Element(rpki.left_right.tag_msg, nsmap = rpki.left_right.nsmap,
+ type = "reply", version = rpki.left_right.version)
try:
- q_cms = rpki.left_right.cms_msg(DER = query)
- q_msg = q_cms.unwrap((serverCA.certificate, rpkid.certificate))
- self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path)
- if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query():
- raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg)
for q_pdu in q_msg:
- self.dispatch(q_pdu, r_msg)
- except (rpki.async.ExitNow, SystemExit):
- raise
+ getattr(self, "handle_" + q_pdu.tag[len(rpki.left_right.xmlns):])(q_pdu, r_msg)
+
except Exception, e:
- logger.exception("Exception while handling HTTP request")
- if q_pdu is None:
- r_msg.append(rpki.left_right.report_error_elt.from_exception(e))
- else:
- r_msg.append(rpki.left_right.report_error_elt.from_exception(e, q_pdu.self_handle, q_pdu.tag))
- irdbd = serverCA.ee_certificates.get(purpose = "irdbd")
- cb(200, body = rpki.left_right.cms_msg().wrap(r_msg, irdbd.private_key, irdbd.certificate))
- except (rpki.async.ExitNow, SystemExit):
- raise
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_pdu = SubElement(r_msg, rpki.left_right.tag_report_error, error_code = e.__class__.__name__)
+ r_pdu.text = str(e)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ request.send_cms_response(rpki.left_right.cms_msg_no_sax().wrap(r_msg, irdbd.private_key, irdbd.certificate))
+
except Exception, e:
logger.exception("Unhandled exception while processing HTTP request")
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
-
- def dispatch(self, q_pdu, r_msg):
- try:
- handler = self.dispatch_vector[type(q_pdu)]
- except KeyError:
- raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_pdu)
- else:
- handler(q_pdu, r_msg)
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
def __init__(self, **kwargs):
global rpki # pylint: disable=W0602
- os.environ["TZ"] = "UTC"
+ os.environ.update(TZ = "UTC",
+ DJANGO_SETTINGS_MODULE = "rpki.django_settings")
time.tzset()
parser = argparse.ArgumentParser(description = __doc__)
@@ -166,7 +160,7 @@ class main(object):
rpki.log.init("irdbd", args)
- self.cfg = rpki.config.parser(args.config, "irdbd")
+ self.cfg = rpki.config.parser(set_filename = args.config, section = "irdbd")
self.cfg.set_global_flags()
if not args.foreground:
@@ -185,34 +179,14 @@ class main(object):
def main(self):
- global rpki # pylint: disable=W0602
- from django.conf import settings
-
startup_msg = self.cfg.get("startup-message", "")
if startup_msg:
logger.info(startup_msg)
- # Do -not- turn on DEBUG here except for short-lived tests,
- # otherwise irdbd will eventually run out of memory and crash.
- #
- # If you must enable debugging, use django.db.reset_queries() to
- # clear the query list manually, but it's probably better just to
- # run with debugging disabled, since that's the expectation for
- # production code.
- #
- # https://docs.djangoproject.com/en/dev/faq/models/#why-is-django-leaking-memory
-
- settings.configure(
- DATABASES = {
- "default" : {
- "ENGINE" : "django.db.backends.mysql",
- "NAME" : self.cfg.get("sql-database"),
- "USER" : self.cfg.get("sql-username"),
- "PASSWORD" : self.cfg.get("sql-password"),
- "HOST" : "",
- "PORT" : "" }},
- INSTALLED_APPS = ("rpki.irdb",),)
+ # Now that we know which configuration file to use, it's OK to
+ # load modules that require Django's settings module.
+ global rpki # pylint: disable=W0602
import rpki.irdb # pylint: disable=W0621
# Entirely too much fun with read-only access to transactional databases.
@@ -237,31 +211,12 @@ class main(object):
import django.db.transaction
self.start_new_transaction = django.db.transaction.commit_manually(django.db.transaction.commit)
- self.dispatch_vector = {
- rpki.left_right.list_resources_elt : self.handle_list_resources,
- rpki.left_right.list_roa_requests_elt : self.handle_list_roa_requests,
- rpki.left_right.list_ghostbuster_requests_elt : self.handle_list_ghostbuster_requests,
- rpki.left_right.list_ee_certificate_requests_elt : self.handle_list_ee_certificate_requests}
-
- try:
- self.http_server_host = self.cfg.get("server-host", "")
- self.http_server_port = self.cfg.getint("server-port")
- except: # pylint: disable=W0702
- #
- # Backwards compatibility, remove this eventually.
- #
- u = urlparse.urlparse(self.cfg.get("http-url"))
- if (u.scheme not in ("", "http") or
- u.username is not None or
- u.password is not None or
- u.params or u.query or u.fragment):
- raise
- self.http_server_host = u.hostname
- self.http_server_port = int(u.port)
+ self.http_server_host = self.cfg.get("server-host", "")
+ self.http_server_port = self.cfg.getint("server-port")
self.cms_timestamp = None
- rpki.http.server(
+ rpki.http_simple.server(
host = self.http_server_host,
port = self.http_server_port,
handlers = self.handler)
diff --git a/rpki/left_right.py b/rpki/left_right.py
index 68ead08f..3b2c9b9f 100644
--- a/rpki/left_right.py
+++ b/rpki/left_right.py
@@ -21,6 +21,7 @@
RPKI "left-right" protocol.
"""
+import base64
import logging
import rpki.resource_set
import rpki.x509
@@ -36,13 +37,28 @@ import rpki.publication
import rpki.async
import rpki.rpkid_tasks
+from lxml.etree import Element, SubElement
+
logger = logging.getLogger(__name__)
+
+xmlns = rpki.relaxng.left_right.xmlns
+nsmap = rpki.relaxng.left_right.nsmap
+version = rpki.relaxng.left_right.version
+
+tag_list_resources = xmlns + "list_resources"
+tag_list_roa_requests = xmlns + "list_roa_requests"
+tag_msg = xmlns + "msg"
+tag_pkcs10 = xmlns + "pkcs10"
+tag_report_error = xmlns + "report_error"
+
+
## @var enforce_strict_up_down_xml_sender
# Enforce strict checking of XML "sender" field in up-down protocol
enforce_strict_up_down_xml_sender = False
+
class left_right_namespace(object):
"""
XML namespace parameters for left-right protocol.
@@ -67,6 +83,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Fetch self object to which this object links.
"""
+
return self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -75,12 +92,14 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Return BSC object to which this object links.
"""
+
return bsc_elt.sql_fetch(self.gctx, self.bsc_id)
def make_reply_clone_hook(self, r_pdu):
"""
Set handles when cloning, including _id -> _handle translation.
"""
+
if r_pdu.self_handle is None:
r_pdu.self_handle = self.self_handle
for tag, elt in self.handles:
@@ -97,6 +116,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Find an object based on its handle.
"""
+
return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id))
def serve_fetch_one_maybe(self):
@@ -104,6 +124,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
Find the object on which a get, set, or destroy method should
operate, or which would conflict with a create method.
"""
+
where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3)
args = (getattr(self, self.element_name + "_handle"), self.self_handle)
return self.sql_fetch_where1(self.gctx, where, args, "self")
@@ -112,6 +133,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Find the objects on which a list method should operate.
"""
+
where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name
return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self")
@@ -124,6 +146,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
operations, self is the pre-existing object from SQL and q_pdu is
the set request received from the the IRBE.
"""
+
for tag, elt in self.handles:
id_name = tag + "_id"
if getattr(self, id_name, None) is None:
@@ -171,6 +194,7 @@ class self_elt(data_elt):
"""
Fetch all BSC objects that link to this self object.
"""
+
return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -178,6 +202,7 @@ class self_elt(data_elt):
"""
Fetch all repository objects that link to this self object.
"""
+
return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -185,6 +210,7 @@ class self_elt(data_elt):
"""
Fetch all parent objects that link to this self object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -192,6 +218,7 @@ class self_elt(data_elt):
"""
Fetch all child objects that link to this self object.
"""
+
return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -199,6 +226,7 @@ class self_elt(data_elt):
"""
Fetch all ROA objects that link to this self object.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -206,6 +234,7 @@ class self_elt(data_elt):
"""
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,))
@property
@@ -213,6 +242,7 @@ class self_elt(data_elt):
"""
Fetch all EE certificate objects that link to this self object.
"""
+
return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@@ -220,6 +250,7 @@ class self_elt(data_elt):
"""
Extra server actions for self_elt.
"""
+
actions = []
if q_pdu.rekey:
actions.append(self.serve_rekey)
@@ -243,6 +274,7 @@ class self_elt(data_elt):
"""
Handle a left-right rekey action for this self.
"""
+
def loop(iterator, parent):
parent.serve_rekey(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -251,6 +283,7 @@ class self_elt(data_elt):
"""
Handle a left-right revoke action for this self.
"""
+
def loop(iterator, parent):
parent.serve_revoke(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -259,6 +292,7 @@ class self_elt(data_elt):
"""
Handle a left-right reissue action for this self.
"""
+
def loop(iterator, parent):
parent.serve_reissue(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -267,6 +301,7 @@ class self_elt(data_elt):
"""
Handle a left-right revoke_forgotten action for this self.
"""
+
def loop(iterator, parent):
parent.serve_revoke_forgotten(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -275,6 +310,7 @@ class self_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this self.
"""
+
def loop(iterator, obj):
obj.serve_clear_replay_protection(iterator, eb)
rpki.async.iterator(self.parents + self.children + self.repositories, loop, cb)
@@ -283,6 +319,7 @@ class self_elt(data_elt):
"""
Extra cleanup actions when destroying a self_elt.
"""
+
def loop(iterator, parent):
parent.delete(iterator)
rpki.async.iterator(self.parents, loop, cb)
@@ -291,45 +328,44 @@ class self_elt(data_elt):
def serve_publish_world_now(self, cb, eb):
"""
Handle a left-right publish_world_now action for this self.
-
- The publication stuff needs refactoring, right now publication is
- interleaved with local operations in a way that forces far too
- many bounces through the task system for any complex update. The
- whole thing ought to be rewritten to queue up outgoing publication
- PDUs and only send them when we're all done or when we need to
- force publication at a particular point in a multi-phase operation.
-
- Once that reorganization has been done, this method should be
- rewritten to reuse the low-level publish() methods that each
- object will have...but we're not there yet. So, for now, we just
- do this via brute force. Think of it as a trial version to see
- whether we've identified everything that needs to be republished
- for this operation.
"""
+ publisher = rpki.rpkid.publication_queue()
+
def loop(iterator, parent):
- q_msg = rpki.publication.msg.query()
+ repo = parent.repository
for ca in parent.cas:
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.ghostbuster_elt.make_publish(
- g.uri, g.ghostbuster) for g in ca_detail.ghostbusters)
- parent.repository.call_pubd(iterator, eb, q_msg)
+ publisher.queue(
+ uri = ca_detail.crl_uri, new_obj = ca_detail.latest_crl, repository = repo)
+ publisher.queue(
+ uri = ca_detail.manifest_uri, new_obj = ca_detail.latest_manifest, repository = repo)
+ for c in ca_detail.child_certs:
+ publisher.queue(
+ uri = c.uri, new_obj = c.cert, repository = repo)
+ for r in ca_detail.roas:
+ if r.roa is not None:
+ publisher.queue(
+ uri = r.uri, new_obj = r.roa, repository = repo)
+ for g in ca_detail.ghostbusters:
+ publisher.queue(
+ uri = g.uri, new_obj = g.ghostbuster, repository = repo)
+ for c in ca_detail.ee_certificates:
+ publisher.queue(
+ uri = c.uri, new_obj = c.cert, repository = repo)
+ iterator()
- rpki.async.iterator(self.parents, loop, cb)
+ def done():
+ publisher.call_pubd(cb, eb)
+
+ rpki.async.iterator(self.parents, loop, done)
def serve_run_now(self, cb, eb):
"""
Handle a left-right run_now action for this self.
"""
+
logger.debug("Forced immediate run of periodic actions for self %s[%d]",
self.self_handle, self.self_id)
completion = rpki.rpkid_tasks.CompletionHandler(cb)
@@ -342,6 +378,7 @@ class self_elt(data_elt):
Find the self object upon which a get, set, or destroy action
should operate, or which would conflict with a create method.
"""
+
return self.serve_fetch_handle(self.gctx, None, self.self_handle)
@classmethod
@@ -349,6 +386,7 @@ class self_elt(data_elt):
"""
Find a self object based on its self_handle.
"""
+
return cls.sql_fetch_where1(gctx, "self_handle = %s", (self_handle,))
def serve_fetch_all(self):
@@ -357,6 +395,7 @@ class self_elt(data_elt):
This is different from the list action for all other objects,
where list only works within a given self_id context.
"""
+
return self.sql_fetch_all(self.gctx)
def schedule_cron_tasks(self, completion):
@@ -428,6 +467,7 @@ class bsc_elt(data_elt):
"""
Fetch all repository objects that link to this BSC object.
"""
+
return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
@property
@@ -435,6 +475,7 @@ class bsc_elt(data_elt):
"""
Fetch all parent objects that link to this BSC object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
@property
@@ -442,6 +483,7 @@ class bsc_elt(data_elt):
"""
Fetch all child objects that link to this BSC object.
"""
+
return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
@@ -449,6 +491,7 @@ class bsc_elt(data_elt):
Extra server actions for bsc_elt -- handle key generation. For
now this only allows RSA with SHA-256.
"""
+
if q_pdu.generate_keypair:
assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048)
@@ -492,12 +535,14 @@ class repository_elt(data_elt):
"""
Fetch all parent objects that link to this repository object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Extra server actions for repository_elt.
"""
+
actions = []
if q_pdu.clear_replay_protection:
actions.append(self.serve_clear_replay_protection)
@@ -509,55 +554,47 @@ class repository_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this repository.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
- @staticmethod
- def default_pubd_handler(pdu):
- """
- Default handler for publication response PDUs.
- """
- pdu.raise_if_error()
- def call_pubd(self, callback, errback, q_msg, handlers = None):
+ def call_pubd(self, callback, errback, q_msg, handlers = {}):
"""
Send a message to publication daemon and return the response.
As a convenience, attempting to send an empty message returns
immediate success without sending anything.
- Handlers is a dict of handler functions to process the response
+ handlers is a dict of handler functions to process the response
PDUs. If the tag value in the response PDU appears in the dict,
the associated handler is called to process the PDU. If no tag
- matches, default_pubd_handler() is called. A handler value of
- False suppresses calling of the default handler.
+ matches, a default handler is called to check for errors; a
+ handler value of False suppresses calling of the default handler.
"""
try:
self.gctx.sql.sweep()
- if not q_msg:
+ if len(q_msg) == 0:
return callback()
- if handlers is None:
- handlers = {}
-
for q_pdu in q_msg:
- logger.info("Sending %s %s to pubd", q_pdu.action, q_pdu.uri)
+ logger.info("Sending %r to pubd", q_pdu)
bsc = self.bsc
- q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
+ q_der = rpki.publication.cms_msg_no_sax().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
bpki_ta_path = (self.gctx.bpki_ta, self.self.bpki_cert, self.self.bpki_glue, self.bpki_cert, self.bpki_glue)
def done(r_der):
try:
logger.debug("Received response from pubd")
- r_cms = rpki.publication.cms_msg(DER = r_der)
+ r_cms = rpki.publication.cms_msg_no_sax(DER = r_der)
r_msg = r_cms.unwrap(bpki_ta_path)
r_cms.check_replay_sql(self, self.peer_contact_uri)
for r_pdu in r_msg:
- handler = handlers.get(r_pdu.tag, self.default_pubd_handler)
+ handler = handlers.get(r_pdu.get("tag"), rpki.publication.raise_if_error)
if handler:
logger.debug("Calling pubd handler %r", handler)
handler(r_pdu)
@@ -624,6 +661,7 @@ class parent_elt(data_elt):
"""
Fetch repository object to which this parent object links.
"""
+
return repository_elt.sql_fetch(self.gctx, self.repository_id)
@property
@@ -631,12 +669,14 @@ class parent_elt(data_elt):
"""
Fetch all CA objects that link to this parent object.
"""
+
return rpki.rpkid.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Extra server actions for parent_elt.
"""
+
actions = []
if q_pdu.rekey:
actions.append(self.serve_rekey)
@@ -656,6 +696,7 @@ class parent_elt(data_elt):
"""
Handle a left-right rekey action for this parent.
"""
+
def loop(iterator, ca):
ca.rekey(iterator, eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -664,6 +705,7 @@ class parent_elt(data_elt):
"""
Handle a left-right revoke action for this parent.
"""
+
def loop(iterator, ca):
ca.revoke(cb = iterator, eb = eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -672,6 +714,7 @@ class parent_elt(data_elt):
"""
Handle a left-right reissue action for this parent.
"""
+
def loop(iterator, ca):
ca.reissue(cb = iterator, eb = eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -680,6 +723,7 @@ class parent_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this parent.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
@@ -696,10 +740,11 @@ class parent_elt(data_elt):
"""
def done(r_msg):
- cb(dict((rc.class_name, set(c.cert.gSKI() for c in rc.certs))
- for rc in r_msg.payload.classes))
-
- rpki.up_down.list_pdu.query(self, done, eb)
+ cb(dict((rc.get("class_name"),
+ set(rpki.x509.X509(Base64 = c.text).gSKI()
+ for c in rc.getiterator(rpki.up_down.tag_certificate)))
+ for rc in r_msg.getiterator(rpki.up_down.tag_class)))
+ self.up_down_list_query(done, eb)
def revoke_skis(self, rc_name, skis_to_revoke, cb, eb):
@@ -708,12 +753,10 @@ class parent_elt(data_elt):
"""
def loop(iterator, ski):
+ def revoked(r_pdu):
+ iterator()
logger.debug("Asking parent %r to revoke class %r, SKI %s", self, rc_name, ski)
- q_pdu = rpki.up_down.revoke_pdu()
- q_pdu.class_name = rc_name
- q_pdu.ski = ski
- self.query_up_down(q_pdu, lambda r_pdu: iterator(), eb)
-
+ self.up_down_revoke_query(rc_name, ski, revoked, eb)
rpki.async.iterator(skis_to_revoke, loop, cb)
@@ -782,7 +825,51 @@ class parent_elt(data_elt):
self.delete(cb, delete_parent = False)
- def query_up_down(self, q_pdu, cb, eb):
+ def _compose_up_down_query(self, query_type):
+ """
+ Compose top level element of an up-down query to this parent.
+ """
+
+ return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
+ sender = self.sender_name, recipient = self.recipient_name, type = query_type)
+
+
+ def up_down_list_query(self, cb, eb):
+ """
+ Send an up-down list query to this parent.
+ """
+
+ q_msg = self._compose_up_down_query("list")
+ self.query_up_down(q_msg, cb, eb)
+
+
+ def up_down_issue_query(self, ca, ca_detail, cb, eb):
+ """
+ Send an up-down issue query to this parent.
+ """
+
+ pkcs10 = rpki.x509.PKCS10.create(
+ keypair = ca_detail.private_key_id,
+ is_ca = True,
+ caRepository = ca.sia_uri,
+ rpkiManifest = ca_detail.manifest_uri)
+ q_msg = self._compose_up_down_query("issue")
+ q_pdu = SubElement(q_msg, rpki.up_down.tag_request, class_name = ca.parent_resource_class)
+ q_pdu.text = pkcs10.get_Base64()
+ self.query_up_down(q_msg, cb, eb)
+
+
+ def up_down_revoke_query(self, class_name, ski, cb, eb):
+ """
+ Send an up-down revoke query to this parent.
+ """
+
+ q_msg = self._compose_up_down_query("revoke")
+ SubElement(q_msg, rpki.up_down.tag_key, class_name = class_name, ski = ski)
+ self.query_up_down(q_msg, cb, eb)
+
+
+ def query_up_down(self, q_msg, cb, eb):
"""
Client code for sending one up-down query PDU to this parent.
"""
@@ -794,25 +881,21 @@ class parent_elt(data_elt):
if bsc.signing_cert is None:
raise rpki.exceptions.BSCNotReady("BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id))
- q_msg = rpki.up_down.message_pdu.make_query(
- payload = q_pdu,
- sender = self.sender_name,
- recipient = self.recipient_name)
-
- q_der = rpki.up_down.cms_msg().wrap(q_msg, bsc.private_key_id,
- bsc.signing_cert,
- bsc.signing_cert_crl)
+ q_der = rpki.up_down.cms_msg_no_sax().wrap(q_msg, bsc.private_key_id,
+ bsc.signing_cert,
+ bsc.signing_cert_crl)
def unwrap(r_der):
try:
- r_cms = rpki.up_down.cms_msg(DER = r_der)
+ r_cms = rpki.up_down.cms_msg_no_sax(DER = r_der)
r_msg = r_cms.unwrap((self.gctx.bpki_ta,
self.self.bpki_cert,
self.self.bpki_glue,
self.bpki_cms_cert,
self.bpki_cms_glue))
r_cms.check_replay_sql(self, self.peer_contact_uri)
- r_msg.payload.check_response()
+ rpki.up_down.check_response(r_msg, q_msg.get("type"))
+
except (SystemExit, rpki.async.ExitNow):
raise
except Exception, e:
@@ -860,6 +943,7 @@ class child_elt(data_elt):
"""
Fetch all child_cert objects that link to this child object.
"""
+
return rpki.rpkid.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
@property
@@ -867,6 +951,7 @@ class child_elt(data_elt):
"""
Fetch all child_cert objects that link to this child object.
"""
+
return self.fetch_child_certs()
@property
@@ -874,12 +959,14 @@ class child_elt(data_elt):
"""
Fetch all parent objects that link to self object to which this child object links.
"""
+
return parent_elt.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 child_elt.
"""
+
actions = []
if q_pdu.reissue:
actions.append(self.serve_reissue)
@@ -893,6 +980,7 @@ class child_elt(data_elt):
"""
Handle a left-right reissue action for this child.
"""
+
publisher = rpki.rpkid.publication_queue()
for child_cert in self.child_certs:
child_cert.reissue(child_cert.ca_detail, publisher, force = True)
@@ -902,6 +990,7 @@ class child_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this child.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
@@ -910,6 +999,7 @@ class child_elt(data_elt):
"""
Fetch the CA corresponding to an up-down class_name.
"""
+
if not class_name.isdigit():
raise rpki.exceptions.BadClassNameSyntax("Bad class name %s" % class_name)
ca = rpki.rpkid.ca_obj.sql_fetch(self.gctx, long(class_name))
@@ -926,51 +1016,195 @@ class child_elt(data_elt):
"""
Extra server actions when destroying a child_elt.
"""
+
publisher = rpki.rpkid.publication_queue()
for child_cert in self.child_certs:
child_cert.revoke(publisher = publisher,
generate_crl_and_manifest = True)
publisher.call_pubd(cb, eb)
- def serve_up_down(self, query, callback):
+
+ def up_down_handle_list(self, q_msg, r_msg, callback, errback):
+ """
+ Serve one up-down "list" PDU.
+ """
+
+ def got_resources(irdb_resources):
+
+ if irdb_resources.valid_until < rpki.sundial.now():
+ logger.debug("Child %s's resources expired %s", self.child_handle, irdb_resources.valid_until)
+ else:
+ for parent in self.parents:
+ for ca in parent.cas:
+ ca_detail = ca.active_ca_detail
+ if not ca_detail:
+ logger.debug("No active ca_detail, can't issue to %s", self.child_handle)
+ continue
+ resources = ca_detail.latest_ca_cert.get_3779resources() & irdb_resources
+ if resources.empty():
+ logger.debug("No overlap between received resources and what child %s should get ([%s], [%s])",
+ self.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources)
+ continue
+ rc = SubElement(r_msg, rpki.up_down.tag_class,
+ class_name = str(ca.ca_id),
+ cert_url = ca_detail.ca_cert_uri,
+ resource_set_as = str(resources.asn),
+ resource_set_ipv4 = str(resources.v4),
+ resource_set_ipv6 = str(resources.v6),
+ resource_set_notafter = str(resources.valid_until))
+ for child_cert in self.fetch_child_certs(ca_detail = ca_detail):
+ c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
+ c.text = child_cert.cert.get_Base64()
+ SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64()
+ callback()
+
+ self.gctx.irdb_query_child_resources(self.self.self_handle, self.child_handle, got_resources, errback)
+
+
+ def up_down_handle_issue(self, q_msg, r_msg, callback, errback):
+ """
+ Serve one issue request PDU.
+ """
+
+ def got_resources(irdb_resources):
+
+ def done():
+ rc = SubElement(r_msg, rpki.up_down.tag_class,
+ class_name = class_name,
+ cert_url = ca_detail.ca_cert_uri,
+ resource_set_as = str(resources.asn),
+ resource_set_ipv4 = str(resources.v4),
+ resource_set_ipv6 = str(resources.v6),
+ resource_set_notafter = str(resources.valid_until))
+ c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
+ c.text = child_cert.cert.get_Base64()
+ SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64()
+ callback()
+
+ if irdb_resources.valid_until < rpki.sundial.now():
+ raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % (
+ self.child_handle, irdb_resources.valid_until))
+
+ resources = irdb_resources & ca_detail.latest_ca_cert.get_3779resources()
+ resources.valid_until = irdb_resources.valid_until
+ req_key = pkcs10.getPublicKey()
+ req_sia = pkcs10.get_SIA()
+ child_cert = self.fetch_child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True)
+
+ # Generate new cert or regenerate old one if necessary
+
+ publisher = rpki.rpkid.publication_queue()
+
+ if child_cert is None:
+ child_cert = ca_detail.issue(
+ ca = ca,
+ child = self,
+ subject_key = req_key,
+ sia = req_sia,
+ resources = resources,
+ publisher = publisher)
+ else:
+ child_cert = child_cert.reissue(
+ ca_detail = ca_detail,
+ sia = req_sia,
+ resources = resources,
+ publisher = publisher)
+
+ self.gctx.sql.sweep()
+ assert child_cert and child_cert.sql_in_db
+ publisher.call_pubd(done, errback)
+
+ req = q_msg[0]
+ assert req.tag == rpki.up_down.tag_request
+
+ # Subsetting not yet implemented, this is the one place where we
+ # have to handle it, by reporting that we're lame.
+
+ if any(req.get(a) for a in ("req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")):
+ raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry")
+
+ class_name = req.get("class_name")
+ pkcs10 = rpki.x509.PKCS10(Base64 = req.text)
+ pkcs10.check_valid_request_ca()
+ ca = self.ca_from_class_name(class_name)
+ ca_detail = ca.active_ca_detail
+ if ca_detail is None:
+ raise rpki.exceptions.NoActiveCA("No active CA for class %r" % class_name)
+
+ self.gctx.irdb_query_child_resources(self.self.self_handle, self.child_handle, got_resources, errback)
+
+
+ def up_down_handle_revoke(self, q_msg, r_msg, callback, errback):
+ """
+ Serve one revoke request PDU.
+ """
+
+ def done():
+ SubElement(r_msg, key.tag, class_name = class_name, ski = key.get("ski"))
+ callback()
+
+ key = q_msg[0]
+ assert key.tag == rpki.up_down.tag_key
+ class_name = key.get("class_name")
+ ski = base64.urlsafe_b64decode(key.get("ski") + "=")
+
+ publisher = rpki.rpkid.publication_queue()
+
+ ca = child.ca_from_class_name(class_name)
+ for ca_detail in ca.ca_details:
+ for child_cert in child.fetch_child_certs(ca_detail = ca_detail, ski = ski):
+ child_cert.revoke(publisher = publisher)
+
+ self.gctx.sql.sweep()
+ publisher.call_pubd(done, errback)
+
+
+ def serve_up_down(self, q_der, callback):
"""
Outer layer of server handling for one up-down PDU from this child.
"""
+ def done():
+ callback(rpki.up_down.cms_msg_no_sax().wrap(r_msg, bsc.private_key_id,
+ bsc.signing_cert, bsc.signing_cert_crl))
+
+ def lose(e, quiet = False):
+ logger.exception("Unhandled exception serving child %r", self)
+ rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type)
+ done()
+
bsc = self.bsc
if bsc is None:
raise rpki.exceptions.BSCNotFound("Could not find BSC %s" % self.bsc_id)
- q_cms = rpki.up_down.cms_msg(DER = query)
+ q_cms = rpki.up_down.cms_msg_no_sax(DER = q_der)
q_msg = q_cms.unwrap((self.gctx.bpki_ta,
self.self.bpki_cert,
self.self.bpki_glue,
self.bpki_cert,
self.bpki_glue))
q_cms.check_replay_sql(self, "child", self.child_handle)
- q_msg.payload.gctx = self.gctx
- if enforce_strict_up_down_xml_sender and q_msg.sender != self.child_handle:
- raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.sender)
+ q_type = q_msg.get("type")
+ logger.info("Serving %s query from child %s [sender %s, recipient %s]",
+ q_type, self.child_handle, q_msg.get("sender"), q_msg.get("recipient"))
+ if enforce_strict_up_down_xml_sender and q_msg.get("sender") != self.child_handle:
+ raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.get("sender"))
self.gctx.sql.sweep()
- def done(r_msg):
- #
- # Exceptions from this point on are problematic, as we have no
- # sane way of reporting errors in the error reporting mechanism.
- # May require refactoring, ignore the issue for now.
- #
- reply = rpki.up_down.cms_msg().wrap(r_msg, bsc.private_key_id,
- bsc.signing_cert, bsc.signing_cert_crl)
- callback(reply)
+ r_msg = Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
+ sender = q_msg.get("recipient"), recipient = q_msg.get("sender"), type = q_type + "_response")
try:
- q_msg.serve_top_level(self, done)
+ getattr(self, "up_down_handle_" + q_type)(q_msg, r_msg, done, lose)
+
except (rpki.async.ExitNow, SystemExit):
raise
- except rpki.exceptions.NoActiveCA, data:
- done(q_msg.serve_error(data))
+
+ except rpki.exceptions.NoActiveCA, e:
+ lose(e, quiet = True)
+
except Exception, e:
- logger.exception("Unhandled exception serving up-down request from %r", self)
- done(q_msg.serve_error(e))
+ lose(e)
+
class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
"""
@@ -989,6 +1223,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
Handle <list_resources/> element. This requires special handling
due to the data types of some of the attributes.
"""
+
assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
if isinstance(self.valid_until, str):
@@ -1005,6 +1240,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
Generate <list_resources/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
elt = self.make_elt()
if isinstance(self.valid_until, int):
elt.set("valid_until", self.valid_until.toXMLtime())
@@ -1023,6 +1259,7 @@ class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace):
Handle <list_roa_requests/> element. This requires special handling
due to the data types of some of the attributes.
"""
+
assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
if self.ipv4 is not None:
@@ -1068,6 +1305,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
Handle <list_ee_certificate_requests/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
if name not in self.elements:
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
@@ -1086,6 +1324,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
"""
Handle <pkcs10/> sub-element.
"""
+
assert len(self.elements) == 1
if name == self.elements[0]:
self.pkcs10 = rpki.x509.PKCS10(Base64 = text)
@@ -1098,6 +1337,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
Generate <list_ee_certificate_requests/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
if isinstance(self.eku, (tuple, list)):
self.eku = ",".join(self.eku)
elt = self.make_elt()
@@ -1128,6 +1368,7 @@ 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 ca in parent.cas:
ca_detail = ca.active_ca_detail
@@ -1148,6 +1389,7 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace):
"""
Generate one reply PDU.
"""
+
r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle,
uri = uri, child_handle = child_handle)
r_pdu.obj = obj.get_Base64()
@@ -1172,6 +1414,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace)
just dump a bunch of data about every certificate issued to us by
one of our parents, then return.
"""
+
for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents:
for ca in parent.cas:
ca_detail = ca.active_ca_detail
@@ -1183,6 +1426,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace)
"""
Generate one reply PDU.
"""
+
resources = cert.get_3779resources()
return self.make_pdu(
tag = self.tag,
@@ -1216,6 +1460,7 @@ class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace):
"""
Generate a <report_error/> element from an exception.
"""
+
self = cls()
self.self_handle = self_handle
self.tag = tag
@@ -1288,3 +1533,12 @@ class cms_msg(rpki.x509.XML_CMS_object):
encoding = "us-ascii"
schema = rpki.relaxng.left_right
saxify = sax_handler.saxify
+
+class cms_msg_no_sax(cms_msg):
+ """
+ Class to hold a CMS-signed left-right PDU.
+
+ Name is a transition kludge: once we ditch SAX, this will become cms_msg.
+ """
+
+ saxify = None
diff --git a/rpki/log.py b/rpki/log.py
index 2abb3b2c..0794a68f 100644
--- a/rpki/log.py
+++ b/rpki/log.py
@@ -48,7 +48,7 @@ show_python_ids = False
# Whether tracebacks are enabled globally. Individual classes and
# modules may choose to override this.
-enable_tracebacks = False
+enable_tracebacks = True
## @var use_setproctitle
# Whether to use setproctitle (if available) to change name shown for
@@ -96,7 +96,10 @@ class Formatter(object):
yield time.strftime("%Y-%m-%d %H:%M:%S ", time.gmtime(record.created))
yield "%s[%d]: " % (self.ident, record.process)
try:
- yield repr(record.context) + " "
+ if isinstance(record.context, (str, unicode)):
+ yield record.context + " "
+ else:
+ yield repr(record.context) + " "
except AttributeError:
pass
yield record.getMessage()
diff --git a/rpki/old_irdbd.py b/rpki/old_irdbd.py
index 6c026a31..10024290 100644
--- a/rpki/old_irdbd.py
+++ b/rpki/old_irdbd.py
@@ -30,7 +30,7 @@ import time
import logging
import argparse
import urlparse
-import rpki.http
+import rpki.http_simple
import rpki.config
import rpki.resource_set
import rpki.relaxng
@@ -226,7 +226,7 @@ class main(object):
rpki.left_right.list_ghostbuster_requests_elt : handle_list_ghostbuster_requests,
rpki.left_right.list_ee_certificate_requests_elt : handle_list_ee_certificate_requests }
- def handler(self, query, path, cb):
+ def handler(self, request, q_der):
try:
self.db.ping(True)
@@ -235,7 +235,7 @@ class main(object):
try:
- q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.rpkid_cert))
+ q_msg = rpki.left_right.cms_msg(DER = q_der).unwrap((self.bpki_ta, self.rpkid_cert))
if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query():
raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg)
@@ -251,28 +251,19 @@ class main(object):
else:
h(self, q_pdu, r_msg)
- except (rpki.async.ExitNow, SystemExit):
- raise
-
except Exception, e:
logger.exception("Exception serving PDU %r", q_pdu)
r_msg.append(rpki.left_right.report_error_elt.from_exception(e, q_pdu.self_handle, q_pdu.tag))
- except (rpki.async.ExitNow, SystemExit):
- raise
-
except Exception, e:
logger.exception("Exception decoding query")
r_msg.append(rpki.left_right.report_error_elt.from_exception(e))
- cb(200, body = rpki.left_right.cms_msg().wrap(r_msg, self.irdbd_key, self.irdbd_cert))
-
- except (rpki.async.ExitNow, SystemExit):
- raise
+ request.send_cms_response(rpki.left_right.cms_msg().wrap(r_msg, self.irdbd_key, self.irdbd_cert))
except Exception, e:
logger.exception("Unhandled exception, returning HTTP failure")
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
def __init__(self):
@@ -290,7 +281,7 @@ class main(object):
rpki.log.init("irdbd", args)
- self.cfg = rpki.config.parser(args.config, "irdbd")
+ self.cfg = rpki.config.parser(set_filename = args.config, section = "irdbd")
startup_msg = self.cfg.get("startup-message", "")
if startup_msg:
@@ -319,6 +310,6 @@ class main(object):
u.query == "" and \
u.fragment == ""
- rpki.http.server(host = u.hostname or "localhost",
- port = u.port or 443,
- handlers = ((u.path, self.handler),))
+ rpki.http_simple.server(host = u.hostname or "localhost",
+ port = u.port or 443,
+ handlers = ((u.path, self.handler),))
diff --git a/rpki/pubd.py b/rpki/pubd.py
index 79315a78..3bb139cb 100644
--- a/rpki/pubd.py
+++ b/rpki/pubd.py
@@ -23,23 +23,27 @@ RPKI publication engine.
import os
import re
+import uuid
import time
+import socket
import logging
import argparse
+
import rpki.resource_set
-import rpki.up_down
import rpki.x509
-import rpki.sql
-import rpki.http
import rpki.config
import rpki.exceptions
-import rpki.relaxng
import rpki.log
import rpki.publication
+import rpki.publication_control
import rpki.daemonize
+import rpki.http_simple
+
+from lxml.etree import Element, SubElement
logger = logging.getLogger(__name__)
+
class main(object):
"""
Main program for pubd.
@@ -47,7 +51,8 @@ class main(object):
def __init__(self):
- os.environ["TZ"] = "UTC"
+ os.environ.update(TZ = "UTC",
+ DJANGO_SETTINGS_MODULE = "rpki.django_settings")
time.tzset()
self.irbe_cms_timestamp = None
@@ -68,7 +73,7 @@ class main(object):
rpki.log.init("pubd", args)
- self.cfg = rpki.config.parser(args.config, "pubd")
+ self.cfg = rpki.config.parser(set_filename = args.config, section = "pubd")
self.cfg.set_global_flags()
if not args.foreground:
@@ -90,84 +95,198 @@ class main(object):
if self.profile:
logger.info("Running in profile mode with output to %s", self.profile)
- self.sql = rpki.sql.session(self.cfg)
+ global rpki
+ import rpki.pubdb
self.bpki_ta = rpki.x509.X509(Auto_update = self.cfg.get("bpki-ta"))
self.irbe_cert = rpki.x509.X509(Auto_update = self.cfg.get("irbe-cert"))
self.pubd_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-cert"))
self.pubd_key = rpki.x509.RSA( Auto_update = self.cfg.get("pubd-key"))
+ self.pubd_crl = rpki.x509.CRL( Auto_update = self.cfg.get("pubd-crl"))
self.http_server_host = self.cfg.get("server-host", "")
self.http_server_port = self.cfg.getint("server-port")
self.publication_base = self.cfg.get("publication-base", "publication/")
- self.publication_multimodule = self.cfg.getboolean("publication-multimodule", False)
+ self.rrdp_uri_base = self.cfg.get("rrdp-uri-base",
+ "http://%s/rrdp/" % socket.getfqdn())
+ self.rrdp_expiration_interval = rpki.sundial.timedelta.parse(self.cfg.get("rrdp-expiration-interval", "6h"))
+ self.rrdp_publication_base = self.cfg.get("rrdp-publication-base",
+ "rrdp-publication/")
- rpki.http.server(
+ try:
+ self.session = rpki.pubdb.Session.objects.get()
+ except rpki.pubdb.Session.DoesNotExist:
+ self.session = rpki.pubdb.Session.objects.create(uuid = str(uuid.uuid4()), serial = 0)
+
+ rpki.http_simple.server(
host = self.http_server_host,
port = self.http_server_port,
handlers = (("/control", self.control_handler),
("/client/", self.client_handler)))
- def handler_common(self, query, client, cb, certs, crl = None):
+
+ def control_handler(self, request, q_der):
"""
- Common PDU handler code.
+ Process one PDU from the IRBE.
"""
- def done(r_msg):
- reply = rpki.publication.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert, crl)
- self.sql.sweep()
- cb(reply)
+ from django.db import transaction, connection
- q_cms = rpki.publication.cms_msg(DER = query)
- q_msg = q_cms.unwrap(certs)
- if client is None:
+ try:
+ connection.cursor() # Reconnect to mysqld if necessary
+ q_cms = rpki.publication_control.cms_msg_no_sax(DER = q_der)
+ q_msg = q_cms.unwrap((self.bpki_ta, self.irbe_cert))
self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp, "control")
- else:
- q_cms.check_replay_sql(client, client.client_handle)
- q_msg.serve_top_level(self, client, done)
+ if q_msg.get("type") != "query":
+ raise rpki.exceptions.BadQuery("Message type is %s, expected query" % q_msg.get("type"))
+ r_msg = Element(rpki.publication_control.tag_msg, nsmap = rpki.publication_control.nsmap,
+ type = "reply", version = rpki.publication_control.version)
- def control_handler(self, query, path, cb):
- """
- Process one PDU from the IRBE.
- """
+ try:
+ with transaction.atomic(using = "pubdb"):
- def done(body):
- cb(200, body = body)
+ for q_pdu in q_msg:
+ if q_pdu.tag != rpki.publication_control.tag_client:
+ raise rpki.exceptions.BadQuery("PDU is %s, expected client" % q_pdu.tag)
+ client_handle = q_pdu.get("client_handle")
+ action = q_pdu.get("action")
+ if client_handle is None:
+ logger.info("Control %s request", action)
+ else:
+ logger.info("Control %s request for %s", action, client_handle)
+
+ if action in ("get", "list"):
+ if action == "get":
+ clients = rpki.pubdb.Client.objects.get(client_handle = client_handle),
+ else:
+ clients = rpki.pubdb.Client.objects.all()
+ for client in clients:
+ r_pdu = SubElement(r_msg, q_pdu.tag, action = action,
+ client_handle = client.client_handle, base_uri = client.base_uri)
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+ SubElement(r_pdu, rpki.publication_control.tag_bpki_cert).text = client.bpki_cert.get_Base64()
+ if client.bpki_glue is not None:
+ SubElement(r_pdu, rpki.publication_control.tag_bpki_glue).text = client.bpki_glue.get_Base64()
+
+ if action in ("create", "set"):
+ if action == "create":
+ client = rpki.pubdb.Client(client_handle = client_handle)
+ else:
+ client = rpki.pubdb.Client.objects.get(client_handle = client_handle)
+ if q_pdu.get("base_uri"):
+ client.base_uri = q_pdu.get("base_uri")
+ bpki_cert = q_pdu.find(rpki.publication_control.tag_bpki_cert)
+ if bpki_cert is not None:
+ client.bpki_cert = bpki_cert.text.decode("base64")
+ bpki_glue = q_pdu.find(rpki.publication_control.tag_bpki_glue)
+ if bpki_glue is not None:
+ client.bpki_glue = bpki_glue.text.decode("base64")
+ if q_pdu.get("clear_replay_protection") == "yes":
+ client.last_cms_timestamp = None
+ client.save()
+ logger.debug("Stored client_handle %s, base_uri %s, bpki_cert %r, bpki_glue %r, last_cms_timestamp %s",
+ client.client_handle, client.base_uri, client.bpki_cert, client.bpki_glue,
+ client.last_cms_timestamp)
+ r_pdu = SubElement(r_msg, q_pdu.tag, action = action, client_handle = client_handle)
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ if action == "destroy":
+ rpki.pubdb.Client.objects.filter(client_handle = client_handle).delete()
+ r_pdu = SubElement(r_msg, q_pdu.tag, action = action, client_handle = client_handle)
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ except Exception, e:
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_pdu = SubElement(r_msg, rpki.publication_control.tag_report_error, error_code = e.__class__.__name__)
+ r_pdu.text = str(e)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ request.send_cms_response(rpki.publication_control.cms_msg_no_sax().wrap(r_msg, self.pubd_key, self.pubd_cert))
- try:
- self.handler_common(query, None, done, (self.bpki_ta, self.irbe_cert))
- except (rpki.async.ExitNow, SystemExit):
- raise
except Exception, e:
- logger.exception("Unhandled exception processing control query, path %r", path)
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+ logger.exception("Unhandled exception processing control query, path %r", request.path)
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+
client_url_regexp = re.compile("/client/([-A-Z0-9_/]+)$", re.I)
- def client_handler(self, query, path, cb):
+ def client_handler(self, request, q_der):
"""
Process one PDU from a client.
"""
- def done(body):
- cb(200, body = body)
+ from django.db import transaction, connection
try:
- match = self.client_url_regexp.search(path)
+ connection.cursor() # Reconnect to mysqld if necessary
+ match = self.client_url_regexp.search(request.path)
if match is None:
- raise rpki.exceptions.BadContactURL("Bad path: %s" % path)
- client_handle = match.group(1)
- client = rpki.publication.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,))
- if client is None:
- raise rpki.exceptions.ClientNotFound("Could not find client %s" % client_handle)
- config = rpki.publication.config_elt.fetch(self)
- if config is None or config.bpki_crl is None:
- raise rpki.exceptions.CMSCRLNotSet
- self.handler_common(query, client, done, (self.bpki_ta, client.bpki_cert, client.bpki_glue), config.bpki_crl)
- except (rpki.async.ExitNow, SystemExit):
- raise
+ raise rpki.exceptions.BadContactURL("Bad path: %s" % request.path)
+ client = rpki.pubdb.Client.objects.get(client_handle = match.group(1))
+ q_cms = rpki.publication.cms_msg_no_sax(DER = q_der)
+ q_msg = q_cms.unwrap((self.bpki_ta, client.bpki_cert, client.bpki_glue))
+ client.last_cms_timestamp = q_cms.check_replay(client.last_cms_timestamp, client.client_handle)
+ client.save()
+ if q_msg.get("type") != "query":
+ raise rpki.exceptions.BadQuery("Message type is %s, expected query" % q_msg.get("type"))
+ r_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "reply", version = rpki.publication.version)
+ delta = None
+ try:
+ with transaction.atomic(using = "pubdb"):
+ for q_pdu in q_msg:
+ if q_pdu.get("uri"):
+ logger.info("Client %s request for %s", q_pdu.tag, q_pdu.get("uri"))
+ else:
+ logger.info("Client %s request", q_pdu.tag)
+
+ if q_pdu.tag == rpki.publication.tag_list:
+ for obj in client.publishedobject_set.all():
+ r_pdu = SubElement(r_msg, q_pdu.tag, uri = obj.uri, hash = obj.hash)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ else:
+ assert q_pdu.tag in (rpki.publication.tag_publish, rpki.publication.tag_withdraw)
+ if delta is None:
+ delta = self.session.new_delta(rpki.sundial.now() + self.rrdp_expiration_interval)
+ client.check_allowed_uri(q_pdu.get("uri"))
+ if q_pdu.tag == rpki.publication.tag_publish:
+ der = q_pdu.text.decode("base64")
+ logger.info("Publishing %s", rpki.x509.uri_dispatch(q_pdu.get("uri"))(DER = der).tracking_data(q_pdu.get("uri")))
+ delta.publish(client, der, q_pdu.get("uri"), q_pdu.get("hash"))
+ else:
+ logger.info("Withdrawing %s", q_pdu.get("uri"))
+ delta.withdraw(client, q_pdu.get("uri"), q_pdu.get("hash"))
+ r_pdu = SubElement(r_msg, q_pdu.tag, uri = q_pdu.get("uri"))
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ if delta is not None:
+ delta.activate()
+ self.session.generate_snapshot()
+ self.session.expire_deltas()
+
+ except Exception, e:
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_pdu = SubElement(r_msg, rpki.publication.tag_report_error, error_code = e.__class__.__name__)
+ r_pdu.text = str(e)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+
+ else:
+ if delta is not None:
+ self.session.synchronize_rrdp_files(self.rrdp_publication_base, self.rrdp_uri_base)
+ delta.update_rsync_files(self.publication_base)
+
+ request.send_cms_response(rpki.publication.cms_msg_no_sax().wrap(r_msg, self.pubd_key, self.pubd_cert, self.pubd_crl))
+
except Exception, e:
- logger.exception("Unhandled exception processing client query, path %r", path)
- cb(500, reason = "Could not process PDU: %s" % e)
+ logger.exception("Unhandled exception processing client query, path %r", request.path)
+ request.send_error(500, "Could not process PDU: %s" % e)
diff --git a/rpki/pubdb/__init__.py b/rpki/pubdb/__init__.py
new file mode 100644
index 00000000..2c83051f
--- /dev/null
+++ b/rpki/pubdb/__init__.py
@@ -0,0 +1,21 @@
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Package for Django ORM models relating to pubd.
+"""
+
+from rpki.pubdb.models import *
diff --git a/rpki/pubdb/migrations/0001_initial.py b/rpki/pubdb/migrations/0001_initial.py
new file mode 100644
index 00000000..c796d020
--- /dev/null
+++ b/rpki/pubdb/migrations/0001_initial.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import dbs
+from south.v2 import SchemaMigration
+from django.db import models
+
+db = dbs["pubdb"]
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Client'
+ db.create_table(u'pubdb_client', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('client_handle', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
+ ('base_uri', self.gf('django.db.models.fields.TextField')()),
+ ('bpki_cert', self.gf('rpki.fields.BlobField')(default=None, blank=True)),
+ ('bpki_glue', self.gf('rpki.fields.BlobField')(default=None, null=True, blank=True)),
+ ('last_cms_timestamp', self.gf('rpki.fields.SundialField')(null=True, blank=True)),
+ ))
+ db.send_create_signal(u'pubdb', ['Client'])
+
+ # Adding model 'Session'
+ db.create_table(u'pubdb_session', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('uuid', self.gf('django.db.models.fields.CharField')(unique=True, max_length=36)),
+ ('serial', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('snapshot', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('hash', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)),
+ ))
+ db.send_create_signal(u'pubdb', ['Session'])
+
+ # Adding model 'Delta'
+ db.create_table(u'pubdb_delta', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('serial', self.gf('django.db.models.fields.BigIntegerField')()),
+ ('xml', self.gf('django.db.models.fields.TextField')()),
+ ('hash', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('expires', self.gf('rpki.fields.SundialField')()),
+ ('session', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['pubdb.Session'])),
+ ))
+ db.send_create_signal(u'pubdb', ['Delta'])
+
+ # Adding model 'PublishedObject'
+ db.create_table(u'pubdb_publishedobject', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('uri', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('der', self.gf('rpki.fields.BlobField')(default=None, blank=True)),
+ ('hash', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('client', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['pubdb.Client'])),
+ ('session', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['pubdb.Session'])),
+ ))
+ db.send_create_signal(u'pubdb', ['PublishedObject'])
+
+ # Adding unique constraint on 'PublishedObject', fields ['session', 'hash']
+ db.create_unique(u'pubdb_publishedobject', ['session_id', 'hash'])
+
+ # Adding unique constraint on 'PublishedObject', fields ['session', 'uri']
+ db.create_unique(u'pubdb_publishedobject', ['session_id', 'uri'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'PublishedObject', fields ['session', 'uri']
+ db.delete_unique(u'pubdb_publishedobject', ['session_id', 'uri'])
+
+ # Removing unique constraint on 'PublishedObject', fields ['session', 'hash']
+ db.delete_unique(u'pubdb_publishedobject', ['session_id', 'hash'])
+
+ # Deleting model 'Client'
+ db.delete_table(u'pubdb_client')
+
+ # Deleting model 'Session'
+ db.delete_table(u'pubdb_session')
+
+ # Deleting model 'Delta'
+ db.delete_table(u'pubdb_delta')
+
+ # Deleting model 'PublishedObject'
+ db.delete_table(u'pubdb_publishedobject')
+
+
+ models = {
+ u'pubdb.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'base_uri': ('django.db.models.fields.TextField', [], {}),
+ 'bpki_cert': ('rpki.fields.BlobField', [], {'default': 'None', 'blank': 'True'}),
+ 'bpki_glue': ('rpki.fields.BlobField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+ 'client_handle': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_cms_timestamp': ('rpki.fields.SundialField', [], {'null': 'True', 'blank': 'True'})
+ },
+ u'pubdb.delta': {
+ 'Meta': {'object_name': 'Delta'},
+ 'expires': ('rpki.fields.SundialField', [], {}),
+ 'hash': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'serial': ('django.db.models.fields.BigIntegerField', [], {}),
+ 'session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['pubdb.Session']"}),
+ 'xml': ('django.db.models.fields.TextField', [], {})
+ },
+ u'pubdb.publishedobject': {
+ 'Meta': {'unique_together': "((u'session', u'hash'), (u'session', u'uri'))", 'object_name': 'PublishedObject'},
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['pubdb.Client']"}),
+ 'der': ('rpki.fields.BlobField', [], {'default': 'None', 'blank': 'True'}),
+ 'hash': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['pubdb.Session']"}),
+ 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ u'pubdb.session': {
+ 'Meta': {'object_name': 'Session'},
+ 'hash': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'serial': ('django.db.models.fields.BigIntegerField', [], {}),
+ 'snapshot': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
+ }
+ }
+
+ complete_apps = ['pubdb']
diff --git a/rpki/pubdb/migrations/__init__.py b/rpki/pubdb/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rpki/pubdb/migrations/__init__.py
diff --git a/rpki/pubdb/models.py b/rpki/pubdb/models.py
new file mode 100644
index 00000000..f7edfd4a
--- /dev/null
+++ b/rpki/pubdb/models.py
@@ -0,0 +1,310 @@
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Django ORM models for pubd.
+"""
+
+from __future__ import unicode_literals
+from django.db import models
+from rpki.fields import BlobField, CertificateField, SundialField
+from lxml.etree import Element, SubElement, tostring as ElementToString
+
+import os
+import logging
+import rpki.exceptions
+import rpki.relaxng
+
+logger = logging.getLogger(__name__)
+
+
+# Some of this probably ought to move into a rpki.rrdp module.
+
+rrdp_xmlns = rpki.relaxng.rrdp.xmlns
+rrdp_nsmap = rpki.relaxng.rrdp.nsmap
+rrdp_version = "1"
+
+rrdp_tag_delta = rrdp_xmlns + "delta"
+rrdp_tag_deltas = rrdp_xmlns + "deltas"
+rrdp_tag_notification = rrdp_xmlns + "notification"
+rrdp_tag_publish = rrdp_xmlns + "publish"
+rrdp_tag_snapshot = rrdp_xmlns + "snapshot"
+rrdp_tag_withdraw = rrdp_xmlns + "withdraw"
+
+
+# This would probably be useful to more than just this module, not
+# sure quite where to put it at the moment.
+
+def DERSubElement(elt, name, der, attrib = None, **kwargs):
+ """
+ Convenience wrapper around SubElement for use with Base64 text.
+ """
+
+ se = SubElement(elt, name, attrib, **kwargs)
+ se.text = rpki.x509.base64_with_linebreaks(der)
+ se.tail = "\n"
+ return se
+
+
+
+class Client(models.Model):
+ client_handle = models.CharField(unique = True, max_length = 255)
+ base_uri = models.TextField()
+ bpki_cert = CertificateField()
+ bpki_glue = CertificateField(null = True)
+ last_cms_timestamp = SundialField(blank = True, null = True)
+
+
+ def check_allowed_uri(self, uri):
+ """
+ Make sure that a target URI is within this client's allowed URI space.
+ """
+
+ if not uri.startswith(self.base_uri):
+ raise rpki.exceptions.ForbiddenURI
+
+
+class Session(models.Model):
+ uuid = models.CharField(unique = True, max_length=36)
+ serial = models.BigIntegerField()
+ snapshot = models.TextField(blank = True)
+ hash = models.CharField(max_length = 64, blank = True)
+
+
+ def new_delta(self, expires):
+ """
+ Construct a new delta associated with this session.
+ """
+
+ delta = Delta(session = self,
+ serial = self.serial + 1,
+ expires = expires)
+ delta.deltas = Element(rrdp_tag_deltas,
+ nsmap = rrdp_nsmap,
+ version = rrdp_version,
+ session_id = self.uuid)
+ delta.deltas.set("to", str(delta.serial))
+ delta.deltas.set("from", str(delta.serial - 1))
+ SubElement(delta.deltas, rrdp_tag_delta, serial = str(delta.serial)).text = "\n"
+ return delta
+
+
+ def expire_deltas(self):
+ """
+ Delete deltas whose expiration date has passed.
+ """
+
+ self.delta_set.filter(expires__lt = rpki.sundial.now()).delete()
+
+
+ def generate_snapshot(self):
+ """
+ Generate an XML snapshot of this session.
+ """
+
+ xml = Element(rrdp_tag_snapshot, nsmap = rrdp_nsmap,
+ version = rrdp_version,
+ session_id = self.uuid,
+ serial = str(self.serial))
+ xml.text = "\n"
+ for obj in self.publishedobject_set.all():
+ DERSubElement(xml, rrdp_tag_publish,
+ der = obj.der,
+ uri = obj.uri)
+ rpki.relaxng.rrdp.assertValid(xml)
+ self.snapshot = ElementToString(xml, pretty_print = True)
+ self.hash = rpki.x509.sha256(self.snapshot).encode("hex")
+ self.save()
+
+
+ @property
+ def snapshot_fn(self):
+ return "%s/snapshot/%s.xml" % (self.uuid, self.serial)
+
+
+ @property
+ def notification_fn(self):
+ return "updates.xml"
+
+
+ @staticmethod
+ def _write_rrdp_file(fn, text, rrdp_publication_base, overwrite = False):
+ if overwrite or not os.path.exists(os.path.join(rrdp_publication_base, fn)):
+ tn = os.path.join(rrdp_publication_base, fn + ".%s.tmp" % os.getpid())
+ if not os.path.isdir(os.path.dirname(tn)):
+ os.makedirs(os.path.dirname(tn))
+ with open(tn, "w") as f:
+ f.write(text)
+ os.rename(tn, os.path.join(rrdp_publication_base, fn))
+
+
+ @staticmethod
+ def _rrdp_filename_to_uri(fn, rrdp_uri_base):
+ return "%s/%s" % (rrdp_uri_base.rstrip("/"), fn)
+
+
+ def _generate_update_xml(self, rrdp_uri_base):
+ xml = Element(rrdp_tag_notification, nsmap = rrdp_nsmap,
+ version = rrdp_version,
+ session_id = self.uuid,
+ serial = str(self.serial))
+ SubElement(xml, rrdp_tag_snapshot,
+ uri = self._rrdp_filename_to_uri(self.snapshot_fn, rrdp_uri_base),
+ hash = self.hash)
+ for delta in self.delta_set.all():
+ se = SubElement(xml, rrdp_tag_delta,
+ uri = self._rrdp_filename_to_uri(delta.fn, rrdp_uri_base),
+ hash = delta.hash)
+ se.set("to", str(delta.serial))
+ se.set("from", str(delta.serial - 1))
+ rpki.relaxng.rrdp.assertValid(xml)
+ return ElementToString(xml, pretty_print = True)
+
+
+ def synchronize_rrdp_files(self, rrdp_publication_base, rrdp_uri_base):
+ """
+ Write current RRDP files to disk, clean up old files and directories.
+ """
+
+ current_filenames = set()
+
+ for delta in self.delta_set.all():
+ self._write_rrdp_file(delta.fn, delta.xml, rrdp_publication_base)
+ current_filenames.add(delta.fn)
+
+ self._write_rrdp_file(self.snapshot_fn, self.snapshot, rrdp_publication_base)
+ current_filenames.add(self.snapshot_fn)
+
+ self._write_rrdp_file(self.notification_fn, self._generate_update_xml(rrdp_uri_base),
+ rrdp_publication_base, overwrite = True)
+ current_filenames.add(self.notification_fn)
+
+ for root, dirs, files in os.walk(rrdp_publication_base, topdown = False):
+ for fn in files:
+ fn = os.path.join(root, fn)
+ if fn[len(rrdp_publication_base):].lstrip("/") not in current_filenames:
+ os.remove(fn)
+ for dn in dirs:
+ try:
+ os.rmdir(os.path.join(root, dn))
+ except OSError:
+ pass
+
+
+class Delta(models.Model):
+ serial = models.BigIntegerField()
+ xml = models.TextField()
+ hash = models.CharField(max_length = 64)
+ expires = SundialField()
+ session = models.ForeignKey(Session)
+
+
+ @staticmethod
+ def _uri_to_filename(uri, publication_base):
+ if not uri.startswith("rsync://"):
+ raise rpki.exceptions.BadURISyntax(uri)
+ path = uri.split("/")[4:]
+ path.insert(0, publication_base.rstrip("/"))
+ filename = "/".join(path)
+ if "/../" in filename or filename.endswith("/.."):
+ raise rpki.exceptions.BadURISyntax(filename)
+ return filename
+
+
+ @property
+ def fn(self):
+ return "%s/deltas/%s-%s.xml" % (self.session.uuid, self.serial - 1, self.serial)
+
+
+ def activate(self):
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+ self.xml = ElementToString(self.deltas, pretty_print = True)
+ self.hash = rpki.x509.sha256(self.xml).encode("hex")
+ self.save()
+ self.session.serial += 1
+ self.session.save()
+
+
+ def publish(self, client, der, uri, hash):
+ try:
+ obj = client.publishedobject_set.get(session = self.session, uri = uri)
+ if obj.hash == hash:
+ obj.delete()
+ elif hash is None:
+ raise rpki.exceptions.ExistingObjectAtURI("Object already published at %s" % uri)
+ else:
+ raise rpki.exceptions.DifferentObjectAtURI("Found different object at %s (old %s, new %s)" % (uri, obj.hash, hash))
+ except rpki.pubdb.PublishedObject.DoesNotExist:
+ pass
+ logger.debug("Publishing %s", uri)
+ PublishedObject.objects.create(session = self.session, client = client, der = der, uri = uri,
+ hash = rpki.x509.sha256(der).encode("hex"))
+ se = DERSubElement(self.deltas[0], rrdp_tag_publish, der = der, uri = uri)
+ if hash is not None:
+ se.set("hash", hash)
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+
+
+ def withdraw(self, client, uri, hash):
+ obj = client.publishedobject_set.get(session = self.session, uri = uri)
+ if obj.hash != hash:
+ raise rpki.exceptions.DifferentObjectAtURI("Found different object at %s (old %s, new %s)" % (uri, obj.hash, hash))
+ logger.debug("Withdrawing %s", uri)
+ obj.delete()
+ SubElement(self.deltas[0], rrdp_tag_withdraw, uri = uri, hash = hash).tail = "\n"
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+
+
+ def update_rsync_files(self, publication_base):
+ min_path_len = len(publication_base.rstrip("/"))
+ for pdu in self.deltas[0]:
+ assert pdu.tag in (rrdp_tag_publish, rrdp_tag_withdraw)
+ fn = self._uri_to_filename(pdu.get("uri"), publication_base)
+ if pdu.tag == rrdp_tag_publish:
+ tn = fn + ".tmp"
+ dn = os.path.dirname(fn)
+ if not os.path.isdir(dn):
+ os.makedirs(dn)
+ with open(tn, "wb") as f:
+ f.write(pdu.text.decode("base64"))
+ os.rename(tn, fn)
+ else:
+ try:
+ os.remove(fn)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ dn = os.path.dirname(fn)
+ while len(dn) > min_path_len:
+ try:
+ os.rmdir(dn)
+ except OSError:
+ break
+ else:
+ dn = os.path.dirname(dn)
+ del self.deltas
+
+
+class PublishedObject(models.Model):
+ uri = models.CharField(max_length = 255)
+ der = BlobField()
+ hash = models.CharField(max_length = 64)
+ client = models.ForeignKey(Client)
+ session = models.ForeignKey(Session)
+
+ class Meta:
+ unique_together = (("session", "hash"),
+ ("session", "uri"))
diff --git a/rpki/publication.py b/rpki/publication.py
index 5fc7f3dd..ec2dabd4 100644
--- a/rpki/publication.py
+++ b/rpki/publication.py
@@ -1,35 +1,24 @@
# $Id$
#
-# Copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
+# copyright notices and this permission notice appear in all copies.
#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
-RPKI "publication" protocol.
+RPKI publication protocol.
"""
import os
@@ -46,425 +35,52 @@ import rpki.relaxng
import rpki.sundial
import rpki.log
-logger = logging.getLogger(__name__)
-
-class publication_namespace(object):
- """
- XML namespace parameters for publication protocol.
- """
-
- xmlns = rpki.relaxng.publication.xmlns
- nsmap = rpki.relaxng.publication.nsmap
-
-class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_namespace):
- """
- Virtual class for control channel objects.
- """
-
- def serve_dispatch(self, r_msg, cb, eb):
- """
- Action dispatch handler. This needs special handling because we
- need to make sure that this PDU arrived via the control channel.
- """
- if self.client is not None:
- raise rpki.exceptions.BadQuery("Control query received on client channel")
- rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb)
-
-class config_elt(control_elt):
- """
- <config/> element. This is a little weird because there should
- never be more than one row in the SQL config table, but we have to
- put the BPKI CRL somewhere and SQL is the least bad place available.
-
- So we reuse a lot of the SQL machinery, but we nail config_id at 1,
- we don't expose it in the XML protocol, and we only support the get
- and set actions.
- """
-
- attributes = ("action", "tag")
- element_name = "config"
- elements = ("bpki_crl",)
-
- sql_template = rpki.sql.template(
- "config",
- "config_id",
- ("bpki_crl", rpki.x509.CRL))
-
- wired_in_config_id = 1
-
- def startElement(self, stack, name, attrs):
- """
- StartElement() handler for config object. This requires special
- handling because of the weird way we treat config_id.
- """
- control_elt.startElement(self, stack, name, attrs)
- self.config_id = self.wired_in_config_id
-
- @classmethod
- def fetch(cls, gctx):
- """
- Fetch the config object from SQL. This requires special handling
- because of the weird way we treat config_id.
- """
- return cls.sql_fetch(gctx, cls.wired_in_config_id)
-
- def serve_set(self, r_msg, cb, eb):
- """
- Handle a set action. This requires special handling because
- config doesn't support the create method.
- """
- if self.sql_fetch(self.gctx, self.config_id) is None:
- control_elt.serve_create(self, r_msg, cb, eb)
- else:
- control_elt.serve_set(self, r_msg, cb, eb)
-
- def serve_fetch_one_maybe(self):
- """
- Find the config object on which a get or set method should
- operate.
- """
- return self.sql_fetch(self.gctx, self.config_id)
-
-class client_elt(control_elt):
- """
- <client/> element.
- """
-
- element_name = "client"
- attributes = ("action", "tag", "client_handle", "base_uri")
- elements = ("bpki_cert", "bpki_glue")
- booleans = ("clear_replay_protection",)
-
- sql_template = rpki.sql.template(
- "client",
- "client_id",
- "client_handle",
- "base_uri",
- ("bpki_cert", rpki.x509.X509),
- ("bpki_glue", rpki.x509.X509),
- ("last_cms_timestamp", rpki.sundial.datetime))
-
- base_uri = None
- bpki_cert = None
- bpki_glue = None
- last_cms_timestamp = None
-
- def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
- """
- Extra server actions for client_elt.
- """
- actions = []
- if q_pdu.clear_replay_protection:
- actions.append(self.serve_clear_replay_protection)
- def loop(iterator, action):
- action(iterator, eb)
- rpki.async.iterator(actions, loop, cb)
-
- def serve_clear_replay_protection(self, cb, eb):
- """
- Handle a clear_replay_protection action for this client.
- """
- self.last_cms_timestamp = None
- self.sql_mark_dirty()
- cb()
-
- def serve_fetch_one_maybe(self):
- """
- Find the client object on which a get, set, or destroy method
- should operate, or which would conflict with a create method.
- """
- return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,))
-
- def serve_fetch_all(self):
- """
- Find client objects on which a list method should operate.
- """
- return self.sql_fetch_all(self.gctx)
-
- def check_allowed_uri(self, uri):
- """
- Make sure that a target URI is within this client's allowed URI space.
- """
- if not uri.startswith(self.base_uri):
- raise rpki.exceptions.ForbiddenURI
-
-class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace):
- """
- Virtual class for publishable objects. These have very similar
- syntax, differences lie in underlying datatype and methods. XML
- methods are a little different from the pattern used for objects
- that support the create/set/get/list/destroy actions, but
- publishable objects don't go in SQL either so these classes would be
- different in any case.
- """
-
- attributes = ("action", "tag", "client_handle", "uri")
- payload_type = None
- payload = None
-
- def endElement(self, stack, name, text):
- """
- Handle a publishable element element.
- """
- assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
- if text:
- self.payload = self.payload_type(Base64 = text) # pylint: disable=E1102
- stack.pop()
-
- def toXML(self):
- """
- Generate XML element for publishable object.
- """
- elt = self.make_elt()
- if self.payload:
- elt.text = self.payload.get_Base64()
- return elt
+from lxml.etree import Element, SubElement
- def serve_dispatch(self, r_msg, cb, eb):
- """
- Action dispatch handler.
- """
- # pylint: disable=E0203
- try:
- if self.client is None:
- raise rpki.exceptions.BadQuery("Client query received on control channel")
- dispatch = { "publish" : self.serve_publish,
- "withdraw" : self.serve_withdraw }
- if self.action not in dispatch:
- raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action)
- self.client.check_allowed_uri(self.uri)
- dispatch[self.action]()
- r_pdu = self.__class__()
- r_pdu.action = self.action
- r_pdu.tag = self.tag
- r_pdu.uri = self.uri
- r_msg.append(r_pdu)
- cb()
- except rpki.exceptions.NoObjectAtURI, e:
- # This can happen when we're cleaning up from a prior mess, so
- # we generate a <report_error/> PDU then carry on.
- r_msg.append(report_error_elt.from_exception(e, self.tag))
- cb()
-
- def serve_publish(self):
- """
- Publish an object.
- """
- logger.info("Publishing %s", self.payload.tracking_data(self.uri))
- filename = self.uri_to_filename()
- filename_tmp = filename + ".tmp"
- dirname = os.path.dirname(filename)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
- f = open(filename_tmp, "wb")
- f.write(self.payload.get_DER())
- f.close()
- os.rename(filename_tmp, filename)
-
- def serve_withdraw(self):
- """
- Withdraw an object, then recursively delete empty directories.
- """
- logger.info("Withdrawing %s", self.uri)
- filename = self.uri_to_filename()
- try:
- os.remove(filename)
- except OSError, e:
- if e.errno == errno.ENOENT:
- raise rpki.exceptions.NoObjectAtURI("No object published at %s" % self.uri)
- else:
- raise
- min_path_len = len(self.gctx.publication_base.rstrip("/"))
- dirname = os.path.dirname(filename)
- while len(dirname) > min_path_len:
- try:
- os.rmdir(dirname)
- except OSError:
- break
- else:
- dirname = os.path.dirname(dirname)
-
- def uri_to_filename(self):
- """
- Convert a URI to a local filename.
- """
- if not self.uri.startswith("rsync://"):
- raise rpki.exceptions.BadURISyntax(self.uri)
- path = self.uri.split("/")[3:]
- if not self.gctx.publication_multimodule:
- del path[0]
- path.insert(0, self.gctx.publication_base.rstrip("/"))
- filename = "/".join(path)
- if "/../" in filename or filename.endswith("/.."):
- raise rpki.exceptions.BadURISyntax(filename)
- return filename
-
- @classmethod
- def make_publish(cls, uri, obj, tag = None):
- """
- Construct a publication PDU.
- """
- assert cls.payload_type is not None and type(obj) is cls.payload_type
- return cls.make_pdu(action = "publish", uri = uri, payload = obj, tag = tag)
-
- @classmethod
- def make_withdraw(cls, uri, obj, tag = None):
- """
- Construct a withdrawal PDU.
- """
- assert cls.payload_type is not None and type(obj) is cls.payload_type
- return cls.make_pdu(action = "withdraw", uri = uri, tag = tag)
-
- def raise_if_error(self):
- """
- No-op, since this is not a <report_error/> PDU.
- """
- pass
-
-class certificate_elt(publication_object_elt):
- """
- <certificate/> element.
- """
-
- element_name = "certificate"
- payload_type = rpki.x509.X509
-
-class crl_elt(publication_object_elt):
- """
- <crl/> element.
- """
-
- element_name = "crl"
- payload_type = rpki.x509.CRL
+logger = logging.getLogger(__name__)
-class manifest_elt(publication_object_elt):
- """
- <manifest/> element.
- """
- element_name = "manifest"
- payload_type = rpki.x509.SignedManifest
+nsmap = rpki.relaxng.publication.nsmap
+version = rpki.relaxng.publication.version
-class roa_elt(publication_object_elt):
- """
- <roa/> element.
- """
+tag_msg = rpki.relaxng.publication.xmlns + "msg"
+tag_list = rpki.relaxng.publication.xmlns + "list"
+tag_publish = rpki.relaxng.publication.xmlns + "publish"
+tag_withdraw = rpki.relaxng.publication.xmlns + "withdraw"
+tag_report_error = rpki.relaxng.publication.xmlns + "report_error"
- element_name = "roa"
- payload_type = rpki.x509.ROA
-class ghostbuster_elt(publication_object_elt):
+def raise_if_error(pdu):
"""
- <ghostbuster/> element.
- """
-
- element_name = "ghostbuster"
- payload_type = rpki.x509.Ghostbuster
+ Raise an appropriate error if this is a <report_error/> PDU.
-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):
- """
- <report_error/> element.
+ As a convenience, this will also accept a <msg/> PDU and raise an
+ appropriate error if it contains any <report_error/> PDUs or if
+ the <msg/> is not a reply.
"""
- element_name = "report_error"
- attributes = ("tag", "error_code")
- text_attribute = "error_text"
-
- error_text = None
-
- @classmethod
- def from_exception(cls, e, tag = None):
- """
- Generate a <report_error/> element from an exception.
- """
- self = cls()
- self.tag = tag
- self.error_code = e.__class__.__name__
- self.error_text = str(e)
- return self
-
- def __str__(self):
- s = ""
- if getattr(self, "tag", None) is not None:
- s += "[%s] " % self.tag
- s += self.error_code
- if getattr(self, "error_text", None) is not None:
- s += ": " + self.error_text
- return s
-
- def raise_if_error(self):
- """
- Raise exception associated with this <report_error/> PDU.
- """
- t = rpki.exceptions.__dict__.get(self.error_code)
- if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception):
- raise t(getattr(self, "text", None))
+ if pdu.tag == tag_report_error:
+ code = pdu.get("error_code")
+ logger.debug("<report_error/> code %r", code)
+ e = getattr(rpki.exceptions, code, None)
+ if e is not None and issubclass(e, rpki.exceptions.RPKI_Exception):
+ raise e(pdu.text)
else:
- raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self)
-
-class msg(rpki.xml_utils.msg, publication_namespace):
- """
- Publication PDU.
- """
-
- ## @var version
- # Protocol version
- version = int(rpki.relaxng.publication.version)
-
- ## @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, ghostbuster_elt, report_error_elt))
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %r, %r" % (code, pdu))
- def serve_top_level(self, gctx, client, cb):
- """
- Serve one msg PDU.
- """
- if not self.is_query():
- raise rpki.exceptions.BadQuery("Message type is not query")
- r_msg = self.__class__.reply()
+ if pdu.tag == tag_msg:
+ if pdu.get("type") != "reply":
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: expected reply, got %r" % pdu.get("type"))
+ for p in pdu:
+ raise_if_error(p)
- def loop(iterator, q_pdu):
- def fail(e):
- if not isinstance(e, rpki.exceptions.NotFound):
- logger.exception("Exception processing PDU %r", q_pdu)
- r_msg.append(report_error_elt.from_exception(e, q_pdu.tag))
- cb(r_msg)
-
- try:
- q_pdu.gctx = gctx
- q_pdu.client = client
- q_pdu.serve_dispatch(r_msg, iterator, fail)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
- fail(e)
-
- def done():
- cb(r_msg)
-
- rpki.async.iterator(self, loop, done)
-
-class sax_handler(rpki.xml_utils.sax_handler):
- """
- SAX handler for publication protocol.
- """
-
- pdu = msg
- name = "msg"
- version = rpki.relaxng.publication.version
-
-
-class cms_msg(rpki.x509.XML_CMS_object):
+class cms_msg_no_sax(rpki.x509.XML_CMS_object):
"""
Class to hold a CMS-signed publication PDU.
+
+ Name is a transition kludge: once we ditch SAX, this will become cms_msg.
"""
encoding = "us-ascii"
schema = rpki.relaxng.publication
- saxify = sax_handler.saxify
diff --git a/rpki/publication_control.py b/rpki/publication_control.py
new file mode 100644
index 00000000..478f183b
--- /dev/null
+++ b/rpki/publication_control.py
@@ -0,0 +1,269 @@
+# $Id$
+#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
+# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notices and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+RPKI publication control protocol.
+
+Per IETF SIDR WG discussion, this is now separate from the publication
+protocol itself.
+"""
+
+import logging
+import rpki.resource_set
+import rpki.x509
+import rpki.sql
+import rpki.exceptions
+import rpki.xml_utils
+import rpki.http
+import rpki.up_down
+import rpki.relaxng
+import rpki.sundial
+import rpki.log
+
+logger = logging.getLogger(__name__)
+
+
+nsmap = rpki.relaxng.publication_control.nsmap
+version = rpki.relaxng.publication_control.version
+
+tag_msg = rpki.relaxng.publication_control.xmlns + "msg"
+tag_client = rpki.relaxng.publication_control.xmlns + "client"
+tag_bpki_cert = rpki.relaxng.publication_control.xmlns + "bpki_cert"
+tag_bpki_glue = rpki.relaxng.publication_control.xmlns + "bpki_glue"
+tag_report_error = rpki.relaxng.publication_control.xmlns + "report_error"
+
+
+def raise_if_error(pdu):
+ """
+ Raise an appropriate error if this is a <report_error/> PDU.
+
+ As a convience, this will also accept a <msg/> PDU and raise an
+ appropriate error if it contains any <report_error/> PDUs.
+ """
+
+ if pdu.tag == tag_report_error:
+ code = pdu.get("error_code")
+ logger.debug("<report_error/> code %r", code)
+ e = getattr(rpki.exceptions, code, None)
+ if e is not None and issubclass(e, rpki.exceptions.RPKI_Exception):
+ raise e(pdu.text)
+ else:
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %r, %r" % (code, pdu))
+
+ if pdu.tag == tag_msg:
+ for p in pdu:
+ raise_if_error(p)
+
+
+class publication_control_namespace(object):
+ """
+ XML namespace parameters for publication control protocol.
+ """
+
+ xmlns = rpki.relaxng.publication_control.xmlns
+ nsmap = rpki.relaxng.publication_control.nsmap
+
+
+class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_control_namespace):
+ """
+ <client/> element.
+ """
+
+ element_name = "client"
+ attributes = ("action", "tag", "client_handle", "base_uri")
+ elements = ("bpki_cert", "bpki_glue")
+ booleans = ("clear_replay_protection",)
+
+ sql_template = rpki.sql.template(
+ "client",
+ "client_id",
+ "client_handle",
+ "base_uri",
+ ("bpki_cert", rpki.x509.X509),
+ ("bpki_glue", rpki.x509.X509),
+ ("last_cms_timestamp", rpki.sundial.datetime))
+
+ base_uri = None
+ bpki_cert = None
+ bpki_glue = None
+ last_cms_timestamp = None
+
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.client_handle, self.base_uri)
+
+ @property
+ def objects(self):
+ return rpki.pubd.object_obj.sql_fetch_where(self.gctx, "client_id = %s", (self.client_id,))
+
+ def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
+ """
+ Extra server actions for client_elt.
+ """
+
+ if q_pdu.clear_replay_protection:
+ self.last_cms_timestamp = None
+ self.sql_mark_dirty()
+ cb()
+
+ def serve_fetch_one_maybe(self):
+ """
+ Find the client object on which a get, set, or destroy method
+ should operate, or which would conflict with a create method.
+ """
+
+ return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,))
+
+ def serve_fetch_all(self):
+ """
+ Find client objects on which a list method should operate.
+ """
+
+ return self.sql_fetch_all(self.gctx)
+
+ def check_allowed_uri(self, uri):
+ """
+ Make sure that a target URI is within this client's allowed URI space.
+ """
+
+ if not uri.startswith(self.base_uri):
+ raise rpki.exceptions.ForbiddenURI
+
+ def raise_if_error(self):
+ """
+ No-op, because this isn't a <report_error/> PDU.
+ """
+
+ pass
+
+
+class report_error_elt(rpki.xml_utils.text_elt, publication_control_namespace):
+ """
+ <report_error/> element.
+ """
+
+ element_name = "report_error"
+ attributes = ("tag", "error_code")
+ text_attribute = "error_text"
+
+ error_text = None
+
+ @classmethod
+ def from_exception(cls, e, tag = None):
+ """
+ Generate a <report_error/> element from an exception.
+ """
+
+ self = cls()
+ self.tag = tag
+ self.error_code = e.__class__.__name__
+ self.error_text = str(e)
+ return self
+
+ def __str__(self):
+ s = ""
+ if getattr(self, "tag", None) is not None:
+ s += "[%s] " % self.tag
+ s += self.error_code
+ if getattr(self, "error_text", None) is not None:
+ s += ": " + self.error_text
+ return s
+
+ def raise_if_error(self):
+ """
+ Raise exception associated with this <report_error/> PDU.
+ """
+
+ t = getattr(rpki.exceptions, self.error_code, None)
+ if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception):
+ raise t(getattr(self, "text", None))
+ else:
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self)
+
+
+class msg(rpki.xml_utils.msg, publication_control_namespace):
+ """
+ Publication control PDU.
+ """
+
+ ## @var version
+ # Protocol version
+ version = int(rpki.relaxng.publication_control.version)
+
+ ## @var pdus
+ # Dispatch table of PDUs for this protocol.
+ pdus = dict((x.element_name, x) for x in (client_elt, report_error_elt))
+
+ def serve_top_level(self, gctx, cb):
+ """
+ Serve one msg PDU.
+ """
+
+ if not self.is_query():
+ raise rpki.exceptions.BadQuery("Message type is not query")
+ r_msg = self.__class__.reply()
+
+ for q_pdu in self:
+
+ def next():
+ # Relic of asynch I/O structure, clean up eventually
+ pass
+
+ def fail(e):
+ if not isinstance(e, rpki.exceptions.NotFound):
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_msg.append(report_error_elt.from_exception(e, q_pdu.tag))
+ return cb(r_msg)
+
+ try:
+ q_pdu.gctx = gctx
+ q_pdu.serve_dispatch(r_msg, next, fail)
+ except Exception, e:
+ return fail(e)
+
+ return cb(r_msg)
+
+
+class sax_handler(rpki.xml_utils.sax_handler):
+ """
+ SAX handler for publication control protocol.
+ """
+
+ pdu = msg
+ name = "msg"
+ version = rpki.relaxng.publication_control.version
+
+
+class cms_msg(rpki.x509.XML_CMS_object):
+ """
+ Class to hold a CMS-signed publication control PDU.
+ """
+
+ encoding = "us-ascii"
+ schema = rpki.relaxng.publication_control
+ saxify = sax_handler.saxify
+
+
+class cms_msg_no_sax(cms_msg):
+ """
+ Class to hold a CMS-signed publication control PDU without legacy
+ SAX transcoding. The name is a transition kludge, this class will
+ be renamed cms_msg once the SAX code goes away.
+ """
+
+ saxify = None
diff --git a/rpki/rcynic.py b/rpki/rcynic.py
index 10ad7516..a36e4a4e 100644
--- a/rpki/rcynic.py
+++ b/rpki/rcynic.py
@@ -53,6 +53,7 @@ class rcynic_object(object):
Print a bunch of object attributes, quietly ignoring any that
might be missing.
"""
+
for a in attrs:
try:
print "%s: %s" % (a.capitalize(), getattr(self, a))
@@ -63,6 +64,7 @@ class rcynic_object(object):
"""
Print common object attributes.
"""
+
self.show_attrs("filename", "uri", "status", "timestamp")
class rcynic_certificate(rcynic_object):
@@ -91,6 +93,7 @@ class rcynic_certificate(rcynic_object):
"""
Print certificate attributes.
"""
+
rcynic_object.show(self)
self.show_attrs("notBefore", "notAfter", "aia_uri", "sia_directory_uri", "resources")
@@ -128,6 +131,7 @@ class rcynic_roa(rcynic_object):
"""
Print ROA attributes.
"""
+
rcynic_object.show(self)
self.show_attrs("notBefore", "notAfter", "aia_uri", "resources", "asID")
if self.prefix_sets:
diff --git a/rpki/relaxng.py b/rpki/relaxng.py
index e43384e7..71e8ade4 100644
--- a/rpki/relaxng.py
+++ b/rpki/relaxng.py
@@ -6,7 +6,7 @@ from rpki.relaxng_parser import RelaxNGParser
## Parsed RelaxNG left_right schema
left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: left-right-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: left-right.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI left-right protocol.
@@ -1102,7 +1102,7 @@ left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
## Parsed RelaxNG myrpki schema
myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $
RelaxNG schema for MyRPKI XML messages.
@@ -1481,11 +1481,11 @@ myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
-->
''')
-## @var publication
-## Parsed RelaxNG publication schema
-publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+## @var publication_control
+## Parsed RelaxNG publication_control schema
+publication_control = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: publication-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: publication-control.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI publication protocol.
@@ -1506,7 +1506,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->
-<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<define name="version">
<value>1</value>
</define>
@@ -1540,26 +1540,12 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
</start>
<!-- PDUs allowed in a query -->
<define name="query_elt">
- <choice>
- <ref name="config_query"/>
- <ref name="client_query"/>
- <ref name="certificate_query"/>
- <ref name="crl_query"/>
- <ref name="manifest_query"/>
- <ref name="roa_query"/>
- <ref name="ghostbuster_query"/>
- </choice>
+ <ref name="client_query"/>
</define>
<!-- PDUs allowed in a reply -->
<define name="reply_elt">
<choice>
- <ref name="config_reply"/>
<ref name="client_reply"/>
- <ref name="certificate_reply"/>
- <ref name="crl_reply"/>
- <ref name="manifest_reply"/>
- <ref name="roa_reply"/>
- <ref name="ghostbuster_reply"/>
<ref name="report_error_reply"/>
</choice>
</define>
@@ -1603,60 +1589,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<param name="pattern">[\-_A-Za-z0-9/]+</param>
</data>
</define>
- <!--
- <config/> element (use restricted to repository operator)
- config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
- -->
- <define name="config_payload">
- <optional>
- <element name="bpki_crl">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <!-- <client/> element (use restricted to repository operator) -->
+ <!-- <client/> element -->
<define name="client_handle">
<attribute name="client_handle">
<ref name="object_handle"/>
@@ -1801,242 +1734,217 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<ref name="client_handle"/>
</element>
</define>
- <!-- <certificate/> element -->
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <!-- <report_error/> element -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
</define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <define name="report_error_reply">
+ <element name="report_error">
<optional>
<ref name="tag"/>
</optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
+ <attribute name="error_code">
+ <ref name="error"/>
</attribute>
<optional>
- <ref name="tag"/>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
</optional>
- <ref name="uri"/>
</element>
</define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
+''')
+
+## @var publication
+## Parsed RelaxNG publication schema
+publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $
+
+ RelaxNG schema for RPKI publication protocol, from current I-D.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as authors
+ of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ names of specific contributors, may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <!-- This is version 3 of the protocol. -->
+ <define name="version">
+ <value>3</value>
</define>
- <!-- <crl/> element -->
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
+ <!-- Top level PDU is either a query or a reply. -->
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
+ <attribute name="type">
+ <value>query</value>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
</element>
- </define>
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
+ </start>
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
+ <attribute name="type">
+ <value>reply</value>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
</element>
+ </start>
+ <!-- PDUs allowed in queries and replies. -->
+ <define name="query_elt">
+ <choice>
+ <ref name="publish_query"/>
+ <ref name="withdraw_query"/>
+ <ref name="list_query"/>
+ </choice>
</define>
- <!-- <manifest/> element -->
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <define name="reply_elt">
+ <choice>
+ <ref name="publish_reply"/>
+ <ref name="withdraw_reply"/>
+ <ref name="list_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
</define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Tag attributes for bulk operations. -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
</define>
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Base64 encoded DER stuff. -->
+ <define name="base64">
+ <data type="base64Binary"/>
</define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Publication URIs. -->
+ <define name="uri">
+ <attribute name="uri">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </attribute>
</define>
- <!-- <roa/> element -->
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <!-- Digest of objects being withdrawn -->
+ <define name="hash">
+ <attribute name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </attribute>
</define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <!-- Error codes. -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <!-- <publish/> element -->
+ <define name="publish_query">
+ <element name="publish">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
- </element>
- </define>
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
<optional>
- <ref name="tag"/>
+ <ref name="hash"/>
</optional>
- <ref name="uri"/>
+ <ref name="base64"/>
</element>
</define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
+ <define name="publish_reply">
+ <element name="publish">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
</element>
</define>
- <!-- <ghostbuster/> element -->
- <define name="ghostbuster_query" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <!-- <withdraw/> element -->
+ <define name="withdraw_query">
+ <element name="withdraw">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
- <ref name="base64"/>
+ <ref name="hash"/>
</element>
</define>
- <define name="ghostbuster_reply" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <define name="withdraw_reply">
+ <element name="withdraw">
<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>
+ <!-- <list/> element -->
+ <define name="list_query">
+ <element name="list">
<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>
+ <define name="list_reply">
+ <element name="list">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
+ <ref name="hash"/>
</element>
</define>
<!-- <report_error/> element -->
- <define name="error">
- <data type="token">
- <param name="maxLength">1024</param>
- </data>
- </define>
<define name="report_error_reply">
<element name="report_error">
<optional>
@@ -2066,7 +1974,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
## Parsed RelaxNG router_certificate schema
router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for BGPSEC router certificate interchange format.
@@ -2164,11 +2072,178 @@ router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
-->
''')
+## @var rrdp
+## Parsed RelaxNG rrdp schema
+rrdp = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $
+
+ RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+
+ Copyright (C) 2014 Dragon Research Labs ("DRL")
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ PERFORMANCE OF THIS SOFTWARE.
+-->
+<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </define>
+ <define name="serial">
+ <data type="nonNegativeInteger"/>
+ </define>
+ <define name="uri">
+ <data type="anyURI"/>
+ </define>
+ <define name="uuid">
+ <data type="string">
+ <param name="pattern">[\-0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Notification file: lists current snapshots and deltas -->
+ <start combine="choice">
+ <element name="notification">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <element name="snapshot">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ <zeroOrMore>
+ <element name="delta">
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Snapshot segment: think DNS AXFR. -->
+ <start combine="choice">
+ <element name="snapshot">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <zeroOrMore>
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <ref name="base64"/>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Delta segment: think DNS IXFR. -->
+ <start combine="choice">
+ <element name="deltas">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <element name="delta">
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <ref name="delta_element"/>
+ </oneOrMore>
+ </element>
+ </oneOrMore>
+ </element>
+ </start>
+ <define name="delta_element" combine="choice">
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <optional>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="delta_element" combine="choice">
+ <element name="withdraw">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
+''')
+
## @var up_down
## Parsed RelaxNG up_down schema
up_down = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for the up-down protocol, extracted from RFC 6492.
diff --git a/rpki/resource_set.py b/rpki/resource_set.py
index fea6ad2d..130bf4e7 100644
--- a/rpki/resource_set.py
+++ b/rpki/resource_set.py
@@ -86,6 +86,7 @@ class resource_range_as(resource_range):
"""
Convert a resource_range_as to string format.
"""
+
if self.min == self.max:
return str(self.min)
else:
@@ -96,6 +97,7 @@ class resource_range_as(resource_range):
"""
Parse ASN resource range from text (eg, XML attributes).
"""
+
r = re_asn_range.match(x)
if r:
return cls(long(r.group(1)), long(r.group(2)))
@@ -107,6 +109,7 @@ class resource_range_as(resource_range):
"""
Construct ASN range from strings.
"""
+
if b is None:
b = a
return cls(long(a), long(b))
@@ -133,6 +136,7 @@ class resource_range_ip(resource_range):
prefix. Returns prefix length if it can, otherwise raises
MustBePrefix exception.
"""
+
mask = self.min ^ self.max
if self.min & mask != 0:
raise rpki.exceptions.MustBePrefix
@@ -154,6 +158,7 @@ class resource_range_ip(resource_range):
the logic in one place. This property is useful primarily in
context where catching an exception isn't practical.
"""
+
try:
self.prefixlen()
return True
@@ -164,6 +169,7 @@ class resource_range_ip(resource_range):
"""
Convert a resource_range_ip to string format.
"""
+
try:
return str(self.min) + "/" + str(self.prefixlen())
except rpki.exceptions.MustBePrefix:
@@ -174,6 +180,7 @@ class resource_range_ip(resource_range):
"""
Parse IP address range or prefix from text (eg, XML attributes).
"""
+
r = re_address_range.match(x)
if r:
return cls.from_strings(r.group(1), r.group(2))
@@ -192,6 +199,7 @@ class resource_range_ip(resource_range):
"""
Construct a resource range corresponding to a prefix.
"""
+
assert isinstance(prefix, rpki.POW.IPAddress) and isinstance(prefixlen, (int, long))
assert prefixlen >= 0 and prefixlen <= prefix.bits, "Nonsensical prefix length: %s" % prefixlen
mask = (1 << (prefix.bits - prefixlen)) - 1
@@ -203,6 +211,7 @@ class resource_range_ip(resource_range):
Chop up a resource_range_ip into ranges that can be represented as
prefixes.
"""
+
try:
self.prefixlen()
result.append(self)
@@ -226,6 +235,7 @@ class resource_range_ip(resource_range):
"""
Construct IP address range from strings.
"""
+
if b is None:
b = a
a = rpki.POW.IPAddress(a)
@@ -300,6 +310,7 @@ class resource_set(list):
"""
Initialize a resource_set.
"""
+
list.__init__(self)
if isinstance(ini, (int, long)):
ini = str(ini)
@@ -317,6 +328,7 @@ class resource_set(list):
"""
Whack this resource_set into canonical form.
"""
+
assert not self.inherit or len(self) == 0
if not self.canonical:
self.sort()
@@ -339,6 +351,7 @@ class resource_set(list):
"""
Wrapper around list.append() (q.v.) to reset canonical flag.
"""
+
list.append(self, item)
self.canonical = False
@@ -346,6 +359,7 @@ class resource_set(list):
"""
Wrapper around list.extend() (q.v.) to reset canonical flag.
"""
+
list.extend(self, item)
self.canonical = False
@@ -353,6 +367,7 @@ class resource_set(list):
"""
Convert a resource_set to string format.
"""
+
if self.inherit:
return inherit_token
else:
@@ -428,6 +443,7 @@ class resource_set(list):
"""
Set intersection for resource sets.
"""
+
return self._comm(other)[2]
__and__ = intersection
@@ -436,6 +452,7 @@ class resource_set(list):
"""
Set difference for resource sets.
"""
+
return self._comm(other)[0]
__sub__ = difference
@@ -444,6 +461,7 @@ class resource_set(list):
"""
Set symmetric difference (XOR) for resource sets.
"""
+
com = self._comm(other)
return com[0] | com[1]
@@ -453,6 +471,7 @@ class resource_set(list):
"""
Set membership test for resource sets.
"""
+
assert not self.inherit
self.canonize()
if not self:
@@ -479,6 +498,7 @@ class resource_set(list):
"""
Test whether self is a subset (possibly improper) of other.
"""
+
for i in self:
if not other.contains(i):
return False
@@ -490,6 +510,7 @@ class resource_set(list):
"""
Test whether self is a superset (possibly improper) of other.
"""
+
return other.issubset(self)
__ge__ = issuperset
@@ -506,6 +527,7 @@ class resource_set(list):
we can't know the answer here. This is also consistent with __nonzero__
which returns True for inherit sets, and False for empty sets.
"""
+
return self.inherit or other.inherit or list.__ne__(self, other)
def __eq__(self, other):
@@ -516,6 +538,7 @@ class resource_set(list):
Tests whether or not this set is empty. Note that sets with the inherit
bit set are considered non-empty, despite having zero length.
"""
+
return self.inherit or len(self)
@classmethod
@@ -553,6 +576,7 @@ class resource_set(list):
a backwards compatability wrapper, real functionality is now part
of the range classes.
"""
+
return cls.range_type.parse_str(s)
class resource_set_as(resource_set):
@@ -577,6 +601,7 @@ class resource_set_ip(resource_set):
"""
Convert from a resource set to a ROA prefix set.
"""
+
prefix_ranges = []
for r in self:
r.chop_into_prefixes(prefix_ranges)
@@ -632,6 +657,7 @@ class resource_bag(object):
"""
True iff self is oversized with respect to other.
"""
+
return not self.asn.issubset(other.asn) or \
not self.v4.issubset(other.v4) or \
not self.v6.issubset(other.v6)
@@ -640,6 +666,7 @@ class resource_bag(object):
"""
True iff self is undersized with respect to other.
"""
+
return not other.asn.issubset(self.asn) or \
not other.v4.issubset(self.v4) or \
not other.v6.issubset(self.v6)
@@ -650,6 +677,7 @@ class resource_bag(object):
Build a resource bag that just inherits everything from its
parent.
"""
+
self = cls()
self.asn = resource_set_as()
self.v4 = resource_set_ipv4()
@@ -665,6 +693,7 @@ class resource_bag(object):
Parse a comma-separated text string into a resource_bag. Not
particularly efficient, fix that if and when it becomes an issue.
"""
+
asns = []
v4s = []
v6s = []
@@ -689,6 +718,7 @@ class resource_bag(object):
temporary: in the long run, we should be using rpki.POW.IPAddress
rather than long here.
"""
+
asn = inherit_token if resources[0] == "inherit" else [resource_range_as( r[0], r[1]) for r in resources[0] or ()]
v4 = inherit_token if resources[1] == "inherit" else [resource_range_ipv4(r[0], r[1]) for r in resources[1] or ()]
v6 = inherit_token if resources[2] == "inherit" else [resource_range_ipv6(r[0], r[1]) for r in resources[2] or ()]
@@ -700,6 +730,7 @@ class resource_bag(object):
"""
True iff all resource sets in this bag are empty.
"""
+
return not self.asn and not self.v4 and not self.v6
def __nonzero__(self):
@@ -719,6 +750,7 @@ class resource_bag(object):
Compute intersection with another resource_bag. valid_until
attribute (if any) inherits from self.
"""
+
return self.__class__(self.asn & other.asn,
self.v4 & other.v4,
self.v6 & other.v6,
@@ -731,6 +763,7 @@ class resource_bag(object):
Compute union with another resource_bag. valid_until attribute
(if any) inherits from self.
"""
+
return self.__class__(self.asn | other.asn,
self.v4 | other.v4,
self.v6 | other.v6,
@@ -743,6 +776,7 @@ class resource_bag(object):
Compute difference against another resource_bag. valid_until
attribute (if any) inherits from self
"""
+
return self.__class__(self.asn - other.asn,
self.v4 - other.v4,
self.v6 - other.v6,
@@ -755,6 +789,7 @@ class resource_bag(object):
Compute symmetric difference against another resource_bag.
valid_until attribute (if any) inherits from self
"""
+
return self.__class__(self.asn ^ other.asn,
self.v4 ^ other.v4,
self.v6 ^ other.v6,
@@ -816,6 +851,7 @@ class roa_prefix(object):
Initialize a ROA prefix. max_prefixlen is optional and defaults
to prefixlen. max_prefixlen must not be smaller than prefixlen.
"""
+
if max_prefixlen is None:
max_prefixlen = prefixlen
assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen)
@@ -828,6 +864,7 @@ class roa_prefix(object):
Compare two ROA prefix objects. Comparision is based on prefix,
prefixlen, and max_prefixlen, in that order.
"""
+
assert self.__class__ is other.__class__
return (cmp(self.prefix, other.prefix) or
cmp(self.prefixlen, other.prefixlen) or
@@ -837,6 +874,7 @@ class roa_prefix(object):
"""
Convert a ROA prefix to string format.
"""
+
if self.prefixlen == self.max_prefixlen:
return str(self.prefix) + "/" + str(self.prefixlen)
else:
@@ -848,24 +886,28 @@ class roa_prefix(object):
object. This is an irreversable transformation because it loses
the max_prefixlen attribute, nothing we can do about that.
"""
+
return self.range_type.make_prefix(self.prefix, self.prefixlen)
def min(self):
"""
Return lowest address covered by prefix.
"""
+
return self.prefix
def max(self):
"""
Return highest address covered by prefix.
"""
+
return self.prefix | ((1 << (self.prefix.bits - self.prefixlen)) - 1)
def to_POW_roa_tuple(self):
"""
Convert a resource_range_ip to rpki.POW.ROA.setPrefixes() format.
"""
+
return self.prefix, self.prefixlen, self.max_prefixlen
@classmethod
@@ -873,6 +915,7 @@ class roa_prefix(object):
"""
Parse ROA prefix from text (eg, an XML attribute).
"""
+
r = re_prefix_with_maxlen.match(x)
if r:
return cls(rpki.POW.IPAddress(r.group(1)), int(r.group(2)), int(r.group(3)))
@@ -910,6 +953,7 @@ class roa_prefix_set(list):
"""
Initialize a ROA prefix set.
"""
+
list.__init__(self)
if isinstance(ini, str) and len(ini):
self.extend(self.parse_str(s) for s in ini.split(","))
@@ -923,6 +967,7 @@ class roa_prefix_set(list):
"""
Convert a ROA prefix set to string format.
"""
+
return ",".join(str(x) for x in self)
@classmethod
@@ -931,6 +976,7 @@ class roa_prefix_set(list):
Parse ROA prefix from text (eg, an XML attribute).
This method is a backwards compatability shim.
"""
+
return cls.prefix_type.parse_str(s)
def to_resource_set(self):
@@ -942,6 +988,7 @@ class roa_prefix_set(list):
a more efficient way to do this, but start by getting the output
right before worrying about making it fast or pretty.
"""
+
r = self.resource_set_type()
s = self.resource_set_type()
s.append(None)
@@ -982,6 +1029,7 @@ class roa_prefix_set(list):
"""
Convert ROA prefix set to form used by rpki.POW.ROA.setPrefixes().
"""
+
if self:
return tuple(a.to_POW_roa_tuple() for a in self)
else:
diff --git a/rpki/rootd.py b/rpki/rootd.py
index fb445213..987d8356 100644
--- a/rpki/rootd.py
+++ b/rpki/rootd.py
@@ -18,20 +18,22 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
-Trivial RPKI up-down protocol root server. Not recommended for
-production use. Overrides a bunch of method definitions from the
-rpki.* classes in order to reuse as much code as possible.
+Trivial RPKI up-down protocol root server.
"""
import os
+import sys
import time
import logging
+import httplib
import argparse
+import urlparse
import rpki.resource_set
import rpki.up_down
import rpki.left_right
import rpki.x509
import rpki.http
+import rpki.http_simple
import rpki.config
import rpki.exceptions
import rpki.relaxng
@@ -39,106 +41,47 @@ import rpki.sundial
import rpki.log
import rpki.daemonize
+from lxml.etree import Element, SubElement
+
logger = logging.getLogger(__name__)
-rootd = None
-
-class list_pdu(rpki.up_down.list_pdu):
- def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
- r_msg.payload = rpki.up_down.list_response_pdu()
- rootd.compose_response(r_msg)
- callback()
-
-class issue_pdu(rpki.up_down.issue_pdu):
- def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
- self.pkcs10.check_valid_request_ca()
- r_msg.payload = rpki.up_down.issue_response_pdu()
- rootd.compose_response(r_msg, self.pkcs10)
- callback()
-
-class revoke_pdu(rpki.up_down.revoke_pdu):
- def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
- logger.debug("Revocation requested for SKI %s", self.ski)
- subject_cert = rootd.get_subject_cert()
- if subject_cert is None:
- logger.debug("No subject certificate, nothing to revoke")
- raise rpki.exceptions.NotInDatabase
- if subject_cert.gSKI() != self.ski:
- logger.debug("Subject certificate has different SKI %s, not revoking", subject_cert.gSKI())
- raise rpki.exceptions.NotInDatabase
- logger.debug("Revoking certificate %s", self.ski)
- now = rpki.sundial.now()
- rootd.revoke_subject_cert(now)
- rootd.del_subject_cert()
- rootd.del_subject_pkcs10()
- rootd.generate_crl_and_manifest(now)
- r_msg.payload = rpki.up_down.revoke_response_pdu()
- r_msg.payload.class_name = self.class_name
- r_msg.payload.ski = self.ski
- callback()
-
-class error_response_pdu(rpki.up_down.error_response_pdu):
- exceptions = rpki.up_down.error_response_pdu.exceptions.copy()
- exceptions[rpki.exceptions.ClassNameUnknown, revoke_pdu] = 1301
- exceptions[rpki.exceptions.NotInDatabase, revoke_pdu] = 1302
-
-class message_pdu(rpki.up_down.message_pdu):
-
- name2type = {
- "list" : list_pdu,
- "list_response" : rpki.up_down.list_response_pdu,
- "issue" : issue_pdu,
- "issue_response" : rpki.up_down.issue_response_pdu,
- "revoke" : revoke_pdu,
- "revoke_response" : rpki.up_down.revoke_response_pdu,
- "error_response" : error_response_pdu }
-
- type2name = dict((v, k) for k, v in name2type.items())
-
- error_pdu_type = error_response_pdu
-
- def log_query(self, child):
- """
- Log query we're handling.
- """
- logger.info("Serving %s query", self.type)
-
-class sax_handler(rpki.up_down.sax_handler):
- pdu = message_pdu
-
-class cms_msg(rpki.up_down.cms_msg):
- saxify = sax_handler.saxify
+
+class ReplayTracker(object):
+ """
+ Stash for replay protection timestamps.
+ """
+
+ def __init__(self):
+ self.cms_timestamp = None
+
+
class main(object):
- def get_root_cert(self):
- logger.debug("Read root cert %s", self.rpki_root_cert_file)
- self.rpki_root_cert = rpki.x509.X509(Auto_file = self.rpki_root_cert_file)
def root_newer_than_subject(self):
- return os.stat(self.rpki_root_cert_file).st_mtime > \
- os.stat(os.path.join(self.rpki_root_dir, self.rpki_subject_cert)).st_mtime
+ return self.rpki_root_cert.mtime > os.stat(self.rpki_subject_cert_file).st_mtime
+
def get_subject_cert(self):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
try:
- x = rpki.x509.X509(Auto_file = filename)
- logger.debug("Read subject cert %s", filename)
+ x = rpki.x509.X509(Auto_file = self.rpki_subject_cert_file)
+ logger.debug("Read subject cert %s", self.rpki_subject_cert_file)
return x
except IOError:
return None
+
def set_subject_cert(self, cert):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
- logger.debug("Writing subject cert %s, SKI %s", filename, cert.hSKI())
- f = open(filename, "wb")
- f.write(cert.get_DER())
- f.close()
+ logger.debug("Writing subject cert %s, SKI %s", self.rpki_subject_cert_file, cert.hSKI())
+ with open(self.rpki_subject_cert_file, "wb") as f:
+ f.write(cert.get_DER())
+
def del_subject_cert(self):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
- logger.debug("Deleting subject cert %s", filename)
- os.remove(filename)
+ logger.debug("Deleting subject cert %s", self.rpki_subject_cert_file)
+ os.remove(self.rpki_subject_cert_file)
+
def get_subject_pkcs10(self):
try:
@@ -148,11 +91,12 @@ class main(object):
except IOError:
return None
+
def set_subject_pkcs10(self, pkcs10):
logger.debug("Writing subject PKCS #10 %s", self.rpki_subject_pkcs10)
- f = open(self.rpki_subject_pkcs10, "wb")
- f.write(pkcs10.get_DER())
- f.close()
+ with open(self.rpki_subject_pkcs10, "wb") as f:
+ f.write(pkcs10.get_DER())
+
def del_subject_pkcs10(self):
logger.debug("Deleting subject PKCS #10 %s", self.rpki_subject_pkcs10)
@@ -161,9 +105,11 @@ class main(object):
except OSError:
pass
+
def issue_subject_cert_maybe(self, new_pkcs10):
now = rpki.sundial.now()
subject_cert = self.get_subject_cert()
+ hash = None if subject_cert is None else rpki.x509.sha256(subject_cert.get_DER()).encode("hex")
old_pkcs10 = self.get_subject_pkcs10()
if new_pkcs10 is not None and new_pkcs10 != old_pkcs10:
self.set_subject_pkcs10(new_pkcs10)
@@ -179,17 +125,16 @@ class main(object):
logger.debug("Root certificate has changed, regenerating subject")
self.revoke_subject_cert(now)
subject_cert = None
- self.get_root_cert()
if subject_cert is not None:
- return subject_cert
+ return subject_cert, None
pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10
if pkcs10 is None:
logger.debug("No PKCS #10 request, can't generate subject certificate yet")
- return None
+ return None, None
resources = self.rpki_root_cert.get_3779resources()
notAfter = now + self.rpki_subject_lifetime
logger.info("Generating subject cert %s with resources %s, expires %s",
- self.rpki_base_uri + self.rpki_subject_cert, resources, notAfter)
+ self.rpki_subject_cert_uri, resources, notAfter)
req_key = pkcs10.getPublicKey()
req_sia = pkcs10.get_SIA()
self.next_serial_number()
@@ -199,15 +144,22 @@ class main(object):
serial = self.serial_number,
sia = req_sia,
aia = self.rpki_root_cert_uri,
- crldp = self.rpki_base_uri + self.rpki_root_crl,
+ crldp = self.rpki_root_crl_uri,
resources = resources,
notBefore = now,
notAfter = notAfter)
self.set_subject_cert(subject_cert)
- self.generate_crl_and_manifest(now)
- return subject_cert
+ pubd_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "query", version = rpki.publication.version)
+ pdu = SubElement(pubd_msg, rpki.publication.tag_publish, uri = self.rpki_subject_cert_uri)
+ pdu.text = subject_cert.get_Base64()
+ if hash is not None:
+ pdu.set("hash", hash)
+ self.generate_crl_and_manifest(now, pubd_msg)
+ return subject_cert, pubd_msg
+
- def generate_crl_and_manifest(self, now):
+ def generate_crl_and_manifest(self, now, pubd_msg):
subject_cert = self.get_subject_cert()
self.next_serial_number()
self.next_crl_number()
@@ -220,23 +172,26 @@ class main(object):
thisUpdate = now,
nextUpdate = now + self.rpki_subject_regen,
revokedCertificates = self.revoked)
- fn = os.path.join(self.rpki_root_dir, self.rpki_root_crl)
- logger.debug("Writing CRL %s", fn)
- f = open(fn, "wb")
- f.write(crl.get_DER())
- f.close()
- manifest_content = [(self.rpki_root_crl, crl)]
+ hash = self.read_hash_maybe(self.rpki_root_crl_file)
+ logger.debug("Writing CRL %s", self.rpki_root_crl_file)
+ with open(self.rpki_root_crl_file, "wb") as f:
+ f.write(crl.get_DER())
+ pdu = SubElement(pubd_msg, rpki.publication.tag_publish, uri = self.rpki_root_crl_uri)
+ pdu.text = crl.get_Base64()
+ if hash is not None:
+ pdu.set("hash", hash)
+ manifest_content = [(os.path.basename(self.rpki_root_crl_uri), crl)]
if subject_cert is not None:
- manifest_content.append((self.rpki_subject_cert, subject_cert))
+ manifest_content.append((os.path.basename(self.rpki_subject_cert_uri), subject_cert))
manifest_resources = rpki.resource_set.resource_bag.from_inheritance()
manifest_keypair = rpki.x509.RSA.generate()
manifest_cert = self.rpki_root_cert.issue(
keypair = self.rpki_root_key,
subject_key = manifest_keypair.get_public(),
serial = self.serial_number,
- sia = (None, None, self.rpki_base_uri + self.rpki_root_manifest),
+ sia = (None, None, self.rpki_root_manifest_uri),
aia = self.rpki_root_cert_uri,
- crldp = self.rpki_base_uri + self.rpki_root_crl,
+ crldp = self.rpki_root_crl_uri,
resources = manifest_resources,
notBefore = now,
notAfter = now + self.rpki_subject_lifetime,
@@ -248,63 +203,171 @@ class main(object):
names_and_objs = manifest_content,
keypair = manifest_keypair,
certs = manifest_cert)
- fn = os.path.join(self.rpki_root_dir, self.rpki_root_manifest)
- logger.debug("Writing manifest %s", fn)
- f = open(fn, "wb")
- f.write(manifest.get_DER())
- f.close()
+ hash = self.read_hash_maybe(self.rpki_root_manifest_file)
+ logger.debug("Writing manifest %s", self.rpki_root_manifest_file)
+ with open(self.rpki_root_manifest_file, "wb") as f:
+ f.write(manifest.get_DER())
+ pdu = SubElement(pubd_msg, rpki.publication.tag_publish, uri = self.rpki_root_manifest_uri)
+ pdu.text = manifest.get_Base64()
+ if hash is not None:
+ pdu.set("hash", hash)
+ hash = rpki.x509.sha256(self.rpki_root_cert.get_DER()).encode("hex")
+ if hash != self.rpki_root_cert_hash:
+ pdu = SubElement(pubd_msg, rpki.publication.tag_publish, uri = self.rpki_root_cert_uri)
+ pdu.text = self.rpki_root_cert.get_Base64()
+ if self.rpki_root_cert_hash is not None:
+ pdu.set("hash", self.rpki_root_cert_hash)
+ self.rpki_root_cert_hash = hash
+
+
+ @staticmethod
+ def read_hash_maybe(fn):
+ try:
+ with open(fn, "rb") as f:
+ return rpki.x509.sha256(f.read()).encode("hex")
+ except IOError:
+ return None
+
def revoke_subject_cert(self, now):
self.revoked.append((self.get_subject_cert().getSerial(), now))
+
+ def publish(self, q_msg):
+ if q_msg is None:
+ return
+ assert len(q_msg) > 0
+
+ if not all(q_pdu.get("hash") is not None for q_pdu in q_msg):
+ logger.debug("Some publication PDUs are missing hashes, checking published data...")
+ q = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "query", version = rpki.publication.version)
+ SubElement(q, rpki.publication.tag_list)
+ published_hash = dict((r.get("uri"), r.get("hash")) for r in self.call_pubd(q))
+ for q_pdu in q_msg:
+ q_uri = q_pdu.get("uri")
+ if q_pdu.get("hash") is None and published_hash.get(q_uri) is not None:
+ logger.debug("Updating hash of %s to %s from previously published data", q_uri, published_hash[q_uri])
+ q_pdu.set("hash", published_hash[q_uri])
+
+ r_msg = self.call_pubd(q_msg)
+ if len(q_msg) != len(r_msg):
+ raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %s, got %s" % (len(q_msg), len(r_msg)))
+
+
+ def call_pubd(self, q_msg):
+ for q_pdu in q_msg:
+ logger.info("Sending %s to pubd", q_pdu.get("uri"))
+ r_msg = rpki.http_simple.client(
+ proto_cms_msg = rpki.publication.cms_msg_no_sax,
+ client_key = self.rootd_bpki_key,
+ client_cert = self.rootd_bpki_cert,
+ client_crl = self.rootd_bpki_crl,
+ server_ta = self.bpki_ta,
+ server_cert = self.pubd_bpki_cert,
+ url = self.pubd_url,
+ q_msg = q_msg,
+ replay_track = self.pubd_replay_tracker)
+ rpki.publication.raise_if_error(r_msg)
+ return r_msg
+
+
def compose_response(self, r_msg, pkcs10 = None):
- subject_cert = self.issue_subject_cert_maybe(pkcs10)
- rc = rpki.up_down.class_elt()
- rc.class_name = self.rpki_class_name
- rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri)
- rc.from_resource_bag(self.rpki_root_cert.get_3779resources())
- rc.issuer = self.rpki_root_cert
- r_msg.payload.classes.append(rc)
+ subject_cert, pubd_msg = self.issue_subject_cert_maybe(pkcs10)
+ bag = self.rpki_root_cert.get_3779resources()
+ rc = SubElement(r_msg, rpki.up_down.tag_class,
+ class_name = self.rpki_class_name,
+ cert_url = str(rpki.up_down.multi_uri(self.rpki_root_cert_uri)),
+ resource_set_as = str(bag.asn),
+ resource_set_ipv4 = str(bag.v4),
+ resource_set_ipv6 = str(bag.v6),
+ resource_set_notafter = str(bag.valid_until))
if subject_cert is not None:
- rc.certs.append(rpki.up_down.certificate_elt())
- rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_base_uri + self.rpki_subject_cert)
- rc.certs[0].cert = subject_cert
+ c = SubElement(rc, rpki.up_down.tag_certificate,
+ cert_url = str(rpki.up_down.multi_uri(self.rpki_subject_cert_uri)))
+ c.text = subject_cert.get_Base64()
+ SubElement(rc, rpki.up_down.tag_issuer).text = self.rpki_root_cert.get_Base64()
+ self.publish(pubd_msg)
- def up_down_handler(self, query, path, cb):
- try:
- q_cms = cms_msg(DER = query)
- q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert))
- self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
- logger.exception("Problem decoding PDU")
- return cb(400, reason = "Could not decode PDU: %s" % e)
- def done(r_msg):
- cb(200, body = cms_msg().wrap(
- r_msg, self.rootd_bpki_key, self.rootd_bpki_cert,
- self.rootd_bpki_crl if self.include_bpki_crl else None))
+ def handle_list(self, q_msg, r_msg):
+ self.compose_response(r_msg)
+
+ def handle_issue(self, q_msg, r_msg):
+ # This is where we'd check q_msg[0].get("class_name") if this weren't rootd.
+ self.compose_response(r_msg, rpki.x509.PKCS10(Base64 = q_msg[0].text))
+
+
+ def handle_revoke(self, q_msg, r_msg):
+ class_name = q_msg[0].get("class_name")
+ ski = q_msg[0].get("ski")
+ logger.debug("Revocation requested for class %s SKI %s", class_name, ski)
+ subject_cert = self.get_subject_cert()
+ if subject_cert is None:
+ logger.debug("No subject certificate, nothing to revoke")
+ raise rpki.exceptions.NotInDatabase
+ if subject_cert.gSKI() != ski:
+ logger.debug("Subject certificate has different SKI %s, not revoking", subject_cert.gSKI())
+ raise rpki.exceptions.NotInDatabase
+ logger.debug("Revoking certificate %s", ski)
+ now = rpki.sundial.now()
+ pubd_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "query", version = rpki.publication.version)
+ self.revoke_subject_cert(now)
+ self.del_subject_cert()
+ self.del_subject_pkcs10()
+ SubElement(r_msg, q_msg[0].tag, class_name = class_name, ski = ski)
+ self.generate_crl_and_manifest(now, pubd_msg)
+ self.publish(pubd_msg)
+
+
+ # Need to do something about mapping exceptions to up-down error
+ # codes, right now everything shows up as "internal error".
+ #
+ #exceptions = {
+ # rpki.exceptions.ClassNameUnknown : 1201,
+ # rpki.exceptions.NoActiveCA : 1202,
+ # (rpki.exceptions.ClassNameUnknown, revoke_pdu) : 1301,
+ # (rpki.exceptions.NotInDatabase, revoke_pdu) : 1302 }
+ #
+ # Might be that what we want here is a subclass of
+ # rpki.exceptions.RPKI_Exception which carries an extra data field
+ # for the up-down error code, so that we can add the correct code
+ # when we instantiate it.
+ #
+ # There are also a few that are also schema violations, which means
+ # we'd have to catch them before validating or pick them out of a
+ # message that failed validation or otherwise break current
+ # modularity. Maybe an optional pre-validation check method hook in
+ # rpki.x509.XML_CMS_object which we can use to intercept such things?
+
+
+ def handler(self, request, q_der):
try:
- q_msg.serve_top_level(None, done)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
+ q_cms = rpki.up_down.cms_msg_no_sax(DER = q_der)
+ q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert))
+ q_type = q_msg.get("type")
+ logger.info("Serving %s query", q_type)
+ r_msg = Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
+ sender = q_msg.get("recipient"), recipient = q_msg.get("sender"), type = q_type + "_response")
try:
- logger.exception("Exception serving up-down request %r", q_msg)
- done(q_msg.serve_error(e))
- except (rpki.async.ExitNow, SystemExit):
- raise
+ self.rpkid_cms_timestamp = q_cms.check_replay(self.rpkid_cms_timestamp, request.path)
+ getattr(self, "handle_" + q_type)(q_msg, r_msg)
except Exception, e:
- logger.exception("Exception while generating error report")
- cb(500, reason = "Could not process PDU: %s" % e)
+ logger.exception("Exception processing up-down %s message", q_type)
+ rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type)
+ request.send_cms_response(rpki.up_down.cms_msg_no_sax().wrap(r_msg, self.rootd_bpki_key, self.rootd_bpki_cert,
+ self.rootd_bpki_crl if self.include_bpki_crl else None))
+ except Exception, e:
+ logger.exception("Unhandled exception processing up-down message")
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
def next_crl_number(self):
if self.crl_number is None:
try:
- crl = rpki.x509.CRL(DER_file = os.path.join(self.rpki_root_dir, self.rpki_root_crl))
+ crl = rpki.x509.CRL(DER_file = self.rpki_root_crl_file)
self.crl_number = crl.getCRLNumber()
except: # pylint: disable=W0702
self.crl_number = 0
@@ -324,15 +387,11 @@ class main(object):
def __init__(self):
-
- global rootd
- rootd = self # Gross, but simpler than what we'd have to do otherwise
-
- self.rpki_root_cert = None
self.serial_number = None
self.crl_number = None
self.revoked = []
- self.cms_timestamp = None
+ self.rpkid_cms_timestamp = None
+ self.pubd_replay_tracker = ReplayTracker()
os.environ["TZ"] = "UTC"
time.tzset()
@@ -349,7 +408,7 @@ class main(object):
rpki.log.init("rootd", args)
- self.cfg = rpki.config.parser(args.config, "rootd")
+ self.cfg = rpki.config.parser(set_filename = args.config, section = "rootd")
self.cfg.set_global_flags()
if not args.foreground:
@@ -361,28 +420,38 @@ class main(object):
self.rootd_bpki_crl = rpki.x509.CRL( Auto_update = self.cfg.get("rootd-bpki-crl"))
self.child_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("child-bpki-cert"))
+ if self.cfg.has_option("pubd-bpki-cert"):
+ self.pubd_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-bpki-cert"))
+ else:
+ self.pubd_bpki_cert = None
+
self.http_server_host = self.cfg.get("server-host", "")
self.http_server_port = self.cfg.getint("server-port")
- self.rpki_class_name = self.cfg.get("rpki-class-name", "wombat")
+ self.rpki_class_name = self.cfg.get("rpki-class-name")
- self.rpki_root_dir = self.cfg.get("rpki-root-dir")
- self.rpki_base_uri = self.cfg.get("rpki-base-uri", "rsync://" + self.rpki_class_name + ".invalid/")
+ self.rpki_root_key = rpki.x509.RSA( Auto_update = self.cfg.get("rpki-root-key-file"))
+ self.rpki_root_cert = rpki.x509.X509(Auto_update = self.cfg.get("rpki-root-cert-file"))
+ self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri")
+ self.rpki_root_cert_hash = None
- self.rpki_root_key = rpki.x509.RSA(Auto_update = self.cfg.get("rpki-root-key"))
- self.rpki_root_cert_file = self.cfg.get("rpki-root-cert")
- self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "root.cer")
+ self.rpki_root_manifest_file = self.cfg.get("rpki-root-manifest-file")
+ self.rpki_root_manifest_uri = self.cfg.get("rpki-root-manifest-uri")
- self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "root.mft")
- self.rpki_root_crl = self.cfg.get("rpki-root-crl", "root.crl")
- self.rpki_subject_cert = self.cfg.get("rpki-subject-cert", "child.cer")
- self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10", "child.pkcs10")
+ self.rpki_root_crl_file = self.cfg.get("rpki-root-crl-file")
+ self.rpki_root_crl_uri = self.cfg.get("rpki-root-crl-uri")
+ self.rpki_subject_cert_file = self.cfg.get("rpki-subject-cert-file")
+ self.rpki_subject_cert_uri = self.cfg.get("rpki-subject-cert-uri")
+ self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10-file")
self.rpki_subject_lifetime = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-lifetime", "8w"))
- self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen", self.rpki_subject_lifetime.convert_to_seconds() / 2))
+ self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen",
+ self.rpki_subject_lifetime.convert_to_seconds() / 2))
self.include_bpki_crl = self.cfg.getboolean("include-bpki-crl", False)
- rpki.http.server(host = self.http_server_host,
- port = self.http_server_port,
- handlers = self.up_down_handler)
+ self.pubd_url = self.cfg.get("pubd-contact-uri")
+
+ rpki.http_simple.server(host = self.http_server_host,
+ port = self.http_server_port,
+ handlers = self.handler)
diff --git a/rpki/rpkic.py b/rpki/rpkic.py
index d7b76c51..fdde6056 100644
--- a/rpki/rpkic.py
+++ b/rpki/rpkic.py
@@ -124,7 +124,7 @@ class main(Cmd):
global rpki # pylint: disable=W0602
try:
- cfg = rpki.config.parser(self.cfg_file, "myrpki")
+ cfg = rpki.config.parser(set_filename = self.cfg_file, section = "myrpki")
cfg.set_global_flags()
except IOError, e:
sys.exit("%s: %s" % (e.strerror, e.filename))
@@ -132,19 +132,29 @@ class main(Cmd):
self.histfile = cfg.get("history_file", os.path.expanduser("~/.rpkic_history"))
self.autosync = cfg.getboolean("autosync", True, section = "rpkic")
- from django.conf import settings
+ # This should go away now that we have rpki.django_settings, but
+ # let's get a verbose log with it present first to see what
+ # changes.
+
+ use_south = True
+ setup_db = False
+
+ if use_south:
+ os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings")
+
+ else:
+ from django.conf import settings
+ settings.configure(
+ DATABASES = { "default" : {
+ "ENGINE" : "django.db.backends.mysql",
+ "NAME" : cfg.get("sql-database", section = "irdbd"),
+ "USER" : cfg.get("sql-username", section = "irdbd"),
+ "PASSWORD" : cfg.get("sql-password", section = "irdbd"),
+ "HOST" : "",
+ "PORT" : "",
+ "OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}},
+ INSTALLED_APPS = ["rpki.irdb"])
- settings.configure(
- DATABASES = { "default" : {
- "ENGINE" : "django.db.backends.mysql",
- "NAME" : cfg.get("sql-database", section = "irdbd"),
- "USER" : cfg.get("sql-username", section = "irdbd"),
- "PASSWORD" : cfg.get("sql-password", section = "irdbd"),
- "HOST" : "",
- "PORT" : "",
- "OPTIONS" : { "init_command": "SET storage_engine=INNODB" }}},
- INSTALLED_APPS = ("rpki.irdb",),
- )
import rpki.irdb # pylint: disable=W0621
@@ -166,8 +176,12 @@ class main(Cmd):
except rpki.config.ConfigParser.Error:
pass
- import django.core.management
- django.core.management.call_command("syncdb", verbosity = 0, load_initial_data = False)
+ if setup_db:
+ import django.core.management
+ django.core.management.call_command("syncdb", verbosity = 3, load_initial_data = False)
+
+ if setup_db and use_south:
+ django.core.management.call_command("migrate", verbosity = 3)
self.zoo = rpki.irdb.Zookeeper(cfg = cfg, handle = self.handle, logstream = sys.stdout)
diff --git a/rpki/rpkid.py b/rpki/rpkid.py
index 36ee2ea9..07a81f79 100644
--- a/rpki/rpkid.py
+++ b/rpki/rpkid.py
@@ -42,8 +42,11 @@ import rpki.async
import rpki.daemonize
import rpki.rpkid_tasks
+from lxml.etree import Element, SubElement
+
logger = logging.getLogger(__name__)
+
class main(object):
"""
Main program for rpkid.
@@ -75,7 +78,7 @@ class main(object):
rpki.log.init("rpkid", args)
- self.cfg = rpki.config.parser(args.config, "rpkid")
+ self.cfg = rpki.config.parser(set_filename = args.config, section = "rpkid")
self.cfg.set_global_flags()
if not args.foreground:
@@ -116,17 +119,6 @@ class main(object):
self.publication_kludge_base = self.cfg.get("publication-kludge-base", "publication/")
- # Icky hack to let Iain do some testing quickly, should go away
- # once we sort out whether we can make this change permanent.
- #
- # OK, the stuff to add router certificate support makes enough
- # other changes that we're going to need a migration program in
- # any case, so might as well throw the switch here too, or at
- # least find out if it (still) works as expected.
-
- self.merge_publication_directories = self.cfg.getboolean("merge_publication_directories",
- True)
-
self.use_internal_cron = self.cfg.getboolean("use-internal-cron", True)
self.initial_delay = random.randint(self.cfg.getint("initial-delay-min", 10),
@@ -283,14 +275,14 @@ class main(object):
up_down_url_regexp = re.compile("/up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I)
- def up_down_handler(self, query, path, cb):
+ def up_down_handler(self, q_der, path, cb):
"""
Process one up-down PDU.
"""
- def done(reply):
+ def done(r_der):
self.sql.sweep()
- cb(200, body = reply)
+ cb(200, body = r_der)
try:
match = self.up_down_url_regexp.search(path)
@@ -303,7 +295,7 @@ class main(object):
"self")
if child is None:
raise rpki.exceptions.ChildNotFound("Could not find child %s of self %s in up_down_handler()" % (child_handle, self_handle))
- child.serve_up_down(query, done)
+ child.serve_up_down(q_der, done)
except (rpki.async.ExitNow, SystemExit):
raise
except (rpki.exceptions.ChildNotFound, rpki.exceptions.BadContactURL), e:
@@ -318,6 +310,7 @@ class main(object):
Record that we were still alive when we got here, by resetting
keepalive timer.
"""
+
if force or self.cron_timeout is not None:
self.cron_timeout = rpki.sundial.now() + self.cron_keepalive
@@ -325,6 +318,7 @@ class main(object):
"""
Add a task to the scheduler task queue, unless it's already queued.
"""
+
if task not in self.task_queue:
logger.debug("Adding %r to task queue", task)
self.task_queue.append(task)
@@ -339,6 +333,7 @@ class main(object):
queue (we don't want to run it directly, as that could eventually
blow out our call stack).
"""
+
try:
self.task_current = self.task_queue.pop(0)
except IndexError:
@@ -350,6 +345,7 @@ class main(object):
"""
Run first task on the task queue, unless one is running already.
"""
+
if self.task_current is None:
self.task_next()
@@ -446,6 +442,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch parent object to which this CA object links.
"""
+
return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
@property
@@ -453,6 +450,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch all ca_detail objects that link to this CA object.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
@property
@@ -460,6 +458,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
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,))
@property
@@ -467,6 +466,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
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,))
@property
@@ -474,6 +474,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
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,))
@property
@@ -481,6 +482,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch active and deprecated ca_details for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND (state = 'active' OR state = 'deprecated')", (self.ca_id,))
@property
@@ -488,6 +490,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
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,))
@property
@@ -496,7 +499,7 @@ class ca_obj(rpki.sql.sql_persistent):
Fetch ca_details which are candidates for consideration when
processing an up-down issue_response PDU.
"""
- #return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,))
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state != 'revoked'", (self.ca_id,))
def construct_sia_uri(self, parent, rc):
@@ -505,16 +508,12 @@ class ca_obj(rpki.sql.sql_persistent):
information and the parent's up-down protocol list_response PDU.
"""
- sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync()
- if not sia_uri or not sia_uri.startswith(parent.sia_base):
+ sia_uri = rc.get("suggested_sia_head", "")
+ if not sia_uri.startswith("rsync://") or not sia_uri.startswith(parent.sia_base):
sia_uri = parent.sia_base
if not sia_uri.endswith("/"):
raise rpki.exceptions.BadURISyntax("SIA URI must end with a slash: %s" % sia_uri)
- # With luck this can go away sometime soon.
- if self.gctx.merge_publication_directories:
- return sia_uri
- else:
- return sia_uri + str(self.ca_id) + "/"
+ return sia_uri
def check_for_updates(self, parent, rc, cb, eb):
"""
@@ -531,28 +530,40 @@ class ca_obj(rpki.sql.sql_persistent):
self.sia_uri = sia_uri
self.sql_mark_dirty()
- rc_resources = rc.to_resource_bag()
- cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs)
+ class_name = rc.get("class_name")
+
+ rc_resources = rpki.resource_set.resource_bag(
+ rc.get("resource_set_as"),
+ rc.get("resource_set_ipv4"),
+ rc.get("resource_set_ipv6"),
+ rc.get("resource_set_notafter"))
+
+ cert_map = {}
+ for c in rc.getiterator(rpki.up_down.tag_certificate):
+ x = rpki.x509.X509(Base64 = c.text)
+ u = rpki.up_down.multi_uri(c.get("cert_url")).rsync()
+ cert_map[x.gSKI()] = (x, u)
def loop(iterator, ca_detail):
self.gctx.checkpoint()
- rc_cert = cert_map.pop(ca_detail.public_key.get_SKI(), None)
+ rc_cert, rc_cert_uri = cert_map.pop(ca_detail.public_key.gSKI(), (None, None))
if rc_cert is None:
- logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, maybe parent certificate went away?",
- ca_detail.public_key.gSKI(), rc.class_name, parent.self.self_handle, parent.parent_handle)
+ logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, "
+ "maybe parent certificate went away?",
+ ca_detail.public_key.gSKI(), class_name, parent.self.self_handle, parent.parent_handle)
publisher = publication_queue()
ca_detail.delete(ca = ca_detail.ca, publisher = publisher)
return publisher.call_pubd(iterator, eb)
else:
- if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert.cert_url.rsync():
- logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert.cert_url.rsync())
- ca_detail.ca_cert_uri = rc_cert.cert_url.rsync()
+ if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert_uri:
+ logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert_uri)
+ ca_detail.ca_cert_uri = rc_cert_uri
ca_detail.sql_mark_dirty()
if ca_detail.state in ("pending", "active"):
@@ -564,7 +575,7 @@ class ca_obj(rpki.sql.sql_persistent):
if (ca_detail.state == "pending" or
sia_uri_changed or
- ca_detail.latest_ca_cert != rc_cert.cert or
+ ca_detail.latest_ca_cert != rc_cert or
ca_detail.latest_ca_cert.getNotAfter() != rc_resources.valid_until or
current_resources.undersized(rc_resources) or
current_resources.oversized(rc_resources)):
@@ -582,9 +593,7 @@ class ca_obj(rpki.sql.sql_persistent):
def done():
if cert_map:
logger.warning("Unknown certificate SKI%s %s in resource class %s in list_response to %s from %s, maybe you want to \"revoke_forgotten\"?",
- "" if len(cert_map) == 1 else "s",
- ", ".join(c.cert.gSKI() for c in cert_map.values()),
- rc.class_name, parent.self.self_handle, parent.parent_handle)
+ "" if len(cert_map) == 1 else "s", ", ".join(cert_map), class_name, parent.self.self_handle, parent.parent_handle)
self.gctx.sql.sweep()
self.gctx.checkpoint()
cb()
@@ -592,26 +601,25 @@ class ca_obj(rpki.sql.sql_persistent):
ca_details = self.issue_response_candidate_ca_details
if True:
- skis_parent = set(x.cert.gSKI()
- for x in cert_map.itervalues())
+ skis_parent = set(cert_map)
skis_me = set(x.latest_ca_cert.gSKI()
for x in ca_details
if x.latest_ca_cert is not None)
for ski in skis_parent & skis_me:
logger.debug("Parent %s agrees that %s has SKI %s in resource class %s",
- parent.parent_handle, parent.self.self_handle, ski, rc.class_name)
+ parent.parent_handle, parent.self.self_handle, ski, class_name)
for ski in skis_parent - skis_me:
logger.debug("Parent %s thinks %s has SKI %s in resource class %s but I don't think so",
- parent.parent_handle, parent.self.self_handle, ski, rc.class_name)
+ parent.parent_handle, parent.self.self_handle, ski, class_name)
for ski in skis_me - skis_parent:
logger.debug("I think %s has SKI %s in resource class %s but parent %s doesn't think so",
- parent.self.self_handle, ski, rc.class_name, parent.parent_handle)
+ parent.self.self_handle, ski, class_name, parent.parent_handle)
if ca_details:
rpki.async.iterator(ca_details, loop, done)
else:
logger.warning("Existing resource class %s to %s from %s with no certificates, rekeying",
- rc.class_name, parent.self.self_handle, parent.parent_handle)
+ class_name, parent.self.self_handle, parent.parent_handle)
self.gctx.checkpoint()
self.rekey(cb, eb)
@@ -625,7 +633,7 @@ class ca_obj(rpki.sql.sql_persistent):
self = cls()
self.gctx = parent.gctx
self.parent_id = parent.parent_id
- self.parent_resource_class = rc.class_name
+ self.parent_resource_class = rc.get("class_name")
self.sql_store()
try:
self.sia_uri = self.construct_sia_uri(parent, rc)
@@ -634,18 +642,18 @@ class ca_obj(rpki.sql.sql_persistent):
raise
ca_detail = ca_detail_obj.create(self)
- def done(issue_response):
- c = issue_response.payload.classes[0].certs[0]
- logger.debug("CA %r received certificate %s", self, c.cert_url)
+ def done(r_msg):
+ c = r_msg[0][0]
+ logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
ca_detail.activate(
ca = self,
- cert = c.cert,
- uri = c.cert_url,
+ cert = rpki.x509.X509(Base64 = c.text),
+ uri = c.get("cert_url"),
callback = cb,
errback = eb)
logger.debug("Sending issue request to %r from %r", parent, self.create)
- rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb)
+ parent.up_down_issue_query(self, ca_detail, done, eb)
def delete(self, parent, callback):
"""
@@ -677,6 +685,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a certificate serial number.
"""
+
self.last_issued_sn += 1
self.sql_mark_dirty()
return self.last_issued_sn
@@ -685,6 +694,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a manifest serial number.
"""
+
self.last_manifest_sn += 1
self.sql_mark_dirty()
return self.last_manifest_sn
@@ -693,6 +703,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a CRL serial number.
"""
+
self.last_crl_sn += 1
self.sql_mark_dirty()
return self.last_crl_sn
@@ -709,19 +720,19 @@ class ca_obj(rpki.sql.sql_persistent):
old_detail = self.active_ca_detail
new_detail = ca_detail_obj.create(self)
- def done(issue_response):
- c = issue_response.payload.classes[0].certs[0]
- logger.debug("CA %r received certificate %s", self, c.cert_url)
+ def done(r_msg):
+ c = r_msg[0][0]
+ logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
new_detail.activate(
ca = self,
- cert = c.cert,
- uri = c.cert_url,
+ cert = rpki.x509.X509(Base64 = c.text),
+ uri = c.get("cert_url"),
predecessor = old_detail,
callback = cb,
errback = eb)
logger.debug("Sending issue request to %r from %r", parent, self.rekey)
- rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb)
+ parent.up_down_issue_query(self, new_detail, done, eb)
def revoke(self, cb, eb, revoke_all = False):
"""
@@ -783,6 +794,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Extra assertions for SQL decode of a ca_detail_obj.
"""
+
rpki.sql.sql_persistent.sql_decode(self, vals)
assert self.public_key is None or self.private_key_id is None or self.public_key.get_DER() == self.private_key_id.get_public_DER()
assert self.manifest_public_key is None or self.manifest_private_key_id is None or self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
@@ -793,12 +805,14 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch CA object to which this ca_detail links.
"""
+
return ca_obj.sql_fetch(self.gctx, self.ca_id)
def fetch_child_certs(self, child = None, ski = None, unique = False, unpublished = None):
"""
Fetch all child_cert objects that link to this ca_detail.
"""
+
return rpki.rpkid.child_cert_obj.fetch(self.gctx, child, self, ski, unique, unpublished)
@property
@@ -806,6 +820,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all child_cert objects that link to this ca_detail.
"""
+
return self.fetch_child_certs()
def unpublished_child_certs(self, when):
@@ -813,6 +828,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
Fetch all unpublished child_cert objects linked to this ca_detail
with attempted publication dates older than when.
"""
+
return self.fetch_child_certs(unpublished = when)
@property
@@ -820,6 +836,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all revoked_cert objects that link to this ca_detail.
"""
+
return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
@property
@@ -827,6 +844,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all ROA objects that link to this ca_detail.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
def unpublished_roas(self, when):
@@ -834,6 +852,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
Fetch all unpublished ROA objects linked to this ca_detail with
attempted publication dates older than when.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
@property
@@ -841,27 +860,39 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
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,))
+ def unpublished_ghostbusters(self, when):
+ """
+ Fetch all unpublished Ghostbusters objects linked to this
+ ca_detail with attempted publication dates older than when.
+ """
+
+ return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
+
@property
def ee_certificates(self):
"""
Fetch all EE certificate objects that link to this ca_detail.
"""
+
return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
- def unpublished_ghostbusters(self, when):
+ def unpublished_ee_certificates(self, when):
"""
- Fetch all unpublished Ghostbusters objects linked to this
+ Fetch all unpublished EE certificate objects linked to this
ca_detail with attempted publication dates older than when.
"""
- return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
+
+ return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
@property
def crl_uri(self):
"""
Return publication URI for this ca_detail's CRL.
"""
+
return self.ca.sia_uri + self.crl_uri_tail
@property
@@ -869,6 +900,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Return tail (filename portion) of publication URI for this ca_detail's CRL.
"""
+
return self.public_key.gSKI() + ".crl"
@property
@@ -876,12 +908,14 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Return publication URI for this ca_detail's manifest.
"""
+
return self.ca.sia_uri + self.public_key.gSKI() + ".mft"
def has_expired(self):
"""
Return whether this ca_detail's certificate has expired.
"""
+
return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
def covers(self, target):
@@ -901,7 +935,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
publisher = publication_queue()
self.latest_ca_cert = cert
- self.ca_cert_uri = uri.rsync()
+ self.ca_cert_uri = uri
self.generate_manifest_cert()
self.state = "active"
self.generate_crl(publisher = publisher)
@@ -933,11 +967,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
repository = ca.parent.repository
handler = False if allow_failure else None
for child_cert in self.child_certs:
- publisher.withdraw(cls = rpki.publication.certificate_elt,
- uri = child_cert.uri,
- obj = child_cert.cert,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = child_cert.uri,
+ old_obj = child_cert.cert,
+ repository = repository,
+ handler = handler)
child_cert.sql_mark_deleted()
for roa in self.roas:
roa.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
@@ -948,21 +981,19 @@ class ca_detail_obj(rpki.sql.sql_persistent):
except AttributeError:
latest_manifest = None
if latest_manifest is not None:
- publisher.withdraw(cls = rpki.publication.manifest_elt,
- uri = self.manifest_uri,
- obj = self.latest_manifest,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = self.manifest_uri,
+ old_obj = self.latest_manifest,
+ repository = repository,
+ handler = handler)
try:
latest_crl = self.latest_crl
except AttributeError:
latest_crl = None
if latest_crl is not None:
- publisher.withdraw(cls = rpki.publication.crl_elt,
- uri = self.crl_uri,
- obj = self.latest_crl,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = self.crl_uri,
+ old_obj = self.latest_crl,
+ repository = repository,
+ handler = handler)
self.gctx.sql.sweep()
for cert in self.revoked_certs: # + self.child_certs
logger.debug("Deleting %r", cert)
@@ -995,13 +1026,18 @@ class ca_detail_obj(rpki.sql.sql_persistent):
ca = self.ca
parent = ca.parent
+ class_name = ca.parent_resource_class
+ gski = self.latest_ca_cert.gSKI()
def parent_revoked(r_msg):
- if r_msg.payload.ski != self.latest_ca_cert.gSKI():
+ if r_msg[0].get("class_name") != class_name:
+ raise rpki.exceptions.ResourceClassMismatch
+
+ if r_msg[0].get("ski") != gski:
raise rpki.exceptions.SKIMismatch
- logger.debug("Parent revoked %s, starting cleanup", self.latest_ca_cert.gSKI())
+ logger.debug("Parent revoked %s, starting cleanup", gski)
crl_interval = rpki.sundial.timedelta(seconds = parent.self.crl_interval)
@@ -1039,8 +1075,9 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.sql_mark_dirty()
publisher.call_pubd(cb, eb)
- logger.debug("Asking parent to revoke CA certificate %s", self.latest_ca_cert.gSKI())
- rpki.up_down.revoke_pdu.query(ca, self.latest_ca_cert.gSKI(), parent_revoked, eb)
+ logger.debug("Asking parent to revoke CA certificate %s", gski)
+ parent.up_down_revoke_query(class_name, gski, parent_revoked, eb)
+
def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback):
"""
@@ -1048,24 +1085,27 @@ class ca_detail_obj(rpki.sql.sql_persistent):
children of this ca_detail.
"""
- def issued(issue_response):
- c = issue_response.payload.classes[0].certs[0]
- logger.debug("CA %r received certificate %s", self, c.cert_url)
+ def issued(r_msg):
+ c = r_msg[0][0]
+ cert = rpki.x509.X509(Base64 = c.text)
+ cert_url = c.get("cert_url")
+
+ logger.debug("CA %r received certificate %s", self, cert_url)
if self.state == "pending":
return self.activate(
ca = ca,
- cert = c.cert,
- uri = c.cert_url,
+ cert = cert,
+ uri = cert_url,
callback = callback,
errback = errback)
- validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != c.cert.getNotAfter()
+ validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != cert.getNotAfter()
publisher = publication_queue()
- if self.latest_ca_cert != c.cert:
- self.latest_ca_cert = c.cert
+ if self.latest_ca_cert != cert:
+ self.latest_ca_cert = cert
self.sql_mark_dirty()
self.generate_manifest_cert()
self.generate_crl(publisher = publisher)
@@ -1093,7 +1133,8 @@ class ca_detail_obj(rpki.sql.sql_persistent):
publisher.call_pubd(callback, errback)
logger.debug("Sending issue request to %r from %r", parent, self.update)
- rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback)
+ parent.up_down_issue_query(ca, self, issued, errback)
+
@classmethod
def create(cls, ca):
@@ -1172,6 +1213,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
notAfter = resources.valid_until)
if child_cert is None:
+ old_cert = None
child_cert = rpki.rpkid.child_cert_obj(
gctx = child.gctx,
child_id = child.child_id,
@@ -1179,6 +1221,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
cert = cert)
logger.debug("Created new child_cert %r", child_cert)
else:
+ old_cert = child_cert.cert
child_cert.cert = cert
del child_cert.ca_detail
child_cert.ca_detail_id = self.ca_detail_id
@@ -1187,10 +1230,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
child_cert.ski = cert.get_SKI()
child_cert.published = rpki.sundial.now()
child_cert.sql_store()
- publisher.publish(
- cls = rpki.publication.certificate_elt,
+ publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ old_obj = old_cert,
+ new_obj = child_cert.cert,
repository = ca.parent.repository,
handler = child_cert.published_callback)
self.generate_manifest(publisher = publisher)
@@ -1221,6 +1264,8 @@ class ca_detail_obj(rpki.sql.sql_persistent):
certlist.append((revoked_cert.serial, revoked_cert.revoked))
certlist.sort()
+ old_crl = self.latest_crl
+
self.latest_crl = rpki.x509.CRL.generate(
keypair = self.private_key_id,
issuer = self.latest_ca_cert,
@@ -1231,10 +1276,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.crl_published = rpki.sundial.now()
self.sql_mark_dirty()
- publisher.publish(
- cls = rpki.publication.crl_elt,
+ publisher.queue(
uri = self.crl_uri,
- obj = self.latest_crl,
+ old_obj = old_crl,
+ new_obj = self.latest_crl,
repository = parent.repository,
handler = self.crl_published_callback)
@@ -1242,7 +1287,8 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Check result of CRL publication.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.crl_published = None
self.sql_mark_dirty()
@@ -1278,6 +1324,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates)
logger.debug("Building manifest object %s", uri)
+ old_manifest = self.latest_manifest
self.latest_manifest = rpki.x509.SignedManifest.build(
serial = ca.next_manifest_number(),
thisUpdate = now,
@@ -1290,17 +1337,18 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.manifest_published = rpki.sundial.now()
self.sql_mark_dirty()
- publisher.publish(cls = rpki.publication.manifest_elt,
- uri = uri,
- obj = self.latest_manifest,
- repository = parent.repository,
- handler = self.manifest_published_callback)
+ publisher.queue(uri = uri,
+ old_obj = old_manifest,
+ new_obj = self.latest_manifest,
+ repository = parent.repository,
+ handler = self.manifest_published_callback)
def manifest_published_callback(self, pdu):
"""
Check result of manifest publication.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.manifest_published = None
self.sql_mark_dirty()
@@ -1361,21 +1409,19 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.crl_published is not None and \
self.crl_published < stale:
logger.debug("Retrying publication for %s", self.crl_uri)
- publisher.publish(cls = rpki.publication.crl_elt,
- uri = self.crl_uri,
- obj = self.latest_crl,
- repository = repository,
- handler = self.crl_published_callback)
+ publisher.queue(uri = self.crl_uri,
+ new_obj = self.latest_crl,
+ repository = repository,
+ handler = self.crl_published_callback)
if self.latest_manifest is not None and \
self.manifest_published is not None and \
self.manifest_published < stale:
logger.debug("Retrying publication for %s", self.manifest_uri)
- publisher.publish(cls = rpki.publication.manifest_elt,
- uri = self.manifest_uri,
- obj = self.latest_manifest,
- repository = repository,
- handler = self.manifest_published_callback)
+ publisher.queue(uri = self.manifest_uri,
+ new_obj = self.latest_manifest,
+ repository = repository,
+ handler = self.manifest_published_callback)
if not check_all:
return
@@ -1385,31 +1431,37 @@ class ca_detail_obj(rpki.sql.sql_persistent):
for child_cert in self.unpublished_child_certs(stale):
logger.debug("Retrying publication for %s", child_cert)
- publisher.publish(
- cls = rpki.publication.certificate_elt,
+ publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ new_obj = child_cert.cert,
repository = repository,
handler = child_cert.published_callback)
for roa in self.unpublished_roas(stale):
logger.debug("Retrying publication for %s", roa)
- publisher.publish(
- cls = rpki.publication.roa_elt,
+ publisher.queue(
uri = roa.uri,
- obj = roa.roa,
+ new_obj = roa.roa,
repository = repository,
handler = roa.published_callback)
for ghostbuster in self.unpublished_ghostbusters(stale):
logger.debug("Retrying publication for %s", ghostbuster)
- publisher.publish(
- cls = rpki.publication.ghostbuster_elt,
+ publisher.queue(
uri = ghostbuster.uri,
- obj = ghostbuster.ghostbuster,
+ new_obj = ghostbuster.ghostbuster,
repository = repository,
handler = ghostbuster.published_callback)
+ for ee_cert in self.unpublished_ee_certificates(stale):
+ logger.debug("Retrying publication for %s", ee_cert)
+ publisher.queue(
+ uri = ee_cert.uri,
+ new_obj = ee_cert.cert,
+ repository = repository,
+ handler = ee_cert.published_callback)
+
+
class child_cert_obj(rpki.sql.sql_persistent):
"""
Certificate that has been issued to a child.
@@ -1436,6 +1488,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Initialize a child_cert_obj.
"""
+
rpki.sql.sql_persistent.__init__(self)
self.gctx = gctx
self.child_id = child_id
@@ -1451,6 +1504,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Fetch child object to which this child_cert object links.
"""
+
return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
@property
@@ -1459,6 +1513,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this child_cert object links.
"""
+
return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -1473,6 +1528,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Return the tail (filename) portion of the URI for this child_cert.
"""
+
return self.cert.gSKI() + ".cer"
@property
@@ -1480,6 +1536,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this child_cert.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
def revoke(self, publisher, generate_crl_and_manifest = True):
@@ -1491,10 +1548,9 @@ class child_cert_obj(rpki.sql.sql_persistent):
ca = ca_detail.ca
logger.debug("Revoking %r %r", self, self.uri)
revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.withdraw(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ old_obj = self.cert,
repository = ca.parent.repository)
self.gctx.sql.sweep()
self.sql_delete()
@@ -1625,7 +1681,8 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Publication callback: check result and mark published.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.published = None
self.sql_mark_dirty()
@@ -1649,6 +1706,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Initialize a revoked_cert_obj.
"""
+
rpki.sql.sql_persistent.__init__(self)
self.gctx = gctx
self.serial = serial
@@ -1664,6 +1722,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this revoked_cert_obj links.
"""
+
return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@classmethod
@@ -1671,6 +1730,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Revoke a certificate.
"""
+
return cls(
serial = cert.getSerial(),
expires = cert.getNotAfter(),
@@ -1712,6 +1772,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this roa_obj links.
"""
+
return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -1725,6 +1786,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL fetch actions for roa_obj -- handle prefix lists.
"""
+
for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"),
(6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")):
setattr(self, attribute, datatype.from_sql(
@@ -1739,6 +1801,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL insert actions for roa_obj -- handle prefix lists.
"""
+
for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)):
if prefix_set:
self.gctx.sql.executemany(
@@ -1753,6 +1816,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL delete actions for roa_obj -- handle prefix lists.
"""
+
self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,))
def __repr__(self):
@@ -1894,10 +1958,9 @@ class roa_obj(rpki.sql.sql_persistent):
self.sql_store()
logger.debug("Generating %r URI %s", self, self.uri)
- publisher.publish(
- cls = rpki.publication.roa_elt,
+ publisher.queue(
uri = self.uri,
- obj = self.roa,
+ new_obj = self.roa,
repository = ca.parent.repository,
handler = self.published_callback)
if not fast:
@@ -1908,7 +1971,8 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Check publication result.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.published = None
self.sql_mark_dirty()
@@ -1942,9 +2006,10 @@ class roa_obj(rpki.sql.sql_persistent):
logger.debug("Withdrawing %r %s and revoking its EE cert", self, uri)
rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.roa_elt, uri = uri, obj = roa,
- repository = ca_detail.ca.parent.repository,
- handler = False if allow_failure else None)
+ publisher.queue(uri = uri,
+ old_obj = roa,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
if not regenerate:
self.sql_mark_deleted()
@@ -1958,6 +2023,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Reissue ROA associated with this roa_obj.
"""
+
if self.ca_detail is None:
self.generate(publisher = publisher, fast = fast)
else:
@@ -1967,6 +2033,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Return publication URI for a public key.
"""
+
return self.ca_detail.ca.sia_uri + key.gSKI() + ".roa"
@property
@@ -1974,6 +2041,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this roa_obj's ROA.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -1982,6 +2050,7 @@ class roa_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
roa_obj's ROA.
"""
+
return self.cert.gSKI() + ".roa"
@@ -2024,6 +2093,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Fetch self object to which this ghostbuster_obj links.
"""
+
return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -2032,6 +2102,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
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):
@@ -2097,10 +2168,9 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
self.sql_store()
logger.debug("Generating Ghostbuster record %r", self.uri)
- publisher.publish(
- cls = rpki.publication.ghostbuster_elt,
+ publisher.queue(
uri = self.uri,
- obj = self.ghostbuster,
+ new_obj = self.ghostbuster,
repository = ca.parent.repository,
handler = self.published_callback)
if not fast:
@@ -2110,7 +2180,8 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Check publication result.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.published = None
self.sql_mark_dirty()
@@ -2144,9 +2215,10 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
logger.debug("Withdrawing %r %s and revoking its EE cert", self, 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)
+ publisher.queue(uri = uri,
+ old_obj = ghostbuster,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
if not regenerate:
self.sql_mark_deleted()
@@ -2160,6 +2232,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Reissue Ghostbuster associated with this ghostbuster_obj.
"""
+
if self.ghostbuster is None:
self.generate(publisher = publisher, fast = fast)
else:
@@ -2169,6 +2242,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Return publication URI for a public key.
"""
+
return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr"
@property
@@ -2176,6 +2250,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this ghostbuster_obj's ghostbuster.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -2184,6 +2259,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
ghostbuster_obj's ghostbuster.
"""
+
return self.cert.gSKI() + ".gbr"
@@ -2221,6 +2297,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Fetch self object to which this ee_cert_obj links.
"""
+
return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -2229,6 +2306,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this ee_cert_obj links.
"""
+
return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -2246,6 +2324,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
Although, really, one has to ask why we don't just store g(SKI)
in rpkid.sql instead of ski....
"""
+
return base64.urlsafe_b64encode(self.ski).rstrip("=")
@gski.setter
@@ -2257,6 +2336,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this ee_cert_obj.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -2265,6 +2345,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
ee_cert_obj.
"""
+
return self.cert.gSKI() + ".cer"
@classmethod
@@ -2292,12 +2373,11 @@ class ee_cert_obj(rpki.sql.sql_persistent):
ca_detail_id = ca_detail.ca_detail_id,
cert = cert)
- publisher.publish(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ new_obj = self.cert,
repository = ca.parent.repository,
- handler = self.published_callback)
+ handler = self.published_callback)
self.sql_store()
@@ -2316,10 +2396,9 @@ class ee_cert_obj(rpki.sql.sql_persistent):
ca = ca_detail.ca
logger.debug("Revoking %r %r", self, self.uri)
revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
- repository = ca.parent.repository)
+ publisher.queue(uri = self.uri,
+ old_obj = self.cert,
+ repository = ca.parent.repository)
self.gctx.sql.sweep()
self.sql_delete()
if generate_crl_and_manifest:
@@ -2401,12 +2480,12 @@ class ee_cert_obj(rpki.sql.sql_persistent):
self.sql_mark_dirty()
- publisher.publish(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ old_obj = old_cert,
+ new_obj = self.cert,
repository = ca_detail.ca.parent.repository,
- handler = self.published_callback)
+ handler = self.published_callback)
if must_revoke:
revoked_cert_obj.revoke(cert = old_cert.cert, ca_detail = old_ca_detail)
@@ -2423,7 +2502,8 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Publication callback: check result and mark published.
"""
- pdu.raise_if_error()
+
+ rpki.publication.raise_if_error(pdu)
self.published = None
self.sql_mark_dirty()
@@ -2451,29 +2531,49 @@ class publication_queue(object):
if self.replace:
self.uris = {}
- def _add(self, uri, obj, repository, handler, make_pdu):
+ def queue(self, uri, repository, handler = None, old_obj = None, new_obj = None):
+
+ assert old_obj is not None or new_obj is not None
+ assert old_obj is None or isinstance(old_obj, rpki.x509.uri_dispatch(uri))
+ assert new_obj is None or isinstance(new_obj, rpki.x509.uri_dispatch(uri))
+
+ logger.debug("Queuing publication action: uri %s, old %r, new %r", uri, old_obj, new_obj)
+
+ # id(repository) may need to change to repository.peer_contact_uri
+ # once we convert from our custom SQL cache to Django ORM.
+
rid = id(repository)
if rid not in self.repositories:
self.repositories[rid] = repository
- self.msgs[rid] = rpki.publication.msg.query()
+ self.msgs[rid] = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "query", version = rpki.publication.version)
+
if self.replace and uri in self.uris:
- logger.debug("Removing publication duplicate <%s %r %r>",
- self.uris[uri].action, self.uris[uri].uri, self.uris[uri].payload)
- self.msgs[rid].remove(self.uris.pop(uri))
- pdu = make_pdu(uri = uri, obj = obj)
+ logger.debug("Removing publication duplicate %r", self.uris[uri])
+ old_pdu = self.uris.pop(uri)
+ self.msgs[rid].remove(old_pdu)
+ hash = old_pdu.get("hash")
+ elif old_obj is None:
+ hash = None
+ else:
+ hash = rpki.x509.sha256(old_obj.get_DER()).encode("hex")
+
+ if new_obj is None:
+ pdu = SubElement(self.msgs[rid], rpki.publication.tag_withdraw, uri = uri, hash = hash)
+ else:
+ pdu = SubElement(self.msgs[rid], rpki.publication.tag_publish, uri = uri)
+ pdu.text = new_obj.get_Base64()
+ if hash is not None:
+ pdu.set("hash", hash)
+
if handler is not None:
- self.handlers[id(pdu)] = handler
- pdu.tag = id(pdu)
- self.msgs[rid].append(pdu)
+ tag = str(id(pdu))
+ self.handlers[tag] = handler
+ pdu.set("tag", tag)
+
if self.replace:
self.uris[uri] = pdu
- def publish(self, cls, uri, obj, repository, handler = None):
- return self._add( uri, obj, repository, handler, cls.make_publish)
-
- def withdraw(self, cls, uri, obj, repository, handler = None):
- return self._add( uri, obj, repository, handler, cls.make_withdraw)
-
def call_pubd(self, cb, eb):
def loop(iterator, rid):
logger.debug("Calling pubd[%r]", self.repositories[rid])
@@ -2488,5 +2588,5 @@ class publication_queue(object):
return sum(len(self.msgs[rid]) for rid in self.repositories)
def empty(self):
- assert (not self.msgs) == (self.size == 0)
+ assert (not self.msgs) == (self.size == 0), "Assertion failure: not self.msgs: %r, self.size %r" % (not self.msgs, self.size)
return not self.msgs
diff --git a/rpki/rpkid_tasks.py b/rpki/rpkid_tasks.py
index 58b4bcfe..5405834f 100644
--- a/rpki/rpkid_tasks.py
+++ b/rpki/rpkid_tasks.py
@@ -176,12 +176,12 @@ class PollParentTask(AbstractTask):
def parent_loop(self, parent_iterator, parent):
self.parent_iterator = parent_iterator
self.parent = parent
- rpki.up_down.list_pdu.query(parent, self.got_list, self.list_failed)
+ parent.up_down_list_query(self.got_list, self.list_failed)
def got_list(self, r_msg):
self.ca_map = dict((ca.parent_resource_class, ca) for ca in self.parent.cas)
self.gctx.checkpoint()
- rpki.async.iterator(r_msg.payload.classes, self.class_loop, self.class_done)
+ rpki.async.iterator(r_msg.getiterator(rpki.up_down.tag_class), self.class_loop, self.class_done)
def list_failed(self, e):
logger.exception("Couldn't get resource class list from parent %r, skipping", self.parent)
@@ -191,7 +191,7 @@ class PollParentTask(AbstractTask):
self.gctx.checkpoint()
self.class_iterator = class_iterator
try:
- ca = self.ca_map.pop(rc.class_name)
+ ca = self.ca_map.pop(rc.get("class_name"))
except KeyError:
rpki.rpkid.ca_obj.create(self.parent, rc, class_iterator, self.class_create_failed)
else:
@@ -310,10 +310,9 @@ class UpdateChildrenTask(AbstractTask):
self.child.child_handle, child_cert.cert.gSKI(),
old_resources.valid_until, irdb_resources.valid_until)
child_cert.sql_delete()
- self.publisher.withdraw(
- cls = rpki.publication.certificate_elt,
+ self.publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ old_obj = child_cert.cert,
repository = ca.parent.repository)
ca_detail.generate_manifest(publisher = self.publisher)
diff --git a/rpki/rpkidb/__init__.py b/rpki/rpkidb/__init__.py
new file mode 100644
index 00000000..7764913c
--- /dev/null
+++ b/rpki/rpkidb/__init__.py
@@ -0,0 +1,3 @@
+# $Id$
+#
+# Placeholder for rpkidb Django models not yet written.
diff --git a/rpki/rtr/bgpdump.py b/rpki/rtr/bgpdump.py
index fc3ae9df..5ffabc4d 100755
--- a/rpki/rtr/bgpdump.py
+++ b/rpki/rtr/bgpdump.py
@@ -295,7 +295,7 @@ def bgpdump_server_main(args):
rpki.rtr.server.read_current = clock.read_current
try:
- server = rpki.rtr.server.ServerChannel(logger = logger)
+ server = rpki.rtr.server.ServerChannel(logger = logger, refresh = args.refresh, retry = args.retry, expire = args.expire)
old_serial = server.get_serial()
logger.debug("[Starting at serial %d (%s)]", old_serial, old_serial)
while clock:
diff --git a/rpki/sql.py b/rpki/sql.py
index 31ed40ee..55e6f7cb 100644
--- a/rpki/sql.py
+++ b/rpki/sql.py
@@ -56,11 +56,12 @@ class session(object):
ping_threshold = rpki.sundial.timedelta(seconds = 60)
- def __init__(self, cfg):
+ def __init__(self, cfg, autocommit = True):
self.username = cfg.get("sql-username")
self.database = cfg.get("sql-database")
self.password = cfg.get("sql-password")
+ self.autocommit = autocommit
self.conv = MySQLdb.converters.conversions.copy()
self.conv.update({
@@ -78,7 +79,7 @@ class session(object):
passwd = self.password,
conv = self.conv)
self.cur = self.db.cursor()
- self.db.autocommit(True)
+ self.db.autocommit(self.autocommit)
self.timestamp = rpki.sundial.now()
def close(self):
@@ -113,11 +114,37 @@ class session(object):
def lastrowid(self):
return self.cur.lastrowid
+ def commit(self):
+ """
+ Sweep cache, then commit SQL.
+ """
+
+ self.sweep()
+ logger.debug("Executing SQL COMMIT")
+ self.db.commit()
+
+ def rollback(self):
+ """
+ SQL rollback, then clear cache and dirty cache.
+
+ NB: We have no way of clearing other references to cached objects,
+ so if you call this method you MUST forget any state that might
+ cause you to retain such references. This is probably tricky, and
+ is itself a good argument for switching to something like the
+ Django ORM's @commit_on_success semantics, but we do what we can.
+ """
+
+ logger.debug("Executing SQL ROLLBACK, discarding SQL cache and dirty set")
+ self.db.rollback()
+ self.dirty.clear()
+ self.cache.clear()
+
def cache_clear(self):
"""
Clear the SQL object cache. Shouldn't be necessary now that the
cache uses weak references, but should be harmless.
"""
+
logger.debug("Clearing SQL cache")
self.assert_pristine()
self.cache.clear()
@@ -126,14 +153,15 @@ class session(object):
"""
Assert that there are no dirty objects in the cache.
"""
+
assert not self.dirty, "Dirty objects in SQL cache: %s" % self.dirty
def sweep(self):
"""
Write any dirty objects out to SQL.
"""
+
for s in self.dirty.copy():
- #if s.sql_cache_debug:
logger.debug("Sweeping (%s) %r", "deleting" if s.sql_deleted else "storing", s)
if s.sql_deleted:
s.sql_delete()
@@ -150,6 +178,7 @@ class template(object):
"""
Build a SQL template.
"""
+
type_map = dict((x[0], x[1]) for x in data_columns if isinstance(x, tuple))
data_columns = tuple(isinstance(x, tuple) and x[0] or x for x in data_columns)
columns = (index_column,) + data_columns
@@ -220,6 +249,7 @@ class sql_persistent(object):
"""
Fetch one object from SQL, based on an arbitrary SQL WHERE expression.
"""
+
results = cls.sql_fetch_where(gctx, where, args, also_from)
if len(results) == 0:
return None
@@ -235,6 +265,7 @@ class sql_persistent(object):
"""
Fetch all objects of this type from SQL.
"""
+
return cls.sql_fetch_where(gctx, None)
@classmethod
@@ -242,6 +273,7 @@ class sql_persistent(object):
"""
Fetch objects of this type matching an arbitrary SQL WHERE expression.
"""
+
if where is None:
assert args is None and also_from is None
if cls.sql_debug:
@@ -269,6 +301,7 @@ class sql_persistent(object):
"""
Initialize one Python object from the result of a SQL query.
"""
+
self = cls()
self.gctx = gctx
self.sql_decode(dict(zip(cls.sql_template.columns, row)))
@@ -281,6 +314,7 @@ class sql_persistent(object):
"""
Mark this object as needing to be written back to SQL.
"""
+
if self.sql_cache_debug and not self.sql_is_dirty:
logger.debug("Marking %r SQL dirty", self)
self.gctx.sql.dirty.add(self)
@@ -289,6 +323,7 @@ class sql_persistent(object):
"""
Mark this object as not needing to be written back to SQL.
"""
+
if self.sql_cache_debug and self.sql_is_dirty:
logger.debug("Marking %r SQL clean", self)
self.gctx.sql.dirty.discard(self)
@@ -298,12 +333,14 @@ class sql_persistent(object):
"""
Query whether this object needs to be written back to SQL.
"""
+
return self in self.gctx.sql.dirty
def sql_mark_deleted(self):
"""
Mark this object as needing to be deleted in SQL.
"""
+
self.sql_deleted = True
self.sql_mark_dirty()
@@ -311,6 +348,7 @@ class sql_persistent(object):
"""
Store this object to SQL.
"""
+
args = self.sql_encode()
if not self.sql_in_db:
if self.sql_debug:
@@ -333,6 +371,7 @@ class sql_persistent(object):
"""
Delete this object from SQL.
"""
+
if self.sql_in_db:
id = getattr(self, self.sql_template.index) # pylint: disable=W0622
if self.sql_debug:
@@ -352,6 +391,7 @@ class sql_persistent(object):
mapping between column names in SQL and attribute names in Python.
If you need something fancier, override this.
"""
+
d = dict((a, getattr(self, a, None)) for a in self.sql_template.columns)
for i in self.sql_template.map:
if d.get(i) is not None:
@@ -365,6 +405,7 @@ class sql_persistent(object):
between column names in SQL and attribute names in Python. If you
need something fancier, override this.
"""
+
for a in self.sql_template.columns:
if vals.get(a) is not None and a in self.sql_template.map:
setattr(self, a, self.sql_template.map[a].from_sql(vals[a]))
@@ -375,18 +416,21 @@ class sql_persistent(object):
"""
Customization hook.
"""
+
pass
def sql_insert_hook(self):
"""
Customization hook.
"""
+
pass
def sql_update_hook(self):
"""
Customization hook.
"""
+
self.sql_delete_hook()
self.sql_insert_hook()
@@ -394,6 +438,7 @@ class sql_persistent(object):
"""
Customization hook.
"""
+
pass
diff --git a/rpki/sql_schemas.py b/rpki/sql_schemas.py
index 07037970..fc262f12 100644
--- a/rpki/sql_schemas.py
+++ b/rpki/sql_schemas.py
@@ -2,35 +2,24 @@
## @var rpkid
## SQL schema rpkid
-rpkid = '''-- $Id: rpkid.sql 5845 2014-05-29 22:31:15Z sra $
+rpkid = '''-- $Id: rpkid.sql 5881 2014-07-03 16:55:02Z sra $
--- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
-
--- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- SQL objects needed by the RPKI engine (rpkid.py).
@@ -256,50 +245,39 @@ CREATE TABLE ee_cert (
## @var pubd
## SQL schema pubd
-pubd = '''-- $Id: pubd.sql 5757 2014-04-05 22:42:12Z sra $
+pubd = '''-- $Id: pubd.sql 5914 2014-08-06 22:52:28Z sra $
--- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
---
--- Permission to use, copy, modify, and distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
-
--- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- SQL objects needed by pubd.py.
--- The config table is weird because we're really only using it
--- to store one BPKI CRL, but putting this here lets us use a lot of
--- existing machinery and the alternatives are whacky in other ways.
+-- Old tables that should just be flushed if present at all.
-DROP TABLE IF EXISTS client;
DROP TABLE IF EXISTS config;
+DROP TABLE IF EXISTS snapshot;
-CREATE TABLE config (
- config_id SERIAL NOT NULL,
- bpki_crl LONGBLOB,
- PRIMARY KEY (config_id)
-) ENGINE=InnoDB;
+-- DROP TABLE commands must be in correct (reverse dependency) order
+-- to satisfy FOREIGN KEY constraints.
+
+DROP TABLE IF EXISTS object;
+DROP TABLE IF EXISTS delta;
+DROP TABLE IF EXISTS session;
+DROP TABLE IF EXISTS client;
CREATE TABLE client (
client_id SERIAL NOT NULL,
@@ -312,6 +290,43 @@ CREATE TABLE client (
UNIQUE (client_handle)
) ENGINE=InnoDB;
+CREATE TABLE session (
+ session_id SERIAL NOT NULL,
+ uuid VARCHAR(36) NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ snapshot LONGTEXT,
+ hash CHAR(64),
+ PRIMARY KEY (session_id),
+ UNIQUE (uuid)
+) ENGINE=InnoDB;
+
+CREATE TABLE delta (
+ delta_id SERIAL NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ xml LONGTEXT NOT NULL,
+ hash CHAR(64) NOT NULL,
+ expires DATETIME NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (delta_id),
+ CONSTRAINT delta_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE object (
+ object_id SERIAL NOT NULL,
+ uri VARCHAR(255) NOT NULL,
+ der LONGBLOB NOT NULL,
+ hash CHAR(64) NOT NULL,
+ client_id BIGINT UNSIGNED NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (object_id),
+ CONSTRAINT object_client_id
+ FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE,
+ CONSTRAINT object_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE,
+ UNIQUE (session_id, hash)
+) ENGINE=InnoDB;
+
-- Local Variables:
-- indent-tabs-mode: nil
-- End:
diff --git a/rpki/sundial.py b/rpki/sundial.py
index 7be122c8..60037277 100644
--- a/rpki/sundial.py
+++ b/rpki/sundial.py
@@ -51,6 +51,7 @@ def now():
"""
Get current timestamp.
"""
+
return datetime.utcnow()
class ParseFailure(Exception):
@@ -69,6 +70,7 @@ class datetime(pydatetime.datetime):
Convert to seconds from epoch (like time.time()). Conversion
method is a bit silly, but avoids time module timezone whackiness.
"""
+
return int(self.strftime("%s"))
@classmethod
@@ -76,6 +78,7 @@ class datetime(pydatetime.datetime):
"""
Convert from XML time representation.
"""
+
if x is None:
return None
else:
@@ -85,6 +88,7 @@ class datetime(pydatetime.datetime):
"""
Convert to XML time representation.
"""
+
return self.strftime("%Y-%m-%dT%H:%M:%SZ")
def __str__(self):
@@ -96,6 +100,7 @@ class datetime(pydatetime.datetime):
Convert a datetime.datetime object into this subclass. This is
whacky due to the weird constructors for datetime.
"""
+
return cls.combine(x.date(), x.time())
def to_datetime(self):
@@ -104,6 +109,7 @@ class datetime(pydatetime.datetime):
shouldn't be necessary, but convincing SQL interfaces to use
subclasses of datetime can be hard.
"""
+
return pydatetime.datetime(year = self.year, month = self.month, day = self.day,
hour = self.hour, minute = self.minute, second = self.second,
microsecond = 0, tzinfo = None)
@@ -115,6 +121,7 @@ class datetime(pydatetime.datetime):
Convert from the format OpenSSL's command line tool uses into this
subclass. May require rewriting if we run into locale problems.
"""
+
if x.startswith("notBefore=") or x.startswith("notAfter="):
x = x.partition("=")[2]
return cls.strptime(x, "%b %d %H:%M:%S %Y GMT")
@@ -124,24 +131,28 @@ class datetime(pydatetime.datetime):
"""
Convert from SQL storage format.
"""
+
return cls.from_datetime(x)
def to_sql(self):
"""
Convert to SQL storage format.
"""
+
return self.to_datetime()
def later(self, other):
"""
Return the later of two timestamps.
"""
+
return other if other > self else self
def earlier(self, other):
"""
Return the earlier of two timestamps.
"""
+
return other if other < self else self
def __add__(self, y): return _cast(pydatetime.datetime.__add__(self, y))
@@ -216,6 +227,7 @@ class timedelta(pydatetime.timedelta):
"""
Parse text into a timedelta object.
"""
+
if not isinstance(arg, str):
return cls(seconds = arg)
elif arg.isdigit():
@@ -237,6 +249,7 @@ class timedelta(pydatetime.timedelta):
"""
Convert a timedelta interval to seconds.
"""
+
return self.days * 24 * 60 * 60 + self.seconds
@classmethod
@@ -244,6 +257,7 @@ class timedelta(pydatetime.timedelta):
"""
Convert a datetime.timedelta object into this subclass.
"""
+
return cls(days = x.days, seconds = x.seconds, microseconds = x.microseconds)
def __abs__(self): return _cast(pydatetime.timedelta.__abs__(self))
@@ -264,6 +278,7 @@ def _cast(x):
"""
Cast result of arithmetic operations back into correct subtype.
"""
+
if isinstance(x, pydatetime.datetime):
return datetime.from_datetime(x)
if isinstance(x, pydatetime.timedelta):
diff --git a/rpki/up_down.py b/rpki/up_down.py
index 73a0ae99..7b392640 100644
--- a/rpki/up_down.py
+++ b/rpki/up_down.py
@@ -31,74 +31,30 @@ import rpki.log
import rpki.xml_utils
import rpki.relaxng
-logger = logging.getLogger(__name__)
-
-xmlns = rpki.relaxng.up_down.xmlns
-nsmap = rpki.relaxng.up_down.nsmap
-
-class base_elt(object):
- """
- Generic PDU object.
-
- Virtual class, just provides some default methods.
- """
-
- def startElement(self, stack, name, attrs):
- """
- Ignore startElement() if there's no specific handler.
-
- Some elements have no attributes and we only care about their
- text content.
- """
- pass
+from lxml.etree import Element, SubElement, tostring as ElementToString
- def endElement(self, stack, name, text):
- """
- Ignore endElement() if there's no specific handler.
+logger = logging.getLogger(__name__)
- If we don't need to do anything else, just pop the stack.
- """
- stack.pop()
- def make_elt(self, name, *attrs):
- """
- Construct a element, copying over a set of attributes.
- """
- elt = lxml.etree.Element(xmlns + name, nsmap = nsmap)
- for key in attrs:
- val = getattr(self, key, None)
- if val is not None:
- elt.set(key, str(val))
- return elt
-
- def make_b64elt(self, elt, name, value):
- """
- Construct a sub-element with Base64 text content.
- """
- if value is not None and not value.empty():
- lxml.etree.SubElement(elt, xmlns + name, nsmap = nsmap).text = value.get_Base64()
+xmlns = rpki.relaxng.up_down.xmlns
+nsmap = rpki.relaxng.up_down.nsmap
+version = "1"
- def serve_pdu(self, q_msg, r_msg, child, callback, errback):
- """
- Default PDU handler to catch unexpected types.
- """
- raise rpki.exceptions.BadQuery("Unexpected query type %s" % q_msg.type)
+tag_certificate = xmlns + "certificate"
+tag_class = xmlns + "class"
+tag_description = xmlns + "description"
+tag_issuer = xmlns + "issuer"
+tag_message = xmlns + "message"
+tag_request = xmlns + "request"
+tag_status = xmlns + "status"
- def check_response(self):
- """
- Placeholder for response checking.
- """
- pass
class multi_uri(list):
"""
- Container for a set of URIs.
+ Container for a set of URIs. This probably could be simplified.
"""
def __init__(self, ini):
- """
- Initialize a set of URIs, which includes basic some syntax checking.
- """
list.__init__(self)
if isinstance(ini, (list, tuple)):
self[:] = ini
@@ -111,624 +67,97 @@ class multi_uri(list):
raise TypeError
def __str__(self):
- """
- Convert a multi_uri back to a string representation.
- """
return ",".join(self)
def rsync(self):
"""
Find first rsync://... URI in self.
"""
+
for s in self:
if s.startswith("rsync://"):
return s
return None
-class certificate_elt(base_elt):
- """
- Up-Down protocol representation of an issued certificate.
- """
-
- def startElement(self, stack, name, attrs):
- """
- Handle attributes of <certificate/> element.
- """
- assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
- self.cert_url = multi_uri(attrs["cert_url"])
- self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
- self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4"))
- self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6"))
-
- def endElement(self, stack, name, text):
- """
- Handle text content of a <certificate/> element.
- """
- assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
- self.cert = rpki.x509.X509(Base64 = text)
- stack.pop()
-
- def toXML(self):
- """
- Generate a <certificate/> element.
- """
- elt = self.make_elt("certificate", "cert_url",
- "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")
- elt.text = self.cert.get_Base64()
- return elt
-
-class class_elt(base_elt):
- """
- Up-Down protocol representation of a resource class.
- """
-
- issuer = None
-
- def __init__(self):
- """
- Initialize class_elt.
- """
- base_elt.__init__(self)
- self.certs = []
-
- def startElement(self, stack, name, attrs):
- """
- Handle <class/> elements and their children.
- """
- if name == "certificate":
- cert = certificate_elt()
- self.certs.append(cert)
- stack.append(cert)
- cert.startElement(stack, name, attrs)
- elif name != "issuer":
- assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
- self.class_name = attrs["class_name"]
- self.cert_url = multi_uri(attrs["cert_url"])
- self.suggested_sia_head = attrs.get("suggested_sia_head")
- self.resource_set_as = rpki.resource_set.resource_set_as(attrs["resource_set_as"])
- self.resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs["resource_set_ipv4"])
- self.resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs["resource_set_ipv6"])
- self.resource_set_notafter = rpki.sundial.datetime.fromXMLtime(attrs.get("resource_set_notafter"))
-
- def endElement(self, stack, name, text):
- """
- Handle <class/> elements and their children.
- """
- if name == "issuer":
- self.issuer = rpki.x509.X509(Base64 = text)
- else:
- assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
- stack.pop()
-
- def toXML(self):
- """
- Generate a <class/> element.
- """
- elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as",
- "resource_set_ipv4", "resource_set_ipv6",
- "resource_set_notafter", "suggested_sia_head")
- elt.extend([i.toXML() for i in self.certs])
- self.make_b64elt(elt, "issuer", self.issuer)
- return elt
-
- def to_resource_bag(self):
- """
- Build a resource_bag from from this <class/> element.
- """
- return rpki.resource_set.resource_bag(self.resource_set_as,
- self.resource_set_ipv4,
- self.resource_set_ipv6,
- self.resource_set_notafter)
-
- def from_resource_bag(self, bag):
- """
- Set resources of this class element from a resource_bag.
- """
- self.resource_set_as = bag.asn
- self.resource_set_ipv4 = bag.v4
- self.resource_set_ipv6 = bag.v6
- self.resource_set_notafter = bag.valid_until
-
-class list_pdu(base_elt):
- """
- Up-Down protocol "list" PDU.
- """
-
- def toXML(self):
- """Generate (empty) payload of "list" PDU."""
- return []
-
- def serve_pdu(self, q_msg, r_msg, child, callback, errback):
- """
- Serve one "list" PDU.
- """
-
- def handle(irdb_resources):
-
- r_msg.payload = list_response_pdu()
-
- if irdb_resources.valid_until < rpki.sundial.now():
- logger.debug("Child %s's resources expired %s", child.child_handle, irdb_resources.valid_until)
- else:
- for parent in child.parents:
- for ca in parent.cas:
- ca_detail = ca.active_ca_detail
- if not ca_detail:
- logger.debug("No active ca_detail, can't issue to %s", child.child_handle)
- continue
- resources = ca_detail.latest_ca_cert.get_3779resources() & irdb_resources
- if resources.empty():
- logger.debug("No overlap between received resources and what child %s should get ([%s], [%s])",
- child.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources)
- continue
- rc = class_elt()
- rc.class_name = str(ca.ca_id)
- rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
- rc.from_resource_bag(resources)
- for child_cert in child.fetch_child_certs(ca_detail = ca_detail):
- c = certificate_elt()
- c.cert_url = multi_uri(child_cert.uri)
- c.cert = child_cert.cert
- rc.certs.append(c)
- rc.issuer = ca_detail.latest_ca_cert
- r_msg.payload.classes.append(rc)
-
- callback()
-
- self.gctx.irdb_query_child_resources(child.self.self_handle, child.child_handle, handle, errback)
-
- @classmethod
- def query(cls, parent, cb, eb):
- """
- Send a "list" query to parent.
- """
- try:
- logger.info('Sending "list" request to parent %s', parent.parent_handle)
- parent.query_up_down(cls(), cb, eb)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
- eb(e)
-
-class class_response_syntax(base_elt):
- """
- Syntax for Up-Down protocol "list_response" and "issue_response" PDUs.
- """
-
- def __init__(self):
- """
- Initialize class_response_syntax.
- """
- base_elt.__init__(self)
- self.classes = []
-
- def startElement(self, stack, name, attrs):
- """
- Handle "list_response" and "issue_response" PDUs.
- """
- assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
- c = class_elt()
- self.classes.append(c)
- stack.append(c)
- c.startElement(stack, name, attrs)
-
- def toXML(self):
- """Generate payload of "list_response" and "issue_response" PDUs."""
- return [c.toXML() for c in self.classes]
-class list_response_pdu(class_response_syntax):
- """
- Up-Down protocol "list_response" PDU.
- """
- pass
-
-class issue_pdu(base_elt):
- """
- Up-Down protocol "issue" PDU.
- """
+error_response_codes = {
+ 1101 : "Already processing request",
+ 1102 : "Version number error",
+ 1103 : "Unrecognised request type",
+ 1201 : "Request - no such resource class",
+ 1202 : "Request - no resources allocated in resource class",
+ 1203 : "Request - badly formed certificate request",
+ 1301 : "Revoke - no such resource class",
+ 1302 : "Revoke - no such key",
+ 2001 : "Internal Server Error - Request not performed" }
- def startElement(self, stack, name, attrs):
- """
- Handle "issue" PDU.
- """
- assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
- self.class_name = attrs["class_name"]
- self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
- self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4"))
- self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6"))
- def endElement(self, stack, name, text):
- """
- Handle "issue" PDU.
- """
- assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
- self.pkcs10 = rpki.x509.PKCS10(Base64 = text)
- stack.pop()
-
- def toXML(self):
- """
- Generate payload of "issue" PDU.
- """
- elt = self.make_elt("request", "class_name", "req_resource_set_as",
- "req_resource_set_ipv4", "req_resource_set_ipv6")
- elt.text = self.pkcs10.get_Base64()
- return [elt]
-
- def serve_pdu(self, q_msg, r_msg, child, callback, errback):
- """
- Serve one issue request PDU.
- """
-
- # Subsetting not yet implemented, this is the one place where we
- # have to handle it, by reporting that we're lame.
-
- if self.req_resource_set_as or \
- self.req_resource_set_ipv4 or \
- self.req_resource_set_ipv6:
- raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry")
-
- # Check the request
- self.pkcs10.check_valid_request_ca()
- ca = child.ca_from_class_name(self.class_name)
- ca_detail = ca.active_ca_detail
- if ca_detail is None:
- raise rpki.exceptions.NoActiveCA("No active CA for class %r" % self.class_name)
-
- # Check current cert, if any
-
- def got_resources(irdb_resources):
-
- if irdb_resources.valid_until < rpki.sundial.now():
- raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % (
- child.child_handle, irdb_resources.valid_until))
-
- resources = irdb_resources & ca_detail.latest_ca_cert.get_3779resources()
- resources.valid_until = irdb_resources.valid_until
- req_key = self.pkcs10.getPublicKey()
- req_sia = self.pkcs10.get_SIA()
- child_cert = child.fetch_child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True)
-
- # Generate new cert or regenerate old one if necessary
-
- publisher = rpki.rpkid.publication_queue()
-
- if child_cert is None:
- child_cert = ca_detail.issue(
- ca = ca,
- child = child,
- subject_key = req_key,
- sia = req_sia,
- resources = resources,
- publisher = publisher)
- else:
- child_cert = child_cert.reissue(
- ca_detail = ca_detail,
- sia = req_sia,
- resources = resources,
- publisher = publisher)
-
- def done():
- c = certificate_elt()
- c.cert_url = multi_uri(child_cert.uri)
- c.cert = child_cert.cert
- rc = class_elt()
- rc.class_name = self.class_name
- rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
- rc.from_resource_bag(resources)
- rc.certs.append(c)
- rc.issuer = ca_detail.latest_ca_cert
- r_msg.payload = issue_response_pdu()
- r_msg.payload.classes.append(rc)
- callback()
-
- self.gctx.sql.sweep()
- assert child_cert and child_cert.sql_in_db
- publisher.call_pubd(done, errback)
-
- self.gctx.irdb_query_child_resources(child.self.self_handle, child.child_handle, got_resources, errback)
-
- @classmethod
- def query(cls, parent, ca, ca_detail, callback, errback):
- """
- Send an "issue" request to parent associated with ca.
- """
- assert ca_detail is not None and ca_detail.state in ("pending", "active")
- self = cls()
- self.class_name = ca.parent_resource_class
- self.pkcs10 = rpki.x509.PKCS10.create(
- keypair = ca_detail.private_key_id,
- is_ca = True,
- caRepository = ca.sia_uri,
- rpkiManifest = ca_detail.manifest_uri)
- logger.info('Sending "issue" request to parent %s', parent.parent_handle)
- parent.query_up_down(self, callback, errback)
-
-class issue_response_pdu(class_response_syntax):
- """
- Up-Down protocol "issue_response" PDU.
- """
+exception_map = {
+ rpki.exceptions.NoActiveCA : 1202,
+ (rpki.exceptions.ClassNameUnknown, "revoke") : 1301,
+ rpki.exceptions.ClassNameUnknown : 1201,
+ (rpki.exceptions.NotInDatabase, "revoke") : 1302 }
- def check_response(self):
- """
- Check whether this looks like a reasonable issue_response PDU.
- XML schema should be tighter for this response.
- """
- if len(self.classes) != 1 or len(self.classes[0].certs) != 1:
- raise rpki.exceptions.BadIssueResponse
-class revoke_syntax(base_elt):
+def check_response(r_msg, q_type):
"""
- Syntax for Up-Down protocol "revoke" and "revoke_response" PDUs.
+ Additional checks beyond the XML schema for whether this looks like
+ a reasonable up-down response message.
"""
- def startElement(self, stack, name, attrs):
- """Handle "revoke" PDU."""
- self.class_name = attrs["class_name"]
- self.ski = attrs["ski"]
+ r_type = r_msg.get("type")
- def toXML(self):
- """Generate payload of "revoke" PDU."""
- return [self.make_elt("key", "class_name", "ski")]
+ if r_type == "error_response":
+ raise rpki.exceptions.UpstreamError(error_response_codes[int(r_msg.findtext(tag_status))])
-class revoke_pdu(revoke_syntax):
- """
- Up-Down protocol "revoke" PDU.
- """
+ if r_type != q_type + "_response":
+ raise UnexpectedUpDownResponse
- def get_SKI(self):
- """
- Convert g(SKI) encoding from PDU back to raw SKI.
- """
- return base64.urlsafe_b64decode(self.ski + "=")
+ if r_type == "issue_response" and (len(r_msg) != 1 or len(r_msg[0]) != 2):
+ logger.debug("Weird issue_response %r: len(r_msg) %s len(r_msg[0]) %s",
+ r_msg, len(r_msg), len(r_msg[0]) if len(r_msg) else None)
+ logger.debug("Offending message\n%s", ElementToString(r_msg))
+ raise rpki.exceptions.BadIssueResponse
- def serve_pdu(self, q_msg, r_msg, child, cb, eb):
- """
- Serve one revoke request PDU.
- """
- def done():
- r_msg.payload = revoke_response_pdu()
- r_msg.payload.class_name = self.class_name
- r_msg.payload.ski = self.ski
- cb()
-
- ca = child.ca_from_class_name(self.class_name)
- publisher = rpki.rpkid.publication_queue()
- for ca_detail in ca.ca_details:
- for child_cert in child.fetch_child_certs(ca_detail = ca_detail, ski = self.get_SKI()):
- child_cert.revoke(publisher = publisher)
- self.gctx.sql.sweep()
- publisher.call_pubd(done, eb)
-
- @classmethod
- def query(cls, ca, gski, cb, eb):
- """
- Send a "revoke" request for certificate(s) named by gski to parent associated with ca.
- """
- parent = ca.parent
- self = cls()
- self.class_name = ca.parent_resource_class
- self.ski = gski
- logger.info('Sending "revoke" request for SKI %s to parent %s', gski, parent.parent_handle)
- parent.query_up_down(self, cb, eb)
-
-class revoke_response_pdu(revoke_syntax):
+def generate_error_response(r_msg, status = 2001, description = None):
"""
- Up-Down protocol "revoke_response" PDU.
+ Generate an error response. If status is given, it specifies the
+ numeric code to use, otherwise we default to "internal error".
+ If description is specified, we use it as the description, otherwise
+ we just use the default string associated with status.
"""
- pass
-
-class error_response_pdu(base_elt):
- """
- Up-Down protocol "error_response" PDU.
- """
-
- codes = {
- 1101 : "Already processing request",
- 1102 : "Version number error",
- 1103 : "Unrecognised request type",
- 1201 : "Request - no such resource class",
- 1202 : "Request - no resources allocated in resource class",
- 1203 : "Request - badly formed certificate request",
- 1301 : "Revoke - no such resource class",
- 1302 : "Revoke - no such key",
- 2001 : "Internal Server Error - Request not performed" }
-
- exceptions = {
- rpki.exceptions.NoActiveCA : 1202,
- (rpki.exceptions.ClassNameUnknown, revoke_pdu) : 1301,
- rpki.exceptions.ClassNameUnknown : 1201,
- (rpki.exceptions.NotInDatabase, revoke_pdu) : 1302 }
-
- def __init__(self, exception = None, request_payload = None):
- """
- Initialize an error_response PDU from an exception object.
- """
- base_elt.__init__(self)
- if exception is not None:
- logger.debug("Constructing up-down error response from exception %s", exception)
- exception_type = type(exception)
- request_type = None if request_payload is None else type(request_payload)
- logger.debug("Constructing up-down error response: exception_type %s, request_type %s",
- exception_type, request_type)
- if False:
- self.status = self.exceptions.get((exception_type, request_type),
- self.exceptions.get(exception_type, 2001))
- else:
- self.status = self.exceptions.get((exception_type, request_type))
- if self.status is None:
- logger.debug("No request-type-specific match, trying exception match")
- self.status = self.exceptions.get(exception_type)
- if self.status is None:
- logger.debug("No exception match either, defaulting")
- self.status = 2001
- self.description = str(exception)
- logger.debug("Chosen status code: %s", self.status)
-
- def endElement(self, stack, name, text):
- """
- Handle "error_response" PDU.
- """
- if name == "status":
- code = int(text)
- if code not in self.codes:
- raise rpki.exceptions.BadStatusCode("%s is not a known status code" % code)
- self.status = code
- elif name == "description":
- self.description = text
- else:
- assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
- stack.pop()
- stack[-1].endElement(stack, name, text)
+ assert status in error_response_codes
+ del r_msg[:]
+ r_msg.set("type", "error_response")
+ SubElement(r_msg, tag_status).text = str(status)
+ se = SubElement(r_msg, tag_description)
+ se.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US")
+ se.text = str(description or error_response_codes[status])
- def toXML(self):
- """
- Generate payload of "error_response" PDU.
- """
- assert self.status in self.codes
- elt = self.make_elt("status")
- elt.text = str(self.status)
- payload = [elt]
- if self.description:
- elt = self.make_elt("description")
- elt.text = str(self.description)
- elt.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US")
- payload.append(elt)
- return payload
-
- def check_response(self):
- """
- Handle an error response. For now, just raise an exception,
- perhaps figure out something more clever to do later.
- """
- raise rpki.exceptions.UpstreamError(self.codes[self.status])
-class message_pdu(base_elt):
+def generate_error_response_from_exception(r_msg, e, q_type):
"""
- Up-Down protocol message wrapper PDU.
+ Construct an error response from an exception. q_type
+ specifies the kind of query to which this is a response, since the
+ same exception can generate different codes in response to different
+ queries.
"""
- version = 1
-
- name2type = {
- "list" : list_pdu,
- "list_response" : list_response_pdu,
- "issue" : issue_pdu,
- "issue_response" : issue_response_pdu,
- "revoke" : revoke_pdu,
- "revoke_response" : revoke_response_pdu,
- "error_response" : error_response_pdu }
-
- type2name = dict((v, k) for k, v in name2type.items())
-
- error_pdu_type = error_response_pdu
-
- def toXML(self):
- """
- Generate payload of message PDU.
- """
- elt = self.make_elt("message", "version", "sender", "recipient", "type")
- elt.extend(self.payload.toXML())
- return elt
-
- def startElement(self, stack, name, attrs):
- """
- Handle message PDU.
-
- Payload of the <message/> element varies depending on the "type"
- attribute, so after some basic checks we have to instantiate the
- right class object to handle whatever kind of PDU this is.
- """
- assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
- assert self.version == int(attrs["version"])
- self.sender = attrs["sender"]
- self.recipient = attrs["recipient"]
- self.type = attrs["type"]
- self.payload = self.name2type[attrs["type"]]()
- stack.append(self.payload)
-
- def __str__(self):
- """
- Convert a message PDU to a string.
- """
- return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8")
-
- def serve_top_level(self, child, callback):
- """
- Serve one message request PDU.
- """
-
- r_msg = message_pdu()
- r_msg.sender = self.recipient
- r_msg.recipient = self.sender
+ t = type(e)
+ code = (exception_map.get((t, q_type)) or exception_map.get(t) or 2001)
+ generate_error_response(r_msg, code, e)
- def done():
- r_msg.type = self.type2name[type(r_msg.payload)]
- callback(r_msg)
-
- def lose(e):
- logger.exception("Unhandled exception serving child %r", child)
- callback(self.serve_error(e))
-
- try:
- self.log_query(child)
- self.payload.serve_pdu(self, r_msg, child, done, lose)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
- lose(e)
-
- def log_query(self, child):
- """
- Log query we're handling. Separate method so rootd can override.
- """
- logger.info("Serving %s query from child %s [sender %s, recipient %s]", self.type, child.child_handle, self.sender, self.recipient)
- def serve_error(self, exception):
- """
- Generate an error_response message PDU.
- """
- r_msg = message_pdu()
- r_msg.sender = self.recipient
- r_msg.recipient = self.sender
- r_msg.payload = self.error_pdu_type(exception, self.payload)
- r_msg.type = self.type2name[type(r_msg.payload)]
- return r_msg
-
- @classmethod
- def make_query(cls, payload, sender, recipient):
- """
- Construct one message PDU.
- """
- assert not cls.type2name[type(payload)].endswith("_response")
- if sender is None:
- sender = "tweedledee"
- if recipient is None:
- recipient = "tweedledum"
- self = cls()
- self.sender = sender
- self.recipient = recipient
- self.payload = payload
- self.type = self.type2name[type(payload)]
- return self
-
-class sax_handler(rpki.xml_utils.sax_handler):
- """
- SAX handler for Up-Down protocol.
- """
-
- pdu = message_pdu
- name = "message"
- version = "1"
-
-class cms_msg(rpki.x509.XML_CMS_object):
+class cms_msg_no_sax(rpki.x509.XML_CMS_object):
"""
Class to hold a CMS-signed up-down PDU.
+
+ Name is a transition kludge: once we ditch SAX, this will become cms_msg.
"""
encoding = "UTF-8"
schema = rpki.relaxng.up_down
- saxify = sax_handler.saxify
allow_extra_certs = True
allow_extra_crls = True
diff --git a/rpki/x509.py b/rpki/x509.py
index a7e4d17a..89b598d4 100644
--- a/rpki/x509.py
+++ b/rpki/x509.py
@@ -57,6 +57,7 @@ def base64_with_linebreaks(der):
Encode DER (really, anything) as Base64 text, with linebreaks to
keep the result (sort of) readable.
"""
+
b = base64.b64encode(der)
n = len(b)
return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n"
@@ -81,6 +82,27 @@ def first_rsync_uri(xia):
return uri
return None
+def sha1(data):
+ """
+ Calculate SHA-1 digest of some data.
+ Convenience wrapper around rpki.POW.Digest class.
+ """
+
+ d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
+ d.update(data)
+ return d.digest()
+
+def sha256(data):
+ """
+ Calculate SHA-256 digest of some data.
+ Convenience wrapper around rpki.POW.Digest class.
+ """
+
+ d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
+ d.update(data)
+ return d.digest()
+
+
class X501DN(object):
"""
Class to hold an X.501 Distinguished Name.
@@ -207,12 +229,14 @@ class DER_object(object):
"""
Test whether this object is empty.
"""
+
return all(getattr(self, a, None) is None for a in self.formats)
def clear(self):
"""
Make this object empty.
"""
+
for a in self.formats + self.other_clear:
setattr(self, a, None)
self.filename = None
@@ -223,6 +247,7 @@ class DER_object(object):
"""
Initialize a DER_object.
"""
+
self.clear()
if len(kw):
self.set(**kw)
@@ -271,6 +296,7 @@ class DER_object(object):
"""
Check for updates to a DER object that auto-updates from a file.
"""
+
if self.filename is None:
return
try:
@@ -297,10 +323,19 @@ class DER_object(object):
else:
self.lastfail = None
+ @property
+ def mtime(self):
+ """
+ Retrieve os.stat().st_mtime for auto-update files.
+ """
+
+ return os.stat(self.filename).st_mtime
+
def check(self):
"""
Perform basic checks on a DER object.
"""
+
self.check_auto_update()
assert not self.empty()
@@ -309,6 +344,7 @@ class DER_object(object):
Set the POW value of this object based on a PEM input value.
Subclasses may need to override this.
"""
+
assert self.empty()
self.POW = self.POW_class.pemRead(pem)
@@ -317,6 +353,7 @@ class DER_object(object):
Get the DER value of this object.
Subclasses may need to override this method.
"""
+
self.check()
if self.DER:
return self.DER
@@ -330,6 +367,7 @@ class DER_object(object):
Get the rpki.POW value of this object.
Subclasses may need to override this method.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = self.POW_class.derRead(self.get_DER())
@@ -339,18 +377,21 @@ class DER_object(object):
"""
Get the Base64 encoding of the DER value of this object.
"""
+
return base64_with_linebreaks(self.get_DER())
def get_PEM(self):
"""
Get the PEM representation of this object.
"""
+
return self.get_POW().pemWrite()
def __cmp__(self, other):
"""
Compare two DER-encoded objects.
"""
+
if self is None and other is None:
return 0
elif self is None:
@@ -367,6 +408,7 @@ class DER_object(object):
Return hexadecimal string representation of SKI for this object.
Only work for subclasses that implement get_SKI().
"""
+
ski = self.get_SKI()
return ":".join(("%02X" % ord(i) for i in ski)) if ski else ""
@@ -375,6 +417,7 @@ class DER_object(object):
Calculate g(SKI) for this object. Only work for subclasses
that implement get_SKI().
"""
+
return base64.urlsafe_b64encode(self.get_SKI()).rstrip("=")
def hAKI(self):
@@ -382,6 +425,7 @@ class DER_object(object):
Return hexadecimal string representation of AKI for this
object. Only work for subclasses that implement get_AKI().
"""
+
aki = self.get_AKI()
return ":".join(("%02X" % ord(i) for i in aki)) if aki else ""
@@ -390,24 +434,28 @@ class DER_object(object):
Calculate g(AKI) for this object. Only work for subclasses
that implement get_AKI().
"""
+
return base64.urlsafe_b64encode(self.get_AKI()).rstrip("=")
def get_AKI(self):
"""
Get the AKI extension from this object, if supported.
"""
+
return self.get_POW().getAKI()
def get_SKI(self):
"""
Get the SKI extension from this object, if supported.
"""
+
return self.get_POW().getSKI()
def get_EKU(self):
"""
Get the Extended Key Usage extension from this object, if supported.
"""
+
return self.get_POW().getEKU()
def get_SIA(self):
@@ -415,6 +463,7 @@ class DER_object(object):
Get the SIA extension from this object. Only works for subclasses
that support getSIA().
"""
+
return self.get_POW().getSIA()
def get_sia_directory_uri(self):
@@ -422,6 +471,7 @@ class DER_object(object):
Get SIA directory (id-ad-caRepository) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[0])
@@ -430,6 +480,7 @@ class DER_object(object):
Get SIA manifest (id-ad-rpkiManifest) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[1])
@@ -438,6 +489,7 @@ class DER_object(object):
Get SIA object (id-ad-signedObject) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[2])
@@ -446,6 +498,7 @@ class DER_object(object):
Get the SIA extension from this object. Only works for subclasses
that support getAIA().
"""
+
return self.get_POW().getAIA()
def get_aia_uri(self):
@@ -453,6 +506,7 @@ class DER_object(object):
Get AIA (id-ad-caIssuers) URI from this object.
Only works for subclasses that support getAIA().
"""
+
return first_rsync_uri(self.get_POW().getAIA())
def get_basicConstraints(self):
@@ -460,6 +514,7 @@ class DER_object(object):
Get the basicConstraints extension from this object. Only works
for subclasses that support getExtension().
"""
+
return self.get_POW().getBasicConstraints()
def is_CA(self):
@@ -467,6 +522,7 @@ class DER_object(object):
Return True if and only if object has the basicConstraints
extension and its cA value is true.
"""
+
basicConstraints = self.get_basicConstraints()
return basicConstraints is not None and basicConstraints[0]
@@ -474,6 +530,7 @@ class DER_object(object):
"""
Get RFC 3779 resources as rpki.resource_set objects.
"""
+
resources = rpki.resource_set.resource_bag.from_POW_rfc3779(self.get_POW().getRFC3779())
try:
resources.valid_until = self.getNotAfter()
@@ -486,12 +543,14 @@ class DER_object(object):
"""
Convert from SQL storage format.
"""
+
return cls(DER = x)
def to_sql(self):
"""
Convert to SQL storage format.
"""
+
return self.get_DER()
def dumpasn1(self):
@@ -522,11 +581,11 @@ class DER_object(object):
provide more information, but should make sure to include at least
this information at the start of the tracking line.
"""
+
try:
- d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
- d.update(self.get_DER())
- return "%s %s %s" % (uri, self.creation_timestamp,
- "".join(("%02X" % ord(b) for b in d.digest())))
+ return "%s %s %s" % (uri,
+ self.creation_timestamp,
+ "".join(("%02X" % ord(b) for b in sha1(self.get_DER()))))
except: # pylint: disable=W0702
return uri
@@ -534,12 +593,14 @@ class DER_object(object):
"""
Pickling protocol -- pickle the DER encoding.
"""
+
return self.get_DER()
def __setstate__(self, state):
"""
Pickling protocol -- unpickle the DER encoding.
"""
+
self.set(DER = state)
class X509(DER_object):
@@ -559,48 +620,56 @@ class X509(DER_object):
"""
Get the issuer of this certificate.
"""
+
return X501DN.from_POW(self.get_POW().getIssuer())
def getSubject(self):
"""
Get the subject of this certificate.
"""
+
return X501DN.from_POW(self.get_POW().getSubject())
def getNotBefore(self):
"""
Get the inception time of this certificate.
"""
+
return self.get_POW().getNotBefore()
def getNotAfter(self):
"""
Get the expiration time of this certificate.
"""
+
return self.get_POW().getNotAfter()
def getSerial(self):
"""
Get the serial number of this certificate.
"""
+
return self.get_POW().getSerial()
def getPublicKey(self):
"""
Extract the public key from this certificate.
"""
+
return PublicKey(POW = self.get_POW().getPublicKey())
def get_SKI(self):
"""
Get the SKI extension from this object.
"""
+
return self.get_POW().getSKI()
def expired(self):
"""
Test whether this certificate has expired.
"""
+
return self.getNotAfter() <= rpki.sundial.now()
def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter,
@@ -743,6 +812,7 @@ class X509(DER_object):
"""
Issue a BPKI certificate with values taking from an existing certificate.
"""
+
return self.bpki_certify(
keypair = keypair,
subject_name = source_cert.getSubject(),
@@ -759,6 +829,7 @@ class X509(DER_object):
"""
Issue a self-signed BPKI CA certificate.
"""
+
return cls._bpki_certify(
keypair = keypair,
issuer_name = subject_name,
@@ -775,6 +846,7 @@ class X509(DER_object):
"""
Issue a normal BPKI certificate.
"""
+
assert keypair.get_public() == self.getPublicKey()
return self._bpki_certify(
keypair = keypair,
@@ -833,6 +905,7 @@ class X509(DER_object):
allowed cases. So this method allows X509, None, lists, and
tuples, and returns a tuple of X509 objects.
"""
+
if isinstance(chain, cls):
chain = (chain,)
return tuple(x for x in chain if x is not None)
@@ -842,6 +915,7 @@ class X509(DER_object):
"""
Time at which this object was created.
"""
+
return self.getNotBefore()
class PKCS10(DER_object):
@@ -869,6 +943,7 @@ class PKCS10(DER_object):
"""
Get the DER value of this certification request.
"""
+
self.check()
if self.DER:
return self.DER
@@ -881,6 +956,7 @@ class PKCS10(DER_object):
"""
Get the rpki.POW value of this certification request.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.PKCS10.derRead(self.get_DER())
@@ -890,18 +966,21 @@ class PKCS10(DER_object):
"""
Extract the subject name from this certification request.
"""
+
return X501DN.from_POW(self.get_POW().getSubject())
def getPublicKey(self):
"""
Extract the public key from this certification request.
"""
+
return PublicKey(POW = self.get_POW().getPublicKey())
def get_SKI(self):
"""
Compute SKI for public key from this certification request.
"""
+
return self.getPublicKey().get_SKI()
@@ -1150,6 +1229,7 @@ class PrivateKey(DER_object):
"""
Get the DER value of this keypair.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1162,6 +1242,7 @@ class PrivateKey(DER_object):
"""
Get the rpki.POW value of this keypair.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.Asymmetric.derReadPrivate(self.get_DER())
@@ -1171,12 +1252,14 @@ class PrivateKey(DER_object):
"""
Get the PEM representation of this keypair.
"""
+
return self.get_POW().pemWritePrivate()
def _set_PEM(self, pem):
"""
Set the POW value of this keypair from a PEM string.
"""
+
assert self.empty()
self.POW = self.POW_class.pemReadPrivate(pem)
@@ -1184,18 +1267,21 @@ class PrivateKey(DER_object):
"""
Get the DER encoding of the public key from this keypair.
"""
+
return self.get_POW().derWritePublic()
def get_SKI(self):
"""
Calculate the SKI of this keypair.
"""
+
return self.get_POW().calculateSKI()
def get_public(self):
"""
Convert the public key of this keypair into a PublicKey object.
"""
+
return PublicKey(DER = self.get_public_DER())
class PublicKey(DER_object):
@@ -1209,6 +1295,7 @@ class PublicKey(DER_object):
"""
Get the DER value of this public key.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1221,6 +1308,7 @@ class PublicKey(DER_object):
"""
Get the rpki.POW value of this public key.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.Asymmetric.derReadPublic(self.get_DER())
@@ -1230,12 +1318,14 @@ class PublicKey(DER_object):
"""
Get the PEM representation of this public key.
"""
+
return self.get_POW().pemWritePublic()
def _set_PEM(self, pem):
"""
Set the POW value of this public key from a PEM string.
"""
+
assert self.empty()
self.POW = self.POW_class.pemReadPublic(pem)
@@ -1243,6 +1333,7 @@ class PublicKey(DER_object):
"""
Calculate the SKI of this public key.
"""
+
return self.get_POW().calculateSKI()
class KeyParams(DER_object):
@@ -1266,6 +1357,7 @@ class RSA(PrivateKey):
"""
Generate a new keypair.
"""
+
if not quiet:
logger.debug("Generating new %d-bit RSA key", keylength)
if generate_insecure_debug_only_rsa_key is not None:
@@ -1348,6 +1440,7 @@ class CMS_object(DER_object):
"""
Get the DER value of this CMS_object.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1360,6 +1453,7 @@ class CMS_object(DER_object):
"""
Get the rpki.POW value of this CMS_object.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = self.POW_class.derRead(self.get_DER())
@@ -1369,6 +1463,7 @@ class CMS_object(DER_object):
"""
Extract signingTime from CMS signed attributes.
"""
+
return self.get_POW().signingTime()
def verify(self, ta):
@@ -1540,6 +1635,7 @@ class CMS_object(DER_object):
"""
Time at which this object was created.
"""
+
return self.get_signingTime()
@@ -1561,6 +1657,7 @@ class Wrapped_CMS_object(CMS_object):
"""
Get the inner content of this Wrapped_CMS_object.
"""
+
if self.content is None:
raise rpki.exceptions.CMSContentNotSet("Inner content of CMS object %r is not set" % self)
return self.content
@@ -1569,6 +1666,7 @@ class Wrapped_CMS_object(CMS_object):
"""
Set the (inner) content of this Wrapped_CMS_object, clearing the wrapper.
"""
+
self.clear()
self.content = content
@@ -1651,12 +1749,14 @@ class SignedManifest(DER_CMS_object):
"""
Get thisUpdate value from this manifest.
"""
+
return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this manifest.
"""
+
return self.get_POW().getNextUpdate()
@classmethod
@@ -1667,9 +1767,7 @@ class SignedManifest(DER_CMS_object):
filelist = []
for name, obj in names_and_objs:
- d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
- d.update(obj.get_DER())
- filelist.append((name.rpartition("/")[2], d.digest()))
+ filelist.append((name.rpartition("/")[2], sha256(obj.get_DER())))
filelist.sort(key = lambda x: x[0])
obj = cls.POW_class()
@@ -1697,6 +1795,7 @@ class ROA(DER_CMS_object):
"""
Build a ROA.
"""
+
ipv4 = ipv4.to_POW_roa_tuple() if ipv4 else None
ipv6 = ipv6.to_POW_roa_tuple() if ipv6 else None
obj = cls.POW_class()
@@ -1712,6 +1811,7 @@ class ROA(DER_CMS_object):
Return a string containing data we want to log when tracking how
objects move through the RPKI system.
"""
+
msg = DER_CMS_object.tracking_data(self, uri)
try:
self.extract_if_needed()
@@ -1788,12 +1888,13 @@ class XML_CMS_object(Wrapped_CMS_object):
## @var check_outbound_schema
# If set, perform RelaxNG schema check on outbound messages.
- check_outbound_schema = False
+ check_outbound_schema = True
def encode(self):
"""
Encode inner content for signing.
"""
+
return lxml.etree.tostring(self.get_content(),
pretty_print = True,
encoding = self.encoding,
@@ -1803,12 +1904,14 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Decode XML and set inner content.
"""
+
self.content = lxml.etree.fromstring(xml)
def pretty_print_content(self):
"""
Pretty print XML content of this message.
"""
+
return lxml.etree.tostring(self.get_content(),
pretty_print = True,
encoding = self.encoding,
@@ -1818,6 +1921,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Handle XML RelaxNG schema check.
"""
+
try:
self.schema.assertValid(self.get_content())
except lxml.etree.DocumentInvalid:
@@ -1830,6 +1934,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Write DER of current message to disk, for debugging.
"""
+
f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
f.write(self.get_DER())
f.close()
@@ -1838,6 +1943,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Wrap an XML PDU in CMS and return its DER encoding.
"""
+
if self.saxify is None:
self.set_content(msg)
else:
@@ -1853,6 +1959,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Unwrap a CMS-wrapped XML PDU and return Python objects.
"""
+
if self.dump_inbound_cms:
self.dump_inbound_cms.dump(self)
self.verify(ta)
@@ -1869,6 +1976,7 @@ class XML_CMS_object(Wrapped_CMS_object):
timestamp. Raises an exception if the recorded timestamp is more
recent, otherwise returns the new timestamp.
"""
+
new_timestamp = self.get_signingTime()
if timestamp is not None and timestamp > new_timestamp:
if context:
@@ -1884,6 +1992,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"last_cms_timestamp" field of an SQL object and stores the new
timestamp back in that same field.
"""
+
obj.last_cms_timestamp = self.check_replay(obj.last_cms_timestamp, *context)
obj.sql_mark_dirty()
@@ -1913,6 +2022,7 @@ class Ghostbuster(Wrapped_CMS_object):
Encode inner content for signing. At the moment we're treating
the VCard as an opaque byte string, so no encoding needed here.
"""
+
return self.get_content()
def decode(self, vcard):
@@ -1920,6 +2030,7 @@ class Ghostbuster(Wrapped_CMS_object):
Decode XML and set inner content. At the moment we're treating
the VCard as an opaque byte string, so no encoding needed here.
"""
+
self.content = vcard
@classmethod
@@ -1927,6 +2038,7 @@ class Ghostbuster(Wrapped_CMS_object):
"""
Build a Ghostbuster record.
"""
+
self = cls()
self.set_content(vcard)
self.sign(keypair, certs)
@@ -1944,6 +2056,7 @@ class CRL(DER_object):
"""
Get the DER value of this CRL.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1956,6 +2069,7 @@ class CRL(DER_object):
"""
Get the rpki.POW value of this CRL.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.CRL.derRead(self.get_DER())
@@ -1965,24 +2079,28 @@ class CRL(DER_object):
"""
Get thisUpdate value from this CRL.
"""
+
return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this CRL.
"""
+
return self.get_POW().getNextUpdate()
def getIssuer(self):
"""
Get issuer value of this CRL.
"""
+
return X501DN.from_POW(self.get_POW().getIssuer())
def getCRLNumber(self):
"""
Get CRL Number value for this CRL.
"""
+
return self.get_POW().getCRLNumber()
@classmethod
@@ -1990,6 +2108,7 @@ class CRL(DER_object):
"""
Generate a new CRL.
"""
+
crl = rpki.POW.CRL()
crl.setVersion(version)
crl.setIssuer(issuer.getSubject().get_POW())
@@ -2006,6 +2125,7 @@ class CRL(DER_object):
"""
Time at which this object was created.
"""
+
return self.getThisUpdate()
## @var uri_dispatch_map
@@ -2024,4 +2144,5 @@ def uri_dispatch(uri):
"""
Return the Python class object corresponding to a given URI.
"""
+
return uri_dispatch_map[os.path.splitext(uri)[1]]
diff --git a/rpki/xml_utils.py b/rpki/xml_utils.py
index c276ce98..9b443d0b 100644
--- a/rpki/xml_utils.py
+++ b/rpki/xml_utils.py
@@ -32,11 +32,15 @@
XML utilities.
"""
+import logging
import xml.sax
import lxml.sax
import lxml.etree
import rpki.exceptions
+logger = logging.getLogger(__name__)
+
+
class sax_handler(xml.sax.handler.ContentHandler):
"""
SAX handler for RPKI protocols.
@@ -56,6 +60,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Initialize SAX handler.
"""
+
xml.sax.handler.ContentHandler.__init__(self)
self.text = ""
self.stack = []
@@ -64,18 +69,21 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Redirect startElementNS() events to startElement().
"""
+
return self.startElement(name[1], attrs)
def endElementNS(self, name, qname):
"""
Redirect endElementNS() events to endElement().
"""
+
return self.endElement(name[1])
def characters(self, content):
"""
Accumulate a chuck of element content (text).
"""
+
self.text += content
def startElement(self, name, attrs):
@@ -111,6 +119,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
Handle endElement() events. Mostly this means handling any
accumulated element text.
"""
+
text = self.text.encode("ascii").strip()
self.text = ""
self.stack[-1].endElement(self.stack, name, text)
@@ -120,6 +129,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Create a one-off SAX parser, parse an ETree, return the result.
"""
+
self = cls()
lxml.sax.saxify(elt, self)
return self.result
@@ -128,6 +138,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Handle top-level PDU for this protocol.
"""
+
assert name == self.name and attrs["version"] == self.version
return self.pdu()
@@ -154,6 +165,7 @@ class base_elt(object):
"""
Default startElement() handler: just process attributes.
"""
+
if name not in self.elements:
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
@@ -162,6 +174,7 @@ class base_elt(object):
"""
Default endElement() handler: just pop the stack.
"""
+
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
stack.pop()
@@ -169,12 +182,14 @@ class base_elt(object):
"""
Default toXML() element generator.
"""
+
return self.make_elt()
def read_attrs(self, attrs):
"""
Template-driven attribute reader.
"""
+
for key in self.attributes:
val = attrs.get(key, None)
if isinstance(val, str) and val.isdigit() and not key.endswith("_handle"):
@@ -187,6 +202,7 @@ class base_elt(object):
"""
XML element constructor.
"""
+
elt = lxml.etree.Element(self.xmlns + self.element_name, nsmap = self.nsmap)
for key in self.attributes:
val = getattr(self, key, None)
@@ -201,6 +217,7 @@ class base_elt(object):
"""
Constructor for Base64-encoded subelement.
"""
+
if value is not None and not value.empty():
lxml.etree.SubElement(elt, self.xmlns + name, nsmap = self.nsmap).text = value.get_Base64()
@@ -208,6 +225,7 @@ class base_elt(object):
"""
Convert a base_elt object to string format.
"""
+
return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
@classmethod
@@ -215,6 +233,7 @@ class base_elt(object):
"""
Generic PDU constructor.
"""
+
self = cls()
for k, v in kargs.items():
if isinstance(v, bool):
@@ -235,6 +254,7 @@ class text_elt(base_elt):
"""
Extract text from parsed XML.
"""
+
base_elt.endElement(self, stack, name, text)
setattr(self, self.text_attribute, text)
@@ -242,6 +262,7 @@ class text_elt(base_elt):
"""
Insert text into generated XML.
"""
+
elt = self.make_elt()
elt.text = getattr(self, self.text_attribute) or None
return elt
@@ -258,6 +279,7 @@ class data_elt(base_elt):
that sub-elements are Base64-encoded using the sql_template
mechanism.
"""
+
if name in self.elements:
elt_type = self.sql_template.map.get(name)
assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack)
@@ -271,6 +293,7 @@ class data_elt(base_elt):
Default element generator for SQL-based objects. This assumes
that sub-elements are Base64-encoded DER objects.
"""
+
elt = self.make_elt()
for i in self.elements:
self.make_b64elt(elt, i, getattr(self, i, None))
@@ -280,6 +303,7 @@ class data_elt(base_elt):
"""
Construct a reply PDU.
"""
+
if r_pdu is None:
r_pdu = self.__class__()
self.make_reply_clone_hook(r_pdu)
@@ -297,6 +321,7 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
pass
def serve_fetch_one(self):
@@ -304,6 +329,7 @@ class data_elt(base_elt):
Find the object on which a get, set, or destroy method should
operate.
"""
+
r = self.serve_fetch_one_maybe()
if r is None:
raise rpki.exceptions.NotFound
@@ -313,12 +339,14 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
cb()
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Overridable hook.
"""
+
cb()
def serve_create(self, r_msg, cb, eb):
@@ -371,6 +399,7 @@ class data_elt(base_elt):
"""
Handle a get action.
"""
+
r_pdu = self.serve_fetch_one()
self.make_reply(r_pdu)
r_msg.append(r_pdu)
@@ -380,6 +409,7 @@ class data_elt(base_elt):
"""
Handle a list action for non-self objects.
"""
+
for r_pdu in self.serve_fetch_all():
self.make_reply(r_pdu)
r_msg.append(r_pdu)
@@ -389,12 +419,14 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
cb()
def serve_destroy(self, r_msg, cb, eb):
"""
Handle a destroy action.
"""
+
def done():
db_pdu.sql_delete()
r_msg.append(self.make_reply())
@@ -406,19 +438,17 @@ class data_elt(base_elt):
"""
Action dispatch handler.
"""
- dispatch = { "create" : self.serve_create,
- "set" : self.serve_set,
- "get" : self.serve_get,
- "list" : self.serve_list,
- "destroy" : self.serve_destroy }
- if self.action not in dispatch:
+
+ method = getattr(self, "serve_" + self.action, None)
+ if method is None:
raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action)
- dispatch[self.action](r_msg, cb, eb)
+ method(r_msg, cb, eb)
def unimplemented_control(self, *controls):
"""
Uniform handling for unimplemented control operations.
"""
+
unimplemented = [x for x in controls if getattr(self, x, False)]
if unimplemented:
raise rpki.exceptions.NotImplementedYet("Unimplemented control %s" % ", ".join(unimplemented))
@@ -432,6 +462,7 @@ class msg(list):
"""
Handle top-level PDU.
"""
+
if name == "msg":
assert self.version == int(attrs["version"])
self.type = attrs["type"]
@@ -445,6 +476,7 @@ class msg(list):
"""
Handle top-level PDU.
"""
+
assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
assert len(stack) == 1
stack.pop()
@@ -453,14 +485,16 @@ class msg(list):
"""
Convert msg object to string.
"""
+
return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
def toXML(self):
"""
Generate top-level PDU.
"""
+
elt = lxml.etree.Element(self.xmlns + "msg", nsmap = self.nsmap, version = str(self.version), type = self.type)
- elt.extend([i.toXML() for i in self])
+ elt.extend(i.toXML() for i in self)
return elt
@classmethod
@@ -468,6 +502,7 @@ class msg(list):
"""
Create a query PDU.
"""
+
self = cls(args)
self.type = "query"
return self
@@ -477,6 +512,7 @@ class msg(list):
"""
Create a reply PDU.
"""
+
self = cls(args)
self.type = "reply"
return self
@@ -485,10 +521,12 @@ class msg(list):
"""
Is this msg a query?
"""
+
return self.type == "query"
def is_reply(self):
"""
Is this msg a reply?
"""
+
return self.type == "reply"
diff --git a/schemas/relaxng/left-right-schema.rnc b/schemas/relaxng/left-right.rnc
index 201f8ff0..201f8ff0 100644
--- a/schemas/relaxng/left-right-schema.rnc
+++ b/schemas/relaxng/left-right.rnc
diff --git a/schemas/relaxng/left-right-schema.rng b/schemas/relaxng/left-right.rng
index c5596a2f..15dd2fa1 100644
--- a/schemas/relaxng/left-right-schema.rng
+++ b/schemas/relaxng/left-right.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: left-right-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: left-right.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI left-right protocol.
diff --git a/schemas/relaxng/myrpki.rng b/schemas/relaxng/myrpki.rng
index 8c7473eb..3beafe8f 100644
--- a/schemas/relaxng/myrpki.rng
+++ b/schemas/relaxng/myrpki.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $
RelaxNG schema for MyRPKI XML messages.
diff --git a/schemas/relaxng/publication-schema.rnc b/schemas/relaxng/publication-control.rnc
index fdf38c9e..ac59c617 100644
--- a/schemas/relaxng/publication-schema.rnc
+++ b/schemas/relaxng/publication-control.rnc
@@ -19,7 +19,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-default namespace = "http://www.hactrn.net/uris/rpki/publication-spec/"
+default namespace = "http://www.hactrn.net/uris/rpki/publication-control/"
version = "1"
@@ -32,12 +32,10 @@ start = element msg {
}
# PDUs allowed in a query
-query_elt = ( config_query | client_query | certificate_query | crl_query |
- manifest_query | roa_query | ghostbuster_query )
+query_elt = client_query
# PDUs allowed in a reply
-reply_elt = ( config_reply | client_reply | certificate_reply | crl_reply |
- manifest_reply | roa_reply | ghostbuster_reply | report_error_reply )
+reply_elt = ( client_reply | report_error_reply )
# Tag attributes for bulk operations
tag = attribute tag { xsd:token {maxLength="1024" } }
@@ -58,17 +56,7 @@ uri = attribute uri { uri_t }
# hierarchy delimiter.
object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]+" }
-# <config/> element (use restricted to repository operator)
-# config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
-
-config_payload = (element bpki_crl { base64 }?)
-
-config_query |= element config { attribute action { "set" }, tag?, config_payload }
-config_reply |= element config { attribute action { "set" }, tag? }
-config_query |= element config { attribute action { "get" }, tag? }
-config_reply |= element config { attribute action { "get" }, tag?, config_payload }
-
-# <client/> element (use restricted to repository operator)
+# <client/> element
client_handle = attribute client_handle { object_handle }
@@ -87,41 +75,6 @@ client_reply |= element client { attribute action { "list" }, tag?, client_ha
client_query |= element client { attribute action { "destroy" }, tag?, client_handle }
client_reply |= element client { attribute action { "destroy" }, tag?, client_handle }
-# <certificate/> element
-
-certificate_query |= element certificate { attribute action { "publish" }, tag?, uri, base64 }
-certificate_reply |= element certificate { attribute action { "publish" }, tag?, uri }
-certificate_query |= element certificate { attribute action { "withdraw" }, tag?, uri }
-certificate_reply |= element certificate { attribute action { "withdraw" }, tag?, uri }
-
-# <crl/> element
-
-crl_query |= element crl { attribute action { "publish" }, tag?, uri, base64 }
-crl_reply |= element crl { attribute action { "publish" }, tag?, uri }
-crl_query |= element crl { attribute action { "withdraw" }, tag?, uri }
-crl_reply |= element crl { attribute action { "withdraw" }, tag?, uri }
-
-# <manifest/> element
-
-manifest_query |= element manifest { attribute action { "publish" }, tag?, uri, base64 }
-manifest_reply |= element manifest { attribute action { "publish" }, tag?, uri }
-manifest_query |= element manifest { attribute action { "withdraw" }, tag?, uri }
-manifest_reply |= element manifest { attribute action { "withdraw" }, tag?, uri }
-
-# <roa/> element
-
-roa_query |= element roa { attribute action { "publish" }, tag?, uri, base64 }
-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/schemas/relaxng/publication-control.rng b/schemas/relaxng/publication-control.rng
new file mode 100644
index 00000000..606deb53
--- /dev/null
+++ b/schemas/relaxng/publication-control.rng
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication-control.rnc 5903 2014-07-18 17:08:13Z sra $
+
+ RelaxNG schema for RPKI publication protocol.
+
+ Copyright (C) 2012- -2014 Dragon Research Labs ("DRL")
+ Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC")
+ Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN")
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notices and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+ ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <value>1</value>
+ </define>
+ <!-- Top level PDU -->
+ <start>
+ <element name="msg">
+ <attribute name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </attribute>
+ <choice>
+ <group>
+ <attribute name="type">
+ <value>query</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
+ </group>
+ <group>
+ <attribute name="type">
+ <value>reply</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
+ </group>
+ </choice>
+ </element>
+ </start>
+ <!-- PDUs allowed in a query -->
+ <define name="query_elt">
+ <ref name="client_query"/>
+ </define>
+ <!-- PDUs allowed in a reply -->
+ <define name="reply_elt">
+ <choice>
+ <ref name="client_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
+ </define>
+ <!-- Tag attributes for bulk operations -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
+ </define>
+ <!--
+ Base64 encoded DER stuff
+ base64 = xsd:base64Binary { maxLength="512000" }
+
+ Sadly, it turns out that CRLs can in fact get longer than this for an active CA.
+ Remove length limit for now, think about whether to put it back later.
+ -->
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Publication URLs -->
+ <define name="uri_t">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </define>
+ <define name="uri">
+ <attribute name="uri">
+ <ref name="uri_t"/>
+ </attribute>
+ </define>
+ <!--
+ Handles on remote objects (replaces passing raw SQL IDs). NB:
+ Unlike the up-down protocol, handles in this protocol allow "/" as a
+ hierarchy delimiter.
+ -->
+ <define name="object_handle">
+ <data type="string">
+ <param name="maxLength">255</param>
+ <param name="pattern">[\-_A-Za-z0-9/]+</param>
+ </data>
+ </define>
+ <!-- <client/> element -->
+ <define name="client_handle">
+ <attribute name="client_handle">
+ <ref name="object_handle"/>
+ </attribute>
+ </define>
+ <define name="client_bool">
+ <optional>
+ <attribute name="clear_replay_protection">
+ <value>yes</value>
+ </attribute>
+ </optional>
+ </define>
+ <define name="client_payload">
+ <optional>
+ <attribute name="base_uri">
+ <ref name="uri_t"/>
+ </attribute>
+ </optional>
+ <optional>
+ <element name="bpki_cert">
+ <ref name="base64"/>
+ </element>
+ </optional>
+ <optional>
+ <element name="bpki_glue">
+ <ref name="base64"/>
+ </element>
+ </optional>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>create</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_bool"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>create</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>set</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_bool"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>set</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>get</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>get</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>list</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>list</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>destroy</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>destroy</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <!-- <report_error/> element -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <define name="report_error_reply">
+ <element name="report_error">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <attribute name="error_code">
+ <ref name="error"/>
+ </attribute>
+ <optional>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/publication-schema.rng b/schemas/relaxng/publication-schema.rng
deleted file mode 100644
index 482fa477..00000000
--- a/schemas/relaxng/publication-schema.rng
+++ /dev/null
@@ -1,577 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- $Id: publication-schema.rnc 5902 2014-07-18 16:37:04Z sra $
-
- RelaxNG schema for RPKI publication protocol.
-
- Copyright (C) 2012- -2014 Dragon Research Labs ("DRL")
- Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC")
- Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN")
-
- Permission to use, copy, modify, and distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notices and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--->
-<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
- <define name="version">
- <value>1</value>
- </define>
- <!-- Top level PDU -->
- <start>
- <element name="msg">
- <attribute name="version">
- <data type="positiveInteger">
- <param name="maxInclusive">1</param>
- </data>
- </attribute>
- <choice>
- <group>
- <attribute name="type">
- <value>query</value>
- </attribute>
- <zeroOrMore>
- <ref name="query_elt"/>
- </zeroOrMore>
- </group>
- <group>
- <attribute name="type">
- <value>reply</value>
- </attribute>
- <zeroOrMore>
- <ref name="reply_elt"/>
- </zeroOrMore>
- </group>
- </choice>
- </element>
- </start>
- <!-- PDUs allowed in a query -->
- <define name="query_elt">
- <choice>
- <ref name="config_query"/>
- <ref name="client_query"/>
- <ref name="certificate_query"/>
- <ref name="crl_query"/>
- <ref name="manifest_query"/>
- <ref name="roa_query"/>
- <ref name="ghostbuster_query"/>
- </choice>
- </define>
- <!-- PDUs allowed in a reply -->
- <define name="reply_elt">
- <choice>
- <ref name="config_reply"/>
- <ref name="client_reply"/>
- <ref name="certificate_reply"/>
- <ref name="crl_reply"/>
- <ref name="manifest_reply"/>
- <ref name="roa_reply"/>
- <ref name="ghostbuster_reply"/>
- <ref name="report_error_reply"/>
- </choice>
- </define>
- <!-- Tag attributes for bulk operations -->
- <define name="tag">
- <attribute name="tag">
- <data type="token">
- <param name="maxLength">1024</param>
- </data>
- </attribute>
- </define>
- <!--
- Base64 encoded DER stuff
- base64 = xsd:base64Binary { maxLength="512000" }
-
- Sadly, it turns out that CRLs can in fact get longer than this for an active CA.
- Remove length limit for now, think about whether to put it back later.
- -->
- <define name="base64">
- <data type="base64Binary"/>
- </define>
- <!-- Publication URLs -->
- <define name="uri_t">
- <data type="anyURI">
- <param name="maxLength">4096</param>
- </data>
- </define>
- <define name="uri">
- <attribute name="uri">
- <ref name="uri_t"/>
- </attribute>
- </define>
- <!--
- Handles on remote objects (replaces passing raw SQL IDs). NB:
- Unlike the up-down protocol, handles in this protocol allow "/" as a
- hierarchy delimiter.
- -->
- <define name="object_handle">
- <data type="string">
- <param name="maxLength">255</param>
- <param name="pattern">[\-_A-Za-z0-9/]+</param>
- </data>
- </define>
- <!--
- <config/> element (use restricted to repository operator)
- config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
- -->
- <define name="config_payload">
- <optional>
- <element name="bpki_crl">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <!-- <client/> element (use restricted to repository operator) -->
- <define name="client_handle">
- <attribute name="client_handle">
- <ref name="object_handle"/>
- </attribute>
- </define>
- <define name="client_bool">
- <optional>
- <attribute name="clear_replay_protection">
- <value>yes</value>
- </attribute>
- </optional>
- </define>
- <define name="client_payload">
- <optional>
- <attribute name="base_uri">
- <ref name="uri_t"/>
- </attribute>
- </optional>
- <optional>
- <element name="bpki_cert">
- <ref name="base64"/>
- </element>
- </optional>
- <optional>
- <element name="bpki_glue">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>create</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_bool"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>create</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_bool"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>list</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>list</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>destroy</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>destroy</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <!-- <certificate/> element -->
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <crl/> element -->
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <manifest/> element -->
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <roa/> element -->
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <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">
- <param name="maxLength">1024</param>
- </data>
- </define>
- <define name="report_error_reply">
- <element name="report_error">
- <optional>
- <ref name="tag"/>
- </optional>
- <attribute name="error_code">
- <ref name="error"/>
- </attribute>
- <optional>
- <data type="string">
- <param name="maxLength">512000</param>
- </data>
- </optional>
- </element>
- </define>
-</grammar>
-<!--
- Local Variables:
- indent-tabs-mode: nil
- comment-start: "# "
- comment-start-skip: "#[ \t]*"
- End:
--->
diff --git a/schemas/relaxng/publication.rnc b/schemas/relaxng/publication.rnc
new file mode 100644
index 00000000..f3d1f94e
--- /dev/null
+++ b/schemas/relaxng/publication.rnc
@@ -0,0 +1,111 @@
+# $Id$
+#
+# RelaxNG schema for RPKI publication protocol, from current I-D.
+#
+# Copyright (c) 2014 IETF Trust and the persons identified as authors
+# of the code. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# * Neither the name of Internet Society, IETF or IETF Trust, nor the
+# names of specific contributors, may be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+default namespace =
+ "http://www.hactrn.net/uris/rpki/publication-spec/"
+
+# This is version 3 of the protocol.
+
+version = "3"
+
+# Top level PDU is either a query or a reply.
+
+start |= element msg {
+ attribute version { version },
+ attribute type { "query" },
+ query_elt*
+}
+
+start |= element msg {
+ attribute version { version },
+ attribute type { "reply" },
+ reply_elt*
+}
+
+# PDUs allowed in queries and replies.
+
+query_elt = publish_query | withdraw_query | list_query
+reply_elt = publish_reply | withdraw_reply | list_reply | report_error_reply
+
+# Tag attributes for bulk operations.
+
+tag = attribute tag { xsd:token { maxLength="1024" } }
+
+# Base64 encoded DER stuff.
+
+base64 = xsd:base64Binary
+
+# Publication URIs.
+
+uri = attribute uri { xsd:anyURI { maxLength="4096" } }
+
+# Digest of objects being withdrawn
+
+hash = attribute hash { xsd:string { pattern = "[0-9a-fA-F]+" } }
+
+# Error codes.
+
+error = xsd:token { maxLength="1024" }
+
+# <publish/> element
+
+publish_query = element publish { tag?, uri, hash?, base64 }
+publish_reply = element publish { tag?, uri }
+
+# <withdraw/> element
+
+withdraw_query = element withdraw { tag?, uri, hash }
+withdraw_reply = element withdraw { tag?, uri }
+
+# <list/> element
+
+list_query = element list { tag? }
+list_reply = element list { tag?, uri, hash }
+
+# <report_error/> element
+
+report_error_reply = element report_error {
+ tag?,
+ attribute error_code { error },
+ xsd:string { maxLength="512000" }?
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# comment-start: "# "
+# comment-start-skip: "#[ \t]*"
+# End:
diff --git a/schemas/relaxng/publication.rng b/schemas/relaxng/publication.rng
new file mode 100644
index 00000000..5e72407e
--- /dev/null
+++ b/schemas/relaxng/publication.rng
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $
+
+ RelaxNG schema for RPKI publication protocol, from current I-D.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as authors
+ of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ names of specific contributors, may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <!-- This is version 3 of the protocol. -->
+ <define name="version">
+ <value>3</value>
+ </define>
+ <!-- Top level PDU is either a query or a reply. -->
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="type">
+ <value>query</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
+ </element>
+ </start>
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="type">
+ <value>reply</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- PDUs allowed in queries and replies. -->
+ <define name="query_elt">
+ <choice>
+ <ref name="publish_query"/>
+ <ref name="withdraw_query"/>
+ <ref name="list_query"/>
+ </choice>
+ </define>
+ <define name="reply_elt">
+ <choice>
+ <ref name="publish_reply"/>
+ <ref name="withdraw_reply"/>
+ <ref name="list_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
+ </define>
+ <!-- Tag attributes for bulk operations. -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Base64 encoded DER stuff. -->
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Publication URIs. -->
+ <define name="uri">
+ <attribute name="uri">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Digest of objects being withdrawn -->
+ <define name="hash">
+ <attribute name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Error codes. -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <!-- <publish/> element -->
+ <define name="publish_query">
+ <element name="publish">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <optional>
+ <ref name="hash"/>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="publish_reply">
+ <element name="publish">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ </element>
+ </define>
+ <!-- <withdraw/> element -->
+ <define name="withdraw_query">
+ <element name="withdraw">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <ref name="hash"/>
+ </element>
+ </define>
+ <define name="withdraw_reply">
+ <element name="withdraw">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ </element>
+ </define>
+ <!-- <list/> element -->
+ <define name="list_query">
+ <element name="list">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ </element>
+ </define>
+ <define name="list_reply">
+ <element name="list">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <ref name="hash"/>
+ </element>
+ </define>
+ <!-- <report_error/> element -->
+ <define name="report_error_reply">
+ <element name="report_error">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <attribute name="error_code">
+ <ref name="error"/>
+ </attribute>
+ <optional>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/router-certificate-schema.rnc b/schemas/relaxng/router-certificate.rnc
index 8cc325ce..8cc325ce 100644
--- a/schemas/relaxng/router-certificate-schema.rnc
+++ b/schemas/relaxng/router-certificate.rnc
diff --git a/schemas/relaxng/router-certificate-schema.rng b/schemas/relaxng/router-certificate.rng
index 90b50107..9352ed76 100644
--- a/schemas/relaxng/router-certificate-schema.rng
+++ b/schemas/relaxng/router-certificate.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for BGPSEC router certificate interchange format.
diff --git a/schemas/relaxng/rrdp.rnc b/schemas/relaxng/rrdp.rnc
new file mode 100644
index 00000000..2829605d
--- /dev/null
+++ b/schemas/relaxng/rrdp.rnc
@@ -0,0 +1,83 @@
+# $Id$
+#
+# RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+default namespace = "http://www.ripe.net/rpki/rrdp"
+
+version = xsd:positiveInteger { maxInclusive="1" }
+serial = xsd:nonNegativeInteger
+uri = xsd:anyURI
+uuid = xsd:string { pattern = "[\-0-9a-fA-F]+" }
+hash = xsd:string { pattern = "[0-9a-fA-F]+" }
+base64 = xsd:base64Binary
+
+# Notification file: lists current snapshots and deltas
+
+start |= element notification {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute serial { serial },
+ element snapshot {
+ attribute uri { uri },
+ attribute hash { hash }
+ },
+ element delta {
+ attribute from { serial },
+ attribute to { serial },
+ attribute uri { uri },
+ attribute hash { hash }
+ }*
+}
+
+# Snapshot segment: think DNS AXFR.
+
+start |= element snapshot {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute serial { serial },
+ element publish { attribute uri { uri }, base64 }*
+}
+
+# Delta segment: think DNS IXFR.
+
+start |= element deltas {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute from { serial },
+ attribute to { serial },
+ element delta {
+ attribute serial { serial },
+ delta_element+
+ }+
+}
+
+delta_element |= element publish {
+ attribute uri { uri },
+ attribute hash { hash }?,
+ base64
+}
+
+delta_element |= element withdraw {
+ attribute uri { uri },
+ attribute hash { hash }
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# comment-start: "# "
+# comment-start-skip: "#[ \t]*"
+# End:
diff --git a/schemas/relaxng/rrdp.rng b/schemas/relaxng/rrdp.rng
new file mode 100644
index 00000000..9bd3a207
--- /dev/null
+++ b/schemas/relaxng/rrdp.rng
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $
+
+ RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+
+ Copyright (C) 2014 Dragon Research Labs ("DRL")
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ PERFORMANCE OF THIS SOFTWARE.
+-->
+<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </define>
+ <define name="serial">
+ <data type="nonNegativeInteger"/>
+ </define>
+ <define name="uri">
+ <data type="anyURI"/>
+ </define>
+ <define name="uuid">
+ <data type="string">
+ <param name="pattern">[\-0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Notification file: lists current snapshots and deltas -->
+ <start combine="choice">
+ <element name="notification">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <element name="snapshot">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ <zeroOrMore>
+ <element name="delta">
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Snapshot segment: think DNS AXFR. -->
+ <start combine="choice">
+ <element name="snapshot">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <zeroOrMore>
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <ref name="base64"/>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Delta segment: think DNS IXFR. -->
+ <start combine="choice">
+ <element name="deltas">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <element name="delta">
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <ref name="delta_element"/>
+ </oneOrMore>
+ </element>
+ </oneOrMore>
+ </element>
+ </start>
+ <define name="delta_element" combine="choice">
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <optional>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="delta_element" combine="choice">
+ <element name="withdraw">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/up-down-schema.rnc b/schemas/relaxng/up-down.rnc
index a603b8fe..a603b8fe 100644
--- a/schemas/relaxng/up-down-schema.rnc
+++ b/schemas/relaxng/up-down.rnc
diff --git a/schemas/relaxng/up-down-schema.rng b/schemas/relaxng/up-down.rng
index 89235b7e..a0fc0514 100644
--- a/schemas/relaxng/up-down-schema.rng
+++ b/schemas/relaxng/up-down.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for the up-down protocol, extracted from RFC 6492.
diff --git a/schemas/sql/pubd.sql b/schemas/sql/pubd.sql
index 3a58ec00..2a0e2851 100644
--- a/schemas/sql/pubd.sql
+++ b/schemas/sql/pubd.sql
@@ -1,47 +1,36 @@
-- $Id$
--- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
-
--- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- SQL objects needed by pubd.py.
--- The config table is weird because we're really only using it
--- to store one BPKI CRL, but putting this here lets us use a lot of
--- existing machinery and the alternatives are whacky in other ways.
+-- Old tables that should just be flushed if present at all.
-DROP TABLE IF EXISTS client;
DROP TABLE IF EXISTS config;
+DROP TABLE IF EXISTS snapshot;
-CREATE TABLE config (
- config_id SERIAL NOT NULL,
- bpki_crl LONGBLOB,
- PRIMARY KEY (config_id)
-) ENGINE=InnoDB;
+-- DROP TABLE commands must be in correct (reverse dependency) order
+-- to satisfy FOREIGN KEY constraints.
+
+DROP TABLE IF EXISTS object;
+DROP TABLE IF EXISTS delta;
+DROP TABLE IF EXISTS session;
+DROP TABLE IF EXISTS client;
CREATE TABLE client (
client_id SERIAL NOT NULL,
@@ -54,6 +43,43 @@ CREATE TABLE client (
UNIQUE (client_handle)
) ENGINE=InnoDB;
+CREATE TABLE session (
+ session_id SERIAL NOT NULL,
+ uuid VARCHAR(36) NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ snapshot LONGTEXT,
+ hash CHAR(64),
+ PRIMARY KEY (session_id),
+ UNIQUE (uuid)
+) ENGINE=InnoDB;
+
+CREATE TABLE delta (
+ delta_id SERIAL NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ xml LONGTEXT NOT NULL,
+ hash CHAR(64) NOT NULL,
+ expires DATETIME NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (delta_id),
+ CONSTRAINT delta_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE object (
+ object_id SERIAL NOT NULL,
+ uri VARCHAR(255) NOT NULL,
+ der LONGBLOB NOT NULL,
+ hash CHAR(64) NOT NULL,
+ client_id BIGINT UNSIGNED NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (object_id),
+ CONSTRAINT object_client_id
+ FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE,
+ CONSTRAINT object_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE,
+ UNIQUE (session_id, hash)
+) ENGINE=InnoDB;
+
-- Local Variables:
-- indent-tabs-mode: nil
-- End:
diff --git a/schemas/sql/rpkid.sql b/schemas/sql/rpkid.sql
index ad0c39b0..f3b899ee 100644
--- a/schemas/sql/rpkid.sql
+++ b/schemas/sql/rpkid.sql
@@ -1,32 +1,21 @@
-- $Id$
--- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
-
--- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
--- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
--- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
--- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
--- PERFORMANCE OF THIS SOFTWARE.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- SQL objects needed by the RPKI engine (rpkid.py).