@cyclonedx/cdxgen 12.3.2 → 12.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +141 -39
- package/lib/cli/index.poku.js +579 -1
- package/lib/helpers/agentFormulationParser.js +6 -2
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/analyzer.js +38 -9
- package/lib/helpers/analyzer.poku.js +67 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +45 -22
- package/lib/helpers/display.poku.js +47 -60
- package/lib/helpers/mcpConfigParser.js +21 -5
- package/lib/helpers/mcpConfigParser.poku.js +39 -2
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +951 -40
- package/lib/helpers/utils.poku.js +882 -0
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/auditBom.poku.js +644 -2
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
|
@@ -121,7 +121,11 @@ it("parseImageName tests", () => {
|
|
|
121
121
|
);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
async function loadDockerModule({
|
|
124
|
+
async function loadDockerModule({
|
|
125
|
+
clientResponse,
|
|
126
|
+
fsOverrides,
|
|
127
|
+
utilsOverrides,
|
|
128
|
+
} = {}) {
|
|
125
129
|
const dockerClient = sinon.stub().resolves(
|
|
126
130
|
clientResponse || {
|
|
127
131
|
Id: "sha256:hello-world",
|
|
@@ -129,6 +133,13 @@ async function loadDockerModule({ clientResponse, utilsOverrides } = {}) {
|
|
|
129
133
|
},
|
|
130
134
|
);
|
|
131
135
|
dockerClient.stream = sinon.stub();
|
|
136
|
+
const fsStub = {
|
|
137
|
+
createReadStream: sinon.stub(),
|
|
138
|
+
lstatSync: sinon.stub(),
|
|
139
|
+
readdirSync: sinon.stub().returns([]),
|
|
140
|
+
readFileSync: sinon.stub(),
|
|
141
|
+
...fsOverrides,
|
|
142
|
+
};
|
|
132
143
|
const gotStub = {
|
|
133
144
|
extend: sinon.stub().returns(dockerClient),
|
|
134
145
|
get: sinon.stub().resolves({ body: "OK" }),
|
|
@@ -150,12 +161,107 @@ async function loadDockerModule({ clientResponse, utilsOverrides } = {}) {
|
|
|
150
161
|
...utilsOverrides,
|
|
151
162
|
};
|
|
152
163
|
const dockerModule = await esmock("./docker.js", {
|
|
164
|
+
"node:fs": fsStub,
|
|
153
165
|
got: { default: gotStub },
|
|
154
166
|
"../helpers/utils.js": utilsStub,
|
|
155
167
|
});
|
|
156
|
-
return { dockerClient, dockerModule, gotStub, utilsStub };
|
|
168
|
+
return { dockerClient, dockerModule, fsStub, gotStub, utilsStub };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const decodeRegistryAuthHeader = (header) =>
|
|
172
|
+
JSON.parse(Buffer.from(header, "base64url").toString("utf-8"));
|
|
173
|
+
|
|
174
|
+
const dockerConfigExistsStub = () =>
|
|
175
|
+
sinon.stub().callsFake((filePath) => filePath.endsWith("config.json"));
|
|
176
|
+
|
|
177
|
+
const encodedAuth = Buffer.from("trusted-user:trusted-pass").toString("base64");
|
|
178
|
+
|
|
179
|
+
const authConfigData = (configuredRegistry) =>
|
|
180
|
+
JSON.stringify({
|
|
181
|
+
auths: {
|
|
182
|
+
[configuredRegistry]: {
|
|
183
|
+
auth: encodedAuth,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const credHelperConfigData = (configuredRegistry) =>
|
|
189
|
+
JSON.stringify({
|
|
190
|
+
credHelpers: {
|
|
191
|
+
[configuredRegistry]: "osxkeychain",
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const credHelperExe = (helperSuffix) =>
|
|
196
|
+
isWin
|
|
197
|
+
? `docker-credential-${helperSuffix}.exe`
|
|
198
|
+
: `docker-credential-${helperSuffix}`;
|
|
199
|
+
|
|
200
|
+
async function loadDockerModuleWithAuths(configuredRegistry) {
|
|
201
|
+
return await loadDockerModule({
|
|
202
|
+
fsOverrides: {
|
|
203
|
+
readFileSync: sinon.stub().returns(authConfigData(configuredRegistry)),
|
|
204
|
+
},
|
|
205
|
+
utilsOverrides: {
|
|
206
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function loadDockerModuleWithCredHelpers(
|
|
212
|
+
configuredRegistry,
|
|
213
|
+
safeSpawnSync,
|
|
214
|
+
) {
|
|
215
|
+
return await loadDockerModule({
|
|
216
|
+
fsOverrides: {
|
|
217
|
+
readFileSync: sinon
|
|
218
|
+
.stub()
|
|
219
|
+
.returns(credHelperConfigData(configuredRegistry)),
|
|
220
|
+
},
|
|
221
|
+
utilsOverrides: {
|
|
222
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
223
|
+
safeSpawnSync,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
157
226
|
}
|
|
158
227
|
|
|
228
|
+
const withDockerConfig = async (callback) => {
|
|
229
|
+
const originalDockerConfig = process.env.DOCKER_CONFIG;
|
|
230
|
+
process.env.DOCKER_CONFIG = "/tmp/cdxgen-docker-config";
|
|
231
|
+
try {
|
|
232
|
+
await callback();
|
|
233
|
+
} finally {
|
|
234
|
+
if (originalDockerConfig === undefined) {
|
|
235
|
+
delete process.env.DOCKER_CONFIG;
|
|
236
|
+
} else {
|
|
237
|
+
process.env.DOCKER_CONFIG = originalDockerConfig;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const withEnv = async (updates, callback) => {
|
|
243
|
+
const originalEnv = {};
|
|
244
|
+
for (const envKey of Object.keys(updates)) {
|
|
245
|
+
originalEnv[envKey] = process.env[envKey];
|
|
246
|
+
if (updates[envKey] === undefined) {
|
|
247
|
+
delete process.env[envKey];
|
|
248
|
+
} else {
|
|
249
|
+
process.env[envKey] = updates[envKey];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
await callback();
|
|
254
|
+
} finally {
|
|
255
|
+
for (const envKey of Object.keys(updates)) {
|
|
256
|
+
if (originalEnv[envKey] === undefined) {
|
|
257
|
+
delete process.env[envKey];
|
|
258
|
+
} else {
|
|
259
|
+
process.env[envKey] = originalEnv[envKey];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
159
265
|
await it("docker connection uses the detected daemon client", async () => {
|
|
160
266
|
const { dockerModule, gotStub, dockerClient } = await loadDockerModule();
|
|
161
267
|
const dockerConn = await dockerModule.getConnection();
|
|
@@ -323,6 +429,1040 @@ await it("docker exportImage ignores local directories", async () => {
|
|
|
323
429
|
assert.strictEqual(imageData, undefined);
|
|
324
430
|
});
|
|
325
431
|
|
|
432
|
+
await it("docker makeRequest prefers DOCKER_AUTH_CONFIG over config.json entries for all registries", async () => {
|
|
433
|
+
await withDockerConfig(async () => {
|
|
434
|
+
await withEnv(
|
|
435
|
+
{
|
|
436
|
+
DOCKER_AUTH_CONFIG: "opaque-global-auth-token",
|
|
437
|
+
},
|
|
438
|
+
async () => {
|
|
439
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
440
|
+
status: 0,
|
|
441
|
+
stdout: JSON.stringify({
|
|
442
|
+
username: "helper-user",
|
|
443
|
+
Secret: "helper-pass",
|
|
444
|
+
}),
|
|
445
|
+
stderr: "",
|
|
446
|
+
});
|
|
447
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
448
|
+
fsOverrides: {
|
|
449
|
+
readFileSync: sinon.stub().returns(
|
|
450
|
+
JSON.stringify({
|
|
451
|
+
auths: {
|
|
452
|
+
"registry.example.com": {
|
|
453
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
454
|
+
"base64",
|
|
455
|
+
),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
credHelpers: {
|
|
459
|
+
"registry.example.com": "osxkeychain",
|
|
460
|
+
},
|
|
461
|
+
}),
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
utilsOverrides: {
|
|
465
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
466
|
+
safeSpawnSync,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await dockerModule.makeRequest(
|
|
471
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
472
|
+
"POST",
|
|
473
|
+
"registry.example.com/team/app",
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
477
|
+
assert.strictEqual(
|
|
478
|
+
requestOptions.headers["X-Registry-Auth"],
|
|
479
|
+
"opaque-global-auth-token",
|
|
480
|
+
);
|
|
481
|
+
sinon.assert.notCalled(safeSpawnSync);
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
await it("docker makeRequest prefers DOCKER_USER credentials over matching config.json entries", async () => {
|
|
488
|
+
await withDockerConfig(async () => {
|
|
489
|
+
await withEnv(
|
|
490
|
+
{
|
|
491
|
+
DOCKER_USER: "env-user",
|
|
492
|
+
DOCKER_PASSWORD: "env-pass",
|
|
493
|
+
DOCKER_EMAIL: "env@example.com",
|
|
494
|
+
},
|
|
495
|
+
async () => {
|
|
496
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
497
|
+
fsOverrides: {
|
|
498
|
+
readFileSync: sinon.stub().returns(
|
|
499
|
+
JSON.stringify({
|
|
500
|
+
auths: {
|
|
501
|
+
"registry.example.com": {
|
|
502
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
503
|
+
"base64",
|
|
504
|
+
),
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
}),
|
|
508
|
+
),
|
|
509
|
+
},
|
|
510
|
+
utilsOverrides: {
|
|
511
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await dockerModule.makeRequest(
|
|
516
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
517
|
+
"POST",
|
|
518
|
+
"registry.example.com/team/app",
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const registryAuthHeader =
|
|
522
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
523
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
524
|
+
username: "env-user",
|
|
525
|
+
password: "env-pass",
|
|
526
|
+
email: "env@example.com",
|
|
527
|
+
serveraddress: "registry.example.com",
|
|
528
|
+
});
|
|
529
|
+
},
|
|
530
|
+
);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
await it("docker makeRequest applies DOCKER_USER credentials regardless of configured registry entries", async () => {
|
|
535
|
+
await withDockerConfig(async () => {
|
|
536
|
+
await withEnv(
|
|
537
|
+
{
|
|
538
|
+
DOCKER_USER: "env-user",
|
|
539
|
+
DOCKER_PASSWORD: "env-pass",
|
|
540
|
+
DOCKER_EMAIL: "env@example.com",
|
|
541
|
+
},
|
|
542
|
+
async () => {
|
|
543
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
544
|
+
status: 0,
|
|
545
|
+
stdout: JSON.stringify({
|
|
546
|
+
username: "helper-user",
|
|
547
|
+
Secret: "helper-pass",
|
|
548
|
+
}),
|
|
549
|
+
stderr: "",
|
|
550
|
+
});
|
|
551
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
552
|
+
fsOverrides: {
|
|
553
|
+
readFileSync: sinon.stub().returns(
|
|
554
|
+
JSON.stringify({
|
|
555
|
+
auths: {
|
|
556
|
+
"other-registry.example.com": {
|
|
557
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
558
|
+
"base64",
|
|
559
|
+
),
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
credHelpers: {
|
|
563
|
+
"other-registry.example.com": "osxkeychain",
|
|
564
|
+
},
|
|
565
|
+
}),
|
|
566
|
+
),
|
|
567
|
+
},
|
|
568
|
+
utilsOverrides: {
|
|
569
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
570
|
+
safeSpawnSync,
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
await dockerModule.makeRequest(
|
|
575
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
576
|
+
"POST",
|
|
577
|
+
"registry.example.com/team/app",
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const registryAuthHeader =
|
|
581
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
582
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
583
|
+
username: "env-user",
|
|
584
|
+
password: "env-pass",
|
|
585
|
+
email: "env@example.com",
|
|
586
|
+
serveraddress: "registry.example.com",
|
|
587
|
+
});
|
|
588
|
+
sinon.assert.notCalled(safeSpawnSync);
|
|
589
|
+
},
|
|
590
|
+
);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
await it("docker makeRequest does not forward auth for substring-matched registries", async () => {
|
|
595
|
+
const originalDockerConfig = process.env.DOCKER_CONFIG;
|
|
596
|
+
process.env.DOCKER_CONFIG = "/tmp/cdxgen-docker-config";
|
|
597
|
+
try {
|
|
598
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
599
|
+
fsOverrides: {
|
|
600
|
+
readFileSync: sinon.stub().returns(
|
|
601
|
+
JSON.stringify({
|
|
602
|
+
auths: {
|
|
603
|
+
"private-registry.example.com": {
|
|
604
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
605
|
+
"base64",
|
|
606
|
+
),
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
}),
|
|
610
|
+
),
|
|
611
|
+
},
|
|
612
|
+
utilsOverrides: {
|
|
613
|
+
safeExistsSync: sinon
|
|
614
|
+
.stub()
|
|
615
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
await dockerModule.makeRequest(
|
|
620
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
621
|
+
"POST",
|
|
622
|
+
"registry.example.com",
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
626
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
627
|
+
} finally {
|
|
628
|
+
if (originalDockerConfig === undefined) {
|
|
629
|
+
delete process.env.DOCKER_CONFIG;
|
|
630
|
+
} else {
|
|
631
|
+
process.env.DOCKER_CONFIG = originalDockerConfig;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
await it("docker makeRequest accepts exact normalized registry matches from config auths", async () => {
|
|
637
|
+
const originalDockerConfig = process.env.DOCKER_CONFIG;
|
|
638
|
+
process.env.DOCKER_CONFIG = "/tmp/cdxgen-docker-config";
|
|
639
|
+
try {
|
|
640
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
641
|
+
fsOverrides: {
|
|
642
|
+
readFileSync: sinon.stub().returns(
|
|
643
|
+
JSON.stringify({
|
|
644
|
+
auths: {
|
|
645
|
+
"https://registry.example.com/v2/": {
|
|
646
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
647
|
+
"base64",
|
|
648
|
+
),
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
}),
|
|
652
|
+
),
|
|
653
|
+
},
|
|
654
|
+
utilsOverrides: {
|
|
655
|
+
safeExistsSync: sinon
|
|
656
|
+
.stub()
|
|
657
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
await dockerModule.makeRequest(
|
|
662
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
663
|
+
"POST",
|
|
664
|
+
"registry.example.com/team/app",
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const registryAuthHeader =
|
|
668
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
669
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
670
|
+
username: "trusted-user",
|
|
671
|
+
password: "trusted-pass",
|
|
672
|
+
serveraddress: "https://registry.example.com/v2/",
|
|
673
|
+
});
|
|
674
|
+
} finally {
|
|
675
|
+
if (originalDockerConfig === undefined) {
|
|
676
|
+
delete process.env.DOCKER_CONFIG;
|
|
677
|
+
} else {
|
|
678
|
+
process.env.DOCKER_CONFIG = originalDockerConfig;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await it("docker makeRequest accepts normalized exact matches across ipv4 ipv6 explicit ports and scoped subpaths from config auths", async () => {
|
|
684
|
+
const cases = [
|
|
685
|
+
{
|
|
686
|
+
configuredRegistry: "127.0.0.1:5000",
|
|
687
|
+
requestedRegistry: "127.0.0.1:5000/team/app",
|
|
688
|
+
expectedServerAddress: "127.0.0.1:5000",
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
configuredRegistry: "[::1]:5000",
|
|
692
|
+
requestedRegistry: "[::1]:5000/team/app",
|
|
693
|
+
expectedServerAddress: "[::1]:5000",
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
configuredRegistry: "https://[2001:db8::1]:5000/v2/",
|
|
697
|
+
requestedRegistry: "[2001:db8::1]:5000/team/app",
|
|
698
|
+
expectedServerAddress: "https://[2001:db8::1]:5000/v2/",
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
configuredRegistry: "HTTPS://REGISTRY.EXAMPLE.COM/V2/",
|
|
702
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
703
|
+
expectedServerAddress: "HTTPS://REGISTRY.EXAMPLE.COM/V2/",
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
configuredRegistry: "https://registry.example.com:443/v2/",
|
|
707
|
+
requestedRegistry: "registry.example.com:443/team/app",
|
|
708
|
+
expectedServerAddress: "https://registry.example.com:443/v2/",
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
configuredRegistry: "http://registry.example.com:80/v2/",
|
|
712
|
+
requestedRegistry: "registry.example.com:80/team/app",
|
|
713
|
+
expectedServerAddress: "http://registry.example.com:80/v2/",
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
configuredRegistry: "https://registry.example.com/custom/subpath",
|
|
717
|
+
requestedRegistry: "registry.example.com/custom/subpath/team/app",
|
|
718
|
+
expectedServerAddress: "https://registry.example.com/custom/subpath",
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
configuredRegistry: "https://registry.example.com/custom/subpath/v2/",
|
|
722
|
+
requestedRegistry: "registry.example.com/custom/subpath/team/app",
|
|
723
|
+
expectedServerAddress: "https://registry.example.com/custom/subpath/v2/",
|
|
724
|
+
},
|
|
725
|
+
];
|
|
726
|
+
|
|
727
|
+
await withDockerConfig(async () => {
|
|
728
|
+
for (const testCase of cases) {
|
|
729
|
+
const { dockerClient, dockerModule } = await loadDockerModuleWithAuths(
|
|
730
|
+
testCase.configuredRegistry,
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
await dockerModule.makeRequest(
|
|
734
|
+
`images/create?fromImage=${testCase.requestedRegistry}:latest`,
|
|
735
|
+
"POST",
|
|
736
|
+
testCase.requestedRegistry,
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
const registryAuthHeader =
|
|
740
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
741
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
742
|
+
username: "trusted-user",
|
|
743
|
+
password: "trusted-pass",
|
|
744
|
+
serveraddress: testCase.expectedServerAddress,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
await it("docker makeRequest rejects wildcard unicode bidi explicit-default-port port-boundary and unrelated scoped-path mismatches from config auths", async () => {
|
|
751
|
+
const bidiRegistry = "reg\u202eistry.example.com";
|
|
752
|
+
const unicodeConfusableRegistry = "reg\u0456stry.example.com";
|
|
753
|
+
const cases = [
|
|
754
|
+
{
|
|
755
|
+
configuredRegistry: "*.example.com",
|
|
756
|
+
requestedRegistry: "team.example.com/app",
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
configuredRegistry: "registry.example.com",
|
|
760
|
+
requestedRegistry: "registry.example.com:80/team/app",
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
configuredRegistry: "registry.example.com:443",
|
|
764
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
configuredRegistry: "127.0.0.1:5001",
|
|
768
|
+
requestedRegistry: "127.0.0.1:5000/team/app",
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
configuredRegistry: "[::1]:5001",
|
|
772
|
+
requestedRegistry: "[::1]:5000/team/app",
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
configuredRegistry: "https://registry.example.com.evil.invalid/v2/",
|
|
776
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
configuredRegistry: "https://registry.example.com/custom/subpath",
|
|
780
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
configuredRegistry: "https://registry.example.com/custom/subpath",
|
|
784
|
+
requestedRegistry: "registry.example.com/custom/subpathology/team/app",
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
configuredRegistry: "https://registry.example.com:443/v2/",
|
|
788
|
+
requestedRegistry: "registry.example.com:444/team/app",
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
configuredRegistry: unicodeConfusableRegistry,
|
|
792
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
configuredRegistry: bidiRegistry,
|
|
796
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
797
|
+
},
|
|
798
|
+
];
|
|
799
|
+
|
|
800
|
+
await withDockerConfig(async () => {
|
|
801
|
+
for (const testCase of cases) {
|
|
802
|
+
const { dockerClient, dockerModule } = await loadDockerModuleWithAuths(
|
|
803
|
+
testCase.configuredRegistry,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
await dockerModule.makeRequest(
|
|
807
|
+
`images/create?fromImage=${testCase.requestedRegistry}:latest`,
|
|
808
|
+
"POST",
|
|
809
|
+
testCase.requestedRegistry,
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
813
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
await it("docker makeRequest accepts raw host:port registry matches from config auths", async () => {
|
|
819
|
+
await withDockerConfig(async () => {
|
|
820
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
821
|
+
fsOverrides: {
|
|
822
|
+
readFileSync: sinon.stub().returns(
|
|
823
|
+
JSON.stringify({
|
|
824
|
+
auths: {
|
|
825
|
+
"localhost:5000": {
|
|
826
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
827
|
+
"base64",
|
|
828
|
+
),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
}),
|
|
832
|
+
),
|
|
833
|
+
},
|
|
834
|
+
utilsOverrides: {
|
|
835
|
+
safeExistsSync: sinon
|
|
836
|
+
.stub()
|
|
837
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
await dockerModule.makeRequest(
|
|
842
|
+
"images/create?fromImage=localhost:5000/team/app:latest",
|
|
843
|
+
"POST",
|
|
844
|
+
"localhost:5000/team/app",
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const registryAuthHeader =
|
|
848
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
849
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
850
|
+
username: "trusted-user",
|
|
851
|
+
password: "trusted-pass",
|
|
852
|
+
serveraddress: "localhost:5000",
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
await it("docker makeRequest keeps raw host:port registries separated by port", async () => {
|
|
858
|
+
await withDockerConfig(async () => {
|
|
859
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
860
|
+
fsOverrides: {
|
|
861
|
+
readFileSync: sinon.stub().returns(
|
|
862
|
+
JSON.stringify({
|
|
863
|
+
auths: {
|
|
864
|
+
"localhost:5001": {
|
|
865
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
866
|
+
"base64",
|
|
867
|
+
),
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
}),
|
|
871
|
+
),
|
|
872
|
+
},
|
|
873
|
+
utilsOverrides: {
|
|
874
|
+
safeExistsSync: sinon
|
|
875
|
+
.stub()
|
|
876
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
await dockerModule.makeRequest(
|
|
881
|
+
"images/create?fromImage=localhost:5000/team/app:latest",
|
|
882
|
+
"POST",
|
|
883
|
+
"localhost:5000/team/app",
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
887
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
await it("docker makeRequest preserves Docker Hub auth aliases without substring matching", async () => {
|
|
892
|
+
const originalDockerConfig = process.env.DOCKER_CONFIG;
|
|
893
|
+
process.env.DOCKER_CONFIG = "/tmp/cdxgen-docker-config";
|
|
894
|
+
try {
|
|
895
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
896
|
+
fsOverrides: {
|
|
897
|
+
readFileSync: sinon.stub().returns(
|
|
898
|
+
JSON.stringify({
|
|
899
|
+
auths: {
|
|
900
|
+
"https://index.docker.io/v1/": {
|
|
901
|
+
auth: Buffer.from("hub-user:hub-pass").toString("base64"),
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
}),
|
|
905
|
+
),
|
|
906
|
+
},
|
|
907
|
+
utilsOverrides: {
|
|
908
|
+
safeExistsSync: sinon
|
|
909
|
+
.stub()
|
|
910
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
await dockerModule.makeRequest(
|
|
915
|
+
"images/create?fromImage=docker.io/library/alpine:latest",
|
|
916
|
+
"POST",
|
|
917
|
+
"docker.io",
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
const registryAuthHeader =
|
|
921
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
922
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
923
|
+
username: "hub-user",
|
|
924
|
+
password: "hub-pass",
|
|
925
|
+
serveraddress: "https://index.docker.io/v1/",
|
|
926
|
+
});
|
|
927
|
+
} finally {
|
|
928
|
+
if (originalDockerConfig === undefined) {
|
|
929
|
+
delete process.env.DOCKER_CONFIG;
|
|
930
|
+
} else {
|
|
931
|
+
process.env.DOCKER_CONFIG = originalDockerConfig;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
await it("docker makeRequest resolves unqualified image pulls to Docker Hub auth entries", async () => {
|
|
937
|
+
const requestedImages = ["myorg/app:latest", "alpine:latest"];
|
|
938
|
+
|
|
939
|
+
await withDockerConfig(async () => {
|
|
940
|
+
for (const requestedImage of requestedImages) {
|
|
941
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
942
|
+
fsOverrides: {
|
|
943
|
+
readFileSync: sinon.stub().returns(
|
|
944
|
+
JSON.stringify({
|
|
945
|
+
auths: {
|
|
946
|
+
"https://index.docker.io/v1/": {
|
|
947
|
+
auth: Buffer.from("hub-user:hub-pass").toString("base64"),
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
}),
|
|
951
|
+
),
|
|
952
|
+
},
|
|
953
|
+
utilsOverrides: {
|
|
954
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
955
|
+
},
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
await dockerModule.makeRequest(
|
|
959
|
+
`images/create?fromImage=${requestedImage}`,
|
|
960
|
+
"POST",
|
|
961
|
+
"",
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const registryAuthHeader =
|
|
965
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
966
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
967
|
+
username: "hub-user",
|
|
968
|
+
password: "hub-pass",
|
|
969
|
+
serveraddress: "https://index.docker.io/v1/",
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
await it("docker makeRequest skips credHelpers for substring-matched registries", async () => {
|
|
976
|
+
const originalDockerConfig = process.env.DOCKER_CONFIG;
|
|
977
|
+
process.env.DOCKER_CONFIG = "/tmp/cdxgen-docker-config";
|
|
978
|
+
try {
|
|
979
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
980
|
+
status: 0,
|
|
981
|
+
stdout: JSON.stringify({
|
|
982
|
+
Username: "trusted-user",
|
|
983
|
+
Secret: "trusted-pass",
|
|
984
|
+
}),
|
|
985
|
+
stderr: "",
|
|
986
|
+
});
|
|
987
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
988
|
+
fsOverrides: {
|
|
989
|
+
readFileSync: sinon.stub().returns(
|
|
990
|
+
JSON.stringify({
|
|
991
|
+
credHelpers: {
|
|
992
|
+
"private-registry.example.com": "osxkeychain",
|
|
993
|
+
},
|
|
994
|
+
}),
|
|
995
|
+
),
|
|
996
|
+
},
|
|
997
|
+
utilsOverrides: {
|
|
998
|
+
safeExistsSync: sinon
|
|
999
|
+
.stub()
|
|
1000
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
1001
|
+
safeSpawnSync,
|
|
1002
|
+
},
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
await dockerModule.makeRequest(
|
|
1006
|
+
"images/create?fromImage=registry.example.com/team/app:latest",
|
|
1007
|
+
"POST",
|
|
1008
|
+
"registry.example.com",
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
1012
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
1013
|
+
sinon.assert.notCalled(safeSpawnSync);
|
|
1014
|
+
} finally {
|
|
1015
|
+
if (originalDockerConfig === undefined) {
|
|
1016
|
+
delete process.env.DOCKER_CONFIG;
|
|
1017
|
+
} else {
|
|
1018
|
+
process.env.DOCKER_CONFIG = originalDockerConfig;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
await it("docker makeRequest accepts raw host:port registry matches from credHelpers", async () => {
|
|
1024
|
+
await withDockerConfig(async () => {
|
|
1025
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1026
|
+
status: 0,
|
|
1027
|
+
stdout: JSON.stringify({
|
|
1028
|
+
username: "trusted-user",
|
|
1029
|
+
Secret: "trusted-pass",
|
|
1030
|
+
}),
|
|
1031
|
+
stderr: "",
|
|
1032
|
+
});
|
|
1033
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
1034
|
+
fsOverrides: {
|
|
1035
|
+
readFileSync: sinon.stub().returns(
|
|
1036
|
+
JSON.stringify({
|
|
1037
|
+
credHelpers: {
|
|
1038
|
+
"localhost:5000": "osxkeychain",
|
|
1039
|
+
},
|
|
1040
|
+
}),
|
|
1041
|
+
),
|
|
1042
|
+
},
|
|
1043
|
+
utilsOverrides: {
|
|
1044
|
+
safeExistsSync: sinon
|
|
1045
|
+
.stub()
|
|
1046
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
1047
|
+
safeSpawnSync,
|
|
1048
|
+
},
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
await dockerModule.makeRequest(
|
|
1052
|
+
"images/create?fromImage=localhost:5000/team/app:latest",
|
|
1053
|
+
"POST",
|
|
1054
|
+
"localhost:5000/team/app",
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
sinon.assert.calledOnceWithExactly(
|
|
1058
|
+
safeSpawnSync,
|
|
1059
|
+
credHelperExe("osxkeychain"),
|
|
1060
|
+
["get"],
|
|
1061
|
+
{
|
|
1062
|
+
input: "localhost:5000",
|
|
1063
|
+
},
|
|
1064
|
+
);
|
|
1065
|
+
const registryAuthHeader =
|
|
1066
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
1067
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
1068
|
+
username: "trusted-user",
|
|
1069
|
+
password: "trusted-pass",
|
|
1070
|
+
email: "trusted-user",
|
|
1071
|
+
serveraddress: "localhost:5000",
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
await it("docker getCredsFromHelper normalizes cache keys for equivalent registry hosts", async () => {
|
|
1077
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1078
|
+
status: 0,
|
|
1079
|
+
stdout: JSON.stringify({
|
|
1080
|
+
username: "trusted-user",
|
|
1081
|
+
Secret: "trusted-pass",
|
|
1082
|
+
}),
|
|
1083
|
+
stderr: "",
|
|
1084
|
+
});
|
|
1085
|
+
const { dockerModule } = await loadDockerModule({
|
|
1086
|
+
utilsOverrides: {
|
|
1087
|
+
safeSpawnSync,
|
|
1088
|
+
},
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
const firstToken = dockerModule.getCredsFromHelper(
|
|
1092
|
+
"osxkeychain",
|
|
1093
|
+
"registry.example.com",
|
|
1094
|
+
);
|
|
1095
|
+
const secondToken = dockerModule.getCredsFromHelper(
|
|
1096
|
+
"osxkeychain",
|
|
1097
|
+
"https://registry.example.com/v2/",
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
assert.strictEqual(firstToken, secondToken);
|
|
1101
|
+
sinon.assert.calledOnceWithExactly(
|
|
1102
|
+
safeSpawnSync,
|
|
1103
|
+
credHelperExe("osxkeychain"),
|
|
1104
|
+
["get"],
|
|
1105
|
+
{
|
|
1106
|
+
input: "registry.example.com",
|
|
1107
|
+
},
|
|
1108
|
+
);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
await it("docker getCredsFromHelper keeps scoped path cache keys isolated", async () => {
|
|
1112
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1113
|
+
status: 0,
|
|
1114
|
+
stdout: JSON.stringify({
|
|
1115
|
+
username: "trusted-user",
|
|
1116
|
+
Secret: "trusted-pass",
|
|
1117
|
+
}),
|
|
1118
|
+
stderr: "",
|
|
1119
|
+
});
|
|
1120
|
+
const { dockerModule } = await loadDockerModule({
|
|
1121
|
+
utilsOverrides: {
|
|
1122
|
+
safeSpawnSync,
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
const firstToken = dockerModule.getCredsFromHelper(
|
|
1127
|
+
"osxkeychain",
|
|
1128
|
+
"https://registry.example.com/custom/subpath/v2/",
|
|
1129
|
+
);
|
|
1130
|
+
const secondToken = dockerModule.getCredsFromHelper(
|
|
1131
|
+
"osxkeychain",
|
|
1132
|
+
"https://registry.example.com/custom/subpath/v2/",
|
|
1133
|
+
);
|
|
1134
|
+
const thirdToken = dockerModule.getCredsFromHelper(
|
|
1135
|
+
"osxkeychain",
|
|
1136
|
+
"https://registry.example.com/other/subpath/v2/",
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
assert.strictEqual(firstToken, secondToken);
|
|
1140
|
+
assert.notStrictEqual(firstToken, thirdToken);
|
|
1141
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(firstToken), {
|
|
1142
|
+
username: "trusted-user",
|
|
1143
|
+
password: "trusted-pass",
|
|
1144
|
+
email: "trusted-user",
|
|
1145
|
+
serveraddress: "https://registry.example.com/custom/subpath/v2/",
|
|
1146
|
+
});
|
|
1147
|
+
sinon.assert.calledTwice(safeSpawnSync);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
await it("docker makeRequest accepts ipv4 ipv6 explicit-port and scoped-subpath registry matches from credHelpers", async () => {
|
|
1151
|
+
const cases = [
|
|
1152
|
+
{
|
|
1153
|
+
configuredRegistry: "127.0.0.1:5000",
|
|
1154
|
+
requestedRegistry: "127.0.0.1:5000/team/app",
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
configuredRegistry: "[::1]:5000",
|
|
1158
|
+
requestedRegistry: "[::1]:5000/team/app",
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
configuredRegistry: "https://registry.example.com:443/v2/",
|
|
1162
|
+
requestedRegistry: "registry.example.com:443/team/app",
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
configuredRegistry: "http://registry.example.com:80/v2/",
|
|
1166
|
+
requestedRegistry: "registry.example.com:80/team/app",
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
configuredRegistry: "https://registry.example.com/custom/subpath/v2/",
|
|
1170
|
+
requestedRegistry: "registry.example.com/custom/subpath/team/app",
|
|
1171
|
+
},
|
|
1172
|
+
];
|
|
1173
|
+
|
|
1174
|
+
await withDockerConfig(async () => {
|
|
1175
|
+
for (const testCase of cases) {
|
|
1176
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1177
|
+
status: 0,
|
|
1178
|
+
stdout: JSON.stringify({
|
|
1179
|
+
username: "trusted-user",
|
|
1180
|
+
Secret: "trusted-pass",
|
|
1181
|
+
}),
|
|
1182
|
+
stderr: "",
|
|
1183
|
+
});
|
|
1184
|
+
const { dockerClient, dockerModule } =
|
|
1185
|
+
await loadDockerModuleWithCredHelpers(
|
|
1186
|
+
testCase.configuredRegistry,
|
|
1187
|
+
safeSpawnSync,
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
await dockerModule.makeRequest(
|
|
1191
|
+
`images/create?fromImage=${testCase.requestedRegistry}:latest`,
|
|
1192
|
+
"POST",
|
|
1193
|
+
testCase.requestedRegistry,
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
sinon.assert.calledOnceWithExactly(
|
|
1197
|
+
safeSpawnSync,
|
|
1198
|
+
credHelperExe("osxkeychain"),
|
|
1199
|
+
["get"],
|
|
1200
|
+
{
|
|
1201
|
+
input: testCase.configuredRegistry,
|
|
1202
|
+
},
|
|
1203
|
+
);
|
|
1204
|
+
const registryAuthHeader =
|
|
1205
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
1206
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
1207
|
+
username: "trusted-user",
|
|
1208
|
+
password: "trusted-pass",
|
|
1209
|
+
email: "trusted-user",
|
|
1210
|
+
serveraddress: testCase.configuredRegistry,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
await it("docker makeRequest does not invoke credHelpers for wildcard unicode bidi explicit-default-port or port-boundary mismatches", async () => {
|
|
1217
|
+
const bidiRegistry = "reg\u202eistry.example.com";
|
|
1218
|
+
const unicodeConfusableRegistry = "reg\u0456stry.example.com";
|
|
1219
|
+
const cases = [
|
|
1220
|
+
{
|
|
1221
|
+
configuredRegistry: "*.example.com",
|
|
1222
|
+
requestedRegistry: "team.example.com/app",
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
configuredRegistry: "registry.example.com",
|
|
1226
|
+
requestedRegistry: "registry.example.com:80/team/app",
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
configuredRegistry: "registry.example.com:443",
|
|
1230
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
configuredRegistry: "127.0.0.1:5001",
|
|
1234
|
+
requestedRegistry: "127.0.0.1:5000/team/app",
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
configuredRegistry: "[::1]:5001",
|
|
1238
|
+
requestedRegistry: "[::1]:5000/team/app",
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
configuredRegistry: "https://registry.example.com/custom/subpath/v2/",
|
|
1242
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
configuredRegistry: "https://registry.example.com/custom/subpath/v2/",
|
|
1246
|
+
requestedRegistry: "registry.example.com/custom/subpathology/team/app",
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
configuredRegistry: "https://registry.example.com:443/v2/",
|
|
1250
|
+
requestedRegistry: "registry.example.com:444/team/app",
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
configuredRegistry: unicodeConfusableRegistry,
|
|
1254
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
configuredRegistry: bidiRegistry,
|
|
1258
|
+
requestedRegistry: "registry.example.com/team/app",
|
|
1259
|
+
},
|
|
1260
|
+
];
|
|
1261
|
+
|
|
1262
|
+
await withDockerConfig(async () => {
|
|
1263
|
+
for (const testCase of cases) {
|
|
1264
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1265
|
+
status: 0,
|
|
1266
|
+
stdout: JSON.stringify({
|
|
1267
|
+
username: "trusted-user",
|
|
1268
|
+
Secret: "trusted-pass",
|
|
1269
|
+
}),
|
|
1270
|
+
stderr: "",
|
|
1271
|
+
});
|
|
1272
|
+
const { dockerClient, dockerModule } =
|
|
1273
|
+
await loadDockerModuleWithCredHelpers(
|
|
1274
|
+
testCase.configuredRegistry,
|
|
1275
|
+
safeSpawnSync,
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
await dockerModule.makeRequest(
|
|
1279
|
+
`images/create?fromImage=${testCase.requestedRegistry}:latest`,
|
|
1280
|
+
"POST",
|
|
1281
|
+
testCase.requestedRegistry,
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
1285
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
1286
|
+
sinon.assert.notCalled(safeSpawnSync);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
await it("docker makeRequest resolves unqualified image pulls to Docker Hub credHelpers", async () => {
|
|
1292
|
+
const requestedImages = ["myorg/app:latest", "alpine:latest"];
|
|
1293
|
+
|
|
1294
|
+
await withDockerConfig(async () => {
|
|
1295
|
+
for (const requestedImage of requestedImages) {
|
|
1296
|
+
const safeSpawnSync = sinon.stub().returns({
|
|
1297
|
+
status: 0,
|
|
1298
|
+
stdout: JSON.stringify({
|
|
1299
|
+
username: "hub-user",
|
|
1300
|
+
Secret: "hub-pass",
|
|
1301
|
+
}),
|
|
1302
|
+
stderr: "",
|
|
1303
|
+
});
|
|
1304
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
1305
|
+
fsOverrides: {
|
|
1306
|
+
readFileSync: sinon.stub().returns(
|
|
1307
|
+
JSON.stringify({
|
|
1308
|
+
credHelpers: {
|
|
1309
|
+
"docker.io": "osxkeychain",
|
|
1310
|
+
},
|
|
1311
|
+
}),
|
|
1312
|
+
),
|
|
1313
|
+
},
|
|
1314
|
+
utilsOverrides: {
|
|
1315
|
+
safeExistsSync: dockerConfigExistsStub(),
|
|
1316
|
+
safeSpawnSync,
|
|
1317
|
+
},
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
await dockerModule.makeRequest(
|
|
1321
|
+
`images/create?fromImage=${requestedImage}`,
|
|
1322
|
+
"POST",
|
|
1323
|
+
"",
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
sinon.assert.calledOnceWithExactly(
|
|
1327
|
+
safeSpawnSync,
|
|
1328
|
+
credHelperExe("osxkeychain"),
|
|
1329
|
+
["get"],
|
|
1330
|
+
{
|
|
1331
|
+
input: "docker.io",
|
|
1332
|
+
},
|
|
1333
|
+
);
|
|
1334
|
+
const registryAuthHeader =
|
|
1335
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
1336
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
1337
|
+
username: "hub-user",
|
|
1338
|
+
password: "hub-pass",
|
|
1339
|
+
email: "hub-user",
|
|
1340
|
+
serveraddress: "docker.io",
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
await it("docker makeRequest accepts normalized exact matches for common public registries without aliasing hosts", async () => {
|
|
1347
|
+
const cases = [
|
|
1348
|
+
{
|
|
1349
|
+
configuredRegistry: "https://ghcr.io/v2/",
|
|
1350
|
+
requestedRegistry: "ghcr.io/org/image",
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
configuredRegistry: "https://quay.io/v2/",
|
|
1354
|
+
requestedRegistry: "quay.io/org/image",
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
configuredRegistry: "https://public.ecr.aws/v2/",
|
|
1358
|
+
requestedRegistry: "public.ecr.aws/alias/image",
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
configuredRegistry: "https://gcr.io/v2/",
|
|
1362
|
+
requestedRegistry: "gcr.io/project/image",
|
|
1363
|
+
},
|
|
1364
|
+
];
|
|
1365
|
+
|
|
1366
|
+
await withDockerConfig(async () => {
|
|
1367
|
+
for (const { configuredRegistry, requestedRegistry } of cases) {
|
|
1368
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
1369
|
+
fsOverrides: {
|
|
1370
|
+
readFileSync: sinon.stub().returns(
|
|
1371
|
+
JSON.stringify({
|
|
1372
|
+
auths: {
|
|
1373
|
+
[configuredRegistry]: {
|
|
1374
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
1375
|
+
"base64",
|
|
1376
|
+
),
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
}),
|
|
1380
|
+
),
|
|
1381
|
+
},
|
|
1382
|
+
utilsOverrides: {
|
|
1383
|
+
safeExistsSync: sinon
|
|
1384
|
+
.stub()
|
|
1385
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
1386
|
+
},
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
await dockerModule.makeRequest(
|
|
1390
|
+
`images/create?fromImage=${requestedRegistry}:latest`,
|
|
1391
|
+
"POST",
|
|
1392
|
+
requestedRegistry,
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
const registryAuthHeader =
|
|
1396
|
+
dockerClient.firstCall.args[1].headers["X-Registry-Auth"];
|
|
1397
|
+
assert.deepStrictEqual(decodeRegistryAuthHeader(registryAuthHeader), {
|
|
1398
|
+
username: "trusted-user",
|
|
1399
|
+
password: "trusted-pass",
|
|
1400
|
+
serveraddress: configuredRegistry,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
await it("docker makeRequest keeps ghcr quay aws and gcp registries on separate trust boundaries", async () => {
|
|
1407
|
+
const cases = [
|
|
1408
|
+
{
|
|
1409
|
+
configuredRegistry: "https://tenant.ghcr.io/v2/",
|
|
1410
|
+
requestedRegistry: "ghcr.io",
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
configuredRegistry: "https://quay.io.evil.example/v2/",
|
|
1414
|
+
requestedRegistry: "quay.io",
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
configuredRegistry:
|
|
1418
|
+
"https://123456789012.dkr.ecr.us-east-1.amazonaws.com/v2/",
|
|
1419
|
+
requestedRegistry: "public.ecr.aws",
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
configuredRegistry: "https://mirror.gcr.io/v2/",
|
|
1423
|
+
requestedRegistry: "gcr.io",
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
configuredRegistry: "https://us-docker.pkg.dev/v2/",
|
|
1427
|
+
requestedRegistry: "gcr.io",
|
|
1428
|
+
},
|
|
1429
|
+
];
|
|
1430
|
+
|
|
1431
|
+
await withDockerConfig(async () => {
|
|
1432
|
+
for (const { configuredRegistry, requestedRegistry } of cases) {
|
|
1433
|
+
const { dockerClient, dockerModule } = await loadDockerModule({
|
|
1434
|
+
fsOverrides: {
|
|
1435
|
+
readFileSync: sinon.stub().returns(
|
|
1436
|
+
JSON.stringify({
|
|
1437
|
+
auths: {
|
|
1438
|
+
[configuredRegistry]: {
|
|
1439
|
+
auth: Buffer.from("trusted-user:trusted-pass").toString(
|
|
1440
|
+
"base64",
|
|
1441
|
+
),
|
|
1442
|
+
},
|
|
1443
|
+
},
|
|
1444
|
+
}),
|
|
1445
|
+
),
|
|
1446
|
+
},
|
|
1447
|
+
utilsOverrides: {
|
|
1448
|
+
safeExistsSync: sinon
|
|
1449
|
+
.stub()
|
|
1450
|
+
.callsFake((filePath) => filePath.endsWith("config.json")),
|
|
1451
|
+
},
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
await dockerModule.makeRequest(
|
|
1455
|
+
`images/create?fromImage=${requestedRegistry}/team/app:latest`,
|
|
1456
|
+
"POST",
|
|
1457
|
+
requestedRegistry,
|
|
1458
|
+
);
|
|
1459
|
+
|
|
1460
|
+
const requestOptions = dockerClient.firstCall.args[1];
|
|
1461
|
+
assert.strictEqual(requestOptions.headers, undefined);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
});
|
|
1465
|
+
|
|
326
1466
|
await it("extractFromManifest derives PATH metadata from archive config", async () => {
|
|
327
1467
|
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-docker-"));
|
|
328
1468
|
try {
|