Skip to content

Commit 135d1ae

Browse files
committed
feat: add the ability to define a custom ca certificate for docker images
1 parent 6d26ee8 commit 135d1ae

File tree

6 files changed

+90
-4
lines changed

6 files changed

+90
-4
lines changed

src/argv.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export class Argv {
152152
return typeof val == "string" ? val.split(" ") : val;
153153
}
154154

155+
get caFile (): string | null {
156+
return this.map.get("caFile") ?? null;
157+
}
158+
155159
get ignoreSchemaPaths (): string[] {
156160
return this.map.get("ignoreSchemaPaths") ?? Argv.default.ignoreSchemaPaths;
157161
}

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ process.on("SIGUSR2", async () => await cleanupJobResources(jobs));
247247
description: "Add extra docker host entries",
248248
requiresArg: false,
249249
})
250+
.option("ca-file", {
251+
type: "string",
252+
description: "Path to custom CA certificate file to mount in containers",
253+
requiresArg: false,
254+
})
250255
.option("pull-policy", {
251256
type: "string",
252257
description: "Set image pull-policy (always or if-not-present)",

src/job.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {resolveIncludeLocal, validateIncludeLocal} from "./parser-includes.js";
1919
import globby from "globby";
2020
import terminalLink from "terminal-link";
2121
import * as crypto from "crypto";
22+
import * as path from "path";
2223

2324
const GCL_SHELL_PROMPT_PLACEHOLDER = "<gclShellPromptPlaceholder>";
2425
interface JobOptions {
@@ -687,10 +688,23 @@ export class Job {
687688
if (helperImageName) {
688689
await this.pullImage(writeStreams, helperImageName);
689690
}
690-
const {stdout: containerId} = await Utils.spawn([
691-
this.argv.containerExecutable, "create", "--user=0:0", `--volume=${buildVolumeName}:${this.ciProjectDir}`, `--volume=${tmpVolumeName}:${this.fileVariablesDir}`, `${helperImageName}`,
692-
...["sh", "-c", `chown ${chownOpt} -R ${this.ciProjectDir} && chmod ${chmodOpt} -R ${this.ciProjectDir} && chown ${chownOpt} -R /tmp/ && chmod ${chmodOpt} -R /tmp/`],
693-
], argv.cwd);
691+
692+
const helperContainerArgs = [
693+
this.argv.containerExecutable, "create", "--user=0:0",
694+
`--volume=${buildVolumeName}:${this.ciProjectDir}`,
695+
`--volume=${tmpVolumeName}:${this.fileVariablesDir}`,
696+
];
697+
698+
if (this.argv.caFile) {
699+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
700+
if (await fs.pathExists(caFilePath)) {
701+
helperContainerArgs.push(`--volume=${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro`);
702+
}
703+
}
704+
705+
helperContainerArgs.push(`${helperImageName}`, "sh", "-c", `chown ${chownOpt} -R ${this.ciProjectDir} && chmod ${chmodOpt} -R ${this.ciProjectDir} && chown ${chownOpt} -R /tmp/ && chmod ${chmodOpt} -R /tmp/`);
706+
707+
const {stdout: containerId} = await Utils.spawn(helperContainerArgs, argv.cwd);
694708
this._containersToClean.push(containerId);
695709
if (await fs.pathExists(fileVariablesDir)) {
696710
await Utils.spawn([this.argv.containerExecutable, "cp", `${fileVariablesDir}/.`, `${containerId}:${fileVariablesDir}`], argv.cwd);
@@ -966,6 +980,17 @@ export class Job {
966980
dockerCmd += `--add-host=${extraHost} `;
967981
}
968982

983+
if (this.argv.caFile) {
984+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
985+
if (await fs.pathExists(caFilePath)) {
986+
dockerCmd += `--volume ${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro `;
987+
expanded["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt";
988+
expanded["SSL_CERT_DIR"] = "/etc/ssl/certs";
989+
} else {
990+
writeStreams.stderr(chalk`{yellow WARNING: CA file not found: ${caFilePath}}\n`);
991+
}
992+
}
993+
969994
for (const [key, val] of Object.entries(expanded)) {
970995
// Replacing `'` with `'\''` to correctly handle single quotes(if `val` contains `'`) in shell commands
971996
dockerCmd += ` -e '${key}=${val.toString().replace(/'/g, "'\\''")}' \\\n`;
@@ -1491,6 +1516,15 @@ export class Job {
14911516
dockerCmd += `--add-host=${extraHost} `;
14921517
}
14931518

1519+
if (this.argv.caFile) {
1520+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
1521+
if (await fs.pathExists(caFilePath)) {
1522+
dockerCmd += `--volume ${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro `;
1523+
expanded["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt";
1524+
expanded["SSL_CERT_DIR"] = "/etc/ssl/certs";
1525+
}
1526+
}
1527+
14941528
const serviceAlias = service.alias;
14951529
const serviceName = service.name;
14961530
const serviceNameWithoutVersion = serviceName.replace(/(.*)(:.*)/, "$1");
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
test-ca-cert:
3+
image: alpine:latest
4+
script:
5+
- echo "Testing CA certificate mounting"
6+
- test -f /etc/ssl/certs/ca-certificates.crt && echo "CA cert file exists" || echo "CA cert file NOT found"
7+
- echo "SSL_CERT_FILE=$SSL_CERT_FILE"
8+
- echo "SSL_CERT_DIR=$SSL_CERT_DIR"
9+
- cat /etc/ssl/certs/ca-certificates.crt | head -3
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDXTCCAkWgAwIBAgIJAKJ3L7jLvE1JMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
3+
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
4+
aWRnaXRzIFB0eSBMdGQwHhcNMTcwMjIyMjIxNjA2WhcNMjcwMjIwMjIxNjA2WjBF
5+
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
6+
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
7+
CgKCAQEAwU3oGrJYYV6Y3A4TmLLqCYxPyGlqLuO/U7VB3P8TqLKfwYEGv9e5cj6P
8+
-----END CERTIFICATE-----
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {WriteStreamsMock} from "../../../src/write-streams.js";
2+
import {handler} from "../../../src/handler.js";
3+
import chalk from "chalk";
4+
import {initSpawnSpy} from "../../mocks/utils.mock.js";
5+
import {WhenStatics} from "../../mocks/when-statics.js";
6+
7+
beforeAll(() => {
8+
initSpawnSpy(WhenStatics.all);
9+
});
10+
11+
test("custom-ca-cert <test-ca-cert>", async () => {
12+
const writeStreams = new WriteStreamsMock();
13+
await handler({
14+
cwd: "tests/test-cases/custom-ca-cert",
15+
job: ["test-ca-cert"],
16+
caFile: "ca-cert.crt",
17+
}, writeStreams);
18+
19+
const expected = [
20+
chalk`{blueBright test-ca-cert} {greenBright >} CA cert file exists`,
21+
chalk`{blueBright test-ca-cert} {greenBright >} SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt`,
22+
chalk`{blueBright test-ca-cert} {greenBright >} SSL_CERT_DIR=/etc/ssl/certs`,
23+
chalk`{blueBright test-ca-cert} {greenBright >} -----BEGIN CERTIFICATE-----`,
24+
];
25+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
26+
});

0 commit comments

Comments
 (0)