replace apache with generic and extensible hooks

This commit is contained in:
Ben Schmidt 2016-10-12 02:22:37 +11:00
parent 8b78ce694a
commit 5a1abb7e83
4 changed files with 88 additions and 67 deletions

View File

@ -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) 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. 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 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 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 open. This facility can work with any web server as long as it supports server
with other servers as long as they support server name indication (SNI) and you name indication (SNI) and you can provide a configuration file template and
can provide a configuration file template and hooks to install and uninstall it shell hooks to install and uninstall the configuration (without downtime). In
(without downtime). In fact, it doesn't even need to be a webserver (though it fact, it doesn't even need to be a webserver (though it must run on port 443);
must run on port 443); it could be another server that performs SSL/TLS it could be another server that performs SSL/TLS negotiation with SNI.
negotiation with SNI.
The process works something like this. You would run: The process works something like this. You would run:
```bash ```bash
sudo letsencrypt certonly \ sudo letsencrypt certonly \
--agree-tos --email john.doe@example.com \ --agree-tos --email john.doe@example.com \
--apache \ --hooks --hooks-server apache2-debian \
--config-dir /etc/letsencrypt \ --config-dir /etc/letsencrypt \
--domains example.com,www.example.com \ --domains example.com,www.example.com \
--server https://acme-staging.api.letsencrypt.org/directory --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 After the domain has been validated externally, hooks are run to disable the
configuration fragment, and again check and reload the configuration. 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/ ls /etc/letsencrypt/live/
``` ```
To tailor this for your server setup, see all the `apache-` options in the list Tailor to your server and distro using the `--hooks-server` option. So far, the
below. Also note that the following substitutions are available for use in the following are supported (contributions for additional servers welcome):
commands supplied to those options, and in any alternative template you
provide: * 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 * `{{{token}}}`: the token
* `{{{domain}}}`: the domain for which a certificate is being sought (beware of * `{{{domain}}}`: the domain for which a certificate is being sought (beware of
this if using multiple domains per certificate) this if using multiple domains per certificate)
* `{{{subject}}}`: the domain for which the generated challenge-fulfilling * `{{{subject}}}`: the domain for which the generated challenge-fulfilling
certificate must be used (only available when generating it) certificate must be used (only available when generating it)
* `{{{cert}}}`: the path to the generated certificate: `apache-path/token.crt` * `{{{cert}}}`: the path to the generated certificate: `hooks-path/token.crt`
* `{{{privkey}}}`: the path to the generated private key: `apache-path/token.key` * `{{{privkey}}}`: the path to the generated private key: `hooks-path/token.key`
* `{{{conf}}}`: the path to the generated config file: `apache-path/token.conf` * `{{{conf}}}`: the path to the generated config file: `hooks-path/token.conf`
* `{{{bind}}}`: the value of the `apache-bind` option * `{{{bind}}}`: the value of the `hooks-bind` option
* `{{{port}}}`: the value of the `apache-port` option * `{{{port}}}`: the value of the `hooks-port` option
* `{{{webroot}}}`: the value of the `apache-webroot` option * `{{{webroot}}}`: the value of the `hooks-webroot` option
### Interactive (for debugging) ### 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 sudo chown -R $(whoami) /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt
``` ```
## Command line Options ## Command Line Options
``` ```
Usage: Usage:
@ -285,33 +288,34 @@ Options:
--webroot-path STRING public_html / webroot path. --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) (Default is ~/letsencrypt/apache)
--apache-bind [STRING] IP address to use for Apache virtual host. (Default is *) --hooks-server STRING Type of webserver to configure. Sets defaults for all the following --hooks- options.
(This is used in the default template.) 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) --hooks-template STRING Template to use for hooks configuration file.
(This is used in the default template.) 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) 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. --hooks-enable STRING Hook to enable the webserver configuration.
(Default is `ln -s {{{conf}}} /etc/apache2/sites-enabled`)
--apache-check STRING Command to run to check Apache configuration. --hooks-pre-reload STRING Hook to check the webserver configuration prior to reloading.
(Default is `apache2ctl configtest`)
--apache-reload STRING Command to run to reload Apache. --hooks-reload STRING Hook to reload the webserver.
(Default is `/etc/init.d/apache2 reload`)
--apache-disable STRING Command to run to disable the site in Apache. --hooks-disable STRING Hook to disable the webserver configuration.
(Default is `rm /etc/apache2/sites-enabled/{{{token}}}.conf`)
--debug BOOLEAN show traces and logs --debug BOOLEAN show traces and logs

View File

