From 04e1538ab962e707f54b5abad3bf2abe5f279757 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Mon, 30 Aug 2021 17:55:24 +0200 Subject: [PATCH 1/2] Allow customers to specify custom CA image In some cases a GHES instance might have custom CA certificates set up, this is the case for our dev environments for example. In order to support this, we need the proxy to be made aware of the cert, otherwise it will refuse any requests made to the GHES instance. This will look for a `CUSTOM_CA_PATH` environment variable (which should be defined in the `runsvc.sh` file when running actions as a service), and copy it into the proxy container and update its certificates. --- __tests__/proxy-integration.test.ts | 5 +++++ src/proxy.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/__tests__/proxy-integration.test.ts b/__tests__/proxy-integration.test.ts index 3c673ae..5d03748 100644 --- a/__tests__/proxy-integration.test.ts +++ b/__tests__/proxy-integration.test.ts @@ -53,6 +53,11 @@ describe('ProxyBuilder', () => { const containerInfo = await proxy.container.inspect() expect(containerInfo.Name).toBe('/job-1-proxy') expect(containerInfo.HostConfig.NetworkMode).toBe('job-1-network') + expect(containerInfo.Config.Cmd).toEqual([ + 'sh', + '-c', + '/usr/sbin/update-ca-certificates && /update-job-proxy' + ]) const networkInfo = await proxy.network.inspect() expect(networkInfo.Name).toBe('job-1-network') diff --git a/src/proxy.ts b/src/proxy.ts index fe0a6bf..ea15ff9 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import * as core from '@actions/core' import Docker, {Container, Network} from 'dockerode' import crypto from 'crypto' @@ -15,6 +16,8 @@ const KEY_SIZE = 2048 const KEY_EXPIRY_YEARS = 2 const CONFIG_FILE_PATH = '/' const CONFIG_FILE_NAME = 'config.json' +const CA_CERT_INPUT_PATH = '/usr/local/share/ca-certificates' +const CUSTOM_CA_CERT_NAME = 'custom-ca-cert.crt' const CERT_SUBJECT = [ { name: 'commonName', @@ -74,6 +77,19 @@ export class ProxyBuilder { config ) + if (process.env.CUSTOM_CA_PATH) { + // read the file defined at the CUSTOM_CA_PATH environment variable + const customCert = fs + .readFileSync(process.env.CUSTOM_CA_PATH, 'utf8') + .toString() + await ContainerService.storeCert( + CUSTOM_CA_CERT_NAME, + CA_CERT_INPUT_PATH, + container, + customCert + ) + } + const stream = await container.attach({ stream: true, stdout: true, @@ -161,6 +177,12 @@ export class ProxyBuilder { AttachStdout: true, AttachStderr: true, Env: [`JOB_ID=${jobID}`], + Cmd: [ + 'sh', + '-c', + '/usr/sbin/update-ca-certificates && /update-job-proxy' + ], + HostConfig: { NetworkMode: networkName } From d198c7de074f73513c192a4a108fa809a53bbba7 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Tue, 31 Aug 2021 11:08:21 +0200 Subject: [PATCH 2/2] Test proxy copies in custom certificate --- __tests__/proxy-integration.test.ts | 39 +++++++++++++++++++++++++++++ src/proxy.ts | 2 -- src/updater.ts | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/__tests__/proxy-integration.test.ts b/__tests__/proxy-integration.test.ts index 5d03748..18b6e61 100644 --- a/__tests__/proxy-integration.test.ts +++ b/__tests__/proxy-integration.test.ts @@ -4,6 +4,9 @@ import {ImageService} from '../src/image-service' import {PROXY_IMAGE_NAME} from '../src/main' import {ProxyBuilder} from '../src/proxy' import {removeDanglingUpdaterContainers} from './helpers' +import {spawnSync} from 'child_process' +import fs from 'fs' +import path from 'path' describe('ProxyBuilder', () => { const docker = new Docker() @@ -46,6 +49,7 @@ describe('ProxyBuilder', () => { } const proxy = await builder.run(details, credentials) + await proxy.container.start() expect(proxy.networkName).toBe('job-1-network') expect(proxy.url).toMatch(/^http:\/\/1:.+job-1-proxy:1080$/) @@ -63,6 +67,41 @@ describe('ProxyBuilder', () => { expect(networkInfo.Name).toBe('job-1-network') expect(networkInfo.Internal).toBe(false) + // run a bash command that executes docker and returns contents of /config.json + const id = proxy.container.id + const proc = spawnSync('docker', ['exec', id, 'cat', '/config.json']) + const stdout = proc.stdout.toString() + const config = JSON.parse(stdout) + expect(config.all_credentials).toEqual(credentials) + await proxy.shutdown() }) + + it('copies in a custom root CA if configured', async () => { + if (process.env.SKIP_INTEGRATION_TESTS) { + return + } + + // make a tmp dir at the repo root unless it already exists + const tmpDir = path.join(__dirname, '../tmp') + if (!fs.existsSync(tmpDir)) { + fs.mkdirSync(tmpDir) + } + const certPath = path.join(__dirname, '../tmp/custom-cert.crt') + fs.writeFileSync(certPath, 'ca-pem-contents') + process.env.CUSTOM_CA_PATH = certPath + + const proxy = await builder.run(details, credentials) + await proxy.container.start() + + const id = proxy.container.id + const proc = spawnSync('docker', [ + 'exec', + id, + 'cat', + '/usr/local/share/ca-certificates/custom-ca-cert.crt' + ]) + const stdout = proc.stdout.toString() + expect(stdout).toEqual('ca-pem-contents') + }) }) diff --git a/src/proxy.ts b/src/proxy.ts index ea15ff9..839eaa7 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -78,7 +78,6 @@ export class ProxyBuilder { ) if (process.env.CUSTOM_CA_PATH) { - // read the file defined at the CUSTOM_CA_PATH environment variable const customCert = fs .readFileSync(process.env.CUSTOM_CA_PATH, 'utf8') .toString() @@ -101,7 +100,6 @@ export class ProxyBuilder { errStream(' proxy') ) - container.start() const url = `http://${config.proxy_auth.username}:${config.proxy_auth.password}@${name}:1080` return { container, diff --git a/src/updater.ts b/src/updater.ts index 6bdaa78..924f3bc 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -38,6 +38,7 @@ export class Updater { this.details, this.credentials ) + proxy.container.start() try { const files = await this.runFileFetcher(proxy)