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

feat: use SSM Parameter Store to store secrets #2

Merged
merged 1 commit into from
Apr 28, 2021
Merged
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
24 changes: 0 additions & 24 deletions modules/runners/encrypt.tf

This file was deleted.

16 changes: 8 additions & 8 deletions modules/runners/lambdas/runners/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { scaleUp } from './scale-runners/scale-up';
import { scaleDown } from './scale-runners/scale-down';
import { scaleUp as scaleUp_ } from './scale-runners/scale-up';
import { scaleDown as scaleDown_ } from './scale-runners/scale-down';
import { SQSEvent, ScheduledEvent, Context } from 'aws-lambda';

module.exports.scaleUp = async (event: SQSEvent, context: Context, callback: any) => {
export async function scaleUp(event: SQSEvent, context: Context, callback: any) {
console.dir(event, { depth: 5 });
try {
for (const e of event.Records) {
await scaleUp(e.eventSource, JSON.parse(e.body));
await scaleUp_(e.eventSource, JSON.parse(e.body));
}
return callback(null);
} catch (e) {
console.error(e);
return callback('Failed handling SQS event');
}
};
}

module.exports.scaleDown = async (event: ScheduledEvent, context: Context, callback: any) => {
export async function scaleDown(event: ScheduledEvent, context: Context, callback: any) {
try {
scaleDown();
scaleDown_();
return callback(null);
} catch (e) {
console.error(e);
return callback('Failed');
}
};
}
106 changes: 63 additions & 43 deletions modules/runners/lambdas/runners/src/scale-runners/gh-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { createOctoClient, createGithubAuth } from './gh-auth';
import nock from 'nock';
import { createAppAuth } from '@octokit/auth-app';
import { StrategyOptions } from '@octokit/auth-app/dist-types/types';
import { decrypt } from './kms';
import SSM from './ssm';
import { RequestInterface } from '@octokit/types';
import { mock, MockProxy } from 'jest-mock-extended';
import { request } from '@octokit/request';

jest.mock('./kms');
jest.mock('@octokit/auth-app');
jest.mock('./ssm');

const cleanEnv = process.env;

Expand All @@ -19,7 +19,7 @@ beforeEach(() => {
nock.disableNetConnect();
});

