aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xpotpourri/bgpsec-rpki-router-key-hack103
-rw-r--r--rpki/rtr/client.py54
-rw-r--r--rpki/rtr/main.py8
3 files changed, 136 insertions, 29 deletions
diff --git a/potpourri/bgpsec-rpki-router-key-hack b/potpourri/bgpsec-rpki-router-key-hack
new file mode 100755
index 00000000..b1e3845c
--- /dev/null
+++ b/potpourri/bgpsec-rpki-router-key-hack
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009-2013 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.
+
+import os
+import glob
+import shutil
+import sqlite3
+import rpki.rtr.client
+import rpki.rtr.channels
+
+def find_free_name(fmt, n = 10):
+ """
+ Handle SKI hash collisions by allowing a small number of distinct
+ filenames for each ASN/SKI pair.
+ """
+
+ for i in xrange(n):
+ fn = fmt % i
+ if not os.path.exists(fn):
+ return fn
+ raise RuntimeError("Couldn't find a free filename for key %s after %d tries" % (fmt, n))
+
+class ClientChannel(rpki.rtr.client.ClientChannel):
+ """
+ Subclass ClientChannel to extend .end_of_data() method.
+ """
+
+ def end_of_data(self, version, serial, nonce, refresh, retry, expire):
+ """
+ Call base method to do all the normal EndOfData processing, then
+ dump out the key database using the symlink-to-directory hack so
+ we can rename() the result to perform an atomic installation.
+ """
+
+ # Run the base method
+ super(ClientChannel, self).end_of_data(version, serial, nonce, refresh, retry, expire)
+
+ # Set up our new output directory
+ dn = "%s.%s" % (self.args.bgpsec_key_directory, rpki.rtr.channels.Timestamp.now())
+ ln = "%s.%s" % (self.args.bgpsec_key_directory, ".tmp")
+ if os.path.exists(ln):
+ os.unlink(ln)
+ os.makedirs(dn)
+
+ # Write all the keys
+ for asn, gski, key in self.sql.execute("SELECT asn, ski, key FROM routerkey"):
+ with open(find_free_name("%s/%s.%s.%%d.key" % (dn, asn, gski)), "wb") as f:
+ f.write(key.decode("base64"))
+
+ # Install the new directory
+ os.symlink(os.path.basename(dn), ln)
+ os.rename(ln, self.args.bgpsec_key_directory)
+
+ # Clean up old output directories
+ pattern = ".[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z"
+ for gn in glob.iglob(self.args.bgpsec_key_directory + pattern):
+ if gn != dn:
+ shutil.rmtree(gn)
+
+def SymlinkToDirectory(path):
+ """
+ argparse "type" function to clean up and sanity check --bgpsec-key-directory.
+ """
+
+ path = os.path.abspath(path).rstrip("/")
+ if os.path.exists(path) and not os.path.islink(path):
+ raise ValueError
+ return path
+
+# Grab handle on the normal client argparse setup function
+client_argparse_setup = rpki.rtr.client.argparse_setup
+
+# Extend argparse setup to add our own (required) parameter
+def argparse_setup(subparsers):
+ subparser = client_argparse_setup(subparsers)
+ subparser.add_argument("--bgpsec-key-directory", required = True, type = SymlinkToDirectory,
+ help = "where to write BGPSEC router keys")
+ return subparser
+
+# Splice our extensions into client
+rpki.rtr.client.argparse_setup = argparse_setup
+rpki.rtr.client.ClientChannelClass = ClientChannel
+
+# And run the program
+import rpki.rtr.main
+rpki.rtr.main.main()
diff --git a/rpki/rtr/client.py b/rpki/rtr/client.py
index 6d567a1b..eb9ce5a0 100644
--- a/rpki/rtr/client.py
+++ b/rpki/rtr/client.py
@@ -181,18 +181,21 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
expire = rpki.rtr.pdus.default_expire
updated = Timestamp(0)
- def __init__(self, sock, proc, killsig, host, port, version):
+ def __init__(self, sock, proc, killsig, args, host = None, port = None):
self.killsig = killsig
self.proc = proc
- self.host = host
- self.port = port
+ self.args = args
+ self.host = args.host if host is None else host
+ self.port = args.port if port is None else port
super(ClientChannel, self).__init__(sock = sock, root_pdu_class = PDU)
- if version is not None:
- self.version = version
+ if args.force_version is not None:
+ self.version = args.force_version
self.start_new_pdu()
+ if args.sql_database:
+ self.setup_sql()
@classmethod
- def ssh(cls, host, port, version):
+ def ssh(cls, args):
"""
Set up ssh connection and start listening for first PDU.
"""
@@ -203,11 +206,10 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
return cls(sock = s[1],
proc = subprocess.Popen(argv, executable = "/usr/bin/ssh",
stdin = s[0], stdout = s[0], close_fds = True),
- killsig = signal.SIGKILL,
- host = host, port = port, version = version)
+ killsig = signal.SIGKILL, args = args)
@classmethod
- def tcp(cls, host, port, version):
+ def tcp(cls, args):
"""
Set up TCP connection and start listening for first PDU.
"""
@@ -232,12 +234,11 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
logging.exception("[socket.connect() failed: %s]", e)
s.close()
continue
- return cls(sock = s, proc = None, killsig = None,
- host = host, port = port, version = version)
+ return cls(sock = s, proc = None, killsig = None, args = args)
sys.exit(1)
@classmethod
- def loopback(cls, host, port, version):
+ def loopback(cls, args):
"""
Set up loopback connection and start listening for first PDU.
"""
@@ -247,11 +248,11 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
argv = (sys.executable, sys.argv[0], "server")
return cls(sock = s[1],
proc = subprocess.Popen(argv, stdin = s[0], stdout = s[0], close_fds = True),
- killsig = signal.SIGINT,
- host = host or "none", port = port or "none", version = version)
+ killsig = signal.SIGINT, args = args,
+ host = args.host or "none", port = args.port or "none")
@classmethod
- def tls(cls, host, port, version):
+ def tls(cls, args):
"""
Set up TLS connection and start listening for first PDU.
@@ -268,18 +269,17 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
s = socket.socketpair()
return cls(sock = s[1],
proc = subprocess.Popen(argv, stdin = s[0], stdout = s[0], close_fds = True),
- killsig = signal.SIGKILL,
- host = host, port = port, version = version)
+ killsig = signal.SIGKILL, args = args)
- def setup_sql(self, sqlname, reset_session):
+ def setup_sql(self):
"""
Set up an SQLite database to contain the table we receive. If
necessary, we will create the database.
"""
import sqlite3
- missing = not os.path.exists(sqlname)
- self.sql = sqlite3.connect(sqlname, detect_types = sqlite3.PARSE_DECLTYPES)
+ missing = not os.path.exists(self.args.sql_database)
+ self.sql = sqlite3.connect(self.args.sql_database, detect_types = sqlite3.PARSE_DECLTYPES)
self.sql.text_factory = str
cur = self.sql.cursor()
cur.execute("PRAGMA foreign_keys = on")
@@ -319,7 +319,7 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
key TEXT NOT NULL,
UNIQUE (cache_id, asn, ski),
UNIQUE (cache_id, asn, key))''')
- elif reset_session:
+ elif self.args.reset_session:
cur.execute("DELETE FROM cache WHERE host = ? and port = ?", (self.host, self.port))
cur.execute("SELECT cache_id, version, nonce, serial, refresh, retry, expire, updated "
"FROM cache WHERE host = ? AND port = ?",
@@ -452,6 +452,10 @@ class ClientChannel(rpki.rtr.channels.PDUChannel):
super(ClientChannel, self).handle_close()
+# Hack to let us subclass this from scripts without needing to rewrite client_main().
+
+ClientChannelClass = ClientChannel
+
def client_main(args):
"""
Test client, intended primarily for debugging.
@@ -459,13 +463,12 @@ def client_main(args):
logging.debug("[Startup]")
- constructor = getattr(rpki.rtr.client.ClientChannel, args.protocol)
+ assert issubclass(ClientChannelClass, ClientChannel)
+ constructor = getattr(ClientChannelClass, args.protocol)
client = None
try:
- client = constructor(host = args.host, port = args.port, version = args.force_version)
- if args.sql_database:
- client.setup_sql(args.sql_database, args.reset_session)
+ client = constructor(args)
polled = client.updated
wakeup = None
@@ -523,3 +526,4 @@ def argparse_setup(subparsers):
subparser.add_argument("protocol", choices = ("loopback", "tcp", "ssh", "tls"), help = "connection protocol")
subparser.add_argument("host", nargs = "?", help = "server host")
subparser.add_argument("port", nargs = "?", help = "server port")
+ return subparser
diff --git a/rpki/rtr/main.py b/rpki/rtr/main.py
index 6add407d..29a4873b 100644
--- a/rpki/rtr/main.py
+++ b/rpki/rtr/main.py
@@ -28,10 +28,6 @@ import logging
import logging.handlers
import argparse
-from rpki.rtr.server import argparse_setup as argparse_setup_server
-from rpki.rtr.client import argparse_setup as argparse_setup_client
-from rpki.rtr.generator import argparse_setup as argparse_setup_generator
-
class Formatter(logging.Formatter):
@@ -57,6 +53,10 @@ def main():
os.environ["TZ"] = "UTC"
time.tzset()
+ from rpki.rtr.server import argparse_setup as argparse_setup_server
+ from rpki.rtr.client import argparse_setup as argparse_setup_client
+ from rpki.rtr.generator import argparse_setup as argparse_setup_generator
+
if "rpki.rtr.bgpdump" in sys.modules:
from rpki.rtr.bgpdump import argparse_setup as argparse_setup_bgpdump
else: