From 5a1abb7e83ab8c16c6ca3a999ecea3b94ccda536 Mon Sep 17 00:00:00 2001 From: Ben Schmidt Date: Wed, 12 Oct 2016 02:22:37 +1100 Subject: [PATCH 1/2] replace apache with generic and extensible hooks --- README.md | 76 ++++++++++++++++++++++++---------------------- bin/letsencrypt.js | 22 ++++++++------ index.js | 53 ++++++++++++++++++++------------ package.json | 4 +-- 4 files changed, 88 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 5c3a693..449c873 100644 --- a/README.md +++ b/README.md @@ -111,23 +111,22 @@ ls /etc/letsencrypt/live/ You can use a cron job to run the script above every 80 days (the certificates expire after 90 days) so that you always have fresh certificates. -### TLS SNI (production option 2) +### Hooks (production option 2) You can also integrate with a secure server. This is more complicated than the webroot option, but it allows you to obtain certificates with only port 443 -open. This facility was developed for the Apache webserver, but it could work -with other servers as long as they support server name indication (SNI) and you -can provide a configuration file template and hooks to install and uninstall it -(without downtime). In fact, it doesn't even need to be a webserver (though it -must run on port 443); it could be another server that performs SSL/TLS -negotiation with SNI. +open. This facility can work with any web server as long as it supports server +name indication (SNI) and you can provide a configuration file template and +shell hooks to install and uninstall the configuration (without downtime). In +fact, it doesn't even need to be a webserver (though it must run on port 443); +it could be another server that performs SSL/TLS negotiation with SNI. The process works something like this. You would run: ```bash sudo letsencrypt certonly \ --agree-tos --email john.doe@example.com \ - --apache \ + --hooks --hooks-server apache2-debian \ --config-dir /etc/letsencrypt \ --domains example.com,www.example.com \ --server https://acme-staging.api.letsencrypt.org/directory @@ -149,28 +148,32 @@ domain to prove you own the domain you're getting a certificate for. After the domain has been validated externally, hooks are run to disable the configuration fragment, and again check and reload the configuration. -Find your brand new certs in: +You can then find your brand new certs in: ``` ls /etc/letsencrypt/live/ ``` -To tailor this for your server setup, see all the `apache-` options in the list -below. Also note that the following substitutions are available for use in the -commands supplied to those options, and in any alternative template you -provide: +Tailor to your server and distro using the `--hooks-server` option. So far, the +following are supported (contributions for additional servers welcome): + +* apache2-debian + +To tweak it for your setup and taste, see all the `hooks-` options in the +Command Line Options section below. Also note that the following substitutions +are available for use in the hooks and the template: * `{{{token}}}`: the token * `{{{domain}}}`: the domain for which a certificate is being sought (beware of this if using multiple domains per certificate) * `{{{subject}}}`: the domain for which the generated challenge-fulfilling certificate must be used (only available when generating it) -* `{{{cert}}}`: the path to the generated certificate: `apache-path/token.crt` -* `{{{privkey}}}`: the path to the generated private key: `apache-path/token.key` -* `{{{conf}}}`: the path to the generated config file: `apache-path/token.conf` -* `{{{bind}}}`: the value of the `apache-bind` option -* `{{{port}}}`: the value of the `apache-port` option -* `{{{webroot}}}`: the value of the `apache-webroot` option +* `{{{cert}}}`: the path to the generated certificate: `hooks-path/token.crt` +* `{{{privkey}}}`: the path to the generated private key: `hooks-path/token.key` +* `{{{conf}}}`: the path to the generated config file: `hooks-path/token.conf` +* `{{{bind}}}`: the value of the `hooks-bind` option +* `{{{port}}}`: the value of the `hooks-port` option +* `{{{webroot}}}`: the value of the `hooks-webroot` option ### Interactive (for debugging) @@ -230,7 +233,7 @@ you could change the permissions on them. **Probably a BAD IDEA**. Probabry a se sudo chown -R $(whoami) /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt ``` -## Command line Options +## Command Line Options ``` Usage: @@ -285,33 +288,34 @@ Options: --webroot-path STRING public_html / webroot path. - --apache BOOLEAN Obtain certs using Apache virtual hosts. + --hooks BOOLEAN Obtain certs with hooks that configure a webserver to meet TLS-SNI-01 challenges. - --apache-path STRING Path in which to store files for Apache virtual hosts. + --hooks-path STRING Path in which to store files for hooks. (Default is ~/letsencrypt/apache) - --apache-bind [STRING] IP address to use for Apache virtual host. (Default is *) - (This is used in the default template.) + --hooks-server STRING Type of webserver to configure. Sets defaults for all the following --hooks- options. + Either --hooks-server or --hooks-template must be given. + (See the Hooks section above for a list of supported servers.) - --apache-port [NUMBER] Port to use for Apache virtual host. (Default is 443) - (This is used in the default template.) + --hooks-template STRING Template to use for hooks configuration file. + Either --hooks-server or --hooks-template must be given. - --apache-webroot STRING Webroot to use for Apache virtual host (e.g. an empty dir). + --hooks-bind STRING IP address to use in configuration for hooks. (Default is *) + + --hooks-port STRING Port to use in configuration for hooks. (Default is 443) + + --hooks-webroot STRING Webroot to use in configuration for hooks (e.g. empty dir). Nothing should actually be served from here. (Default is /var/www) - --apache-template STRING Alternative template to use for Apache configuration file. + --hooks-pre-enable STRING Hook to check the webserver configuration prior to enabling. - --apache-enable STRING Command to run to enable the site in Apache. - (Default is `ln -s {{{conf}}} /etc/apache2/sites-enabled`) + --hooks-enable STRING Hook to enable the webserver configuration. - --apache-check STRING Command to run to check Apache configuration. - (Default is `apache2ctl configtest`) + --hooks-pre-reload STRING Hook to check the webserver configuration prior to reloading. - --apache-reload STRING Command to run to reload Apache. - (Default is `/etc/init.d/apache2 reload`) + --hooks-reload STRING Hook to reload the webserver. - --apache-disable STRING Command to run to disable the site in Apache. - (Default is `rm /etc/apache2/sites-enabled/{{{token}}}.conf`) + --hooks-disable STRING Hook to disable the webserver configuration. --debug BOOLEAN show traces and logs diff --git a/bin/letsencrypt.js b/bin/letsencrypt.js index 6f90851..d25cd09 100755 --- a/bin/letsencrypt.js +++ b/bin/letsencrypt.js @@ -25,16 +25,18 @@ cli.parse({ , manual: [ false, " Print the token and key to the screen and wait for you to hit enter, giving you time to copy it somewhere before continuing (default: false)", 'boolean', false ] , webroot: [ false, " Obtain certs by placing files in a webroot directory.", 'boolean', false ] , 'webroot-path': [ false, " public_html / webroot path.", 'string' ] -, apache: [ false, " Obtain certs using Apache virtual hosts.", 'boolean', false ] -, 'apache-path': [ false, " Path in which to store files for Apache virtual hosts.", 'string' ] -, 'apache-bind': [ false, " IP address to use for Apache virtual host.", 'string', "*" ] -, 'apache-port': [ false, " Port to use for Apache virtual host.", 'int', 443 ] -, 'apache-webroot': [ false, " Webroot to use for Apache virtual host (e.g. empty dir).", 'string' ] -, 'apache-template': [ false, " Alternative template to use for Apache configuration file.", 'string' ] -, 'apache-enable': [ false, " Command to run to enable the site in Apache.", 'string' ] -, 'apache-check': [ false, " Command to run to check Apache configuration.", 'string' ] -, 'apache-reload': [ false, " Command to run to reload Apache.", 'string' ] -, 'apache-disable': [ false, " Command to run to disable the site in Apache.", 'string' ] +, hooks: [ false, " Obtain certs with hooks that configure a webserver to meet TLS-SNI-01 challenges.", 'boolean', false ] +, 'hooks-path': [ false, " Path in which to store files for hooks.", 'string' ] +, 'hooks-server': [ false, " Type of webserver to configure.", 'string' ] +, 'hooks-template': [ false, " Template to use for hooks configuration file.", 'string' ] +, 'hooks-bind': [ false, " IP address to use in configuration for hooks.", 'string' ] +, 'hooks-port': [ false, " Port to use in configuration for hooks.", 'string' ] +, 'hooks-webroot': [ false, " Webroot to use in configuration for hooks (e.g. empty dir).", 'string' ] +, 'hooks-pre-enable': [ false, " Hook to check the webserver configuration prior to enabling.", 'string' ] +, 'hooks-enable': [ false, " Hook to enable the webserver configuration.", 'string' ] +, 'hooks-pre-reload': [ false, " Hook to check the webserver configuration prior to reloading.", 'string' ] +, 'hooks-reload': [ false, " Hook to reload the webserver.", 'string' ] +, 'hooks-disable': [ false, " Hook to disable the webserver configuration.", 'string' ] //, 'standalone-supported-challenges': [ false, " Supported challenges, order preferences are randomly chosen. (default: http-01,tls-sni-01)", 'string', 'http-01,tls-sni-01'] , debug: [ false, " show traces and logs", 'boolean', false ] , 'work-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/lib/' ] diff --git a/index.js b/index.js index 85e0b0c..49e0324 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ module.exports.run = function (args) { challengeType = 'dns-01'; args.webrootPath = ''; args.standalone = USE_DNS; - } else if (args.tlsSni01Port || args.apache) { + } else if (args.tlsSni01Port || args.hooks) { challengeType = 'tls-sni-01'; args.webrootPath = ''; } else /*if (args.http01Port)*/ { @@ -25,17 +25,19 @@ module.exports.run = function (args) { if (args.manual) { leChallenge = require('le-challenge-manual').create({}); } - else if (args.apache) { - leChallenge = require('le-challenge-apache').create({ - apachePath: args.apachePath - , apacheBind: args.apacheBind - , apachePort: args.apachePort - , apacheWebroot: args.apacheWebroot - , apacheTemplate: args.apacheTemplate - , apacheEnable: args.apacheEnable - , apacheCheck: args.apacheCheck - , apacheReload: args.apacheReload - , apacheDisable: args.apacheDisable + else if (args.hooks) { + leChallenge = require('le-challenge-hooks').create({ + hooksPath: args.hooksPath + , hooksServer: args.hooksServer + , hooksTemplate: args.hooksTemplate + , hooksBind: args.hooksBind + , hooksPort: args.hooksPort + , hooksWebroot: args.hooksWebroot + , hooksPreEnable: args.hooksPreEnable + , hooksEnable: args.hooksEnable + , hooksPreReload: args.hooksPreReload + , hooksReload: args.hooksReload + , hooksDisable: args.hooksDisable }); } else if (args.webrootPath) { @@ -52,9 +54,10 @@ module.exports.run = function (args) { servers = require('./lib/servers').create(leChallenge); } + var privkeyPath = args.domainKeyPath || ':configDir/live/:hostname/privkey.pem'; //args.privkeyPath leStore = require('le-store-certbot').create({ configDir: args.configDir - , privkeyPath: args.domainKeyPath || ':configDir/live/:hostname/privkey.pem' //args.privkeyPath + , privkeyPath: privkeyPath , fullchainPath: args.fullchainPath , certPath: args.certPath , chainPath: args.chainPath @@ -123,14 +126,26 @@ module.exports.run = function (args) { console.log("\tIssued at " + new Date(certs.issuedAt).toISOString() + ""); console.log("\tValid until " + new Date(certs.expiresAt).toISOString() + ""); console.log(""); + console.log('Private key installed at:'); + console.log( + privkeyPath + .replace(/:configDir/g, args.configDir) + .replace(/:hostname/g, args.domains[0]) + ); + console.log(""); // should get back account, path to certs, pems, etc? - console.log('\nCertificates installed at:'); - console.log(Object.keys(args).filter(function (key) { - return /Path/.test(key); - }).map(function (key) { - return args[key]; - }).join('\n').replace(/:hostname/g, args.domains[0])); + console.log('Certificates installed at:'); + console.log( + [ + args.certPath + , args.chainPath + , args.fullchainPath + ].join('\n') + .replace(/:configDir/g, args.configDir) + .replace(/:hostname/g, args.domains[0]) + ); + console.log(""); process.exit(0); }, function (err) { diff --git a/package.json b/package.json index 3ccac6a..79ed54a 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,12 @@ "cli": "^0.11.1", "homedir": "^0.6.0", "le-acme-core": "^2.0.5", - "le-challenge-apache": "^2.0.1", + "le-challenge-hooks": "^2.0.0", "le-challenge-manual": "^2.0.0", "le-challenge-sni": "^2.0.0", "le-challenge-standalone": "^2.0.0", "le-store-certbot": "^2.0.2", - "letsencrypt": "^2.1.2", + "letsencrypt": "^2.1.8", "localhost.daplie.com-certificates": "^1.2.0", "mkdirp": "^0.5.1" } From baa9450caebce272b5a4c514557f3eabf2d091a4 Mon Sep 17 00:00:00 2001 From: Ben Schmidt Date: Wed, 12 Oct 2016 02:24:58 +1100 Subject: [PATCH 2/2] v2.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79ed54a..6f4390c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "letsencrypt-cli", - "version": "2.1.3", + "version": "2.2.0", "description": "CLI for node-letsencrypt modeled after the official client", "main": "index.js", "bin": {