From 9d807e265b67eb4c4abb3d0ad31a154140bb4b2f Mon Sep 17 00:00:00 2001 From: Supratik Das <30755453+supra08@users.noreply.github.com> Date: Tue, 4 Aug 2020 23:35:35 +0530 Subject: [PATCH] feat(1079): Add key pair generation method and push public key to scm [4] (#163) * feat(scm-github): Add key pair generation method and push pub key to scm * feat(scm-github): Add key pair generation method and push pub key to scm * feat(): add accessor function for checking autoDeployKeyGeneration * feat(scm-github): add tests for addDeployKey and checkAutoDeployKeyGeneration * refactor(): change function names and localize constants * refactor(): use object destructing in config and use maps --- index.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- test/index.test.js | 48 ++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e0ea66a..c1f4029 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ const { verify } = require('@octokit/webhooks'); const hoek = require('hoek'); const Path = require('path'); const joi = require('joi'); +const keygen = require('ssh-keygen'); const schema = require('screwdriver-data-schema'); const CHECKOUT_URL_REGEX = schema.config.regex.CHECKOUT_URL; const Scm = require('screwdriver-scm-base'); @@ -43,6 +44,13 @@ const PERMITTED_RELEASE_EVENT = [ 'published' ]; +const DEPLOY_KEY_GENERATOR_CONFIG = { + DEPLOY_KEYS_FILE: `${__dirname}/keys_rsa`, + DEPLOY_KEYS_FORMAT: 'PEM', + DEPLOY_KEYS_PASSWORD: '', + DEPLOY_KEY_TITLE: 'sd@screwdriver.cd' +}; + /** * Get repo information * @method getInfo @@ -124,6 +132,7 @@ class GithubScm extends Scm { username: joi.string().optional().default('sd-buildbot'), email: joi.string().optional().default('dev-null@screwdriver.cd'), commentUserToken: joi.string().optional().description('Token for PR comments'), + autoDeployKeyGeneration: joi.boolean().optional().default(false), https: joi.boolean().optional().default(false), oauthClientId: joi.string().required(), oauthClientSecret: joi.string().required(), @@ -299,6 +308,79 @@ class GithubScm extends Scm { } } + /** + * Generate a deploy private and public key pair + * @async generateDeployKey + * @return {Promise} Resolves to object containing the public and private key pair + */ + async generateDeployKey() { + return new Promise((resolve, reject) => { + const location = DEPLOY_KEY_GENERATOR_CONFIG.DEPLOY_KEYS_FILE; + const comment = this.config.email; + const password = DEPLOY_KEY_GENERATOR_CONFIG.DEPLOY_KEYS_PASSWORD; + const format = DEPLOY_KEY_GENERATOR_CONFIG.DEPLOY_KEYS_FORMAT; + + keygen({ + location, + comment, + password, + read: true, + format + }, (err, keyPair) => { + if (err) { + logger.error('Failed to create keys: ', err); + + return reject(err); + } + + return resolve(keyPair); + }); + }); + } + + /** + * Adds deploy public key to the github repo and returns the private key + * @async _addDeployKey + * @param {Object} config + * @param {Object} config.token Admin token for repo + * @param {String} config.checkoutUrl The checkoutUrl to parse + * @return {Promise} Resolves to the private key string + */ + async _addDeployKey(config) { + const { token, checkoutUrl } = config; + const { owner, repo } = getInfo(checkoutUrl); + const { pubKey, key } = await this.generateDeployKey(); + + try { + await this.breaker.runCommand({ + action: 'createDeployKey', + scopeType: 'repos', + token, + params: { + owner, + repo, + title: DEPLOY_KEY_GENERATOR_CONFIG.DEPLOY_KEY_TITLE, + key: pubKey, + read_only: true + } + }); + + return key; + } catch (err) { + logger.error('Failed to add token: ', err); + throw err; + } + } + + /** + * Returns whether auto deploy key generation is enabled or not + * @async _autoDeployKeyGenerationEnabled + * @return {Boolean} Resolves to the private key string + */ + async _autoDeployKeyGenerationEnabled() { + return this.config.autoDeployKeyGeneration; + } + /** * Look up a webhook from a repo * @async _findWebhook diff --git a/package.json b/package.json index 35dbd44..be17c54 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,9 @@ "hoek": "^6.1.2", "joi": "^13.7.0", "screwdriver-data-schema": "^19.1.1", + "screwdriver-logger": "^1.0.0", "screwdriver-scm-base": "^6.0.0", - "screwdriver-logger": "^1.0.0" + "ssh-keygen": "^0.5.0" }, "release": { "debug": false, diff --git a/test/index.test.js b/test/index.test.js index 6fef2af..8a3c89a 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -72,7 +72,8 @@ describe('index', function () { getCommitRefSha: sinon.stub(), getContents: sinon.stub(), listHooks: sinon.stub(), - listBranches: sinon.stub() + listBranches: sinon.stub(), + createDeployKey: sinon.stub() }, users: { getByUsername: sinon.stub() @@ -1916,6 +1917,51 @@ jobs: }); }); + describe('generateDeployKey', () => { + it('returns a public and private key pair object', () => { + scm.generateDeployKey().then((keys) => { + assert.isObject(keys); + assert.property(keys, 'pubKey'); + assert.property(keys, 'key'); + }); + }); + }); + + describe('addDeployKey', () => { + const addDepKeyConfig = { + checkoutUrl: 'git@github.com:baxterthehacker/public-repo.git', + token: 'token' + }; + const pubKey = 'public_key'; + const privKey = 'private_Key'; + let generateDeployKeyStub; + + beforeEach(() => { + generateDeployKeyStub = sinon.stub(scm, 'generateDeployKey'); + }); + + afterEach(() => { + generateDeployKeyStub.restore(); + }); + + it('returns a private key', async () => { + generateDeployKeyStub.returns(Promise.resolve({ pubKey, key: privKey })); + githubMock.repos.createDeployKey.resolves({ data: pubKey }); + const privateKey = await scm.addDeployKey(addDepKeyConfig); + + assert.isString(privateKey); + assert.deepEqual(privateKey, privKey); + }); + }); + + describe('autoDeployKeyGenerationEnabled', () => { + it('returns a boolean check', () => + scm.autoDeployKeyGenerationEnabled().then((check) => { + assert.isBoolean(check); + }) + ); + }); + describe('getBellConfiguration', () => { it('returns a default configuration', () => ( scm.getBellConfiguration().then((config) => {