Merge pull request #24 from insightfuls/support-apache

Support apache
This commit is contained in:
AJ ONeal 2016-10-10 12:46:48 -06:00 committed by GitHub
commit 5a94360f82
5 changed files with 162 additions and 16 deletions

116
README.md
View File

@ -46,10 +46,11 @@ Want to use the **live server**?
**Note**: This has really only been tested with single domains so if **Note**: This has really only been tested with single domains so if
multiple domains doesn't work for you, file a bug. multiple domains doesn't work for you, file a bug.
### Standalone ### Standalone (primarily for testing)
You can run standalone mode to get a cert **on the server** you will be You can run standalone mode to get a cert **on the server**. You either use an
using it for over ports **80 and 443 (or 5001)** like so: http-01 challenge (the default) on port 80, or a tls-sni-01 challenge on port
443 (or 5001). Like so:
```bash ```bash
letsencrypt certonly \ letsencrypt certonly \
@ -60,6 +61,17 @@ letsencrypt certonly \
--config-dir ~/letsencrypt/etc --config-dir ~/letsencrypt/etc
``` ```
or
```bash
letsencrypt certonly \
--agree-tos --email john.doe@example.com \
--standalone --tls-sni-01-port 443 \
--domains example.com,www.example.com \
--server https://acme-staging.api.letsencrypt.org/directory \
--config-dir ~/letsencrypt/etc
```
Then you can see your certs at `~/letsencrypt/etc/live`. Then you can see your certs at `~/letsencrypt/etc/live`.
``` ```
@ -70,9 +82,10 @@ This option is great for testing, but since it requires the use of
the same ports that your webserver needs, it isn't a good choice the same ports that your webserver needs, it isn't a good choice
for production. for production.
### WebRoot (for production) ### WebRoot (production option 1)
You can specify the path to where you keep your `index.html` with `webroot`. You can specify the path to where you keep your `index.html` with `webroot`, as
long as your server is serving plain HTTP on port 80.
For example, if I want to get a domain for `example.com` and my `index.html` is For example, if I want to get a domain for `example.com` and my `index.html` is
at `/srv/www/example.com`, then I would use this command: at `/srv/www/example.com`, then I would use this command:
@ -98,6 +111,67 @@ 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)
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.
The process works something like this. You would run:
```bash
sudo letsencrypt certonly \
--agree-tos --email john.doe@example.com \
--apache \
--config-dir /etc/letsencrypt \
--domains example.com,www.example.com \
--server https://acme-staging.api.letsencrypt.org/directory
```
Three files are then generated:
* a configuration fragment: `some-long-string.conf`
* a challenge-fulfilling certificate: `the-same-long-string.crt`
* a private key: `the-same-long-string.key`
A hook is then run to enable the fragment, e.g. by linking it (it should not be
moved) into a `conf.d` directory (for Apache on Debian, `sites-enabled`). A
second hook is then run to check the configuration is valid, to avoid
accidental downtime, and then another to signal to the server to reload the
configuration. The server will now serve the generated certificate on a special
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:
```
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:
* `{{{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
### Interactive (for debugging) ### Interactive (for debugging)
The token (for all challenge types) and keyAuthorization (only for https-01) The token (for all challenge types) and keyAuthorization (only for https-01)
@ -174,7 +248,7 @@ Options:
--debug BOOLEAN show traces and logs --debug BOOLEAN show traces and logs
--tls-sni-01-port NUMBER Use TLS-SNI-01 challenge type with this port. (Default is 443) --tls-sni-01-port NUMBER Use TLS-SNI-01 challenge type with this port.
(must be 443 with most production servers) (Boulder allows 5001 in testing mode) (must be 443 with most production servers) (Boulder allows 5001 in testing mode)
--http-01-port [NUMBER] Use HTTP-01 challenge type with this port, used for SimpleHttp challenge. (Default is 80) --http-01-port [NUMBER] Use HTTP-01 challenge type with this port, used for SimpleHttp challenge. (Default is 80)
@ -199,6 +273,34 @@ Options:
--server [STRING] ACME Directory Resource URI. (Default is https://acme-v01.api.letsencrypt.org/directory)) --server [STRING] ACME Directory Resource URI. (Default is https://acme-v01.api.letsencrypt.org/directory))
--apache BOOLEAN Obtain certs using Apache virtual hosts.
--apache-path STRING Path in which to store files for Apache virtual hosts.
(Default is ~/letsencrypt/apache)
--apache-bind [STRING] IP address to use for Apache virtual host. (Default is *)
(This is used in the default template.)
--apache-port [NUMBER] Port to use for Apache virtual host. (Default is 443)
(This is used in the default template.)
--apache-webroot STRING Webroot to use for Apache virtual host (e.g. an empty dir).
Nothing should actually be served from here. (Default is /var/www)
--apache-template STRING Alternative template to use for Apache configuration file.
--apache-enable STRING Command to run to enable the site in Apache.
(Default is `ln -s {{{conf}}} /etc/apache2/sites-enabled`)
--apache-check STRING Command to run to check Apache configuration.
(Default is `apache2ctl configtest`)
--apache-reload STRING Command to run to reload Apache.
(Default is `/etc/init.d/apache2 reload`)
--apache-disable STRING Command to run to disable the site in Apache.
(Default is `rm /etc/apache2/sites-enabled/{{{token}}}.conf`)
--standalone [BOOLEAN] Obtain certs using a "standalone" webserver. (Default is true) --standalone [BOOLEAN] Obtain certs using a "standalone" webserver. (Default is true)
--manual [BOOLEAN] Print the token and key to the screen and wait for you to hit enter, --manual [BOOLEAN] Print the token and key to the screen and wait for you to hit enter,
@ -206,7 +308,7 @@ Options:
--webroot BOOLEAN Obtain certs by placing files in a webroot directory. --webroot BOOLEAN Obtain certs by placing files in a webroot directory.
--webroot-path STRING public_html / webroot path. --webroot-path STRING public_html / webroot path.
-h, --help Display help and usage details -h, --help Display help and usage details
``` ```

View File

@ -10,8 +10,8 @@ cli.parse({
, duplicate: [ false, " Allow getting a certificate that duplicates an existing one", 'boolean', false ] , duplicate: [ false, " Allow getting a certificate that duplicates an existing one", 'boolean', false ]
, 'agree-tos': [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ] , 'agree-tos': [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ]
, debug: [ false, " show traces and logs", 'boolean', false ] , debug: [ false, " show traces and logs", 'boolean', false ]
, 'tls-sni-01-port': [ false, " Use TLS-SNI-01 challenge type with this port (only port 443 is valid with most production servers) (default: 443,5001)", 'string' ] , 'tls-sni-01-port': [ false, " Use TLS-SNI-01 challenge type with this port (only port 443 is valid with most production servers)", 'int' ]
, 'http-01-port': [ false, " Use HTTP-01 challenge type with this port (only port 80 is valid with most production servers) (default: 80)", 'string' ] , 'http-01-port': [ false, " Use HTTP-01 challenge type with this port (only port 80 is valid with most production servers) (default: 80)", 'int' ]
, 'dns-01': [ false, " Use DNS-01 challange type", 'boolean', false ] , 'dns-01': [ false, " Use DNS-01 challange type", 'boolean', false ]
, 'rsa-key-size': [ false, " Size (in bits) of the RSA key.", 'int', 2048 ] , 'rsa-key-size': [ false, " Size (in bits) of the RSA key.", 'int', 2048 ]
, 'cert-path': [ false, " Path to where new cert.pem is saved", 'string',':configDir/live/:hostname/cert.pem' ] , 'cert-path': [ false, " Path to where new cert.pem is saved", 'string',':configDir/live/:hostname/cert.pem' ]
@ -25,6 +25,16 @@ 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 ]
, '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' ]
//, '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']
, 'work-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/lib/' ] , 'work-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/lib/' ]
, 'logs-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/log/' ] , 'logs-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/log/' ]

