diff options
Diffstat (limited to 'rp/config')
-rw-r--r-- | rp/config/Makefile.in | 88 | ||||
l--------- | rp/config/rpki | 1 | ||||
-rwxr-xr-x | rp/config/rpki-confgen | 281 | ||||
-rw-r--r-- | rp/config/rpki-confgen.xml | 1062 | ||||
-rwxr-xr-x | rp/config/rpki-generate-root-certificate | 77 | ||||
-rwxr-xr-x | rp/config/rpki-manage | 46 | ||||
-rwxr-xr-x | rp/config/rpki-sql-backup | 63 | ||||
-rwxr-xr-x | rp/config/rpki-sql-setup | 348 |
8 files changed, 1966 insertions, 0 deletions
diff --git a/rp/config/Makefile.in b/rp/config/Makefile.in new file mode 100644 index 00000000..c6050f3e --- /dev/null +++ b/rp/config/Makefile.in @@ -0,0 +1,88 @@ +# $Id$ + +PYTHON = @PYTHON@ + +INSTALL = @INSTALL@ -m 555 + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +datarootdir = @datarootdir@ +datadir = @datadir@ +localstatedir = @localstatedir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +bindir = @bindir@ +sbindir = @sbindir@ +libexecdir = @libexecdir@ +sysconfdir = @sysconfdir@ + +abs_builddir = @abs_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +abs_top_builddir= @abs_top_builddir@ +srcdir = @srcdir@ + +CFG_INSTALL_TARGETS = @CFG_INSTALL_TARGETS@ + +all:: rpki.rp.xml rpki.rp.conf.sample + +clean:: + @true + +install:: ${CFG_INSTALL_TARGETS} + +install-always:: all + @echo + @echo "== Default configuration file location is ${sysconfdir}/rpki.conf ==" + @echo + ${INSTALL} -d ${DESTDIR}${sysconfdir}/rpki + ${INSTALL} rpki.rp.xml rpki.rp.conf.sample ${DESTDIR}${sysconfdir}/rpki + +test uninstall deinstall:: + @true + +distclean:: clean + rm -f Makefile + +rpki.rp.xml: ${abs_top_srcdir}/rpki/autoconf.py rpki-confgen rpki-confgen.xml + ${PYTHON} rpki-confgen \ + --read-xml rpki-confgen.xml \ + --autoconf \ + --set myrpki::handle=`hostname -f | sed 's/[.]/_/g'` \ + --set myrpki::rpkid_server_host=`hostname -f` \ + --set myrpki::pubd_server_host=`hostname -f` \ + --pwgen myrpki::shared_sql_password \ + --pwgen web_portal::secret-key \ + --set myrpki::run_rpkid=no \ + --set myrpki::run_pubd=no \ + --write-xml $@ + +rpki.rp.conf.sample: rpki.rp.xml + ${PYTHON} rpki-confgen \ + --read-xml rpki.rp.xml \ + --write-conf $@ + +clean:: + rm -f rpki.rp.xml rpki.rp.conf.sample + +install-postconf: \ + install-user install-conf install-sql install-django + +# This should create user "rpki" and group "rpki", but rcynic already +# does that...but we probably need to do it here instead, bother. + +install-user: + @true + +install-conf: + test -f ${DESTDIR}${sysconfdir}/rpki.conf ||\ + cp -p ${DESTDIR}${sysconfdir}/rpki/rpki.rp.conf.sample ${DESTDIR}${sysconfdir}/rpki.conf + +#uninstall deinstall:: +# rm -f ${DESTDIR}${sysconfdir}/rpki/rpki.rp.xml ${DESTDIR}${sysconfdir}/rpki/rpki.rp.conf.sample + +install-sql: + ${sbindir}/rpki-sql-setup create + +install-django: + ${sbindir}/rpki-manage syncdb --noinput + ${sbindir}/rpki-manage migrate app diff --git a/rp/config/rpki b/rp/config/rpki new file mode 120000 index 00000000..d39d05b6 --- /dev/null +++ b/rp/config/rpki @@ -0,0 +1 @@ +../../rpki
\ No newline at end of file diff --git a/rp/config/rpki-confgen b/rp/config/rpki-confgen new file mode 100755 index 00000000..7fac9eab --- /dev/null +++ b/rp/config/rpki-confgen @@ -0,0 +1,281 @@ +#!/usr/bin/env python + +# $Id$ +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 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 sys +import argparse +import base64 +import textwrap + +from lxml.etree import Element, SubElement, ElementTree, Comment + +space4 = " " * 4 +space6 = " " * 6 +space8 = " " * 8 +star78 = "*" * 78 + +wiki_wrapper = textwrap.TextWrapper() +conf_wrapper = textwrap.TextWrapper(initial_indent = "# ", subsequent_indent = "# ") +xml6_wrapper = textwrap.TextWrapper(initial_indent = space6, subsequent_indent = space6) +xml8_wrapper = textwrap.TextWrapper(initial_indent = space8, subsequent_indent = space8) + +class Option(object): + + def __init__(self, name, value, doc): + self.name = name + self.value = value + self.doc = doc + + @property + def width(self): + return len(self.name) + + def to_xml(self): + x = Element("option", name = self.name) + if self.value is not None: + x.set("value", self.value) + for d in self.doc: + SubElement(x, "doc").text = "\n" + xml8_wrapper.fill(d) + "\n" + space6 + return x + + def to_wiki(self, f): + f.write("\n== {0.name} == #{0.name}\n".format(self)) + for d in self.doc: + f.write("\n{0}\n".format(wiki_wrapper.fill(d))) + if self.value is None: + f.write("\n{0}\n".format(wiki_wrapper.fill("No default value."))) + else: + f.write("\n{{{{{{\n#!ini\n{0.name} = {0.value}\n}}}}}}\n".format(self)) + + def to_conf(self, f, width): + for i, d in enumerate(self.doc): + f.write("{}\n{}\n".format("" if i == 0 else "#", + conf_wrapper.fill(d))) + if self.value is None: + f.write("\n#{1.name:{0}} = ???\n".format(width - 1, self)) + else: + f.write("\n{1.name:{0}} = {1.value}\n".format(width, self)) + +class Section(object): + + def __init__(self, name): + self.name = name + self.doc = [] + self.options = [] + + @property + def width(self): + return max(o.width for o in self.options) + + @classmethod + def from_xml(cls, elt): + self = cls(name = elt.get("name")) + for x in elt.iterchildren("doc"): + self.doc.append(" ".join(x.text.split())) + for x in elt.iterchildren("option"): + self.options.append(Option(name = x.get("name"), value = x.get("value"), + doc = [" ".join(d.text.split()) + for d in x.iterchildren("doc")])) + return self + + def to_xml(self): + x = Element("section", name = self.name) + for d in self.doc: + SubElement(x, "doc").text = "\n" + xml6_wrapper.fill(d) + "\n" + space4 + x.extend(o.to_xml() for o in self.options) + return x + + def to_wiki(self, f): + f.write("\n= [{0}] section = #{0}\n".format(self.name)) + for d in self.doc: + f.write("\n{0}\n".format(wiki_wrapper.fill(d))) + for o in self.options: + o.to_wiki(f) + + def to_conf(self, f, width): + f.write("\n" + "#" * 78 + "\n\n[" + self.name + "]\n") + if self.doc: + f.write("\n##") + for i, d in enumerate(self.doc): + f.write("{}\n{}\n".format("" if i == 0 else "#", + conf_wrapper.fill(d))) + f.write("##\n") + for o in self.options: + o.to_conf(f, width) + +def wiki_header(f, ident, toc): + f.write(textwrap.dedent('''\ + {{{{{{ + #!comment + + {star78} + THIS PAGE WAS GENERATED AUTOMATICALLY, DO NOT EDIT. + + Generated from {ident} + by $Id$ + {star78} + + }}}}}} + '''.format(star78 = star78, + ident = ident))) + if toc is not None: + f.write("[[TracNav({})]]\n".format(toc)) + f.write("[[PageOutline]]\n") + +def conf_header(f, ident): + f.write(textwrap.dedent('''\ + # Automatically generated. Edit as needed, but be careful of overwriting. + # + # Generated from {ident} + # by $Id$ + + '''.format(ident = ident))) + + +# http://stackoverflow.com/questions/9027028/argparse-argument-order + +class CustomAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string = None): + if not "ordered_args" in namespace: + namespace.ordered_args = [] + namespace.ordered_args.append((self.dest, values)) + +class CustomFlagAction(CustomAction): + + def __init__(self, option_strings, dest, default = None, + required = False, help = None): # pylint: disable=W0622 + super(CustomFlagAction, self).__init__( + option_strings = option_strings, + dest = dest, + nargs = 0, + const = None, + default = default, + required = required, + help = help) + + +class main(object): + + def __init__(self): + self.sections = [] + self.section_map = None + self.option_map = None + self.ident = None + self.toc = None + + parser = argparse.ArgumentParser(description = __doc__) + parser.add_argument("--read-xml", type = argparse.FileType("r"), metavar = "FILE", action = CustomAction, help = "XML input file defining sections and options", required = True) + parser.add_argument("--write-xml", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "XML output file to snapshot configuration") + parser.add_argument("--write-conf", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "rpki.conf configuration file to write") + parser.add_argument("--write-wiki", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "TracWiki file to write (monolithic)") + parser.add_argument("--write-wiki-pages", metavar = "PATTERN", action = CustomAction, help = "TracWiki filenames (pattern) to write (one section per page)") + parser.add_argument("--set", metavar = "VARVAL", action = CustomAction, help = "variable setting in form \"VAR=VAL\"") + parser.add_argument("--pwgen", metavar = "VAR", action = CustomAction, help = "set variable to generated password") + parser.add_argument("--toc", metavar = "TOCVAL", action = CustomAction, help = "set TOC value to use with TracNav plugin") + parser.add_argument("--autoconf", action = CustomFlagAction, help = "configure [autoconf] section") + args = parser.parse_args() + + for cmd, arg in args.ordered_args: + getattr(self, "do_" + cmd)(arg) + + def do_read_xml(self, arg): + self.option_map = None + root = ElementTree(file = arg).getroot() + self.ident = root.get("ident") + self.sections.extend(Section.from_xml(x) for x in root.iterchildren("section")) + self.option_map = {} + self.section_map = {} + for section in self.sections: + if section.name in self.section_map: + sys.exit("Duplicate section {}".format(section.name)) + self.section_map[section.name] = section + for option in section.options: + name = (section.name, option.name) + if name in self.option_map: + sys.exit("Duplicate option {}::{}".format(*name)) + self.option_map[name] = option + + def do_set(self, arg): + try: + name, value = arg.split("=", 1) + section, option = name.split("::") + except ValueError: + sys.exit("Couldn't parse --set specification \"{}\"".format(arg)) + name = (section, option) + if name not in self.option_map: + sys.exit("Couldn't find option {}::{}".format(*name)) + self.option_map[name].value = value + + def do_pwgen(self, arg): + try: + section, option = arg.split("::") + except ValueError: + sys.exit("Couldn't parse --pwgen specification \"{}\"".format(arg)) + name = (section, option) + if name not in self.option_map: + sys.exit("Couldn't find option {}::{}".format(name)) + self.option_map[name].value = base64.urlsafe_b64encode(os.urandom(66)) + + def do_autoconf(self, ignored): + try: + import rpki.autoconf + for option in self.section_map["autoconf"].options: + try: + option.value = getattr(rpki.autoconf, option.name) + except AttributeError: + pass + except ImportError: + sys.exit("rpki.autoconf module is not available") + except KeyError: + sys.exit("Couldn't find autoconf section") + + def do_write_xml(self, arg): + x = Element("configuration", ident = self.ident) + x.append(Comment(" Machine-editable configuration snapshot, generated automatically, do not touch ")) + x.extend(s.to_xml() for s in self.sections) + ElementTree(x).write(arg, pretty_print = True, encoding = "us-ascii") + + def do_write_wiki(self, arg): + for i, section in enumerate(self.sections): + if i == 0: + wiki_header(arg, self.ident, self.toc) + else: + arg.write("\f\n") + section.to_wiki(arg) + + def do_write_wiki_pages(self, arg): + for section in self.sections: + with open(arg % section.name, "w") as f: + wiki_header(f, self.ident, self.toc) + section.to_wiki(f) + + def do_write_conf(self, arg): + conf_header(arg, self.ident) + width = max(s.width for s in self.sections) + for section in self.sections: + section.to_conf(arg, width) + + def do_toc(self, arg): + self.toc = arg + + +if __name__ == "__main__": + main() diff --git a/rp/config/rpki-confgen.xml b/rp/config/rpki-confgen.xml new file mode 100644 index 00000000..b7bc2f62 --- /dev/null +++ b/rp/config/rpki-confgen.xml @@ -0,0 +1,1062 @@ +<!-- -*- SGML -*- + $Id$ + + Documented option definitions for rpki-confgen to use in generating + rpki.conf and TracWiki documentation. + + 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 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. +--> + +<configuration ident = "$Id$"> + + <section name = "myrpki"> + + <doc> + The "`[myrpki]`" section contains all the parameters that you + really need to configure. The name "`myrpki`" is historical and + may change in the future. + </doc> + + <option name = "handle"> + <doc> + Every resource-holding or server-operating entity needs a + "handle", which is just an identifier by which the entity + calls itself. Handles do not need to be globally unique, but + should be chosen with an eye towards debugging operational + problems: it's best if you use a handle that your parents and + children will recognize as being you. + </doc> + <doc> + The "`handle`" option in the "`[myrpki]`" section specifies the + default handle for this installation. Previous versions of + the CA tools required a separate configuration file, each with + its own handle setting, for each hosted entity. The current + code allows the current handle to be selected at runtime in + both the GUI and command line user interface tools, so the + handle setting here is just the default when you don't set one + explictly. In the long run, this option may go away entirely, + but for now you need to set this. + </doc> + <doc> + Syntax is an identifier (ASCII letters, digits, hyphen, + underscore -- no whitespace, non-ASCII characters, or other + punctuation). + </doc> + </option> + + <option name = "bpki_servers_directory" + value = "${autoconf::datarootdir}/rpki/bpki"> + <doc> + Directory for BPKI files generated by rpkic and used by rpkid + and pubd. You will not normally need to change this. + </doc> + </option> + + <option name = "run_rpkid" + value = "yes"> + <doc> + Whether you want to run your own copy of rpkid (and irdbd). + Leave this alone unless you're doing something unusual like + running a pubd-only installation. + </doc> + </option> + + <option name = "rpkid_server_host"> + <doc> + DNS hostname for rpkid. In most cases, this must resolve to a + publicly-reachable address to be useful, as your RPKI children + will need to contact your rpkid at this address. + </doc> + </option> + + <option name = "rpkid_server_port" + value = "4404"> + <doc> + Server port number for rpkid. This can be any legal TCP port + number that you're not using for something else. + </doc> + </option> + + <option name = "irdbd_server_host" + value = "localhost"> + <doc> + DNS hostname for irdbd, or "`localhost`". This should be + "`localhost`" unless you really know what you are doing. + </doc> + </option> + + <option name = "irdbd_server_port" + value = "4403"> + <doc> + Server port number for irdbd. This can be any legal TCP port + number that you're not using for something else. + </doc> + </option> + + <option name = "run_pubd" + value = "yes"> + <doc> + Whether you want to run your own copy of pubd. In general, + it's best to use your parent's pubd if your parent allows you + to do so, because this will reduce the overall number of + publication sites from which relying parties will need to + retrieve data. However, not all parents offer publication + service, or you may need to run pubd yourself for reliability + reasons, or because you're certifying private address space or + private Autonomous System Numbers. + </doc> + <doc> + The out of band setup protocol will attempt to negotiate + publication service for you with whatever publication service + your parent is using, if it can and if you let it. + </doc> + </option> + + <option name = "pubd_server_host"> + <doc> + DNS hostname for pubd, if you're running it. This must + resolve to a publicly reachable address to be useful. + </doc> + </option> + + <option name = "pubd_server_port" + value = "4402"> + <doc> + Server port number for pubd. This can be any legal TCP port + number that you're not using for something else. + </doc> + </option> + + <option name = "pubd_contact_info"> + <doc> + Contact information to include in offers of repository + service. This only matters when you're running pubd. This + should be a human readable string, perhaps containing an email + address or URL. + </doc> + </option> + + <option name = "publication_base_directory" + value = "${autoconf::datarootdir}/rpki/publication"> + <doc> + Root of local directory tree where pubd should write out published + data. You need to configure this, and the configuration should + match up with the directory where you point rsyncd. Neither pubd + nor rsyncd much cares //where// you tell it 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 rpkid's published outputs. + </doc> + </option> + + <option name = "rrdp_publication_base_directory" + value = "${autoconf::datarootdir}/rpki/rrdp-publication"> + <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 = "publication_rsync_module" + value = "rpki"> + <doc> + rsyncd module name corresponding to publication_base_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> + Hostname and optional port number for rsync URIs. In most cases + this should just be the same value as pubd_server_host. + </doc> + </option> + + <option name = "publication_rrdp_base_uri" + value = "https://${myrpki::pubd_server_host}/rrdp/"> + <doc> + Base URI for RRDP notification, snapshot, and delta files. + In most cases this should be a HTTPS URL for the directory + on the publication server where the notify.xml lives. + </doc> + </option> + + <option name = "publication_rrdp_notification_uri" + value = "${myrpki::publication_rrdp_base_uri}notify.xml"> + <doc> + URI for RRDP notification file. You shouldn't need to change this. + </doc> + </option> + + <option name = "start_rpkid" + value = "${myrpki::run_rpkid}"> + <doc> + rpkid startup control. This should usually have the same value as + run_rpkid: the only case where you would want to change this is + when you are running the back-end code on a different machine from + one or more of the daemons, in which case you need finer control + over which daemons to start on which machines. In such cases, + run_rpkid controls whether the back-end code is doing things to + manage rpkid, while start_rpkid controls whether + rpki-start-servers attempts to start rpkid on this machine. + </doc> + </option> + + <option name = "start_irdbd" + value = "${myrpki::run_rpkid}"> + <doc> + irdbd startup control. This should usually have the same value as + run_rpkid: the only case where you would want to change this is + when you are running the back-end code on a different machine from + one or more of the daemons, in which case you need finer control + over which daemons to start on which machines. In such cases, + run_rpkid controls whether the back-end code is doing things to + manage rpkid, while start_irdbd controls whether + rpki-start-servers attempts to start irdbd on this machine. + </doc> + </option> + + <option name = "start_pubd" + value = "${myrpki::run_pubd}"> + <doc> + pubd startup control. This should usually have the same value as + run_pubd: the only case where you would want to change this is + when you are running the back-end code on a different machine from + one or more of the daemons, in which case you need finer control + over which daemons to start on which machines. In such cases, + run_pubd controls whether the back-end code is doing things to + manage pubd, while start_pubd controls whether + rpki-start-servers attempts to start pubd on this machine. + </doc> + </option> + + <option name = "shared_sql_engine" + value = "mysql"> + <doc> + Database engine to use. Default is MySQL, because that's what + we've been using for years. Now that all runtime database + access is via Django ORM, changing to another engine supported + by Django is just a configuration issue. + </doc> + <doc> + Current supported values are "mysql" (the default), "sqlite3", + and "postgresql". In theory it should be straightforward to + add support for any SQL engine Django supports. + </doc> + </option> + + <option name = "shared_sql_username" + value = "rpki"> + <doc> + If you're comfortable with having all of the databases use the + same SQL username, set that value here. The default setting + of this variable should be fine. + </doc> + </option> + + <option name = "shared_sql_password"> + <doc> + If you're comfortable with having all of the databases use the + same SQL password, set that value here. You should use a + locally generated password either here or in the individual + settings below. The installation process generates a random + value for this option, which satisfies this requirement, so + ordinarily you should have no need to change this option. + </doc> + </option> + + <option name = "rcynic_sql_engine" + value = "${myrpki::shared_sql_engine}"> + <doc> + SQL engine to use for rcynic's database. The default setting + of this variable should be fine. + </doc> + </option> + + <option name = "rcynic_sql_database" + value = "rcynic"> + <doc> + SQL database name for rcynic's database. The default setting of + this variable should be fine. + </doc> + </option> + + <option name = "rcynic_sql_username" + value = "${myrpki::shared_sql_username}"> + <doc> + If you want to use a separate SQL username for rcynic's database, + set it here. + </doc> + </option> + + <option name = "rcynic_sql_password" + value = "${myrpki::shared_sql_password}"> + <doc> + If you want to use a separate SQL password for rcynic's database, + set it here. + </doc> + </option> + + <option name = "rpkid_sql_engine" + value = "${myrpki::shared_sql_engine}"> + <doc> + SQL engine to use for rpkid's database. The default setting + of this variable should be fine. + </doc> + </option> + + <option name = "rpkid_sql_database" + value = "rpkid"> + <doc> + SQL database name for rpkid's database. The default setting of + this variable should be fine. + </doc> + </option> + + <option name = "rpkid_sql_username" + value = "${myrpki::shared_sql_username}"> + <doc> + If you want to use a separate SQL username for rpkid's database, + set it here. + </doc> + </option> + + <option name = "rpkid_sql_password" + value = "${myrpki::shared_sql_password}"> + <doc> + If you want to use a separate SQL password for rpkid's database, + set it here. + </doc> + </option> + + <option name = "irdbd_sql_engine" + value = "${myrpki::shared_sql_engine}"> + <doc> + SQL engine to use for irdbd's database. The default setting + of this variable should be fine. + </doc> + </option> + + <option name = "irdbd_sql_database" + value = "irdbd"> + <doc> + SQL database for irdbd's database. The default setting of this + variable should be fine. + </doc> + </option> + + <option name = "irdbd_sql_username" + value = "${myrpki::shared_sql_username}"> + <doc> + If you want to use a separate SQL username for irdbd's database, + set it here. + </doc> + </option> + + <option name = "irdbd_sql_password" + value = "${myrpki::shared_sql_password}"> + <doc> + If you want to use a separate SQL password for irdbd's database, + set it here. + </doc> + </option> + + <option name = "pubd_sql_engine" + value = "${myrpki::shared_sql_engine}"> + <doc> + SQL engine to use for pubd's database. The default setting + of this variable should be fine. + </doc> + </option> + + <option name = "pubd_sql_database" + value = "pubd"> + <doc> + SQL database name for pubd's database. The default setting of + this variable should be fine. + </doc> + </option> + + <option name = "pubd_sql_username" + value = "${myrpki::shared_sql_username}"> + <doc> + If you want to use a separate SQL username for pubd's database, + set it here. + </doc> + </option> + + <option name = "pubd_sql_password" + value = "${myrpki::shared_sql_password}"> + <doc> + If you want to use a separate SQL password for pubd's database, + set it here. + </doc> + </option> + + <option name = "log-destination" + value = "file"> + <doc> + Default logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-directory" + value = "/var/log/rpki"> + <doc> + Where to write log files when logging to files. + </doc> + </option> + + <option name = "log-level" + value = "info"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "3"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "56"> + <doc> + How many old logs to keep before deleting. + </doc> + </option> + + </section> + + <section name = "rcynic"> + + <doc> + rcynicng, unlike it's predecessor, uses the same `rpki.conf` + file as all the other programs in the RPKI toolkit. Start + rcynicng with "`-c filename`" to choose a different + configuration file. All options are in the "`[rcynic]`" + section. + </doc> + + <option name = "sql-engine" + value = "${myrpki::rcynic_sql_engine}"> + <doc> + SQL engine for rcynic. + </doc> + </option> + + <option name = "sql-database" + value = "${myrpki::rcynic_sql_database}"> + <doc> + SQL database name for rcynic. + </doc> + </option> + + <option name = "sql-username" + value = "${myrpki::rcynic_sql_username}"> + <doc> + SQL user name for rcynic. + </doc> + </option> + + <option name = "sql-password" + value = "${myrpki::rcynic_sql_password}"> + <doc> + SQL password for rcynic. + </doc> + </option> + + <option name = "log-destination" + value = "${myrpki::log-destination}"> + <doc> + Logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-filename" + value = "${myrpki::log-directory}/rcynic.log"> + <doc> + Where to write log file when logging to a file. + </doc> + </option> + + <option name = "log-level" + value = "${myrpki::log-level}"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "${myrpki::log-time-limit}"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "${myrpki::log-count}"> + <doc> + How many old logs to keep before deleting. + </doc> + </option> + + </section> + + <section name = "rpkid"> + + <doc> + rpkid's default config file is the system `rpki.conf` file. + Start rpkid with "`-c filename`" to choose a different config + file. All options are in the "`[rpkid]`" section. BPKI + Certificates and keys may be in either DER or PEM format. + </doc> + + <option name = "sql-engine" + value = "${myrpki::rpkid_sql_engine}"> + <doc> + SQL engine for rpkid. + </doc> + </option> + + <option name = "sql-database" + value = "${myrpki::rpkid_sql_database}"> + <doc> + SQL database name for rpkid. + </doc> + </option> + + <option name = "sql-username" + value = "${myrpki::rpkid_sql_username}"> + <doc> + SQL user name for rpkid. + </doc> + </option> + + <option name = "sql-password" + value = "${myrpki::rpkid_sql_password}"> + <doc> + SQL password for rpkid. + </doc> + </option> + + <option name = "server-host" + value = "${myrpki::rpkid_server_host}"> + <doc> + Host on which rpkid should listen for HTTP service requests. + </doc> + </option> + + <option name = "server-port" + value = "${myrpki::rpkid_server_port}"> + <doc> + Port on which rpkid should listen for HTTP service requests. + </doc> + </option> + + <option name = "irdb-url" + value = "http://${myrpki::irdbd_server_host}:${myrpki::irdbd_server_port}/"> + <doc> + HTTP service URL rpkid should use to contact irdbd. If irdbd is + running on the same machine as rpkid, this can and probably should + be a loopback URL, since nobody but rpkid needs to talk to irdbd. + </doc> + </option> + + <option name = "bpki-ta" + value = "${myrpki::bpki_servers_directory}/ca.cer"> + <doc> + Where rpkid should look for the BPKI trust anchor. All BPKI + certificate verification within rpkid traces back to this + trust anchor. Don't change this unless you really know what + you are doing. + </doc> + </option> + + <option name = "rpkid-cert" + value = "${myrpki::bpki_servers_directory}/rpkid.cer"> + <doc> + Where rpkid should look for its own BPKI EE certificate. Don't + change this unless you really know what you are doing. + </doc> + </option> + + <option name = "rpkid-key" + value = "${myrpki::bpki_servers_directory}/rpkid.key"> + <doc> + Where rpkid should look for the private key corresponding to its + own BPKI EE certificate. Don't change this unless you really know + what you are doing. + </doc> + </option> + + <option name = "irdb-cert" + value = "${myrpki::bpki_servers_directory}/irdbd.cer"> + <doc> + Where rpkid should look for irdbd's 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> + Where rpkid should look for the back-end control client's BPKI EE + certificate. Don't change this unless you really know what you + are doing. + </doc> + </option> + + <option name = "log-destination" + value = "${myrpki::log-destination}"> + <doc> + Logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-filename" + value = "${myrpki::log-directory}/rpkid.log"> + <doc> + Where to write log file when logging to a file. + </doc> + </option> + + <option name = "log-level" + value = "${myrpki::log-level}"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "${myrpki::log-time-limit}"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "${myrpki::log-count}"> + <doc> + How many old logs to keep before deleting. + </doc> + </option> + + </section> + + <section name = "irdbd"> + + <doc> + irdbd's default configuration file is the system `rpki.conf` + file. Start irdbd with "`-c filename`" to choose a different + configuration file. All options are in the "`[irdbd]`" section. + </doc> + + <doc> + Since irdbd is part of the back-end system, it has direct access to + the back-end's SQL database, and thus is able to pull its own BPKI + configuration directly from the database, and thus needs a bit less + configuration than the other daemons. + </doc> + + <option name = "sql-engine" + value = "${myrpki::irdbd_sql_engine}"> + <doc> + SQL engine for irdbd. + </doc> + </option> + + <option name = "sql-database" + value = "${myrpki::irdbd_sql_database}"> + <doc> + SQL database name for irdbd. + </doc> + </option> + + <option name = "sql-username" + value = "${myrpki::irdbd_sql_username}"> + <doc> + SQL user name for irdbd. + </doc> + </option> + + <option name = "sql-password" + value = "${myrpki::irdbd_sql_password}"> + <doc> + SQL password for irdbd. + </doc> + </option> + + <option name = "server-host" + value = "${myrpki::irdbd_server_host}"> + <doc> + Host on which irdbd should listen for HTTP service requests. + </doc> + </option> + + <option name = "server-port" + value = "${myrpki::irdbd_server_port}"> + <doc> + Port on which irdbd should listen for HTTP service requests. + </doc> + </option> + + <option name = "startup-message"> + <doc> + String to log on startup, useful when debugging a collection + of irdbd instances at once. + </doc> + </option> + + <option name = "log-destination" + value = "${myrpki::log-destination}"> + <doc> + Logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-filename" + value = "${myrpki::log-directory}/irdbd.log"> + <doc> + Where to write log file when logging to a file. + </doc> + </option> + + <option name = "log-level" + value = "${myrpki::log-level}"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "${myrpki::log-time-limit}"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "${myrpki::log-count}"> + <doc> + How many old logs to keep before deleting. + </doc> + </option> + + </section> + + <section name = "pubd"> + + <doc> + pubd's default configuration file is the system `rpki.conf` + file. Start pubd with "`-c filename`" to choose a different + configuration file. All options are in the "`[pubd]`" section. + BPKI certificates and keys may be either DER or PEM format. + </doc> + + <option name = "sql-engine" + value = "${myrpki::pubd_sql_engine}"> + <doc> + SQL engine for pubd. + </doc> + </option> + + <option name = "sql-database" + value = "${myrpki::pubd_sql_database}"> + <doc> + SQL database name for pubd. + </doc> + </option> + + <option name = "sql-username" + value = "${myrpki::pubd_sql_username}"> + <doc> + SQL user name for pubd. + </doc> + </option> + + <option name = "sql-password" + value = "${myrpki::pubd_sql_password}"> + <doc> + SQL password for pubd. + </doc> + </option> + + <option name = "publication-base" + value = "${myrpki::publication_base_directory}"> + <doc> + Root of directory tree where pubd should write out published data. + You need to configure this, and the configuration should match up + with the directory where you point rsyncd. Neither pubd 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 rpkid's published outputs. + </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> + Host on which pubd should listen for HTTP service requests. + </doc> + </option> + + <option name = "server-port" + value = "${myrpki::pubd_server_port}"> + <doc> + Port on which pubd should listen for HTTP service requests. + </doc> + </option> + + <option name = "bpki-ta" + value = "${myrpki::bpki_servers_directory}/ca.cer"> + <doc> + Where pubd should look for the BPKI trust anchor. All BPKI + certificate verification within pubd traces back to this + trust anchor. Don't change this unless you really know what + you are doing. + </doc> + </option> + + <option name = "pubd-cert" + value = "${myrpki::bpki_servers_directory}/pubd.cer"> + <doc> + Where pubd should look for its own BPKI EE certificate. Don't + change this unless you really know what you are doing. + </doc> + </option> + + <option name = "pubd-key" + value = "${myrpki::bpki_servers_directory}/pubd.key"> + <doc> + Where pubd should look for the private key corresponding to its + own BPKI EE certificate. Don't change this unless you really know + what you are doing. + </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> + Where pubd should look for the back-end control client's BPKI EE + certificate. Don't change this unless you really know what you + are doing. + </doc> + </option> + + <option name = "rrdp-base-uri" + value = "${myrpki::publication_rrdp_base_uri}"> + <doc> + RRDP base URI for naming snapshots and deltas. + </doc> + </option> + + <option name = "log-destination" + value = "${myrpki::log-destination}"> + <doc> + Logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-filename" + value = "${myrpki::log-directory}/pubd.log"> + <doc> + Where to write log file when logging to a file. + </doc> + </option> + + <option name = "log-level" + value = "${myrpki::log-level}"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "${myrpki::log-time-limit}"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "${myrpki::log-count}"> + <doc> + How many old logs to keep before deleting. + </doc> + </option> + + </section> + + <section name = "rpki-nanny"> + + <option name = "log-destination" + value = "${myrpki::log-destination}"> + <doc> + Logging mechanism, can be "file", "syslog", "stderr", or "stdout". + </doc> + </option> + + <option name = "log-filename" + value = "${myrpki::log-directory}/rpki-nanny.log"> + <doc> + Where to write log file when logging to a file. + </doc> + </option> + + <option name = "log-level" + value = "${myrpki::log-level}"> + <doc> + Default logging level. + </doc> + </option> + + <option name = "log-time-limit" + value = "${myrpki::log-time-limit}"> + <doc> + Interval between log file rotations, in hours. + Set to zero to disable automatic rotations. + </doc> + </option> + + <option name = "log-count" + value = "${myrpki::log-count}"> + <doc> + How many old logs to keep before deleting. + </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> + Site-specific secret key for Django. + </doc> + </option> + + <option name = "allowed-hosts"> + <doc> + Name of virtual host that runs the Django GUI, if this is not + the same as the system hostname. Django's security code wants + to know the name of the virtual host on which Django is + running, and will fail when it thinks it's running on a + disallowed host. + </doc> + <doc> + If you get an error like "Invalid HTTP_HOST header (you may + need to set ALLOWED_HOSTS)", you will need to set this option. + </doc> + </option> + + <option name = "download-directory" + value = "/var/tmp"> + <doc> + A directory large enough to hold the RouteViews.org routing table dump + fetched by the rpkigui-import-routes script. + </doc> + </option> + + </section> + + <section name = "autoconf"> + + <doc> + rpki-confgen --autoconf records the current autoconf settings + here, so that other options can refer to them. The section name + "autoconf" is magic, don't change it. + </doc> + + <option name = "bindir"> + <doc> + Usually /usr/bin or /usr/local/bin. + </doc> + </option> + + <option name = "datarootdir"> + <doc> + Usually /usr/share or /usr/local/share. + </doc> + </option> + + <option name = "sbindir"> + <doc> + Usually /usr/sbin or /usr/local/sbin. + </doc> + </option> + + <option name = "sysconfdir"> + <doc> + Usually /etc or /usr/local/etc. + </doc> + </option> + + </section> + +</configuration> diff --git a/rp/config/rpki-generate-root-certificate b/rp/config/rpki-generate-root-certificate new file mode 100755 index 00000000..10b8b194 --- /dev/null +++ b/rp/config/rpki-generate-root-certificate @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +""" +Generate an RPKI root certificate for rootd. In most cases you should +not need to do this; see caveats in the manual about running rootd if +you think you need this. This script does nothing that can't also be +done with the OpenSSL command line tool, but on some platforms the +installed copy of openssl doesn't understand the RFC 3779 extensions. +""" + +import os +import sys +import pwd +import time +import rpki.x509 +import rpki.config +import rpki.sundial +import rpki.autoconf +import rpki.resource_set + +os.environ["TZ"] = "UTC" +time.tzset() + +cfg = rpki.config.argparser(section = "rootd", doc = __doc__) + +default_certfile = cfg.get("rpki-root-cert-file", "root.cer") +default_keyfile = cfg.get("rpki-root-key-file", "root.key") +default_talfile = os.path.splitext(default_certfile)[0] + ".tal" + +cfg.argparser.add_argument("-a", "--asns", help = "ASN resources", default = "0-4294967295") +cfg.argparser.add_argument("-4", "--ipv4", help = "IPv4 resources", default = "0.0.0.0/0") +cfg.argparser.add_argument("-6", "--ipv6", help = "IPv6 resources", default = "::/0") +cfg.argparser.add_argument("--certificate", help = "certificate file", default = default_certfile) +cfg.argparser.add_argument("--key", help = "key file", default = default_keyfile) +cfg.argparser.add_argument("--tal", help = "TAL file", default = default_talfile) + +args = cfg.argparser.parse_args() + +resources = rpki.resource_set.resource_bag( + asn = args.asns, + v4 = args.ipv4, + v6 = args.ipv6) + +keypair = rpki.x509.RSA.generate(quiet = True) + +sia = (cfg.get("rpki_base_uri") + "/", + cfg.get("rpki-root-manifest-uri"), + None, + cfg.get("publication_rrdp_notification_uri", section = "myrpki")) + +uris = (cfg.get("rpki-root-cert-uri"), + cfg.get("publication_rrdp_base_uri", section = "myrpki") + "root.cer") + +cert = rpki.x509.X509.self_certify( + keypair = keypair, + subject_key = keypair.get_public(), + serial = 1, + sia = sia, + notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365), + resources = resources) + +with open(args.certificate, "wb") as f: + f.write(cert.get_DER()) + +with open(args.tal, "w") as f: + for uri in uris: + f.write(uri + "\n") + f.write(keypair.get_public().get_Base64()) + +with os.fdopen(os.open(args.key, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0400), "w") as f: + f.write(keypair.get_DER()) + +try: + pw = pwd.getpwnam(rpki.autoconf.RPKI_USER) + os.chown(args.key, pw.pw_uid, pw.pw_gid) +except: + pass diff --git a/rp/config/rpki-manage b/rp/config/rpki-manage new file mode 100755 index 00000000..ac3cc967 --- /dev/null +++ b/rp/config/rpki-manage @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Using a Python script to run sudo to run a Python script is a bit +# silly, but it lets us use rpki.autoconf to locate sudo, lets us +# avoid needing a custom setuid wrapper, lets us avoid another pass +# through the adventures of shell quoting and tokenization, and +# generally is just a lot simpler to implement correctly. +# +# OK, it's probably a few milliseconds slower. Big deal. + +if __name__ == "__main__": + + import os + import pwd + import sys + import rpki.autoconf + + try: + uid = pwd.getpwnam(rpki.autoconf.RPKI_USER).pw_uid + except: + uid = None + + if uid is None or uid == os.geteuid(): + + # 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.setdefault("DJANGO_SETTINGS_MODULE", "rpki.django_settings.gui") + + from django.core.management import execute_from_command_line + + execute_from_command_line() + + else: + + try: + argv = [rpki.autoconf.SUDO, "-u", rpki.autoconf.RPKI_USER, sys.executable] + argv.extend(os.path.abspath(a) if i == 0 else a for i, a in enumerate(sys.argv)) + os.execv(argv[0], argv) + sys.exit("rpki-manage startup failure, no exception so don't know why, sorry") + + except Exception as e: + sys.exit("Couldn't exec sudo python rpki-manage: {!s}".format(e)) diff --git a/rp/config/rpki-sql-backup b/rp/config/rpki-sql-backup new file mode 100755 index 00000000..09e5856e --- /dev/null +++ b/rp/config/rpki-sql-backup @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +# $Id$ +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2010-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. + +""" +Back up data from SQL databases, looking at config file to figure out +which databases and what credentials to use with them, and eliminating +duplicates in cases where we've configured multiple applications to +share a single database. +""" + +import os +import sys +import time +import argparse +import subprocess +import rpki.config + +os.environ["TZ"] = "UTC" +time.tzset() + +cfg = rpki.config.argparser(doc = __doc__, section = "myrpki") +cfg.argparser.add_argument("-o", "--output", type = argparse.FileType("wb"), default = sys.stdout, + help = "destination for SQL dump (default: stdout)") +cfg.argparser.add_argument("-v", "--verbose", action = "store_true", + help = "whistle while you work") +args = cfg.argparser.parse_args() + +templates = dict(mysql = "mysqldump --add-drop-database -u{username} -p{password} -B{database}", + sqlite3 = "sqlite3 {database} .dump", + postgresql = "sudo -u {username} pg_dump {database}") + +cmds = [] + +for name in ("rpkid", "irdbd", "pubd"): + if cfg.getboolean("start_" + name, False): + cmd = templates[cfg.get("sql-engine", section = name)] + cmd = cmd.format(database = cfg.get("sql-database", section = name), + username = cfg.get("sql-username", section = name), + password = cfg.get("sql-password", section = name)) + if cmd not in cmds: + cmds.append(cmd) + +for cmd in cmds: + if args.verbose: + sys.stderr.write("[Running \"{}\"]\n".format(cmd)) + subprocess.check_call(cmd.split(), stdout = args.output) diff --git a/rp/config/rpki-sql-setup b/rp/config/rpki-sql-setup new file mode 100755 index 00000000..6fd64588 --- /dev/null +++ b/rp/config/rpki-sql-setup @@ -0,0 +1,348 @@ +#!/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. + +""" +Automated setup of SQL stuff used by the RPKI tools. Pulls +configuration from rpki.conf, prompts for SQL password when needed. +""" + +import os +import pwd +import sys +import getpass +import textwrap +import argparse +import rpki.config + + +class Abstract_Driver(object): + + # Kludge to make classes derived from this into singletons. Net + # of a Million Lies says this is Not Pythonic, but it seems to + # work, so long as one doesn't attempt to subclass the resulting + # driver classes. For our purposes, it will do. + + __instance = None + + def __new__(cls, *args, **kwargs): + if cls.__instance is None: + cls.__instance = object.__new__(cls, *args, **kwargs) + return cls.__instance + + def db_accessible(self, udb): + try: + self._db_accessible_test(udb) + except: + return False + else: + return True + + def fetchone(self): + return self._cur.fetchone() + + def fetchall(self): + return self._cur.fetchall() + + def close(self): + self._cur.close() + self._db.close() + + def log(self, msg): + if self.args.verbose: + sys.stderr.write(msg + "\n") + + +class MySQL_Driver(Abstract_Driver): + + _initialized = False + + def __init__(self, args): + try: + self.driver + except AttributeError: + from rpki.mysql_import import MySQLdb + self.driver = MySQLdb + self.args = args + + def _db_accessible_test(self, udb): + self.driver.connect(db = udb.database, user = udb.username, passwd = udb.password).close() + + def db_exists(self, udb): + self.execute("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '{0.database}'".format(udb)) + return bool(self.fetchone()[0]) + + def execute(*args): + try: + self._cur + except AttributeError: + self.log("MySQL driver initializing root connection") + if self.args.mysql_defaults: + mysql_cfg = rpki.config.parser(set_filename = self.args.mysql_defaults, section = "client") + self._db = self.driver.connect(db = "mysql", + user = mysql_cfg.get("user"), + passwd = mysql_cfg.get("password")) + else: + self._db = self.driver.connect(db = "mysql", + user = "root", + passwd = getpass.getpass("Please enter your MySQL root password: ")) + self._db.autocommit(True) + self._cur = self._db.cursor() + self.log("MySQL driver executing {}".format(", ".join(args))) + return self._cur.execute(*args) + + def create(self, udb): + self.execute("CREATE DATABASE IF NOT EXISTS {0.database}".format(udb)) + self.fix_grants(udb) + + def drop(self, udb): + self.execute("DROP DATABASE IF EXISTS {0.database}".format(udb)) + + def script_drop(self, udb): + self.args.script_output.write("DROP DATABASE IF EXISTS {};\n".format(udb.database)) + + def fix_grants(self, udb): + self.execute("GRANT ALL ON {0.database}.* TO {0.username}@localhost IDENTIFIED BY %s".format(udb), + (udb.password,)) + +class SQLite3_Driver(Abstract_Driver): + + def __init__(self, args): + try: + self.driver + except AttributeError: + import sqlite3 + self.driver = sqlite3 + self.args = args + + def _db_accessible_test(self, udb): + self.driver.connect(udb.database).close() + + def db_exists(self, udb): + return os.path.exists(udb.database) + + def _grant(self, udb): + if udb.username and os.geteuid() == 0: + pw = pwd.getpwnam(udb.username) + os.chown(udb.database, pw.pw_uid, pw.pw_gid) + + def create(self, udb): + self._db_accessible_test(udb.database) + self._grant(udb) + + def drop(self, udb): + os.unlink(udb.database) + + def script_drop(self, udb): + self.args.script_output.write("rm {}\n".format(udb.database)) + + def fix_grants(self, udb): + self._grant(udb) + + +class PostgreSQL_Driver(Abstract_Driver): + + def __init__(self, args): + try: + self.driver + except AttributeError: + import psycopg2 + self.driver = psycopg2 + self.args = args + if args.postgresql_root_username and (os.getuid() == 0 or os.geteuid() == 0): + self._pw = pwd.getpwnam(args.postgresql_root_username) + else: + self._pw = None + self.log("Initialized PostgreSQL driver, pw {!r}".format(self._pw)) + + def _seteuid(self, new_uid): + old_uid = os.geteuid() + if new_uid != old_uid: + self.log("PostgreSQL driver changing EUID from {} to {}".format(old_uid, new_uid)) + os.seteuid(new_uid) + return old_uid + + def execute(self, *args): + try: + self._cur + except AttributeError: + self.log("PostgreSQL driver opening connection to database {}".format(self.args.postgresql_root_database)) + if self._pw is not None: + euid = self._seteuid(self._pw.pw_uid) + try: + self._db = self.driver.connect(database = self.args.postgresql_root_database) + self._db.autocommit = True + self._cur = self._db.cursor() + finally: + if self._pw is not None: + self._seteuid(euid) + self.log("PostgreSQL driver executing {}".format(", ".join(args))) + return self._cur.execute(*args) + + def _db_accessible_test(self, udb): + pw = pwd.getpwnam(udb.username) + uid = self._seteuid(pw.pw_uid) + try: + self.driver.connect(database = udb.database, user = udb.username , password = udb.password).close() + finally: + self._seteuid(uid) + + def db_exists(self, udb): + self.execute("SELECT COUNT(*) FROM pg_database WHERE datname = '{0.database}'".format(udb)) + return bool(self.fetchone()[0]) + + def role_in_use(self, udb): + self.execute(textwrap.dedent('''\ + SELECT COUNT(*) FROM pg_database + JOIN pg_roles ON pg_database.datdba = pg_roles.oid + WHERE pg_roles.rolname = '{0.username}' + '''.format(udb))) + return bool(self.fetchone()[0]) + + def create(self, udb): + if not self.role_in_use(udb): + self.execute("CREATE ROLE {0.username} LOGIN PASSWORD '{0.password}'".format(udb)) + if not self.db_exists(udb): + self.execute("CREATE DATABASE {0.database} OWNER {0.username}".format(udb)) + + def drop(self, udb): + self.execute("DROP DATABASE IF EXISTS {0.database}".format(udb)) + if not self.role_in_use(udb): + self.execute("DROP ROLE IF EXISTS {0.username}".format(udb)) + + def script_drop(self, udb): + self.args.script_output.write(textwrap.dedent('''\ + DROP DATABASE IF EXISTS {0.database}; + DO $$ BEGIN + IF NOT EXISTS (SELECT * FROM pg_database JOIN pg_roles + ON pg_database.datdba = pg_roles.oid + WHERE pg_roles.rolname = '{0.username}') + THEN + DROP ROLE IF EXISTS {0.username}; + END IF; + END $$; + '''.format(udb))) + + def fix_grants(self, udb): + self.execute("ALTER DATABASE {0.database} OWNER TO {0.username}".format(udb)) + self.execute("ALTER ROLE {0.username} WITH PASSWORD '{0.password}".format(udb)) + + +class UserDB(object): + """ + Class to wrap access parameters for a particular database. + """ + + drivers = dict(sqlite3 = SQLite3_Driver, + mysql = MySQL_Driver, + postgresql = PostgreSQL_Driver) + + def __init__(self, args, name): + self.database = cfg.get("sql-database", section = name) + self.username = cfg.get("sql-username", section = name) + self.password = cfg.get("sql-password", section = name) + self.engine = cfg.get("sql-engine", section = name) + self.driver = self.drivers[self.engine](args) + self.args = args + + def drop(self): + if self.args.force or self.driver.db_accessible(self): + self.driver.drop(self) + + def create(self): + if self.args.force or not self.driver.db_accessible(self): + self.driver.create(self) + + def script_drop(self): + self.driver.script_drop(self) + + def drop_and_create(self): + if self.args.force or self.driver.db_accessible(self): + self.driver.drop(self) + self.driver.create(self) + + def fix_grants(self): + if self.args.force or not self.driver.db_accessible(self): + self.driver.fix_grants(self) + + +parser = argparse.ArgumentParser(description = __doc__) +parser.add_argument("-c", "--config", + help = "specify alternate location for rpki.conf") +parser.add_argument("-d", "--debug", action = "store_true", + help = "enable debugging (eg, Python backtraces)") +parser.add_argument("-v", "--verbose", action = "store_true", + help = "whistle while you work") +parser.add_argument("-f", "--force", action = "store_true", + help = "force database create, drop, or grant regardless of current state") + +parser.add_argument("--mysql-defaults", + help = "specify MySQL root access credentials via a configuration file") + + +parser.add_argument("--postgresql-root-database", default = "postgres", + help = "name of PostgreSQL control database") +parser.add_argument("--postgresql-root-username", + help = "username of PostgreSQL control role") + +subparsers = parser.add_subparsers(title = "Commands", metavar = "", dest = "dispatch") + +subparsers.add_parser("create", + help = "create databases and load schemas") + +subparsers.add_parser("drop", + help = "drop databases") + +subparser = subparsers.add_parser("script-drop", + help = "show SQL commands to drop databases") +subparser.add_argument("script_output", + nargs = "?", type = argparse.FileType("w"), default = "-", + help = "destination for drop script") + +subparsers.add_parser("drop-and-create", + help = "drop databases then recreate them and load schemas") + +subparsers.add_parser("fix-grants", + help = "whack database to match configuration file") + +args = parser.parse_args() + +try: + + cfg = rpki.config.parser(set_filename = args.config, section = "myrpki") + + names = [name for name in ("irdbd", "rpkid", "pubd") + if cfg.getboolean("start_" + name, False)] + names.append("rcynic") + + # For now, we quietly ignore missing sections rather than throwing an exception. + # I could make a case either way for this, but ignoring missing sections is a + # lot easier to clean up while debugging the installation scripts. + + for name in names: + if cfg.has_section(name): + udb = UserDB(args = args, name = name) + method = args.dispatch.replace("-", "_") + getattr(udb, method)() + +except Exception, e: + if args.debug: + raise + else: + sys.exit(str(e)) |