From b773081e5f48ee64f9c1f66c5053ca1446ad5bb4 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Fri, 15 May 2020 18:18:52 +0100 Subject: [PATCH] Even more confs! --- src/nginxconfig/generators/proxy.conf.js | 18 ++++ .../generators/python_uwsgi.conf.js | 17 ++++ src/nginxconfig/generators/security.conf.js | 29 +++++++ src/nginxconfig/generators/website.conf.js | 86 +++++++++++++++++++ src/nginxconfig/generators/wordpress.conf.js | 44 ++++++++++ src/nginxconfig/util/common_hsts.js | 11 +++ src/nginxconfig/util/get_ssl_certificate.js | 19 ++++ 7 files changed, 224 insertions(+) create mode 100644 src/nginxconfig/generators/proxy.conf.js create mode 100644 src/nginxconfig/generators/python_uwsgi.conf.js create mode 100644 src/nginxconfig/generators/security.conf.js create mode 100644 src/nginxconfig/generators/website.conf.js create mode 100644 src/nginxconfig/generators/wordpress.conf.js create mode 100644 src/nginxconfig/util/common_hsts.js create mode 100644 src/nginxconfig/util/get_ssl_certificate.js diff --git a/src/nginxconfig/generators/proxy.conf.js b/src/nginxconfig/generators/proxy.conf.js new file mode 100644 index 0000000..bff4e65 --- /dev/null +++ b/src/nginxconfig/generators/proxy.conf.js @@ -0,0 +1,18 @@ +export default () => { + const config = {}; + + config.proxy_http_version = '1.1'; + config.proxy_cache_bypass = '$http_upgrade'; + + config['proxy_set_header Upgrade'] = '$http_upgrade'; + config['proxy_set_header Connection'] = '"upgrade"'; + config['proxy_set_header Host'] = '$host'; + config['proxy_set_header X-Real-IP'] = '$remote_addr'; + config['proxy_set_header X-Forwarded-For'] = '$proxy_add_x_forwarded_for'; + config['proxy_set_header X-Forwarded-Proto'] = '$scheme'; + config['proxy_set_header X-Forwarded-Host'] = '$host'; + config['proxy_set_header X-Forwarded-Port'] = '$server_port'; + + // Done! + return config; +}; diff --git a/src/nginxconfig/generators/python_uwsgi.conf.js b/src/nginxconfig/generators/python_uwsgi.conf.js new file mode 100644 index 0000000..f042ead --- /dev/null +++ b/src/nginxconfig/generators/python_uwsgi.conf.js @@ -0,0 +1,17 @@ +export default (domains, global) => { + const config = {}; + + config['# default uwsgi_params'] = ''; + config.include = 'uwsgi_params'; + + config['# uwsgi settings'] = ''; + config.uwsgi_pass = (global.python.pythonServer.computed[0] === '/' ? 'unix:' : '') + + global.python.pythonServer.computed; + config['uwsgi_param Host'] = '$host'; + config['uwsgi_param X-Real-IP'] = '$remote_addr'; + config['uwsgi_param X-Forwarded-For'] = '$proxy_add_x_forwarded_for'; + config['uwsgi_param X-Forwarded-Proto'] = '$http_x_forwarded_proto'; + + // Done! + return config; +}; diff --git a/src/nginxconfig/generators/security.conf.js b/src/nginxconfig/generators/security.conf.js new file mode 100644 index 0000000..90e1029 --- /dev/null +++ b/src/nginxconfig/generators/security.conf.js @@ -0,0 +1,29 @@ +import commonHsts from '../util/common_hsts'; + +export default (domains, global) => { + const config = {}; + + config['# security headers'] = ''; + config['add_header X-Frame-Options'] = '"SAMEORIGIN" always'; + config['add_header X-XSS-Protection'] = '"1; mode=block" always'; + config['add_header X-Content-Type-Options'] = '"nosniff" always'; + config['add_header Referrer-Policy'] = `"${global.security.referrerPolicy.computed}" always`; + + if (global.security.contentSecurityPolicy.computed) + config['add_header Content-Security-Policy'] = `"${global.security.contentSecurityPolicy.computed}" always`; + + // Every domain has HSTS enabled, and they all have same hstsSubdomains/hstsPreload settings + if (commonHsts(domains)) { + const commonHSTSSubdomains = domains.length && domains[0].https.hstsSubdomains.computed; + const commonHSTSPreload = domains.length && domains[0].https.hstsPreload.computed; + config['add_header Strict-Transport-Security'] = `"max-age=31536000${commonHSTSSubdomains ? '; includeSubDomains' : ''}${commonHSTSPreload ? '; preload' : ''}" always`; + } + + config['# . files'] = ''; + config['location ~ /\\.(?!well-known)'] = { + deny: 'all', + }; + + // Done! + return config; +}; diff --git a/src/nginxconfig/generators/website.conf.js b/src/nginxconfig/generators/website.conf.js new file mode 100644 index 0000000..dac3aa9 --- /dev/null +++ b/src/nginxconfig/generators/website.conf.js @@ -0,0 +1,86 @@ +import { getSslCertificate, getSslCertificateKey } from '../util/get_ssl_certificate'; +import commonHsts from '../util/common_hsts'; +import securityConf from './security.conf'; + +export default (domain, domains, global) => { + // Use kv so we can use the same key multiple times + const config = []; + + // Build the server config on its own before adding it to the parent config + const serverConfig = []; + const ipv4Pre = domain.server.listenIpv4.computed === '*' ? '' : `${domain.server.listenIpv4.computed}:`; + + // Not HTTPS or not force HTTPS + if (!domain.https.https.computed || !domain.https.forceHttps.computed) { + serverConfig.push(['listen', `${ipv4Pre}80`]); + + // v6 + if (domain.server.listenIpv6.computed) + serverConfig.push(['listen', `[${domain.server.listenIpv6.computed}]:80`]); + } + + // HTTPS + if (domain.https.https.computed) { + serverConfig.push(['listen', `${ipv4Pre}443 ssl${domain.https.http2.computed ? ' http2' : ''}`]); + + // v6 + if (domain.server.listenIpv6.computed) + serverConfig.push(['listen', + `[${domain.server.listenIpv6.computed}]:443 ssl${domain.https.http2.computed ? ' http2' : ''}`]); + } + + serverConfig.push(['server_name', + `${domain.server.wwwSubdomain.computed ? 'www.' : ''}${domain.server.domain.computed}`]); + + // PHP or Django + if (domain.php.php.computed || (domain.python.python.computed && domain.python.djangoRules.computed)) { + serverConfig.push(['set', `$base ${domain.server.path.computed}`]); + + // root + if (domain.routing.root.computed) + serverConfig.push(['root', `$base${domain.server.documentRoot.computed}`]); + } + + // Not PHP and not Django and root + if (!domain.php.php.computed + && (!domain.python.python.computed || !domain.python.djangoRules.computed) + && domain.routing.root.computed) + serverConfig.push(['root', `${domain.server.path.computed}${domain.server.documentRoot.computed}`]); + + // HTTPS + if (domain.https.https.computed) { + serverConfig.push(['# SSL', '']); + serverConfig.push(['ssl_certificate', getSslCertificate(domain, global)]); + serverConfig.push(['ssl_certificate_key', getSslCertificateKey(domain, global)]); + + // Let's encrypt + if (domain.https.certType.computed === 'letsEncrypt') + serverConfig.push(['ssl_trusted_certificate', + `/etc/letsencrypt/live/${domain.server.domain.computed}/chain.pem`]); + } + + // HSTS + if (!commonHsts(domains) && domain.https.hsts.computed) { + serverConfig.push(['# HSTS', '']); + serverConfig.push(['add_header Strict-Transport-Security', + `'"max-age=31536000${domain.https.hstsSubdomains.computed ? '; includeSubDomains' : ''}${domain.https.hstsPreload.computed ? '; preload' : ''}" always'`]); + } + + // Security + if (global.tools.modularizedStructure.computed) { + // Modularized + serverConfig.push(['# security', '']); + serverConfig.push(['include', 'nginxconfig.io/security.conf']); + } else { + // Unified + serverConfig.push(...Object.entries(securityConf(domains, global))); + } + + // Access log or error log for domain + // TODO: this & beyond + + // Add the server config to the parent config now its built + config.push(['server', serverConfig]); + + return config; +}; diff --git a/src/nginxconfig/generators/wordpress.conf.js b/src/nginxconfig/generators/wordpress.conf.js new file mode 100644 index 0000000..1ad0aac --- /dev/null +++ b/src/nginxconfig/generators/wordpress.conf.js @@ -0,0 +1,44 @@ +export default (domains, global) => { + const config = {}; + + config['# WordPress: allow TinyMCE'] = ''; + config['location = /wp-includes/js/tinymce/wp-tinymce.php'] = { + include: 'nginxconfig.io/php_fastcgi.conf', + }; + + config['# WordPress: deny wp-content, wp-includes php files'] = ''; + config['~* ^/(?:wp-content|wp-includes)/.*\\.php$'] = { + deny: 'all', + }; + + config['# WordPress: deny wp-content/uploads nasty stuff'] = ''; + config['location ~* ^/wp-content/uploads/.*\\.(?:s?html?|php|js|swf)$'] = { + deny: 'all', + }; + + config['# WordPress: deny wp-content/plugins (except earlier rules)'] = ''; + config['location ~ ^/wp-content/plugins'] = { + deny: 'all', + }; + + config['# WordPress: deny scripts and styles concat'] = ''; + config['location ~* \\/wp-admin\\/load-(?:scripts|styles)\\.php'] = { + deny: 'all', + }; + + config['# WordPress: deny general stuff'] = ''; + config['location ~* ^/(?:xmlrpc\\.php|wp-links-opml\\.php|wp-config\\.php|wp-config-sample\\.php|wp-comments-post\\.php|readme\\.html|license\\.txt)$'] = { + deny: 'all', + }; + + if (global.security.limitReq.computed) { + config['# WordPress: throttle wp-login.php'] = ''; + config['location = /wp-login.php'] = { + limit_req: 'zone=login burst=2 nodelay', + include: 'nginxconfig.io/php_fastcgi.conf', + }; + } + + // Done! + return config; +}; diff --git a/src/nginxconfig/util/common_hsts.js b/src/nginxconfig/util/common_hsts.js new file mode 100644 index 0000000..c828a6f --- /dev/null +++ b/src/nginxconfig/util/common_hsts.js @@ -0,0 +1,11 @@ +export default domains => { + return domains.every(d => d.https.hsts.computed) + && ( + domains.every(d => d.https.hstsSubdomains.computed) + || domains.every(d => !d.https.hstsSubdomains.computed) + ) + && ( + domains.every(d => d.https.hstsPreload.computed) + || domains.every(d => !d.https.hstsPreload.computed) + ); +}; diff --git a/src/nginxconfig/util/get_ssl_certificate.js b/src/nginxconfig/util/get_ssl_certificate.js new file mode 100644 index 0000000..6be1251 --- /dev/null +++ b/src/nginxconfig/util/get_ssl_certificate.js @@ -0,0 +1,19 @@ +export const getSslCertificate = (domain, global) => { + if (domain.https.certType.computed === 'letsEncrypt') + `/etc/letsencrypt/live/${domain.server.domain.computed}/fullchain.pem`; + + if (domain.https.sslCertificate.computed) + return domain.https.sslCertificate.computed; + + return `${global.nginx.nginxConfigDirectory.computed.replace(/\/+$/, '')}/ssl/${domain.server.domain.computed}.crt`; +}; + +export const getSslCertificateKey = (domain, global) => { + if (domain.https.certType.computed === 'letsEncrypt') + `/etc/letsencrypt/live/${domain.server.domain.computed}/privkey.pem`; + + if (domain.https.sslCertificateKey.computed) + return domain.https.sslCertificateKey.computed; + + return `${global.nginx.nginxConfigDirectory.computed.replace(/\/+$/, '')}/ssl/${domain.server.domain.computed}.key`; +};