Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add auto certification support #9

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
npm-debug.log
*.DS_Store
node_modules
package-lock.json
tests/nginx/*
tests/test.json*
tests/mechanic-overrides
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Let's add a single proxy that talks to one node process, which is listening on p

*All commands must be run as root.*

**(Optional) Step Four:**

Install letsencrypt for automatic host certification or configure your own certificate provider.

## Adding a site

```
Expand Down Expand Up @@ -149,6 +153,46 @@ Next we decide we want the site to be secure all the time, redirecting any traff
mechanic update mysite --https=true --redirect-to-https=true
```

## Certifying a secure site

You may use a certificate authority to automatically get SSL certificates for the hostnames on your site.

`letsencrypt` is included by default.

You may add more providers as shell scripts in `/etc/nginx/mechanic-certproviders`.

The files can be nunjucks templates, where the following variables are available:

`hosts`: array of hostnames configured for your site, including aliases
`name`: the name of your site
`host`: your site's default host

To automatically generate certificates for a site, add the `--certify` flag to your site configuration, like so:

```
mechanic update mysite --certify=true
```
or
```
mechanic add mysite --host=example.com --aliases=example1.com,example2.com --https=true --certify=true
```

To change the global certificate provider, run
```
mechanic set certProvider myCertProvider
```
where `myCertProvider` is the name of the file you've created in `/etc/nginx/mechanic-certproviders`.

You may change the certificate provider per site.

To do so, run
```
mechanic update mysite --certProvider=myCertProvider --certify
```

For the default letsencrypt provider, certificates are symlinked to the configuration folder,
so you can use `letsencrypt renew` to renew all your certificates automatically.

## Shutting off HTTPS

Now we've decided we don't want ecommerce anymore. Let's shut that off:
Expand Down Expand Up @@ -341,4 +385,3 @@ Killed support for `tlsv1` as it is insecure.
0.1.1, 0.1.2: `reset` command works.

0.1.0: initial release.

67 changes: 62 additions & 5 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ var argv = require('boring')();
var _ = require('lodash');
var fs = require('fs');
var shelljs = require('shelljs');
var resolve = require('path').resolve;
var path = require('path');
var resolve = path.resolve;
var shellEscape = require('shell-escape');
var debug = false;

var dataFile;
if (argv.data) {
Expand All @@ -15,27 +17,44 @@ if (argv.data) {
// have a /var/lib/misc folder for storage of "state files
// that don't need a directory." But create it if it's
// somehow missing (Mac for instance).
if (!fs.existsSync('/var/lib/misc')) {
if (!debug && !fs.existsSync('/var/lib/misc')) {
fs.mkdirSync('/var/lib/misc', 0700);
}
}

var certProviders = {
letsencrypt: 'letsencrypt certonly --standalone {% for host in hosts %}-d {{host}} {% endfor %}; ln -s /etc/letsencrypt/live/{{host}}/fullchain.pem /etc/nginx/certs/{{name}}.cer; ln -s /etc/letsencrypt/live/{{host}}/privkey.pem /etc/nginx/certs/{{name}}.key'
}


var data = require('prettiest')({ json: dataFile });

var defaultSettings = {
conf: '/etc/nginx/conf.d',
certProviders: '/etc/nginx/mechanic-certproviders',
overrides: '/etc/nginx/mechanic-overrides',
logs: '/var/log/nginx',
restart: 'nginx -s reload',
bind: '*'
bind: '*',
autoCert: 'false',
certProvider: null,
preConfig: 'service nginx stop',
postConfig: 'service nginx start'
};

if(shelljs.exec('which letsencrypt').code === 0) {
defaultSettings.certProvider = 'letsencrypt';
}

_.defaults(data, { settings: {} });
_.defaults(data.settings, defaultSettings);

var settings = data.settings;

if (!debug && !fs.existsSync(settings.certProviders)) {
fs.mkdirSync(settings.certProviders);
}

var nunjucks = require('nunjucks');

var command = argv._[0];
Expand All @@ -56,7 +75,9 @@ var options = {
'static': 'string',
'autoindex': 'boolean',
'https': 'boolean',
'redirect-to-https': 'boolean'
'redirect-to-https': 'boolean',
'certify': 'boolean',
'certProvider': 'string'
};

var parsers = {
Expand Down Expand Up @@ -179,6 +200,11 @@ function update(add) {
var shortname = argv._[1];
var site;

if(settings.preConfig) {
if(shelljs.exec(settings.preConfig).code !== 0) {
console.error('ERROR: failed to run preConfig script: \'' + settings.preConfig + '\'.');
}
}
if (add) {
if (findSite(shortname)) {
usage('Site already exists, use update');
Expand Down Expand Up @@ -213,6 +239,37 @@ function update(add) {
}
});

var _hardProviders = debug?[]:fs.readdirSync(settings.certProviders),
hardProviders = {};
for(var provider of _hardProviders) {
hardProviders[provider] = fs.readFileSync(path.join(settings.certProviders,provider)).toString();
}
var providers = {};
Object.assign(providers, certProviders, hardProviders);

var _provider = site.certProvider || settings.certProvider;
if(site.certify && _provider) {
var provider = providers[_provider];
var aliases = site.aliases||[];

if(provider) {
var result = 0;
var parts = provider.replace(/\n/g,';').split(';');
for(var part of parts) {
result |= shelljs.exec(nunjucks.renderString(part, {
hosts: [site.host].concat(aliases),
name: site.shortname,
host: site.host
})).code;
}
if(result) console.error('ERROR: certProvider ' + _provider + ' terminated with non-zero exit code.');
}
}
if(settings.postConfig) {
if(shelljs.exec(settings.postConfig).code !== 0) {
console.error('ERROR: failed to run postConfig script: \'' + settings.postConfig + '\'.');
}
}
go();
}

Expand Down Expand Up @@ -284,7 +341,7 @@ function go() {
});
});

fs.writeFileSync(settings.conf + '/mechanic.conf', output);
if(!debug) fs.writeFileSync(settings.conf + '/mechanic.conf', output);

if (settings.restart !== false) {
var restart = settings.restart || 'service nginx reload';
Expand Down