@ -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 ] , 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: [ false, " Obtain certs by placing files in a webroot directory.", 'boolean', false ]
, 'webroot-path': [ false, " public_html / webroot path.", 'string' ] , 'webroot-path': [ false, " public_html / webroot path.", 'string' ]
, apache: [ false, " Obtain certs using Apache virtual hosts.", 'boolean', false ] , hooks: [ false, " Obtain certs with hooks that configure a webserver to meet TLS-SNI-01 challenges.", 'boolean', false ]
, 'apache-path': [ false, " Path in which to store files for Apache virtual hosts.", 'string' ] , 'hooks-path': [ false, " Path in which to store files for hooks.", 'string' ]
, 'apache-bind': [ false, " IP address to use for Apache virtual host.", 'string', "*" ] , 'hooks-server': [ false, " Type of webserver to configure.", 'string' ]
, 'apache-port': [ false, " Port to use for Apache virtual host.", 'int', 443 ] , 'hooks-template': [ false, " Template to use for hooks configuration file.", 'string' ]
, 'apache-webroot': [ false, " Webroot to use for Apache virtual host (e.g. empty dir).", 'string' ] , 'hooks-bind': [ false, " IP address to use in configuration for hooks.", 'string' ]
, 'apache-template': [ false, " Alternative template to use for Apache configuration file.", 'string' ] , 'hooks-port': [ false, " Port to use in configuration for hooks.", 'string' ]
, 'apache-enable': [ false, " Command to run to enable the site in Apache.", 'string' ] , 'hooks-webroot': [ false, " Webroot to use in configuration for hooks (e.g. empty dir).", 'string' ]
, 'apache-check': [ false, " Command to run to check Apache configuration.", 'string' ] , 'hooks-pre-enable': [ false, " Hook to check the webserver configuration prior to enabling.", 'string' ]
, 'apache-reload': [ false, " Command to run to reload Apache.", 'string' ] , 'hooks-enable': [ false, " Hook to enable the webserver configuration.", 'string' ]
, 'apache-disable': [ false, " Command to run to disable the site in Apache.", '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'] //, '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 ] , debug: [ false, " show traces and logs", 'boolean', false ]
, 'work-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/lib/' ] , 'work-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/lib/' ]

View File

@ -15,7 +15,7 @@ module.exports.run = function (args) {
challengeType = 'dns-01'; challengeType = 'dns-01';
args.webrootPath = ''; args.webrootPath = '';
args.standalone = USE_DNS; args.standalone = USE_DNS;
} else if (args.tlsSni01Port || args.apache) { } else if (args.tlsSni01Port || args.hooks) {
challengeType = 'tls-sni-01'; challengeType = 'tls-sni-01';
args.webrootPath = ''; args.webrootPath = '';
} else /*if (args.http01Port)*/ { } else /*if (args.http01Port)*/ {
@ -25,17 +25,19 @@ module.exports.run = function (args) {
if (args.manual) { if (args.manual) {
leChallenge = require('le-challenge-manual').create({}); leChallenge = require('le-challenge-manual').create({});
} }
else if (args.apache) { else if (args.hooks) {
leChallenge = require('le-challenge-apache').create({ leChallenge = require('le-challenge-hooks').create({
apachePath: args.apachePath hooksPath: args.hooksPath
, apacheBind: args.apacheBind , hooksServer: args.hooksServer
, apachePort: args.apachePort , hooksTemplate: args.hooksTemplate
, apacheWebroot: args.apacheWebroot , hooksBind: args.hooksBind
, apacheTemplate: args.apacheTemplate , hooksPort: args.hooksPort
, apacheEnable: args.apacheEnable , hooksWebroot: args.hooksWebroot
, apacheCheck: args.apacheCheck , hooksPreEnable: args.hooksPreEnable
, apacheReload: args.apacheReload , hooksEnable: args.hooksEnable
, apacheDisable: args.apacheDisable , hooksPreReload: args.hooksPreReload
, hooksReload: args.hooksReload
, hooksDisable: args.hooksDisable
}); });
} }
else if (args.webrootPath) { else if (args.webrootPath) {
@ -52,9 +54,10 @@ module.exports.run = function (args) {
servers = require('./lib/servers').create(leChallenge); servers = require('./lib/servers').create(leChallenge);
} }
var privkeyPath = args.domainKeyPath || ':configDir/live/:hostname/privkey.pem'; //args.privkeyPath
leStore = require('le-store-certbot').create({ leStore = require('le-store-certbot').create({
configDir: args.configDir configDir: args.configDir
, privkeyPath: args.domainKeyPath || ':configDir/live/:hostname/privkey.pem' //args.privkeyPath , privkeyPath: privkeyPath
, fullchainPath: args.fullchainPath , fullchainPath: args.fullchainPath
, certPath: args.certPath , certPath: args.certPath
, chainPath: args.chainPath , chainPath: args.chainPath
@ -123,14 +126,26 @@ module.exports.run = function (args) {
console.log("\tIssued at " + new Date(certs.issuedAt).toISOString() + ""); console.log("\tIssued at " + new Date(certs.issuedAt).toISOString() + "");
console.log("\tValid until " + new Date(certs.expiresAt).toISOString() + ""); console.log("\tValid until " + new Date(certs.expiresAt).toISOString() + "");
console.log(""); 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? // should get back account, path to certs, pems, etc?
console.log('\nCertificates installed at:'); console.log('Certificates installed at:');
console.log(Object.keys(args).filter(function (key) { console.log(
return /Path/.test(key); [
}).map(function (key) { args.certPath
return args[key]; , args.chainPath
}).join('\n').replace(/:hostname/g, args.domains[0])); , args.fullchainPath
].join('\n')
.replace(/:configDir/g, args.configDir)
.replace(/:hostname/g, args.domains[0])
);
console.log("");
process.exit(0); process.exit(0);
}, function (err) { }, function (err) {

View File

@ -36,12 +36,12 @@
"cli": "^0.11.1", "cli": "^0.11.1",
"homedir": "^0.6.0", "homedir": "^0.6.0",
"le-acme-core": "^2.0.5", "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-manual": "^2.0.0",
"le-challenge-sni": "^2.0.0", "le-challenge-sni": "^2.0.0",
"le-challenge-standalone": "^2.0.0", "le-challenge-standalone": "^2.0.0",
"le-store-certbot": "^2.0.2", "le-store-certbot": "^2.0.2",
"letsencrypt": "^2.1.2", "letsencrypt": "^2.1.8",
"localhost.daplie.com-certificates": "^1.2.0", "localhost.daplie.com-certificates": "^1.2.0",
"mkdirp": "^0.5.1" "mkdirp": "^0.5.1"
} }