aboutsummaryrefslogtreecommitdiff
path: root/potpourri/bgpsec-rpki-router-key-hack
blob: b1e3845c7324a1bf867d378f2da5d1816b9d5eca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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()