Skip to content

Instantly share code, notes, and snippets.

@lukemelia
Created June 3, 2016 03:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save lukemelia/bf727fb4f28811c115023e0aa0368021 to your computer and use it in GitHub Desktop.
Save lukemelia/bf727fb4f28811c115023e0aa0368021 to your computer and use it in GitHub Desktop.
letsencrypt with node and heroku
var LE = require('letsencrypt');
var pem = require('pem');
var RSVP = require('rsvp');
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var domains, herokuAppName, duplicate;
if (process.env.YAPP_ENV === 'qa') {
domains = ['heroku.yappqa.us', 'my.yappqa.us', 'api.yappqa.us', 'support.yappqa.us' ];
herokuAppName = 'qa-yapp-cedar';
duplicate = true; // create new cert even if they are less than 1 week old
} else if (process.env.YAPP_ENV === 'prod') {
domains = ['heroku.yapp.us', 'my.yapp.us', 'api.yapp.us', 'support.yapp.us'];
herokuAppName = 'yapp-cedar';
duplicate = false;
} else {
throw new Error('Must provide valid YAPP_ENV (qa or prod)');
}
var redisUrlExtraction = new RSVP.Promise(function(resolve, reject){
var exec = require('child_process').exec;
exec('heroku config:get REDISTOGO_URL --app ' + herokuAppName, function (error, stdout, stderr) {
var redisUrl = stdout.replace(/\n/, '').replace(/\/\/redistogo:/, '//:');
if (error) {
reject(error);
} else {
resolve(redisUrl);
}
});
});
var createPrivateKey = RSVP.denodeify(pem.createPrivateKey);
var privateKeyGeneration = createPrivateKey(2048).then(function(privateKey) {
var pemPath = require('homedir')() + '/letsencrypt/etc/live/' + domains[0] + '/privkey.pem';
mkdirp.sync(path.dirname(pemPath));
fs.writeFileSync(pemPath, privateKey.key);
}).catch(function(reason){
console.log(reason);
throw reason;
});
privateKeyGeneration.then(function(){
var config = {
server: LE.productionServerUrl,
manual: true,
configDir: require('homedir')() + '/letsencrypt/etc',
privkeyPath: ':config/live/:hostname/privkey.pem',
fullchainPath: ':config/live/:hostname/fullchain.pem',
certPath: ':config/live/:hostname/cert.pem',
chainPath: ':config/live/:hostname/chain.pem',
debug: true
};
var handlers = {
setChallenge: function (opts, hostname, key, val, cb) {
// called during the ACME server handshake, before validation
redisUrlExtraction.then(function(redisUrl){
var redis = require("redis");
var redisClient = redis.createClient(redisUrl);
redisClient.set("letsencrypt:" + key, val, function(err){
// console.log("set redis key letsencrypt:" + key + " = " + val);
if (err) {
console.log(err);
throw err;
}
cb();
});
}).catch(function(reason){
console.log(reason);
throw reason;
});
},
removeChallenge: function (opts, hostname, key, cb) {} // called after validation on both success and failure
};
var le = LE.create(config, handlers);
le.register({ // either renews or registers
domains: domains,
email: 'tech@yapp.us',
agreeTos: true,
duplicate: true
}, function (err, results) {
if (err) {
console.error('[Error]: node-letsencrypt');
console.error(err.stack || err);
return;
}
if (!results || ('object' !== typeof results)) {
console.error("Error: An unknown error occurred. My best guess is that we got an error that we're not used to from the ACME server and accidentally interpretted it as a success... or forgot to expose the error.");
console.error(results);
err = new Error("Here's a stack trace, in case it helps:");
console.error(err.stack);
return;
}
// if (handlers.closeServers) {
// handlers.closeServers();
// }
console.log('\nCertificate files downloaded! Please run:');
console.log("heroku certs:update " + results.fullchainPath + ' ' + results.privkeyPath + ' --app ' + herokuAppName);
process.exit(0);
});
});
{
"name": "node-ssl-script",
"dependencies": {
"homedir": "^0.6.0",
"letsencrypt": "1.4.4",
"mkdirp": "^0.5.1",
"pem": "^1.8.3",
"redis": "^2.6.1",
"rsvp": "^3.2.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment