From 724e89d308a841a3d869ffb4bf2d8ee11947dc1d Mon Sep 17 00:00:00 2001
From: Jamie Curnow <jcurnow@vision6.com.au>
Date: Fri, 17 Aug 2018 09:25:59 +1000
Subject: [PATCH] Nginx templates

---
 src/backend/index.js                        |  12 +-
 src/backend/internal/certificate.js         |  52 ++++++-
 src/backend/internal/nginx.js               |  22 +--
 src/backend/internal/ssl.js                 | 164 --------------------
 src/backend/templates/_assets.conf          |   4 +
 src/backend/templates/_certificates.conf    |  12 ++
 src/backend/templates/_exploits.conf        |   4 +
 src/backend/templates/_forced_ssl.conf      |   6 +
 src/backend/templates/_header_comment.conf  |   3 +
 src/backend/templates/_listen.conf          |   5 +
 src/backend/templates/dead_host.conf        |  23 +--
 src/backend/templates/letsencrypt.conf      |  10 --
 src/backend/templates/proxy_host.conf       |  39 +----
 src/backend/templates/redirection_host.conf |  38 ++---
 src/backend/templates/stream.conf           |  10 +-
 15 files changed, 129 insertions(+), 275 deletions(-)
 delete mode 100644 src/backend/internal/ssl.js
 create mode 100644 src/backend/templates/_assets.conf
 create mode 100644 src/backend/templates/_certificates.conf
 create mode 100644 src/backend/templates/_exploits.conf
 create mode 100644 src/backend/templates/_forced_ssl.conf
 create mode 100644 src/backend/templates/_header_comment.conf
 create mode 100644 src/backend/templates/_listen.conf
 delete mode 100644 src/backend/templates/letsencrypt.conf

