#!/usr/bin/env python # $Id$ # # Copyright (C) 2015-2016 Parsons Government Services ("PARSONS") # Portions 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 PARSONS, DRL, AND ISC DISCLAIM # ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL # PARSONS, 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 socket import urllib2 import argparse import platform import textwrap import subprocess 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. # %(allow)s # # 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. # %(allow)s # # 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. # Timeout is per current RRDP I-D, this will need to track # any changes as the specification evolves. # ExpiresActive on ExpiresDefault "access plus 1 minute" # # By default, this configuration assumes that you use name-based # virtual hosting. If that's not what you want, you may need # to change this. # # # By default, we enable an HTTPS virtual host on this machine's # fully qualified domain name. This works for simple # configurations, but if you're running a more complex Apache # configuration or want to run the GUI on a different hostname, # you may need to change this. # ServerName %(fqdn)s # # Configure the WSGI application to run as a separate process from # the Apache daemon itself. # %(WSGI_DAEMON_PROCESS)s %(WSGI_PROCESS_GROUP)s # # Allow access to our WSGI directory. # %(allow)s # # Define the URL to the RPKI GUI # WSGIScriptAlias / %(datarootdir)s/rpki/wsgi/rpki.wsgi # # Allow access to static content (icons, etc). # %(allow)s # # Add the aliases Django expects for static content. # Alias /media/
<?xml version="1.0" encoding="US-ASCII"?>
<!--Automatically generated, do not edit.-->
<msg xmlns="http://www.hactrn.net/uris/rpki/left-right-spec/" version="1">
  <route_origin action="set" type="reply" self_id="42" route_origin_id="88"/>
