add PRIVATE_KEY_PATH env var support; closes #122

The loading preference is:

1. CLI flag
2. `PRIVATE_KEY`
3. `PRIVATE_KEY_PATH`
4. Find a `.pem` file in the current working dir

Changes:

- break out PK loading logic into module `lib/private-key.js`
- add more info to error messaging if probot fails to find a PK
- add tests
- add note to deployment docs
This commit is contained in:
Christopher Hiller 2017-04-24 23:02:08 -07:00
parent f639dfc30d
commit 0f14bf3499
4 changed files with 153 additions and 21 deletions

View File

@ -3,20 +3,14 @@
require('dotenv').config();
const program = require('commander');
const {findPrivateKey} = require('../lib/private-key');
program
.usage('[options] <plugins...>')
.option('-i, --integration <id>', 'ID of the GitHub Integration', process.env.INTEGRATION_ID)
.option('-s, --secret <secret>', 'Webhook secret of the GitHub Integration', process.env.WEBHOOK_SECRET || 'development')
.option('-p, --port <n>', 'Port to start the server on', process.env.PORT || 3000)
.option('-P, --private-key <file>', 'Path to certificate of the GitHub Integration', path => {
try {
return require('fs').readFileSync(path);
} catch (err) {
console.warn(err.message);
process.exit(1);
}
}, process.env.PRIVATE_KEY)
.option('-P, --private-key <file>', 'Path to certificate of the GitHub Integration', findPrivateKey)
.option('-t, --tunnel <subdomain>', 'Expose your local bot to the internet', process.env.SUBDOMAIN || process.env.NODE_ENV != 'production')
.parse(process.argv);
@ -25,18 +19,6 @@ if(!program.integration) {
program.help();
}
if(!program.privateKey) {
const fs = require('fs');
const key = fs.readdirSync(process.cwd()).find(path => path.endsWith('.pem'));
if (key) {
program.privateKey = fs.readFileSync(key);
} else {
console.warn("Missing GitHub Integration private key.\nUse --private-key flag or set PRIVATE_KEY environment variable.");
process.exit(1);
}
}
if(program.tunnel) {
try {
setupTunnel();

View File

@ -32,7 +32,13 @@ To deploy a plugin to any cloud provider, you will need 3 environment variables:
- `INTEGRATION_ID`: the ID of the integration, which you can get from the [integration settings page](https://github.com/settings/integrations).
- `WEBHOOK_SECRET`: the **Webhook Secret** that you generated when you created the integration.
- `PRIVATE_KEY`: the contents of the private key you downloaded after creating the integration.
And one of:
- `PRIVATE_KEY`: the contents of the private key you downloaded after creating the integration, OR...
- `PRIVATE_KEY_PATH`: the path to a private key file.
`PRIVATE_KEY` takes precedence over `PRIVATE_KEY_PATH`.
### Heroku

37
lib/private-key.js Normal file
View File

@ -0,0 +1,37 @@
const fs = require('fs');
/**
* Finds a private key through various user-(un)specified methods.
* Order of precedence:
* 1. Explicit path (CLI option)
* 2. `PRIVATE_KEY` env var
* 3. `PRIVATE_KEY_PATH` env var
* 4. Any file w/ `.pem` extension in current working dir
* @param {string} [filepath] - Explicit, user-defined path to keyfile
* @returns {string} Private key
*/
function findPrivateKey(filepath) {
if (filepath) {
return fs.readFileSync(filepath);
}
if (process.env.PRIVATE_KEY) {
return process.env.PRIVATE_KEY;
}
if (process.env.PRIVATE_KEY_PATH) {
return fs.readFileSync(process.env.PRIVATE_KEY_PATH);
}
const foundPath = fs.readdirSync(process.cwd())
.find(path => path.endsWith('.pem'));
if (foundPath) {
return findPrivateKey(foundPath);
}
throw new Error(`Missing private key for GitHub Integration. Please use:
* \`--private-key=/path/to/private-key\` flag, or
* \`PRIVATE_KEY\` environment variable, or
* \`PRIVATE_KEY_PATH\` environment variable
`);
}
module.exports = {
findPrivateKey
};

107
test/private-key.js Normal file
View File

@ -0,0 +1,107 @@
const fs = require('fs');
const expect = require('expect');
const {findPrivateKey} = require('../lib/private-key');
describe('private-key', function () {
let privateKey;
let keyfilePath;
beforeEach(function () {
privateKey = 'I AM PRIVET KEY!?!!~1!';
keyfilePath = '/some/path';
expect.spyOn(fs, 'readFileSync')
.andReturn(privateKey);
});
afterEach(function () {
expect.restoreSpies();
});
describe('findPrivateKey()', function () {
describe('when a filepath is provided', function () {
it('should read the file at given filepath', function () {
findPrivateKey(keyfilePath);
expect(fs.readFileSync)
.toHaveBeenCalledWith(keyfilePath);
});
it('should return the key', function () {
expect(findPrivateKey(keyfilePath))
.toEqual(privateKey);
});
});
describe('when a PRIVATE_KEY env var is provided', function () {
beforeEach(function () {
process.env.PRIVATE_KEY = privateKey;
});
afterEach(function () {
delete process.env.PRIVATE_KEY;
});
it('should return the key', function () {
expect(findPrivateKey())
.toEqual(privateKey);
});
});
describe('when a PRIVATE_KEY_PATH env var is provided', function () {
beforeEach(function () {
process.env.PRIVATE_KEY_PATH = keyfilePath;
});
afterEach(function () {
delete process.env.PRIVATE_KEY_PATH;
});
it('should read the file at given filepath', function () {
findPrivateKey();
expect(fs.readFileSync)
.toHaveBeenCalledWith(keyfilePath);
});
it('should return the key', function () {
expect(findPrivateKey())
.toEqual(privateKey);
});
});
describe('when no private key is provided', function () {
beforeEach(function () {
expect.spyOn(fs, 'readdirSync')
.andReturn([
'foo.txt',
'foo.pem'
]);
});
it('should look for one in the current directory', function () {
findPrivateKey();
expect(fs.readdirSync)
.toHaveBeenCalledWith(process.cwd());
});
describe('and a key file is present', function () {
it('should load the key file', function () {
findPrivateKey();
expect(fs.readFileSync)
.toHaveBeenCalledWith('foo.pem');
});
});
describe('and a key file is not present', function () {
beforeEach(function () {
fs.readdirSync.restore();
});
it('should throw an error', function () {
expect(findPrivateKey)
.toThrow(Error, /missing private key for github integrationy/i);
});
});
});
});
});