diff --git a/manager/src/backend/internal/ssl.js b/manager/src/backend/internal/ssl.js index 9746c58..b9f42be 100644 --- a/manager/src/backend/internal/ssl.js +++ b/manager/src/backend/internal/ssl.js @@ -26,7 +26,7 @@ const internalSsl = { processExpiringHosts: () => { if (!internalSsl.interval_processing) { logger.info('Renewing SSL certs close to expiry...'); - return utils.exec('/usr/bin/letsencrypt renew') + return utils.exec('/usr/bin/certbot renew --webroot=/config/letsencrypt-acme-challenge') .then(result => { logger.info(result); internalSsl.interval_processing = false; @@ -55,7 +55,7 @@ const internalSsl = { requestSsl: host => { logger.info('Requesting SSL certificates for ' + host.hostname); - return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot --webroot-path=' + host.root_path + ' -d "' + host.hostname + '"') + return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot --webroot-path=/config/letsencrypt-acme-challenge -d "' + host.hostname + '"') .then(result => { logger.info(result); return result; @@ -69,7 +69,7 @@ const internalSsl = { renewSsl: host => { logger.info('Renewing SSL certificates for ' + host.hostname); - return utils.exec('/usr/bin/letsencrypt renew --force-renewal --disable-hook-validation --cert-name "' + host.hostname + '"') + return utils.exec('/usr/bin/certbot renew --force-renewal --disable-hook-validation --webroot-path=/config/letsencrypt-acme-challenge --cert-name "' + host.hostname + '"') .then(result => { logger.info(result); return result; @@ -83,7 +83,7 @@ const internalSsl = { deleteCerts: host => { logger.info('Deleting SSL certificates for ' + host.hostname); - return utils.exec('/usr/bin/letsencrypt delete -n --cert-name "' + host.hostname + '"') + return utils.exec('/usr/bin/certbot delete -n --cert-name "' + host.hostname + '"') .then(result => { logger.info(result); }) @@ -101,20 +101,17 @@ const internalSsl = { let filename = internalNginx.getConfigName(host); let template_data = host; - template_data.root_path = '/tmp/' + host.hostname; + return new Promise((resolve, reject) => { + try { + template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf.ejs', {encoding: 'utf8'}); + let config_text = ejs.render(template, template_data); + fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - return utils.exec('mkdir -p ' + template_data.root_path) - .then(() => { - try { - template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf.ejs', {encoding: 'utf8'}); - let config_text = ejs.render(template, template_data); - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - - return template_data; - } catch (err) { - throw new error.ConfigurationError(err.message); - } - }); + resolve(template_data); + } catch (err) { + reject(new error.ConfigurationError(err.message)); + } + }); }, /** diff --git a/manager/src/backend/templates/letsencrypt.conf.ejs b/manager/src/backend/templates/letsencrypt.conf.ejs index c2ca6a2..f870f2e 100644 --- a/manager/src/backend/templates/letsencrypt.conf.ejs +++ b/manager/src/backend/templates/letsencrypt.conf.ejs @@ -6,6 +6,6 @@ server { access_log /config/logs/letsencrypt.log proxy; location / { - root <%- root_path %>; + root /config/letsencrypt-acme-challenge; } } diff --git a/manager/src/backend/templates/proxy.conf.ejs b/manager/src/backend/templates/proxy.conf.ejs index f50e147..ae328ad 100644 --- a/manager/src/backend/templates/proxy.conf.ejs +++ b/manager/src/backend/templates/proxy.conf.ejs @@ -14,6 +14,7 @@ server { <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> <% if (typeof ssl !== 'undefined' && ssl) { -%> + include conf.d/include/letsencrypt-acme-challenge.conf; include conf.d/include/ssl-ciphers.conf; ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; diff --git a/rootfs/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf b/rootfs/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf new file mode 100644 index 0000000..cd91d3f --- /dev/null +++ b/rootfs/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf @@ -0,0 +1,25 @@ +# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) +# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel +# other regex checks, because in our other config files have regex rule that denies access to files with dotted names. +location ^~ /.well-known/acme-challenge/ { + + # Set correct content type. According to this: + # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29 + # Current specification requires "text/plain" or no content header at all. + # It seems that "text/plain" is a safe option. + default_type "text/plain"; + + # This directory must be the same as in /etc/letsencrypt/cli.ini + # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter + # there to "webroot". + # Do NOT use alias, use root! Target directory is located here: + # /var/www/common/letsencrypt/.well-known/acme-challenge/ + root /config/letsencrypt-acme-challenge; +} + +# Hide /acme-challenge subdirectory and return 404 on all requests. +# It is somewhat more secure than letting Nginx return 403. +# Ending slash is important! +location = /.well-known/acme-challenge/ { + return 404; +} diff --git a/rootfs/etc/services.d/manager/run b/rootfs/etc/services.d/manager/run index ec6601c..a6f6632 100755 --- a/rootfs/etc/services.d/manager/run +++ b/rootfs/etc/services.d/manager/run @@ -1,4 +1,6 @@ #!/usr/bin/with-contenv bash +mkdir -p /config/letsencrypt-acme-challenge + cd /srv/manager node --abort_on_uncaught_exception --max_old_space_size=250 /srv/manager/src/backend/index.js diff --git a/rootfs/root/.config/letsencrypt/cli.ini b/rootfs/root/.config/letsencrypt/cli.ini new file mode 100644 index 0000000..561b770 --- /dev/null +++ b/rootfs/root/.config/letsencrypt/cli.ini @@ -0,0 +1,4 @@ +text = True +non-interactive = True +authenticator = webroot +webroot-path = /config/letsencrypt-acme-challenge