diff --git a/src/nginxconfig/generators/conf/nginx.conf.js b/src/nginxconfig/generators/conf/nginx.conf.js index b0b08b9..21fb126 100644 --- a/src/nginxconfig/generators/conf/nginx.conf.js +++ b/src/nginxconfig/generators/conf/nginx.conf.js @@ -107,29 +107,39 @@ export default (domains, global) => { config.http.push(['ssl_stapling', 'on']); config.http.push(['ssl_stapling_verify', 'on']); - if (global.https.ocspCloudflare.computed - || global.https.ocspGoogle.computed - || global.https.ocspOpenDns.computed) { - const ips = []; - if (global.https.ocspCloudflare.computed) { - if (['ipv4', 'both'].includes(global.https.ocspCloudflareType.computed)) - ips.push('1.1.1.1', '1.0.0.1'); - if (['ipv6', 'both'].includes(global.https.ocspCloudflareType.computed)) - ips.push('[2606:4700:4700::1111]', '[2606:4700:4700::1001]'); - } - if (global.https.ocspGoogle.computed) { - if (['ipv4', 'both'].includes(global.https.ocspGoogleType.computed)) - ips.push('8.8.8.8', '8.8.4.4'); - if (['ipv6', 'both'].includes(global.https.ocspGoogleType.computed)) - ips.push('[2001:4860:4860::8888]', '[2001:4860:4860::8844]'); - } - if (global.https.ocspOpenDns.computed) { - if (['ipv4', 'both'].includes(global.https.ocspOpenDnsType.computed)) - ips.push('208.67.222.222', '208.67.220.220'); - if (['ipv6', 'both'].includes(global.https.ocspOpenDnsType.computed)) - ips.push('[2620:119:35::35]', '[2620:119:53::53]'); - } + const ips = []; + if (global.https.ocspCloudflare.computed) { + if (['ipv4', 'both'].includes(global.https.ocspCloudflareType.computed)) + ips.push('1.1.1.1', '1.0.0.1'); + if (['ipv6', 'both'].includes(global.https.ocspCloudflareType.computed)) + ips.push('[2606:4700:4700::1111]', '[2606:4700:4700::1001]'); + } + if (global.https.ocspGoogle.computed) { + if (['ipv4', 'both'].includes(global.https.ocspGoogleType.computed)) + ips.push('8.8.8.8', '8.8.4.4'); + if (['ipv6', 'both'].includes(global.https.ocspGoogleType.computed)) + ips.push('[2001:4860:4860::8888]', '[2001:4860:4860::8844]'); + } + if (global.https.ocspOpenDns.computed) { + if (['ipv4', 'both'].includes(global.https.ocspOpenDnsType.computed)) + ips.push('208.67.222.222', '208.67.220.220'); + if (['ipv6', 'both'].includes(global.https.ocspOpenDnsType.computed)) + ips.push('[2620:119:35::35]', '[2620:119:53::53]'); + } + if (global.https.ocspQuad9.computed) { + if (['ipv4', 'both'].includes(global.https.ocspQuad9Type.computed)) + ips.push('9.9.9.9', '149.112.112.112'); + if (['ipv6', 'both'].includes(global.https.ocspQuad9Type.computed)) + ips.push('[2620:fe::fe]', '[2620:fe::9]'); + } + if (global.https.ocspVerisign.computed) { + if (['ipv4', 'both'].includes(global.https.ocspVerisignType.computed)) + ips.push('64.6.64.6', '64.6.65.6'); + if (['ipv6', 'both'].includes(global.https.ocspVerisignType.computed)) + ips.push('[2620:74:1b::1:1]', '[2620:74:1c::2:2]'); + } + if (ips.length) { config.http.push(['resolver', `${ips.join(' ')} valid=60s`]); config.http.push(['resolver_timeout', '2s']); } diff --git a/src/nginxconfig/i18n/en/templates/global_sections/https.js b/src/nginxconfig/i18n/en/templates/global_sections/https.js index 6b3f483..985b362 100644 --- a/src/nginxconfig/i18n/en/templates/global_sections/https.js +++ b/src/nginxconfig/i18n/en/templates/global_sections/https.js @@ -27,6 +27,8 @@ export default { cloudflareResolver: 'Cloudflare Resolver', googlePublicDns: 'Google Public DNS', openDns: 'OpenDNS', + quad9: 'Quad9', + verisign: 'Verisign', letsEncryptWebroot: `${common.letsEncrypt} webroot`, mozillaModern: `${mozilla} Modern`, mozillaIntermediate: `${mozilla} Intermediate`, diff --git a/src/nginxconfig/templates/app.vue b/src/nginxconfig/templates/app.vue index 6ee0349..1b75a8d 100644 --- a/src/nginxconfig/templates/app.vue +++ b/src/nginxconfig/templates/app.vue @@ -70,8 +70,8 @@ limitations under the License.

{{ i18n.templates.app.configFiles }}

- d && d.server.domain.computed === data.server.domain.computed)) { + count++; + data.server.domain.computed = data.server.domain.default.replace('.com', `${count}.com`); + } + data.server.domain.value = data.server.domain.computed; + + // Store + this.$data.domains.push(data); this.$data.active = this.$data.domains.length - 1; }, remove(index) { diff --git a/src/nginxconfig/templates/domain_sections/https.vue b/src/nginxconfig/templates/domain_sections/https.vue index 2c35d7d..d5cbffe 100644 --- a/src/nginxconfig/templates/domain_sections/https.vue +++ b/src/nginxconfig/templates/domain_sections/https.vue @@ -138,7 +138,7 @@ limitations under the License.
@@ -223,6 +223,7 @@ limitations under the License. }, letsEncryptEmail: { default: '', + computed: 'info@example.com', // No default value, but a default computed enabled: true, }, sslCertificate: { @@ -346,6 +347,23 @@ limitations under the License. }, deep: true, }, + // Ensure there is a default email for Let's Encrypt + '$props.data.letsEncryptEmail': { + handler(data) { + if (!data.computed.trim()) { + data.computed = `info@${this.$parent.$props.data.server.domain.computed}`; + } + }, + deep: true, + }, + '$parent.$props.data.server.domain': { + handler(data) { + if (!this.$props.data.letsEncryptEmail.value.trim()) { + this.$props.data.letsEncryptEmail.computed = `info@${data.computed}`; + } + }, + deep: true, + }, }, }; diff --git a/src/nginxconfig/templates/domain_sections/server.vue b/src/nginxconfig/templates/domain_sections/server.vue index c6e7d12..a7789fe 100644 --- a/src/nginxconfig/templates/domain_sections/server.vue +++ b/src/nginxconfig/templates/domain_sections/server.vue @@ -182,15 +182,14 @@ limitations under the License. i18n, }; }, - computed: computedFromDefaults(defaults, 'server'), // Getters & setters for the delegated data + computed: computedFromDefaults(defaults, 'server'), // Getters & setters for the delegated data watch: { '$props.data.domain': { handler(data) { - // This might cause recursion, but seems not to - - // Ignore www. if given + // Ignore www. if given, enable WWW subdomain if (data.computed.startsWith('www.')) { data.computed = data.computed.slice(4); + this.wwwSubdomain = true; } // Use default if empty diff --git a/src/nginxconfig/templates/global_sections/https.vue b/src/nginxconfig/templates/global_sections/https.vue index c96058e..3d2a6ac 100644 --- a/src/nginxconfig/templates/global_sections/https.vue +++ b/src/nginxconfig/templates/global_sections/https.vue @@ -122,6 +122,48 @@ limitations under the License. + +
+
+ + + {{ i18n.templates.globalSections.https.quad9 }} + +
+
+
+
+
+ + + {{ name }} + +
+
+
+ +
+
+ + + {{ i18n.templates.globalSections.https.verisign }} + +
+
+
+
+
+ + + {{ name }} + +
+
+
@@ -195,6 +237,16 @@ limitations under the License. enabled: true, }, ocspOpenDnsType: clone(ipType), + ocspQuad9: { + default: false, + enabled: true, + }, + ocspQuad9Type: clone(ipType), + ocspVerisign: { + default: false, + enabled: true, + }, + ocspVerisignType: clone(ipType), letsEncryptRoot: { default: '/var/www/_letsencrypt/', enabled: true, @@ -238,6 +290,14 @@ limitations under the License. handler: validOptionCheck, deep: true, }, + '$props.data.ocspQuad9Type': { + handler: validOptionCheck, + deep: true, + }, + '$props.data.ocspVerisignType': { + handler: validOptionCheck, + deep: true, + }, '$parent.$parent.$data.domains': { handler(data) { let httpsEnabled = false, leEnabled = false; diff --git a/src/nginxconfig/templates/global_sections/security.vue b/src/nginxconfig/templates/global_sections/security.vue index bea9109..b808241 100644 --- a/src/nginxconfig/templates/global_sections/security.vue +++ b/src/nginxconfig/templates/global_sections/security.vue @@ -151,7 +151,7 @@ limitations under the License. computed: { ...computedFromDefaults(defaults, 'security'), // Getters & setters for the delegated data hasWordPress() { - return this.$parent.$parent.$data.domains.some(d => d.php.wordPressRules.computed); + return this.$parent.$parent.$data.domains.some(d => d && d.php.wordPressRules.computed); }, hasUnsafeEval() { return this.$props.data.contentSecurityPolicy.computed.includes('\'unsafe-eval\''); diff --git a/src/nginxconfig/util/backwards_compatibility.js b/src/nginxconfig/util/backwards_compatibility.js index f6cf422..abadd0f 100644 --- a/src/nginxconfig/util/backwards_compatibility.js +++ b/src/nginxconfig/util/backwards_compatibility.js @@ -121,8 +121,8 @@ export default data => { // If not a global setting and if this is an integer // Then, this is probably an old domain, so we'll try to convert it as such if (!isNaN(parseInt(key))) { - data.domains = data.domains || []; - data.domains.push(data[key]); + data.domains = isObject(data.domains) ? data.domains : {}; + data.domains[key] = data[key]; } } @@ -130,35 +130,35 @@ export default data => { data.global = {...(data.global || {}), ...mappedGlobal}; // Handle converting domain settings - if ('domains' in data && (Array.isArray(data.domains) || isObject(data.domains))) { - // Ensure we're working with an array - const values = isObject(data.domains) ? Object.values(data.domains) : data.domains; + if ('domains' in data && isObject(data.domains)) { + for (const key in data.domains) { + // Don't include inherited props + if (!Object.prototype.hasOwnProperty.call(data.domains, key)) continue; - for (let i = 0; i < values.length; i++) { // Check this is an object - if (!isObject(values[i])) continue; + if (!isObject(data.domains[key])) continue; // Hold any mapped data const mappedData = {}; // Handle converting old domain settings to new ones - for (const key in values[i]) { - if (!Object.prototype.hasOwnProperty.call(values[i], key)) continue; - if (isObject(values[i][key])) continue; + for (const key2 in data.domains[key]) { + // Don't include inherited props + if (!Object.prototype.hasOwnProperty.call(data.domains[key], key2)) continue; + + // Don't convert objects + if (isObject(data.domains[key][key2])) continue; // Map old settings to their new ones - if (key in domainMap) { - const map = domainMap[key]; + if (key2 in domainMap) { + const map = domainMap[key2]; mappedData[map[0]] = mappedData[map[0]] || {}; - mappedData[map[0]][map[1]] = map.length < 3 ? values[i][key] : map[2](values[i][key]); + mappedData[map[0]][map[1]] = map.length < 3 ? data.domains[key][key2] : map[2](data.domains[key][key2]); } } // Overwrite mapped properties - values[i] = {...values[i], ...mappedData}; + data.domains[key] = {...data.domains[key], ...mappedData}; } - - // Store the updated domain data - data.domains = values; } }; diff --git a/src/nginxconfig/util/import_data.js b/src/nginxconfig/util/import_data.js index 9405b19..aa80eb6 100644 --- a/src/nginxconfig/util/import_data.js +++ b/src/nginxconfig/util/import_data.js @@ -54,6 +54,7 @@ export default (query, domains, global, nextTick) => { const data = qs.parse(query, { ignoreQueryPrefix: true, allowDots: true, + parseArrays: false, decoder(value) { value = decodeURIComponent(value); @@ -78,25 +79,23 @@ export default (query, domains, global, nextTick) => { backwardsCompatibility(data); // Handle domains - if ('domains' in data) { - // Check its an array or object - if (Array.isArray(data.domains) || isObject(data.domains)) { - // Ensure we're working with an array - const values = isObject(data.domains) ? Object.values(data.domains) : data.domains; - - // Work through each potential domain - for (const domainData of values) { - // Check this is an object - if (!isObject(domainData)) continue; - - // Create a new domain (assume it has had custom user settings) - const domainImported = clone(Domain.delegated); - domainImported.hasUserInteraction = true; - domains.push(domainImported); - - // Apply the initial values on the next Vue tick, once the watchers are ready - nextTick(() => applyCategories(domainData, domainImported)); + if ('domains' in data && isObject(data.domains)) { + // Work through all valid integer keys in the object + const keys = Object.keys(data.domains).map(x => parseInt(x)).filter(x => !isNaN(x)); + for (let i = 0; i < Math.max(...keys) + 1; i++) { + // If the key doesn't exist or this isn't a valid object, assume it was an untouched example domain + if (!keys.includes(i) || !isObject(data.domains[i])) { + domains.push(clone(Domain.delegated)); + continue; } + + // Create a new domain (assume it has had custom user settings) + const domainImported = clone(Domain.delegated); + domainImported.hasUserInteraction = true; + domains.push(domainImported); + + // Apply the initial values on the next Vue tick, once the watchers are ready + nextTick(() => applyCategories(data.domains[i], domainImported)); } } else { // If no configured domains, add a single default