View File

@ -13,8 +13,9 @@ 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) { } else if (args.tlsSni01Port || args.apache) {
challengeType = 'tls-sni-01'; challengeType = 'tls-sni-01';
args.webrootPath = '';
} else /*if (args.http01Port)*/ { } else /*if (args.http01Port)*/ {
challengeType = 'http-01'; challengeType = 'http-01';
} }
@ -22,17 +23,31 @@ 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) {
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.webrootPath) { else if (args.webrootPath) {
// webrootPath is all that really matters here // webrootPath is all that really matters here
// TODO rename le-challenge-fs to le-challenge-webroot // TODO rename le-challenge-fs to le-challenge-webroot
leChallenge = require('./lib/webroot').create({ webrootPath: args.webrootPath }); leChallenge = require('./lib/webroot').create({ webrootPath: args.webrootPath });
} }
else if (args.tlsSni01Port) {
leChallenge = require('le-challenge-sni').create({});
servers = require('./lib/servers').create(leChallenge);
}
else if (USE_DNS !== args.standalone) { else if (USE_DNS !== args.standalone) {
leChallenge = require('le-challenge-standalone').create({}); leChallenge = require('le-challenge-standalone').create({});
servers = require('./lib/servers').create(leChallenge).startServers( servers = require('./lib/servers').create(leChallenge);
args.http01Port || [80], args.tlsSni01Port || [443, 5001]
, { debug: args.debug }
);
} }
leStore = require('le-store-certbot').create({ leStore = require('le-store-certbot').create({
@ -51,14 +66,31 @@ module.exports.run = function (args) {
} }
// let LE know that we're handling standalone / webroot here // let LE know that we're handling standalone / webroot here
var leChallenges = {};
leChallenges[challengeType] = leChallenge;
var le = LE.create({ var le = LE.create({
debug: args.debug debug: args.debug
, server: args.server , server: args.server
, store: leStore , store: leStore
, challenges: { 'http-01': leChallenge, 'tls-sni-01': leChallenge } , challenges: leChallenges
, duplicate: args.duplicate , duplicate: args.duplicate
}); });
if (servers) {
if (args.tlsSni01Port) {
servers = servers.startServers(
[], args.tlsSni01Port
, { debug: args.debug, httpsOptions: le.httpsOptions }
);
}
else {
servers = servers.startServers(
args.http01Port || [80], []
, { debug: args.debug }
);
}
}
// Note: can't use args directly as null values will overwrite template values // Note: can't use args directly as null values will overwrite template values
le.register({ le.register({
domains: args.domains domains: args.domains

View File

@ -25,7 +25,7 @@ module.exports.create = function (challenge) {
, startServers: function (plainPorts, tlsPorts, opts) { , startServers: function (plainPorts, tlsPorts, opts) {
opts = opts || {}; opts = opts || {};
var httpsOptions = require('localhost.daplie.com-certificates'); var httpsOptions = opts.httpsOptions || require('localhost.daplie.com-certificates');
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');

View File

@ -36,7 +36,9 @@
"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-manual": "^2.0.0", "le-challenge-manual": "^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.2",