Skip to content

Commit 07edf77

Browse files
authored
feat: add the ability to define a custom ca certificate for docker images (#1695)
1 parent d578047 commit 07edf77

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
@@ -153,6 +153,10 @@ export class Argv {
153153
return typeof val == "string" ? val.split(" ") : val;
154154
}
155155

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

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 {
@@ -694,10 +695,23 @@ If you know what you're doing and would like to suppress this warning, use one o
694695
if (helperImageName) {
695696
await this.pullImage(writeStreams, helperImageName);
696697
}
697-
const {stdout: containerId} = await Utils.spawn([
698-
this.argv.containerExecutable, "create", "--user=0:0", `--volume=${buildVolumeName}:${this.ciProjectDir}`, `--volume=${tmpVolumeName}:${this.fileVariablesDir}`, `${helperImageName}`,
699-
...["sh", "-c", `chown ${chownOpt} -R ${this.ciProjectDir} && chmod ${chmodOpt} -R ${this.ciProjectDir} && chown ${chownOpt} -R /tmp/ && chmod ${chmodOpt} -R /tmp/`],
700-
], argv.cwd);
698+
699+
const helperContainerArgs = [
700+
this.argv.containerExecutable, "create", "--user=0:0",
701+
`--volume=${buildVolumeName}:${this.ciProjectDir}`,
702+
`--volume=${tmpVolumeName}:${this.fileVariablesDir}`,
703+
];
704+
705+
if (this.argv.caFile) {
706+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
707+
if (await fs.pathExists(caFilePath)) {
708+
helperContainerArgs.push(`--volume=${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro`);
709+
}
710+
}
711+
712+
helperContainerArgs.push(`${helperImageName}`, "sh", "-c", `chown ${chownOpt} -R ${this.ciProjectDir} && chmod ${chmodOpt} -R ${this.ciProjectDir} && chown ${chownOpt} -R /tmp/ && chmod ${chmodOpt} -R /tmp/`);
713+
714+
const {stdout: containerId} = await Utils.spawn(helperContainerArgs, argv.cwd);
701715
this._containersToClean.push(containerId);
702716
if (await fs.pathExists(fileVariablesDir)) {
703717
await Utils.spawn([this.argv.containerExecutable, "cp", `${fileVariablesDir}/.`, `${containerId}:${fileVariablesDir}`], argv.cwd);
@@ -973,6 +987,17 @@ If you know what you're doing and would like to suppress this warning, use one o
973987
dockerCmd += `--add-host=${extraHost} `;
974988
}
975989

990+
if (this.argv.caFile) {
991+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
992+
if (await fs.pathExists(caFilePath)) {
993+
dockerCmd += `--volume ${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro `;
994+
expanded["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt";
995+
expanded["SSL_CERT_DIR"] = "/etc/ssl/certs";
996+
} else {
997+
writeStreams.stderr(chalk`{yellow WARNING: CA file not found: ${caFilePath}}\n`);
998+
}
999+
}
1000+
9761001
for (const [key, val] of Object.entries(expanded)) {
9771002
// Replacing `'` with `'\''` to correctly handle single quotes(if `val` contains `'`) in shell commands
9781003
dockerCmd += ` -e '${key}=${val.toString().replace(/'/g, "'\\''")}' \\\n`;
@@ -1498,6 +1523,15 @@ If you know what you're doing and would like to suppress this warning, use one o
14981523
dockerCmd += `--add-host=${extraHost} `;
14991524
}
15001525

1526+
if (this.argv.caFile) {
1527+
const caFilePath = path.isAbsolute(this.argv.caFile) ? this.argv.caFile : path.resolve(this.argv.cwd, this.argv.caFile);
1528+
if (await fs.pathExists(caFilePath)) {
1529+
dockerCmd += `--volume ${caFilePath}:/etc/ssl/certs/ca-certificates.crt:ro `;
1530+
expanded["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt";
1531+
expanded["SSL_CERT_DIR"] = "/etc/ssl/certs";
1532+
}
1533+
}
1534+
15011535
const serviceAlias = service.alias;
15021536
const serviceName = service.name;
15031537
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)