</msg>
textwrap.dedent('''\ # It looks like you already have HTTPS enabled in your # Apache configuration, which makes your configuration too # complex for us to enable support for the RPKI GUI automatically. # # To enable support, take a look at %s # and copy what you need from that file into %s, # paying attention to the comments which mark the bits that # you might (or might not) need to change or omit, depending # on the details of your particular Apache configuration. ''' % (self.apache_conf_sample, self.apache_conf)) @property def apache_conf_target(self): raise NotImplementedError def restart(self): raise NotImplementedError def install(self): with open(self.apache_conf_sample, "w") as f: self.log("Writing %s" % f.name) f.write(self.apache_conf_preface) f.write(self.name_virtual_host) f.write(self.vhost) if not os.path.exists(self.apache_conf): self.unlink(self.apache_conf) with open(self.apache_conf, "w") as f: self.log("Writing %s" % f.name) if self.test_url("https://%s/" % fqdn): f.write(self.too_complex) sys.stdout.write(self.too_complex) else: if not self.test_tcp("localhost", 443): f.write(self.apache_conf_preface) f.write(self.name_virtual_host) f.write(self.vhost) if not os.path.exists(self.apache_conf_target): self.unlink(self.apache_conf_target) self.log("Symlinking %s to %s" % ( self.apache_conf_target, self.apache_conf)) os.symlink(self.apache_conf, self.apache_conf_target) self.add_certs() self.enable() self.restart() def enable(self): pass def disable(self): pass def remove(self): try: same = open(self.apache_conf, "r").read() == open(self.apache_conf_sample, "r").read() except: same = False self.unlink(self.apache_conf_sample) if same: self.unlink(self.apache_conf) self.unlink(self.apache_conf_target) self.disable() self.restart() def purge(self): self.remove() self.unlink(self.apache_conf) self.del_certs() @staticmethod def test_url(url = "https://localhost/"): try: urllib2.urlopen(url).close() except IOError: return False else: return True @staticmethod def test_tcp(host = "localhost", port = 443, family = socket.AF_UNSPEC, proto = socket.SOCK_STREAM): try: addrinfo = socket.getaddrinfo(host, port, family, proto) except socket.error: return False for af, socktype, proto, canon, sa in addrinfo: # pylint: disable=W0612 try: s = socket.socket(af, socktype, proto) s.connect(sa) s.close() except socket.error: continue else: return True return False class FreeBSD(Platform): """ FreeBSD. """ # On FreeBSD we have to ask httpd what version it is before we know # where to put files or what to call the service. In FreeBSD's makefiles, # this value is called APACHE_VERSION, and is calculated thusly: # # httpd -V | sed -ne 's/^Server version: Apache\/\([0-9]\)\.\([0-9]*\).*/\1\2/p' _apache_name = None @property def apache_name(self): if self._apache_name is None: self._apache_name = "apache%s" % self.args.apache_version return self._apache_name @property def apache_conf_target(self): return "/usr/local/etc/%s/Includes/rpki.conf" % self.apache_name apache_conf_preface = textwrap.dedent('''\ # These directives tell Apache to listen on the HTTPS port # and to enable name-based virtual hosting. If you already # have HTTPS enabled elsewhere in your configuration, you may # need to remove these. Listen [::]:443 Listen 0.0.0.0:443 ''') def restart(self): self.run("service", self.apache_name, "restart") class Debian(Platform): """ Debian and related platforms like Ubuntu. """ # Pull the current version number for released code. Use # something very large when there is no version (eg, "sid"). @property def distribution_version(self): v = platform.linux_distribution()[1].split(".") if all(d.isdigit() for d in v): return tuple(int(d) for d in v) else: return (99999999, 0) # On Debian, the filename must end in .conf on Stretch and must not # end in .conf on Wheezy. Haven't checked Jessie yet, will need to # update this if we ever sort out the version skew mess on Jessie. @property def apache_conf_target(self): if self.distribution_version < (8, 0): return "/etc/apache2/sites-available/rpki" else: return "/etc/apache2/sites-available/rpki.conf" snake_oil_cer = "/etc/ssl/certs/ssl-cert-snakeoil.pem" snake_oil_key = "/etc/ssl/private/ssl-cert-snakeoil.key" def add_certs(self): if not os.path.exists(self.snake_oil_cer) or not os.path.exists(self.snake_oil_key): return Platform.add_certs(self) if not os.path.exists(self.apache_cer): self.unlink(self.apache_cer) os.symlink(self.snake_oil_cer, self.apache_cer) if not os.path.exists(self.apache_key): self.unlink(self.apache_key) os.symlink(self.snake_oil_key, self.apache_key) 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 # like a bad idea, so make sure it's off. self.run("a2dismod", "-f", "deflate") def disable(self): self.run("a2dissite", "rpki") def restart(self): self.run("service", "apache2", "restart") class Ubuntu(Debian): # On Ubuntu, the filename must end in .conf on Trusty and must not # end in .conf on Precise. @property def apache_conf_target(self): if self.distribution_version < (14, 0): return "/etc/apache2/sites-available/rpki" else: return "/etc/apache2/sites-available/rpki.conf" class NIY(Platform): # pylint: disable=W0223 def __init__(self, args): super(NIY, self).__init__(args) raise NotImplementedError("Platform %s not implemented yet, sorry" % self.__class__.__name__) class Redhat(NIY): # pylint: disable=W0223 "Redhat family of Linux distributions (Fedora, CentOS)." class Darwin(NIY): # pylint: disable=W0223 "Mac OS X (aka Darwin)." def main(): """ Generate and (de)install configuration suitable for using Apache httpd to drive the RPKI web interface under WSGI. """ parser = argparse.ArgumentParser(description = __doc__) group1 = parser.add_mutually_exclusive_group() group2 = parser.add_mutually_exclusive_group() parser.add_argument("-v", "--verbose", help = "whistle while you work", action = "store_true") parser.add_argument("--apache-version", help = "Apache version (default " + rpki.autoconf.APACHE_VERSION + ")", type = int, default = rpki.autoconf.APACHE_VERSION) group1.add_argument("--freebsd", help = "configure for FreeBSD", action = "store_const", dest = "platform", const = FreeBSD) group1.add_argument("--debian", help = "configure for Debian", action = "store_const", dest = "platform", const = Debian) group1.add_argument("--ubuntu", help = "configure for Ubuntu", action = "store_const", dest = "platform", const = Ubuntu) group1.add_argument("--redhat", "--fedora", "--centos", help = "configure for Redhat/Fedora/CentOS", action = "store_const", dest = "platform", const = Redhat) group1.add_argument("--macosx", "--darwin", help = "configure for Mac OS X (Darwin)", action = "store_const", dest = "platform", const = Darwin) group1.add_argument("--guess", help = "guess which platform configuration to use", action = "store_const", dest = "platform", const = Guess) group2.add_argument("-i", "--install", help = "install configuration", action = "store_const", dest = "action", const = "install") group2.add_argument("-r", "--remove", "--deinstall", "--uninstall", help = "remove configuration", action = "store_const", dest = "action", const = "remove") group2.add_argument("-P", "--purge", help = "remove configuration with extreme prejudice", action = "store_const", dest = "action", const = "purge") parser.set_defaults(platform = Guess, action = "install") args = parser.parse_args() try: args.platform(args) except Exception, e: sys.exit(str(e)) if __name__ == "__main__": main()