diff --git a/src/backend/index.js b/src/backend/index.js
index 5a29476..df3d63b 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -5,11 +5,11 @@
 const logger = require('./logger').global;
 
 function appStart () {
-    const migrate      = require('./migrate');
-    const setup        = require('./setup');
-    const app          = require('./app');
-    const apiValidator = require('./lib/validator/api');
-    const internalSsl  = require('./internal/ssl');
+    const migrate             = require('./migrate');
+    const setup               = require('./setup');
+    const app                 = require('./app');
+    const apiValidator        = require('./lib/validator/api');
+    const internalCertificate = require('./internal/certificate');
 
     return migrate.latest()
         .then(() => {
@@ -20,7 +20,7 @@ function appStart () {
         })
         .then(() => {
 
-            internalSsl.initTimer();
+            internalCertificate.initTimer();
 
             const server = app.listen(81, () => {
                 logger.info('PID ' + process.pid + ' listening on port 81 ...');
diff --git a/src/backend/internal/certificate.js b/src/backend/internal/certificate.js
index 70cc990..034a6c4 100644
--- a/src/backend/internal/certificate.js
+++ b/src/backend/internal/certificate.js
@@ -18,7 +18,43 @@ function omissions () {
 
 const internalCertificate = {
 
-    allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'],
+    allowed_ssl_files:   ['certificate', 'certificate_key', 'intermediate_certificate'],
+    interval_timeout:    1000 * 60 * 60 * 12, // 12 hours
+    interval:            null,
+    interval_processing: false,
+
+    initTimer: () => {
+        logger.info('Let\'s Encrypt Renewal Timer initialized');
+        internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.interval_timeout);
+    },
+
+    /**
+     * Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
+     */
+    processExpiringHosts: () => {
+        let internalNginx = require('./nginx');
+
+        if (!internalCertificate.interval_processing) {
+            internalCertificate.interval_processing = true;
+            logger.info('Renewing SSL certs close to expiry...');
+
+            return utils.exec(certbot_command + ' renew -q ' + (debug_mode ? '--staging' : ''))
+                .then(result => {
+                    logger.info(result);
+                    internalCertificate.interval_processing = false;
+
+                    return internalNginx.reload()
+                        .then(() => {
+                            logger.info('Renew Complete');
+                            return result;
+                        });
+                })
+                .catch(err => {
+                    logger.error(err);
+                    internalCertificate.interval_processing = false;
+                });
+        }
+    },
 
     /**
      * @param   {Access}  access
@@ -493,7 +529,7 @@ const internalCertificate = {
      * @returns {Promise}
      */
     requestLetsEncryptSsl: certificate => {
-        logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id  + ': ' + certificate.domain_names.join(', '));
+        logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 
         return utils.exec(certbot_command + ' certonly --cert-name "npm-' + certificate.id + '" --agree-tos ' +
             '--email "' + certificate.meta.letsencrypt_email + '" ' +
@@ -511,7 +547,7 @@ const internalCertificate = {
      * @returns {Promise}
      */
     renewLetsEncryptSsl: certificate => {
-        logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id  + ': ' + certificate.domain_names.join(', '));
+        logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 
         return utils.exec(certbot_command + ' renew -n --force-renewal --disable-hook-validation --cert-name "npm-' + certificate.id + '" ' + (debug_mode ? '--staging' : ''))
             .then(result => {
@@ -519,6 +555,16 @@ const internalCertificate = {
                 return result;
             });
     },
+
+    /**
+     * @param   {Object}  certificate
+     * @returns {Boolean}
+     */
+    hasLetsEncryptSslCerts: certificate => {
+        let le_path = '/etc/letsencrypt/live/npm-' + certificate.id;
+
+        return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
+    }
 };
 
 module.exports = internalCertificate;
diff --git a/src/backend/internal/nginx.js b/src/backend/internal/nginx.js
index c020958..6fce499 100644
--- a/src/backend/internal/nginx.js
+++ b/src/backend/internal/nginx.js
@@ -1,13 +1,13 @@
 'use strict';
 
-const _           = require('lodash');
-const fs          = require('fs');
-const Liquid      = require('liquidjs');
-const logger      = require('../logger').nginx;
-const utils       = require('../lib/utils');
-const error       = require('../lib/error');
-const internalSsl = require('./ssl');
-const debug_mode  = process.env.NODE_ENV !== 'production';
+const _                   = require('lodash');
+const fs                  = require('fs');
+const Liquid              = require('liquidjs');
+const logger              = require('../logger').nginx;
+const utils               = require('../lib/utils');
+const error               = require('../lib/error');
+const internalCertificate = require('./certificate');
+const debug_mode          = process.env.NODE_ENV !== 'production';
 
 const internalNginx = {
 
@@ -32,11 +32,6 @@ const internalNginx = {
                 // We're deleting this config regardless.
                 return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all
             })
-            .then(() => {
-                if (host.ssl && !internalSsl.hasValidSslCerts(host_type, host)) {
-                    return internalSsl.configureSsl(host_type, host);
-                }
-            })
             .then(() => {
                 return internalNginx.generateConfig(host_type, host);
             })
@@ -56,7 +51,6 @@ const internalNginx = {
                             });
                     })
                     .catch(err => {
-
                         if (debug_mode) {
                             logger.error('Nginx test failed:', err.message);
                         }
diff --git a/src/backend/internal/ssl.js b/src/backend/internal/ssl.js
deleted file mode 100644
index 714feff..0000000
--- a/src/backend/internal/ssl.js
+++ /dev/null
@@ -1,164 +0,0 @@
-'use strict';
-
-const fs            = require('fs');
-const Liquid        = require('liquidjs');
-const timestamp     = require('unix-timestamp');
-const internalNginx = require('./nginx');
-const logger        = require('../logger').ssl;
-const utils         = require('../lib/utils');
-const error         = require('../lib/error');
-
-timestamp.round = true;
-
-const internalSsl = {
-
-    interval_timeout:    1000 * 60 * 60 * 12, // 12 hours
-    interval:            null,
-    interval_processing: false,
-
-    initTimer: () => {
-        logger.info('Let\'s Encrypt Renewal Timer initialized');
-        internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout);
-    },
-
-    /**
-     * Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
-     */
-    processExpiringHosts: () => {
-        if (!internalSsl.interval_processing) {
-            logger.info('Renewing SSL certs close to expiry...');
-            return utils.exec('/usr/bin/certbot renew -q')
-                .then(result => {
-                    logger.info(result);
-                    internalSsl.interval_processing = false;
-
-                    return internalNginx.reload()
-                        .then(() => {
-                            logger.info('Renew Complete');
-                            return result;
-                        });
-                })
-                .catch(err => {
-                    logger.error(err);
-                    internalSsl.interval_processing = false;
-                });
-        }
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Boolean}
-     */
-    hasValidSslCerts: (host_type, host) => {
-        host_type   = host_type.replace(new RegExp('-', 'g'), '_');
-        let le_path = '/etc/letsencrypt/live/' + host_type + '-' + host.id;
-
-        return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Promise}
-     */
-    requestSsl: (host_type, host) => {
-        logger.info('Requesting SSL certificates for ' + host_type + ' #' + host.id);
-
-        // TODO
-
-        return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot -d "' + host.hostname + '"')
-            .then(result => {
-                logger.info(result);
-                return result;
-            });
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Promise}
-     */
-    renewSsl: (host_type, host) => {
-        logger.info('Renewing SSL certificates for ' + host_type + ' #' + host.id);
-
-        // TODO
-
-        return utils.exec('/usr/bin/certbot renew --force-renewal --disable-hook-validation --cert-name "' + host.hostname + '"')
-            .then(result => {
-                logger.info(result);
-                return result;
-            });
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Promise}
-     */
-    deleteCerts: (host_type, host) => {
-        logger.info('Deleting SSL certificates for ' + host_type + ' #' + host.id);
-
-        // TODO
-
-        return utils.exec('/usr/bin/certbot delete -n --cert-name "' + host.hostname + '"')
-            .then(result => {
-                logger.info(result);
-            })
-            .catch(err => {
-                logger.error(err);
-            });
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Promise}
-     */
-    generateSslSetupConfig: (host_type, host) => {
-        host_type = host_type.replace(new RegExp('-', 'g'), '_');
-
-        let renderEngine = Liquid();
-        let template     = null;
-        let filename     = internalNginx.getConfigName(host_type, host);
-
-        return new Promise((resolve, reject) => {
-            try {
-                template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf', {encoding: 'utf8'});
-            } catch (err) {
-                reject(new error.ConfigurationError(err.message));
-                return;
-            }
-
-            return renderEngine
-                .parseAndRender(template, host)
-                .then(config_text => {
-                    fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
-                    return template_data;
-                })
-                .catch(err => {
-                    throw new error.ConfigurationError(err.message);
-                });
-        });
-    },
-
-    /**
-     * @param   {String}  host_type
-     * @param   {Object}  host
-     * @returns {Promise}
-     */
-    configureSsl: (host_type, host) => {
-
-        // TODO
-
-        return internalSsl.generateSslSetupConfig(host)
-            .then(data => {
-                return internalNginx.reload()
-                    .then(() => {
-                        return internalSsl.requestSsl(data);
-                    });
-            });
-    }
-};
-
-module.exports = internalSsl;
diff --git a/src/backend/templates/_assets.conf b/src/backend/templates/_assets.conf
new file mode 100644
index 0000000..fb86fc2
--- /dev/null
+++ b/src/backend/templates/_assets.conf
@@ -0,0 +1,4 @@
+{% if caching_enabled == 1 or caching_enabled == true -%}
+  # Asset Caching
+  include conf.d/include/assets.conf;
+{%- endif %}
diff --git a/src/backend/templates/_certificates.conf b/src/backend/templates/_certificates.conf
new file mode 100644
index 0000000..832ac4a
--- /dev/null
+++ b/src/backend/templates/_certificates.conf
@@ -0,0 +1,12 @@
+{%- if certificate and certificate_id > 0 -%}
+{%- if certificate.provider == "letsencrypt" %}
+  # Let's Encrypt SSL
+  include conf.d/include/letsencrypt-acme-challenge.conf;
+  include conf.d/include/ssl-ciphers.conf;
+  ssl_certificate /etc/letsencrypt/live/npm-{{ certificate.id }}/fullchain.pem;
+  ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate.id }}/privkey.pem;
+{%- endif -%}
+
+  # TODO: Custom SSL paths
+
+{%- endif %}
diff --git a/src/backend/templates/_exploits.conf b/src/backend/templates/_exploits.conf
new file mode 100644
index 0000000..c46f391
--- /dev/null
+++ b/src/backend/templates/_exploits.conf
@@ -0,0 +1,4 @@
+{% if block_exploits == 1 or block_exploits == true -%}
+  # Block Exploits
+  include conf.d/include/block-exploits.conf;
+{%- endif -%}
diff --git a/src/backend/templates/_forced_ssl.conf b/src/backend/templates/_forced_ssl.conf
new file mode 100644
index 0000000..467faf3
--- /dev/null
+++ b/src/backend/templates/_forced_ssl.conf
@@ -0,0 +1,6 @@
+{%- if certificate and certificate_id > 0 -%}
+{%- if ssl_forced == 1 or ssl_forced == true -%}
+    # Force SSL
+    include conf.d/include/force-ssl.conf;
+{%- endif -%}
+{%- endif %}
diff --git a/src/backend/templates/_header_comment.conf b/src/backend/templates/_header_comment.conf
new file mode 100644
index 0000000..481d0b7
--- /dev/null
+++ b/src/backend/templates/_header_comment.conf
@@ -0,0 +1,3 @@
+# ------------------------------------------------------------
+# {{ domain_names | join: ", " }}
+# ------------------------------------------------------------
diff --git a/src/backend/templates/_listen.conf b/src/backend/templates/_listen.conf
new file mode 100644
index 0000000..d6861ed
--- /dev/null
+++ b/src/backend/templates/_listen.conf
@@ -0,0 +1,5 @@
+  listen 80;
+{%- if certificate -%}
+  listen 443 ssl;
+{%- endif %}
+  server_name {{ domain_names | join: " " }};
diff --git a/src/backend/templates/dead_host.conf b/src/backend/templates/dead_host.conf
index 102b757..6ddc846 100644
--- a/src/backend/templates/dead_host.conf
+++ b/src/backend/templates/dead_host.conf
@@ -1,21 +1,10 @@
-# {{ domain_names | join: ", " }}
-server {
-  listen 80;
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  listen 443 ssl;
-  {%- endif %}
-  server_name {{ domain_names | join: " " }};
-  access_log /data/logs/proxy_host-{{ id }}.log proxy;
+{% include "_header_comment.conf" %}
 
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  {%- if ssl_provider == "letsencrypt" %}
-  # Let's Encrypt SSL
-  include conf.d/include/letsencrypt-acme-challenge.conf;
-  include conf.d/include/ssl-ciphers.conf;
-  ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem;
-  ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem;
-  {%- endif -%}
-  {%- endif %}
+server {
+  {% include "_listen.conf" %}
+  {% include "_certificates.conf" %}
+
+  access_log /data/logs/dead_host-{{ id }}.log proxy;
 
   # TODO: Advanced config options
 
diff --git a/src/backend/templates/letsencrypt.conf b/src/backend/templates/letsencrypt.conf
deleted file mode 100644
index 0434c5b..0000000
--- a/src/backend/templates/letsencrypt.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-# Letsencrypt Verification Temporary Host: {{ domain_names | join: ", " }}
-server {
-  listen 80;
-  server_name {{ domain_names | join: " " }};
-  access_log /data/logs/letsencrypt.log proxy;
-
-  location / {
-    root /data/letsencrypt-acme-challenge;
-  }
-}
diff --git a/src/backend/templates/proxy_host.conf b/src/backend/templates/proxy_host.conf
index 297ddc1..cb252fe 100644
--- a/src/backend/templates/proxy_host.conf
+++ b/src/backend/templates/proxy_host.conf
@@ -1,33 +1,15 @@
-# {{ domain_names | join: ", " }}
-server {
-  listen 80;
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  listen 443 ssl;
-  {%- endif %}
-  server_name {{ domain_names | join: " " }};
-  access_log /data/logs/proxy_host-{{ id }}.log proxy;
+{% include "_header_comment.conf" %}
 
+server {
   set $server {{ forward_ip }};
   set $port   {{ forward_port }};
 
-  {% if caching_enabled == 1 or caching_enabled == true -%}
-  # Asset Caching
-  include conf.d/include/assets.conf;
-  {%- endif %}
-  {% if block_exploits == 1 or block_exploits == true -%}
-  # Block Exploits
-  include conf.d/include/block-exploits.conf;
-  {%- endif -%}
+  {% include "_listen.conf" %}
+  {% include "_certificates.conf" %}
+  {% include "_assets.conf" %}
+  {% include "_exploits.conf" %}
 
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  {%- if ssl_provider == "letsencrypt" %}
-  # Let's Encrypt SSL
-  include conf.d/include/letsencrypt-acme-challenge.conf;
-  include conf.d/include/ssl-ciphers.conf;
-  ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem;
-  ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem;
-  {%- endif -%}
-  {%- endif %}
+  access_log /data/logs/proxy_host-{{ id }}.log proxy;
 
   # TODO: Advanced config options
 
@@ -38,12 +20,7 @@ server {
     auth_basic_user_file  /config/access/{{ access_list_id }};
     {%- endif %}
 
-    {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-    {%- if ssl_forced == 1 or ssl_forced == true -%}
-    # Force SSL
-    include conf.d/include/force-ssl.conf;
-    {%- endif -%}
-    {%- endif %}
+    {% include "_forced_ssl.conf" %}
 
     # Proxy!
     include conf.d/include/proxy.conf;
diff --git a/src/backend/templates/redirection_host.conf b/src/backend/templates/redirection_host.conf
index 440be91..100a886 100644
--- a/src/backend/templates/redirection_host.conf
+++ b/src/backend/templates/redirection_host.conf
@@ -1,34 +1,20 @@
-# {{ domain_names | join: ", " }}
+{% include "_header_comment.conf" %}
+
 server {
-  listen 80;
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  listen 443 ssl;
-  {%- endif %}
-  server_name {{ domain_names | join: " " }};
-  access_log /data/logs/proxy_host-{{ id }}.log proxy;
+  {% include "_listen.conf" %}
+  {% include "_certificates.conf" %}
+  {% include "_assets.conf" %}
+  {% include "_exploits.conf" %}
 
-  {%- if caching_enabled == 1 or caching_enabled == true %}
-  # Asset Caching
-  include conf.d/include/assets.conf;
-  {%- endif %}
-  {%- if block_exploits == 1 or block_exploits == true %}
-  # Block Exploits
-  include conf.d/include/block-exploits.conf;
-  {%- endif -%}
-
-  {%- if ssl_enabled == 1 or ssl_enabled == true -%}
-  {%- if ssl_provider == "letsencrypt" %}
-  # Let's Encrypt SSL
-  include conf.d/include/letsencrypt-acme-challenge.conf;
-  include conf.d/include/ssl-ciphers.conf;
-  ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem;
-  ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem;
-  {%- endif -%}
-  {%- endif %}
+  access_log /data/logs/redirection_host-{{ id }}.log proxy;
 
   # TODO: Advanced config options
 
   # TODO: Preserve Path Option
 
-  return 301 $scheme://{{ forward_domain_name }}$request_uri;
+  location / {
+    {% include "_forced_ssl.conf" %}
+
+    return 301 $scheme://{{ forward_domain_name }}$request_uri;
+  }
 }
diff --git a/src/backend/templates/stream.conf b/src/backend/templates/stream.conf
index 4a0c033..9bcd76d 100644
--- a/src/backend/templates/stream.conf
+++ b/src/backend/templates/stream.conf
@@ -1,14 +1,16 @@
+# ------------------------------------------------------------
 # {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }}
+# ------------------------------------------------------------
 
 {% if tcp_forwarding == 1 or tcp_forwarding == true -%}
 server {
-    listen {{ incoming_port }};
-    proxy_pass {{ forward_ip }}:{{ forwarding_port }};
+  listen {{ incoming_port }};
+  proxy_pass {{ forward_ip }}:{{ forwarding_port }};
 }
 {% endif %}
 {% if udp_forwarding == 1 or udp_forwarding == true %}
 server {
-    listen {{ incoming_port }} udp;
-    proxy_pass {{ forward_ip }}:{{ forwarding_port }};
+  listen {{ incoming_port }} udp;
+  proxy_pass {{ forward_ip }}:{{ forwarding_port }};
 }
 {% endif %}