Script to generate nginx & apache configs
authorDan Scott <dscott@laurentian.ca>
Sun, 16 Jul 2017 21:07:45 +0000 (14:07 -0700)
committerDan Scott <dan@coffeecode.net>
Sun, 16 Jul 2017 21:25:10 +0000 (17:25 -0400)
Given a set of hostnames, generate the appropriate nginx proxy and apache
config files. Also spit out a certbot command to generate a letsencrypt
certificate based on those hostnames.

Signed-off-by: Dan Scott <dscott@laurentian.ca>
Open-ILS/src/support-scripts/webserver_config.py [new file with mode: 0755]

diff --git a/Open-ILS/src/support-scripts/webserver_config.py b/Open-ILS/src/support-scripts/webserver_config.py
new file mode 100755 (executable)
index 0000000..566d8b4
--- /dev/null
@@ -0,0 +1,406 @@
+#!/usr/bin/env python3
+import os, os.path
+
+ssl_certname = 'boreal-test.concat.ca';
+
+# /etc/nginx/concat_ssl.conf
+ssl_conf = """listen 443 ssl; # managed by Certbot
+ssl_certificate /etc/letsencrypt/live/{certname}/fullchain.pem; # managed by Certbot
+ssl_certificate_key /etc/letsencrypt/live/{certname}/privkey.pem; # managed by Certbot
+include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+
+if ($scheme != "https") {{
+    return 301 https://$host$request_uri;
+}} # managed by Certbot
+
+# generate with openssl dhparam -out dhparams.pem 2048
+ssl_dhparam /etc/letsencrypt/dhparams.pem;
+
+# From https://mozilla.github.io/server-side-tls/ssl-config-generator/
+ssl_session_tickets off;
+
+# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
+add_header Strict-Transport-Security max-age=15768000;
+
+# OCSP Stapling ---
+# fetch OCSP records from URL in ssl_certificate and cache them
+ssl_stapling on;
+ssl_stapling_verify on;
+""".format(certname=ssl_certname)
+
+# /etc/nginx/concat_headers.conf
+headers_conf = """proxy_set_header Host $host;
+proxy_set_header X-Real-IP $remote_addr;
+proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+proxy_set_header X-Forwarded-Proto $scheme;"""
+
+# /etc/nginx/osrf_sockets.conf
+sockets_conf = """location /osrf-websocket-translator {
+    proxy_pass https://localhost:7682;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+    # Needed for websockets proxying.
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+
+    # Raise the default nginx proxy timeout values to an arbitrarily
+    # high value so that we can leverage osrf-websocket-translator's 
+    # timeout settings.
+    proxy_connect_timeout 5m;
+    proxy_send_timeout 1h;
+    proxy_read_timeout 1h;
+}"""
+
+# /etc/nginx/sites-available/conifer-test
+server_block = """server {{
+    listen 80;
+    server_name {hostname};
+
+    include /etc/nginx/concat_ssl.conf;
+    include /etc/nginx/osrf_sockets.conf;
+
+    location / {{
+        proxy_pass https://localhost:7443;
+        include /etc/nginx/concat_headers.conf;
+    }}
+}}
+"""
+
+domains = {
+    'algoma.concat.ca': {
+        'templates': ['algoma'],
+        'locale': 'fr_ca',
+        'physical_loc': 111
+    },
+    'boreal.concat.ca': {
+        'templates': ['boreal'],
+        'locale': 'fr_ca',
+        'default_locale': 'fr-CA',
+        'physical_loc': 135
+    },
+    'ccrconnect.concat.ca': {
+        'locale': 'fr_ca',
+        'physical_loc': 144
+    },
+    'crc.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'default_locale': 'fr-CA',
+        'physical_loc': 130
+    },
+    'hrsrh.concat.ca': {
+        'templates': ['hrsrh'],
+        'locale': 'fr_ca',
+        'physical_loc': 115
+    },
+    'cdev1.concat.ca': {
+        'locale': 'fr_ca',
+    },
+    'hsn.concat.ca': {
+        'templates': ['hrsrh'],
+        'locale': 'fr_ca',
+        'physical_loc': 115
+    },
+    'huntington.concat.ca': {
+        'templates': ['huntington'],
+        'locale': 'fr_ca',
+        'physical_loc': 104
+    },
+    'laurentian.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'physical_loc': 105,
+        'robots': 'osul'
+    },
+    'laurentienne.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'default_locale': 'fr-CA',
+        'physical_loc': 105
+    },
+    'medb.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'physical_loc': 117
+    },
+    'mediacentre.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'physical_loc': 108
+    },
+    'mrc.concat.ca': {
+        'templates': ['laurentian'],
+        'locale': 'fr_ca',
+        'physical_loc': 131
+    },
+    'nosm.concat.ca': {
+        'templates': ['nosm'],
+        'locale': 'fr_ca',
+        'physical_loc': 125
+    },
+    'sah.concat.ca': {
+        'locale': 'fr_ca',
+        'physical_loc': 116
+    },
+    'sjcg.concat.ca': {
+        'templates': ['sjcg'],
+        'locale': 'fr_ca',
+        'physical_loc': 133
+    },
+    'uhearst.concat.ca': {
+        'templates': ['uhearst'],
+        'locale': 'fr_ca',
+        'default_locale': 'fr-CA',
+        'physical_loc': 114
+    },
+    'usudbury.concat.ca': {
+        'templates': ['laurentian', 'usudbury'],
+        'locale': 'fr_ca',
+        'physical_loc': 107
+    },
+    'www.concat.ca': {
+        'locale': 'fr_ca',
+    },
+    '49thregiment.concat.ca': {
+        'physical_loc': 134
+    }
+}
+
+missing_test_domains = (
+    'cfof-test.concat.ca',
+    'medb-test.concat.ca',
+)
+
+def mutate_test_hostname(host):
+    "Generate test hostname for a single string"
+    if not host.startswith('cdev1'):
+        x = host.partition('.')
+        host = ''.join((x[0], '-test', x[1], x[2]))
+    return host
+
+def mutate_test_hostnames(test=True):
+    """Generate proper test hostnames"""
+    testdomains = []
+    
+    for host in sorted(domains.keys()):
+        if test:
+            host = mutate_test_hostname(host)
+        testdomains.append(host)
+    return testdomains
+
+def generate_config(test=True):
+    """Generate nginx config files"""
+    os.makedirs('nginx/sites-available', exist_ok=True)
+    os.makedirs('apache2/sites-available', exist_ok=True)
+    with open(os.path.join('nginx', 'concat_ssl.conf'), 'w') as f:
+        f.write(ssl_conf)
+
+    with open(os.path.join('nginx', 'concat_headers.conf'), 'w') as f:
+        f.write(headers_conf)
+
+    with open(os.path.join('nginx', 'osrf_sockets.conf'), 'w') as f:
+        f.write(sockets_conf)
+
+    with open(os.path.join('nginx/sites-available', 'conifer.conf'), 'w') as f:
+        for host in mutate_test_hostnames():
+            f.write(server_block.format(hostname=host))
+
+    with open(os.path.join('apache2/sites-available', 'conifer.conf'), 'w') as f:
+       f.write(generate_apache_vhost(test))
+
+def generate_certbot(test=True):
+    """Generate certbot command"""
+    certbot = 'certbot --nginx run '
+    for host in mutate_test_hostnames():
+        if host in missing_test_domains:
+            continue
+        certbot = certbot + " -d {hostname}".format(hostname=host)
+    print(certbot)
+
+def generate_apache_vhost(test=True):
+   """Generate apache2/sites-available/eg.conf"""
+   vhost = ApacheVHost.apache_eg_conf
+   for hostname in sorted(domains.keys()):
+       ahost = ApacheVHost(hostname, test)
+       vhost = vhost + ahost.vhost()
+   return vhost
+
+class ApacheVHost:
+    # /etc/apache2/sites-available/eg.conf
+    apache_eg_conf = """LogLevel info 
+# - log locally
+# CustomLog /var/log/apache2/access.log combined
+# ErrorLog /var/log/apache2/error.log
+# - log to syslog 
+CustomLog "|/usr/bin/logger -p local7.info" common
+ErrorLog syslog:local7
+
+# ----------------------------------------------------------------------------------
+# Set up Perl 
+# ----------------------------------------------------------------------------------
+
+# - needed by CGIs
+PerlRequire /etc/apache2/eg_startup
+PerlChildInitHandler OpenILS::WWW::Reporter::child_init
+PerlChildInitHandler OpenILS::WWW::SuperCat::child_init
+PerlChildInitHandler OpenILS::WWW::AddedContent::child_init
+PerlChildInitHandler OpenILS::WWW::AutoSuggest::child_init
+PerlChildInitHandler OpenILS::WWW::PhoneList::child_init
+PerlChildInitHandler OpenILS::WWW::EGWeb::child_init
+
+# ----------------------------------------------------------------------------------
+# Set some defaults for our working directories
+# ----------------------------------------------------------------------------------
+<Directory /openils/var/web>
+    Require all granted
+</Directory>
+
+# ----------------------------------------------------------------------------------
+# XUL directory
+# ----------------------------------------------------------------------------------
+<Directory /openils/var/web/xul>
+    Options Indexes FollowSymLinks
+    AllowOverride None
+    Require all granted
+</Directory>
+
+# ----------------------------------------------------------------------------------
+# Remove the language portion from the URL
+# ----------------------------------------------------------------------------------
+AliasMatch ^/opac/.*/skin/(.*)/(.*)/(.*) /openils/var/web/opac/skin/$1/$2/$3
+AliasMatch ^/opac/.*/extras/slimpac/(.*) /openils/var/web/opac/extras/slimpac/$1
+AliasMatch ^/opac/.*/extras/selfcheck/(.*) /openils/var/web/opac/extras/selfcheck/$1
+
+# ----------------------------------------------------------------------------------
+# System config CGI scripts go here
+# ----------------------------------------------------------------------------------
+Alias /cgi-bin/offline/ "/openils/var/cgi-bin/offline/"
+<Directory "/openils/var/cgi-bin/offline">
+    AddHandler cgi-script .cgi .pl
+    AllowOverride None
+    Options None
+    Require host 10.0.0.0/8
+    Options FollowSymLinks ExecCGI Indexes
+</Directory>
+
+# ----------------------------------------------------------------------------------
+# Updates folder
+# ----------------------------------------------------------------------------------
+Alias /updates/ "/openils/var/updates/pub/"
+<Directory "/openils/var/updates/pub">
+    <Files check>
+        ForceType cgi-script
+    </Files>
+    <Files update.rdf>
+        ForceType cgi-script
+    </Files>
+    <Files manualupdate.html>
+        ForceType cgi-script
+    </Files>
+    <Files download>
+        ForceType cgi-script
+    </Files>
+    AllowOverride None
+    Options None
+    Options ExecCGI
+    Require all granted
+</Directory>
+
+# ----------------------------------------------------------------------------------
+# OPTIONAL: Set how long the client will cache our content.  Change to suit
+# ----------------------------------------------------------------------------------
+ExpiresActive On
+ExpiresDefault "access plus 1 month"
+ExpiresByType text/html "access plus 18 hours"
+ExpiresByType application/xhtml+xml "access plus 18 hours"
+ExpiresByType application/x-javascript "access plus 18 hours"
+ExpiresByType application/javascript "access plus 18 hours"
+ExpiresByType text/css "access plus 50 minutes"
+
+# ----------------------------------------------------------------------------------
+# Set up our SSL virtual host
+# ----------------------------------------------------------------------------------
+#Listen 443
+<VirtualHost *:7443>
+    DocumentRoot "/openils/var/web"
+    ServerName localhost:443
+    ServerAlias 127.0.0.1:443
+
+    # - absorb the shared virtual host settings
+    Include eg_vhost.conf
+    Include eg_vhost_ssl.conf
+
+</VirtualHost>
+"""
+
+    apache_robots = """Alias /robots.txt /openils/var/web/{host}_robots.txt
+"""
+
+    apache_template = """
+        PerlAddVar OILSWebTemplatePath '/openils/var/templates_{template}'"""
+
+    apache_locale = """
+        PerlAddVar OILSWebLocale '{locale}'
+        PerlAddVar OILSWebLocale '/openils/var/data/locale/opac/{locale_upper}.po'"""
+
+    apache_default_locale = """
+        PerlAddVar OILSWebDefaultLocale "{locale}"
+"""
+
+    apache_physical_loc = "SetEnv physical_loc {location}"
+
+    apache_vhost = """
+<VirtualHost *:7443>
+    DocumentRoot '/openils/var/web'
+    ServerName https://{hostname}:443
+
+    # - absorb the shared virtual host settings
+    Include eg_vhost.conf
+    Include eg_vhost_ssl.conf
+    {robots}
+    <Location /eg>{template}{locale}{default_locale}
+    </Location>
+    {physical_loc}
+</VirtualHost>
+"""
+
+    def __init__(self, hostname, test=True):
+        self.hostname = hostname
+        host = domains[self.hostname]
+        if test:
+            self.hostname = mutate_test_hostname(hostname)
+        self.robots = ''
+        self.default_locale = ''
+        self.locale = ''
+        self.physical_loc = ''
+        self.templates = ''
+        if 'robots' in host:
+            self.robots = ApacheVHost.apache_robots.format(host=host['robots'])
+        if 'default_locale' in host:
+            self.default_locale = ApacheVHost.apache_default_locale.format(locale=host['default_locale'])
+        if 'locale' in host:
+            locale = host['locale']
+            locale_upper = ''.join((locale[0:2], '-', locale[3:5].upper()))
+            self.locale = ApacheVHost.apache_locale.format(locale=locale, locale_upper=locale_upper)
+        if 'physical_loc' in host:
+           self.physical_loc = ApacheVHost.apache_physical_loc.format(location=host['physical_loc'])
+        if 'templates' in host:
+            for template in host['templates']:
+                self.templates = self.templates + ApacheVHost.apache_template.format(template=template)
+
+    def vhost(self):
+        return ApacheVHost.apache_vhost.format(
+            hostname=self.hostname,
+            robots=self.robots,
+            template=self.templates,
+            locale = self.locale,
+            default_locale = self.default_locale,
+            physical_loc = self.physical_loc
+        )
+
+if __name__ == '__main__':
+    test = True
+    generate_config(test)
+    generate_certbot(test)