describe('Test createGithubAuth', () => {
describe('Test createOctoClient', () => {
test('Creates app client to GitHub public', async () => {
// Arrange
const token = '123456';
Expand All @@ -46,56 +46,67 @@ describe('Test createGithubAuth', () => {
});

describe('Test createGithubAuth', () => {
const mockedDecrypt = (decrypt as unknown) as jest.Mock;
const mockedSSM = SSM as jest.MockedClass<typeof SSM>;
const mockedCreatAppAuth = (createAppAuth as unknown) as jest.Mock;
const mockedDefaults = jest.spyOn(request, 'defaults');
let mockedRequestInterface: MockProxy<RequestInterface>;

const installationId = 1;
const authType = 'app';
const token = '123456';
const decryptedValue = 'decryptedValue';
const b64 = Buffer.from(decryptedValue, 'binary').toString('base64');
const privateKey = 'my-private-key';
const privateKeyBase64 = Buffer.from(privateKey, 'binary').toString('base64');
const appId = '123';
const clientId = 'abc';
const clientSecret = 'abcdef123456';

beforeEach(() => {
process.env.GITHUB_APP_ID = '1';
process.env.GITHUB_APP_CLIENT_SECRET = 'client_secret';
process.env.GITHUB_APP_KEY_BASE64 = 'base64';
process.env.KMS_KEY_ID = 'key_id';
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME = 'private-key';
process.env.GITHUB_APP_ID_PARAMETER_NAME = 'app-id';
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME = 'client-id';
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = 'client-secret';
process.env.ENVIRONMENT = 'dev';
process.env.GITHUB_APP_CLIENT_ID = '1';
});

test('Creates auth object for public GitHub', async () => {
// Arrange
const authOptions = {
appId: parseInt(process.env.GITHUB_APP_ID as string),
privateKey: 'decryptedValue',
appId: parseInt(appId),
privateKey,
installationId,
clientId: process.env.GITHUB_APP_CLIENT_ID,
clientSecret: 'decryptedValue',
clientId,
clientSecret,
};

mockedDecrypt.mockResolvedValueOnce(decryptedValue).mockResolvedValueOnce(b64);
const mockedGetParameter = jest.fn()
.mockResolvedValueOnce(privateKeyBase64)
.mockResolvedValueOnce(appId)
.mockResolvedValueOnce(clientId)
.mockResolvedValueOnce(clientSecret);
mockedSSM.mockImplementation(() => ({
ssm: null as any,
getParameter: mockedGetParameter,
}));

const mockedAuth = jest.fn();
mockedAuth.mockResolvedValue({ token });
mockedCreatAppAuth.mockImplementation((authOptions: StrategyOptions) => {
return mockedAuth;
});
mockedCreatAppAuth.mockImplementation(() => mockedAuth);

// Act
const result = await createGithubAuth(installationId, authType);

// Assert
expect(mockedDecrypt).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_SECRET,
process.env.KMS_KEY_ID,
process.env.ENVIRONMENT,
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME
);
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_ID_PARAMETER_NAME
);
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME
);
expect(mockedDecrypt).toBeCalledWith(
process.env.GITHUB_APP_KEY_BASE64,
process.env.KMS_KEY_ID,
process.env.ENVIRONMENT,
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME
);
expect(mockedCreatAppAuth).toBeCalledTimes(1);
expect(mockedCreatAppAuth).toBeCalledWith(authOptions);
Expand All @@ -113,34 +124,43 @@ describe('Test createGithubAuth', () => {
});

const authOptions = {
appId: parseInt(process.env.GITHUB_APP_ID as string),
privateKey: 'decryptedValue',
appId: parseInt(appId),
privateKey,
installationId,
clientId: process.env.GITHUB_APP_CLIENT_ID,
clientSecret: 'decryptedValue',
clientId,
clientSecret,
request: mockedRequestInterface.defaults({ baseUrl: githubServerUrl }),
};

mockedDecrypt.mockResolvedValueOnce(decryptedValue).mockResolvedValueOnce(b64);
const mockedGetParameter = jest.fn()
.mockResolvedValueOnce(privateKeyBase64)
.mockResolvedValueOnce(appId)
.mockResolvedValueOnce(clientId)
.mockResolvedValueOnce(clientSecret);
mockedSSM.mockImplementation(() => ({
ssm: null as any,
getParameter: mockedGetParameter,
}));

const mockedAuth = jest.fn();
mockedAuth.mockResolvedValue({ token });
mockedCreatAppAuth.mockImplementation((authOptions: StrategyOptions) => {
return mockedAuth;
});
mockedCreatAppAuth.mockImplementation(() => mockedAuth);

// Act
const result = await createGithubAuth(installationId, authType, githubServerUrl);

// Assert
expect(mockedDecrypt).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_SECRET,
process.env.KMS_KEY_ID,
process.env.ENVIRONMENT,
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME
);
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_ID_PARAMETER_NAME
);
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME
);
expect(mockedDecrypt).toBeCalledWith(
process.env.GITHUB_APP_KEY_BASE64,
process.env.KMS_KEY_ID,
process.env.ENVIRONMENT,
expect(mockedGetParameter).toBeCalledWith(
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME
);
expect(mockedCreatAppAuth).toBeCalledTimes(1);
expect(mockedCreatAppAuth).toBeCalledWith(authOptions);
Expand Down
33 changes: 18 additions & 15 deletions modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { request } from '@octokit/request';
import { createAppAuth } from '@octokit/auth-app';
import { Authentication, StrategyOptions } from '@octokit/auth-app/dist-types/types';
import { OctokitOptions } from '@octokit/core/dist-types/types';
import { decrypt } from './kms';
import SSM from './ssm';

export async function createOctoClient(token: string, ghesApiUrl = ''): Promise<Octokit> {
const ocktokitOptions: OctokitOptions = {
Expand All @@ -21,25 +21,28 @@ export async function createGithubAuth(
authType: 'app' | 'installation',
ghesApiUrl = '',
): Promise<Authentication> {
const clientSecret = await decrypt(
process.env.GITHUB_APP_CLIENT_SECRET as string,
process.env.KMS_KEY_ID as string,
process.env.ENVIRONMENT as string,
const ssm = new SSM();
const privateKeyBase64 = await ssm.getParameter(
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME as string
);
const privateKeyBase64 = await decrypt(
process.env.GITHUB_APP_KEY_BASE64 as string,
process.env.KMS_KEY_ID as string,
process.env.ENVIRONMENT as string,
const appIdString = await ssm.getParameter(
process.env.GITHUB_APP_ID_PARAMETER_NAME as string
);

if (clientSecret === undefined || privateKeyBase64 === undefined) {
throw Error('Cannot decrypt.');
const clientId = await ssm.getParameter(
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME as string
);
const clientSecret = await ssm.getParameter(
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME as string
);
if (privateKeyBase64 === undefined
|| appIdString === undefined
|| clientId === undefined
|| clientSecret === undefined) {
throw Error('Cannot decrypt GitHub App parameters.');
}

const privateKey = Buffer.from(privateKeyBase64, 'base64').toString();

const appId: number = parseInt(process.env.GITHUB_APP_ID as string);
const clientId = process.env.GITHUB_APP_CLIENT_ID as string;
const appId = parseInt(appIdString);

const authOptions: StrategyOptions = {
appId,
Expand Down
25 changes: 0 additions & 25 deletions modules/runners/lambdas/runners/src/scale-runners/kms/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jest.mock('@octokit/rest', () => ({

jest.mock('./runners');

jest.mock('./ssm', () => jest.fn().mockImplementation(() => ({
getParameter: jest.fn().mockImplementation(() => ''),
})));

export interface TestData {
repositoryName: string;
repositoryOwner: string;
Expand Down Expand Up @@ -105,10 +109,10 @@ const DEFAULT_REGISTERED_RUNNERS = [

describe('scaleDown', () => {
beforeEach(() => {
process.env.GITHUB_APP_KEY_BASE64 = 'TEST_CERTIFICATE_DATA';
process.env.GITHUB_APP_ID = '1337';
process.env.GITHUB_APP_CLIENT_ID = 'TEST_CLIENT_ID';
process.env.GITHUB_APP_CLIENT_SECRET = 'TEST_CLIENT_SECRET';
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME = 'private-key';
process.env.GITHUB_APP_ID_PARAMETER_NAME = 'app-id';
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME = 'client-id';
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = 'client-secret';
process.env.RUNNERS_MAXIMUM_COUNT = '3';
process.env.ENVIRONMENT = environment;
process.env.MINIMUM_RUNNING_TIME_IN_MINUTES = minimumRunningTimeInMinutes.toString();
Expand Down Expand Up @@ -319,10 +323,10 @@ describe('scaleDown', () => {

describe('scaleDown ghes', () => {
beforeEach(() => {
process.env.GITHUB_APP_KEY_BASE64 = 'TEST_CERTIFICATE_DATA';
process.env.GITHUB_APP_ID = '1337';
process.env.GITHUB_APP_CLIENT_ID = 'TEST_CLIENT_ID';
process.env.GITHUB_APP_CLIENT_SECRET = 'TEST_CLIENT_SECRET';
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME = 'private-key';
process.env.GITHUB_APP_ID_PARAMETER_NAME = 'app-id';
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME = 'client-id';
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = 'client-secret';
process.env.RUNNERS_MAXIMUM_COUNT = '3';
process.env.ENVIRONMENT = environment;
process.env.MINIMUM_RUNNING_TIME_IN_MINUTES = minimumRunningTimeInMinutes.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jest.mock('@octokit/rest', () => ({

jest.mock('./runners');

jest.mock('./ssm', () => jest.fn().mockImplementation(() => ({
getParameter: jest.fn().mockImplementation(() => ''),
})));

const TEST_DATA: ActionRequestMessage = {
id: 1,
eventType: 'check_run',
Expand All @@ -47,10 +51,10 @@ beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
process.env = { ...cleanEnv };
process.env.GITHUB_APP_KEY_BASE64 = 'TEST_CERTIFICATE_DATA';
process.env.GITHUB_APP_ID = '1337';
process.env.GITHUB_APP_CLIENT_ID = 'TEST_CLIENT_ID';
process.env.GITHUB_APP_CLIENT_SECRET = 'TEST_CLIENT_SECRET';
process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME = 'private-key';
process.env.GITHUB_APP_ID_PARAMETER_NAME = 'app-id';
process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME = 'client-id';
process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = 'client-secret';
process.env.RUNNERS_MAXIMUM_COUNT = '3';
process.env.ENVIRONMENT = 'unit-test-environment';

Expand Down
Loading