diff --git a/README.md b/README.md index 95632ae..a736c87 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ greenlock --email jon@example.com \ Robust configurations for Greenlock as a system service ```bash -sudo greenlock --install systemd --conf /etc/greenlock/greenlock.yml +sudo greenlock --install systemd --config /etc/greenlock/greenlock.yml ``` See explanations below in the **Usage** section. @@ -95,7 +95,7 @@ These commands are shown using the **testing server**. Want to use the **live server**? -1. change server to `--server https://acme-v01.api.letsencrypt.org/directory` +1. change server to `--server https://acme-v02.api.letsencrypt.org/directory` **Note**: This has really only been tested with single domains so if multiple domains doesn't work for you, file a bug. @@ -111,8 +111,9 @@ greenlock certonly \ --agree-tos --email john.doe@example.com \ --standalone \ --domains example.com,www.example.com \ - --server https://acme-staging.api.letsencrypt.org/directory \ - --config-dir ~/letsencrypt/etc + --server https://acme-staging-v02.api.letsencrypt.org/directory \ + --acme-version draft-11 + --config-dir ~/acme/etc ``` or @@ -122,8 +123,9 @@ greenlock 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 + --server https://acme-staging-v02.api.letsencrypt.org/directory \ + --acme-version draft-11 + --config-dir ~/acme/etc ``` Then you can see your certs at `~/letsencrypt/etc/live`. @@ -150,7 +152,8 @@ sudo greenlock certonly \ --webroot --webroot-path /srv/www/example.com \ --config-dir /etc/letsencrypt \ --domains example.com,www.example.com \ - --server https://acme-staging.api.letsencrypt.org/directory + --server https://acme-staging-v02.api.letsencrypt.org/directory + --acme-version draft-11 ``` Note that we use `sudo` because in this example we are using `/etc/letsencrypt` @@ -181,9 +184,10 @@ The process works something like this. You would run: sudo greenlock certonly \ --agree-tos --email john.doe@example.com \ --hooks --hooks-server apache2-debian \ - --config-dir /etc/letsencrypt \ + --config-dir /etc/acme \ --domains example.com,www.example.com \ - --server https://acme-staging.api.letsencrypt.org/directory + --server https://acme-staging-v02.api.letsencrypt.org/directory + --acme-version draft-11 ``` Three files are then generated: @@ -239,9 +243,10 @@ will be printed to the screen and you will be given time to copy it wherever sudo greenlock certonly \ --agree-tos --email john.doe@example.com \ --manual - --config-dir /etc/letsencrypt \ + --config-dir /etc/acme \ --domains example.com,www.example.com \ - --server https://acme-staging.api.letsencrypt.org/directory + --server https://acme-staging-v02.api.letsencrypt.org/directory + --acme-version draft-11 ``` ## Test with a free domain diff --git a/bin/greenlock.js b/bin/greenlock.js index a0c273b..8de5d3b 100755 --- a/bin/greenlock.js +++ b/bin/greenlock.js @@ -5,42 +5,46 @@ var cli = require('cli'); var mkdirp = require('mkdirp'); cli.parse({ - server: [ false, " ACME Directory Resource URI.", 'string', '' ] -, email: [ false, " Email used for registration and recovery contact. (default: null)", 'email' ] -, 'agree-tos': [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ] -, domains: [ false, " Domain names to apply. For multiple domains you can enter a comma separated list of domains as a parameter. (default: [])", 'string' ] -, 'renew-within': [ false, " Renew certificates this many days before expiry", 'int', 7 ] -, duplicate: [ false, " Allow getting a certificate that duplicates an existing one/is an early renewal", 'boolean', false ] -, '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' ] -, 'fullchain-path': [ false, " Path to where new fullchain.pem (cert + chain) is saved", 'string', ':configDir/live/:hostname/fullchain.pem' ] -, 'chain-path': [ false, " Path to where new chain.pem is saved", 'string', ':configDir/live/:hostname/chain.pem' ] -, 'domain-key-path': [ false, " Path to privkey.pem to use for domain (default: generate new)", 'string' ] -, 'account-key-path': [ false, " Path to privkey.pem to use for account (default: generate new)", 'string' ] -, 'config-dir': [ false, " Configuration directory.", 'string', '~/letsencrypt/etc/' ] -, '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)", 'int' ] -, 'dns-01': [ false, " Use DNS-01 challange type", 'boolean', false ] -, standalone: [ false, " Obtain certs using a \"standalone\" webserver.", '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-path': [ false, " public_html / webroot path.", '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/' ] -, 'logs-dir': [ false, "(ignored)", 'string', '~/letsencrypt/var/log/' ] + 'acme-version': + [ false, " v01 (Let's Encrypt v01) or draft-11 (Let's Encrypt v02) (default: draft-11)", 'string', 'draft-11' ] +, 'acme-url': + [ false, " ACME API Directory URL (default: https://acme-v02.api.letsencrypt.org/directory", 'string', '' ] +, config: + [ 'c', " Path to configuration file --config /etc/greenlock/greenlock.yml (default: '')", 'string' ] +, serve: + [ false, " Run as webserver (default: false)", 'boolean', false ] +, email: + [ false, " Email used for registration and recovery contact (default: '')", 'email', '' ] +, analytics: + [ false, " Share analytics with greenlock (default: false)", 'boolean', false ] +, community: + [ false, " Join the greenlock community to get important updates (default: false)", 'boolean', false ] +, 'agree-tos': + [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ] +, domains: + [ false, " Comma-separated list of domains to secure (default: [])", 'string' ] +, 'config-dir': + [ false, " Configuration directory.", 'string', '~/acme/etc/' ] +, 'cert-path': + [ false, " Path where new cert.pem is saved", 'string',':configDir/live/:hostname/cert.pem' ] +, 'fullchain-path': + [ false, " Path where new fullchain.pem (cert + chain) is saved", 'string', ':configDir/live/:hostname/fullchain.pem' ] +, 'chain-path': + [ false, " Path where new chain.pem is saved", 'string', ':configDir/live/:hostname/chain.pem' ] +, 'bundle-path': + [ false, " Path where new bundle.pem (fullchain + privkey) is saved", 'string', ':configDir/live/:hostname/bundle.pem' ] +, 'privkey-path': + [ false, " Path where (new or existing) domain privkey.pem is saved", 'string', ':configDir/live/:hostname/privkey.pem' ] +, 'root': + [ false, " public_html / webroot path /srv/www/:hostname", 'string' ] +, 'renew-within': + [ false, " Renew certificates this many days before expiry", 'int', 11 ] +, standalone: + [ false, " Obtain certs using a \"standalone\" webserver.", '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 ] +, debug: + [ false, " show traces and logs", 'boolean', false ] }); // ignore certonly and extraneous arguments diff --git a/dist/etc/greenlock/greenlock.example.yml b/dist/etc/greenlock/greenlock.example.yml new file mode 100644 index 0000000..0047312 --- /dev/null +++ b/dist/etc/greenlock/greenlock.example.yml @@ -0,0 +1,45 @@ +daemon: true +http_port: 80 +https_port: 443 +version: draft-11 +server: https://acme-staging-v02.api.letsencrypt.org/directory +webroot_path: /srv/www +store: + module: 'le-store-certbot' + options: + config_dir: /etc/greenlock/ +domains: + '*': + check: + module: 'fs-exists' + options: + path: /srv/www/:hostname + email: MY_EMAIL + agree_tos: MY_AGREE + analytics: true + challenges: + 'https-01': + module: 'le-challenge-fs' + 'example.com,www.example.com': + path: /srv/www/example.com + email: MY_EMAIL + agree_tos: MY_AGREE + analytics: true + challenges: + 'dns-01': + module: 'le-challenge-cloudflare' + options: + email: 'jon@example.com' + key: 'xxxxxxxxxxxxxxxxx' + '.example.com': + check: null + path: /srv/www/example.com + email: MY_EMAIL + agree_tos: MY_AGREE + analytics: true + challenges: + 'dns-01': + module: 'le-challenge-digitalocean' + options: + email: 'jon@example.com' + key: 'xxxxxxxxxxxxxxxxx' diff --git a/dist/etc/greenlock/greenlock.yml b/dist/etc/greenlock/greenlock.yml new file mode 100644 index 0000000..e2df548 --- /dev/null +++ b/dist/etc/greenlock/greenlock.yml @@ -0,0 +1,22 @@ +daemon: true +http_port: 80 +https_port: 443 +version: draft-11 +server: https://acme-staging-v02.api.letsencrypt.org/directory +webroot_path: /srv/www +store: + module: 'le-store-certbot' + options: + config_dir: /etc/greenlock/ +domains: + '*': + email: MY_EMAIL + agree_tos: MY_AGREE + analytics: true + checks: + - module: 'greenlock-plugin-approve-fs' + options: + www: false + path: /srv/www/:hostname + challenges: + module: 'le-challenge-fs' diff --git a/dist/etc/systemd/system/greenlock.service b/dist/etc/systemd/system/greenlock.service new file mode 100644 index 0000000..7ed4ecf --- /dev/null +++ b/dist/etc/systemd/system/greenlock.service @@ -0,0 +1,69 @@ +[Unit] +Description=MY_DESC +Documentation=MY_DOCS +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service + +[Service] +# Restart on crash (bad signal), and on 'clean' failure (error exit code) +# Allow up to 3 restarts within 10 seconds +# (it's unlikely that a user or properly-running script will do this) +Restart=on-failure +StartLimitInterval=10 +StartLimitBurst=3 + +# The v8 VM will output a "clean" exit for JavaScript errors. +# If we knew we were never going to accidentally exit cleanly +# we would use on-abnormal: +; Restart=on-abnormal + +# User and group the process will run as +# (www-data is the de facto standard on most systems) +User=MY_USER +Group=MY_GROUP + +# If we need to pass environment variables in the future +Environment=NODE_PATH=MY_GREENLOCK_PATH/lib/node_modules NPM_CONFIG_PREFIX=MY_GREENLOCK_PATH + +# Set a sane working directory, sane flags, and specify how to reload the config file +WorkingDirectory=MY_GREENLOCK_PATH +ExecStart=MY_GREENLOCK_PATH/bin/node MY_GREENLOCK_PATH/bin/greenlock --daemon --config MY_CONFIG_PATH +ExecReload=/bin/kill -USR1 $MAINPID + +# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. +# Unmodified greenlock is not expected to use more than this. +LimitNOFILE=1048576 +LimitNPROC=64 + +# Use private /tmp and /var/tmp, which are discarded after greenlock stops. +PrivateTmp=true +# Use a minimal /dev +PrivateDevices=true +# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. +ProtectHome=true +# Make /usr, /boot, /etc and possibly some more folders read-only. +ProtectSystem=full +# … except TLS/SSL, ACME, and Let's Encrypt certificates +# and /var/log/greenlock, because we want a place where logs can go. +# This merely retains r/w access rights, it does not add any new. Must still be writable on the host! +ReadWriteDirectories=MY_RW_DIRS +# you may also want to add other directories such as /opt/greelock /etc/acme /etc/letsencrypt + +# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories +; ReadWritePaths=/etc/greenlock /var/log/greenlock + +# The following additional security directives only work with systemd v229 or later. +# They further retrict privileges that can be gained. +# Note that you may have to add capabilities required by any plugins in use. +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true + +# Caveat: Some plugins need additional capabilities. +# For example "upload" needs CAP_LEASE +; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE +; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE +; NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/index.js b/index.js index 95c5c57..12a362c 100644 --- a/index.js +++ b/index.js @@ -86,7 +86,7 @@ module.exports.run = function (args) { if (args.tlsSni01Port) { servers.startServers( [], args.tlsSni01Port - , { debug: args.debug, httpsOptions: le.httpsOptions } + , { debug: args.debug, tlsOptions: le.tlsOptions } ); } else { diff --git a/lib/install-systemd.js b/lib/install-systemd.js new file mode 100644 index 0000000..c192b0e --- /dev/null +++ b/lib/install-systemd.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports.install = function (opts, cb) { + var fs = require('fs'); + var path = require('path'); + var service = '/etc/systemd/system/greenlock.service'; + var rwDirs = [ '/etc/greenlock', '/srv/www', '/var/log/greenlock', '/opt/greenlock' ]; + + fs.readFile(path.join(__dirname, '../dist', service), 'utf8', function (e, text) { + if (e) { throw e; } + + text = text + .replace(/MY_DESC/g, opts.description || 'Greenlock Secure Web Server') + .replace(/MY_DOCS/g, opts.homepage || 'https://git.coolaj86.com/coolaj86/greenlock-cli.js') + .replace(/MY_GREENLOCK_PATH/g, opts.greenlockPath || '/opt/greenlock') + .replace(/MY_CONFIG_PATH/g, opts.greenlockPath || '/etc/greenlock/greenlock.yml') + .replace(/MY_USER/g, opts.user || 'greenlock') + .replace(/MY_GROUP/g, opts.user || 'greenlock') + .replace(/MY_RW_DIRS/g, (opts.writableDirs || rwDirs).join(' ')) + ; + fs.writeFile(service, text, 'utf8', function (e) { + if (e) { throw e; } + + console.log("Now reload configs and enable to start on boot:"); + console.log(""); + console.log("\tsudo systemctl daemon-reload"); + console.log("\tsudo systemctl enable greenlock"); + cb(null); + }); + }); +}; diff --git a/lib/servers.js b/lib/servers.js index e1e45e8..845e5d7 100644 --- a/lib/servers.js +++ b/lib/servers.js @@ -25,7 +25,7 @@ module.exports.create = function (challenge) { , startServers: function (plainPorts, tlsPorts, opts) { opts = opts || {}; - var httpsOptions = opts.httpsOptions || require('localhost.daplie.me-certificates'); + var tlsOptions = opts.tlsOptions; var https = require('https'); var http = require('http'); @@ -56,7 +56,7 @@ module.exports.create = function (challenge) { // tls-sni-01-port tlsPorts.forEach(function (port) { - var server = https.createServer(httpsOptions, servers.httpResponder); + var server = https.createServer(tlsOptions, servers.httpResponder); servers._servers.push(server); server.listen(port, function () { diff --git a/package.json b/package.json index 1f684e8..866fdc6 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "le-challenge-sni": "^2.0.0", "le-challenge-standalone": "^2.0.0", "le-store-certbot": "^2.0.2", - "localhost.daplie.me-certificates": "^1.3.2", "mkdirp": "^0.5.1" } }