Skip to content

Commit 7b8e496

Browse files
chrmartiCopilot
andcommitted
Pass OCI auth hosts as CLI state
Propagate cross-origin auth host mappings explicitly through command, resolver, and registry request parameters instead of serializing them through the process environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 05249c9 commit 7b8e496

18 files changed

Lines changed: 130 additions & 55 deletions

‎src/spec-common/injectHeadless.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export interface ResolverParameters {
6969
omitConfigRemotEnvFromMetadata?: boolean;
7070
secretsP?: Promise<Record<string, string>>;
7171
omitSyntaxDirective?: boolean;
72+
allowedCrossOriginAuthHosts?: string[];
7273
}
7374

7475
export interface LifecycleHook {

‎src/spec-configuration/containerCollectionsOCI.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface CommonParams {
1818
env: NodeJS.ProcessEnv;
1919
output: Log;
2020
cachedAuthHeader?: Record<string, string>; // <registry, authHeader>
21+
allowedCrossOriginAuthHosts?: string[];
2122
}
2223

2324
// Represents the unique OCI identifier for a Feature or Template.

‎src/spec-configuration/containerFeaturesConfiguration.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export interface ContainerFeatureInternalParams {
195195
platform: NodeJS.Platform;
196196
noLockfile?: boolean;
197197
frozenLockfile?: boolean;
198+
allowedCrossOriginAuthHosts?: string[];
198199
}
199200

200201
// TODO: Move to node layer.

‎src/spec-configuration/httpOCIRegistry.ts‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ const scopeRegex = /scope="([^"]+)"/;
3737

3838
type RegistryCredentialType = 'basic' | 'refreshToken';
3939

40-
export const allowCrossOriginAuthHostEnv = 'DEVCONTAINERS_INTERNAL_ALLOW_CROSS_ORIGIN_AUTH_HOST';
41-
4240
const builtInCrossOriginAuthHosts = [
4341
'registry-1.docker.io=auth.docker.io',
4442
'registry.docker.io=auth.docker.io',
@@ -76,19 +74,6 @@ export function parseCrossOriginAuthHosts(entries: readonly string[]): Map<strin
7674
return result;
7775
}
7876

79-
function getCrossOriginAuthHosts(env: NodeJS.ProcessEnv) {
80-
const configured = env[allowCrossOriginAuthHostEnv];
81-
let configuredEntries: string[] = [];
82-
if (configured) {
83-
const parsed: unknown = JSON.parse(configured);
84-
if (!Array.isArray(parsed) || parsed.some(entry => typeof entry !== 'string')) {
85-
throw new Error(`Invalid ${allowCrossOriginAuthHostEnv} value.`);
86-
}
87-
configuredEntries = parsed;
88-
}
89-
return parseCrossOriginAuthHosts([...builtInCrossOriginAuthHosts, ...configuredEntries]);
90-
}
91-
9277
function isConfiguredCrossOriginAuthHost(registryUrl: URL, realmUrl: URL, crossOriginAuthHosts: Map<string, Set<string>>) {
9378
return crossOriginAuthHosts.get(registryUrl.host.toLowerCase())?.has(realmUrl.host.toLowerCase()) || false;
9479
}
@@ -235,7 +220,7 @@ export async function requestEnsureAuthenticated(params: CommonParams, httpOptio
235220
}
236221
let crossOriginAuthHosts: Map<string, Set<string>>;
237222
try {
238-
crossOriginAuthHosts = getCrossOriginAuthHosts(params.env);
223+
crossOriginAuthHosts = parseCrossOriginAuthHosts([...builtInCrossOriginAuthHosts, ...(params.allowedCrossOriginAuthHosts || [])]);
239224
} catch (err) {
240225
output.write(`[httpOci] ERR: ${err}`, LogLevel.Error);
241226
return;

‎src/spec-node/devContainers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export interface ProvisionOptions {
7474
omitSyntaxDirective?: boolean;
7575
includeConfig?: boolean;
7676
includeMergedConfig?: boolean;
77+
allowedCrossOriginAuthHosts?: string[];
7778
}
7879

7980
export async function launch(options: ProvisionOptions, providedIdLabels: string[] | undefined, disposables: (() => Promise<unknown> | undefined)[]) {
@@ -162,6 +163,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
162163
targetPath: options.dotfiles.targetPath || '~/dotfiles',
163164
},
164165
omitSyntaxDirective: options.omitSyntaxDirective,
166+
allowedCrossOriginAuthHosts: options.allowedCrossOriginAuthHosts,
165167
};
166168

167169
const dockerPath = options.dockerPath || 'docker';

‎src/spec-node/devContainersSpecCLI.ts‎

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { featuresGenerateDocsHandler, featuresGenerateDocsOptions } from './feat
4545
import { templatesGenerateDocsHandler, templatesGenerateDocsOptions } from './templatesCLI/generateDocs';
4646
import { mapNodeOSToGOOS, mapNodeArchitectureToGOARCH } from '../spec-configuration/containerCollectionsOCI';
4747
import { templateMetadataHandler, templateMetadataOptions } from './templatesCLI/metadata';
48-
import { allowCrossOriginAuthHostEnv, parseCrossOriginAuthHosts } from '../spec-configuration/httpOCIRegistry';
48+
import { parseCrossOriginAuthHosts } from '../spec-configuration/httpOCIRegistry';
4949

5050
const defaultDefaultUserEnvProbe: UserEnvProbe = 'loginInteractiveShell';
5151

@@ -70,14 +70,14 @@ const mountRegex = /^type=(bind|volume),source=([^,]+),target=([^,]+)(?:,externa
7070
.option('allow-cross-origin-auth-host', {
7171
type: 'string',
7272
array: true,
73+
nargs: 1,
7374
global: true,
7475
description: 'Allow an OCI registry to use a cross-origin HTTPS authentication host. Format: <registry-host>=<auth-host>. May be repeated.',
7576
})
76-
.middleware(args => {
77-
const entries = args['allow-cross-origin-auth-host'] || [];
78-
parseCrossOriginAuthHosts(entries);
79-
process.env[allowCrossOriginAuthHostEnv] = JSON.stringify(entries);
80-
}, true)
77+
.check(args => {
78+
parseCrossOriginAuthHosts(getAllowedCrossOriginAuthHosts(args as OciAuthArgs));
79+
return true;
80+
})
8181
.strict();
8282
y.wrap(Math.min(120, y.terminalWidth()));
8383
y.command('up', 'Create and run dev container', provisionOptions, provisionHandler);
@@ -108,6 +108,11 @@ const mountRegex = /^type=(bind|volume),source=([^,]+),target=([^,]+)(?:,externa
108108
})().catch(console.error);
109109

110110
export type UnpackArgv<T> = T extends Argv<infer U> ? U : T;
111+
export type OciAuthArgs = { 'allow-cross-origin-auth-host'?: string[] };
112+
113+
export function getAllowedCrossOriginAuthHosts(args: OciAuthArgs) {
114+
return args['allow-cross-origin-auth-host'] || [];
115+
}
111116

112117
function provisionOptions(y: Argv) {
113118
return y.options({
@@ -188,7 +193,7 @@ function provisionOptions(y: Argv) {
188193
});
189194
}
190195

191-
type ProvisionArgs = UnpackArgv<ReturnType<typeof provisionOptions>>;
196+
type ProvisionArgs = UnpackArgv<ReturnType<typeof provisionOptions>> & OciAuthArgs;
192197

193198
function provisionHandler(args: ProvisionArgs) {
194199
runAsyncHandler(provision.bind(null, args));
@@ -241,6 +246,7 @@ async function provision({
241246
'omit-syntax-directive': omitSyntaxDirective,
242247
'include-configuration': includeConfig,
243248
'include-merged-configuration': includeMergedConfig,
249+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
244250
}: ProvisionArgs) {
245251

246252
warnDeprecatedLockfileFlags(experimentalLockfile, experimentalFrozenLockfile);
@@ -315,6 +321,7 @@ async function provision({
315321
omitSyntaxDirective,
316322
includeConfig,
317323
includeMergedConfig,
324+
allowedCrossOriginAuthHosts,
318325
};
319326

320327
const result = await doProvision(options, providedIdLabels);
@@ -395,7 +402,7 @@ function setUpOptions(y: Argv) {
395402
});
396403
}
397404

398-
type SetUpArgs = UnpackArgv<ReturnType<typeof setUpOptions>>;
405+
type SetUpArgs = UnpackArgv<ReturnType<typeof setUpOptions>> & OciAuthArgs;
399406

400407
function setUpHandler(args: SetUpArgs) {
401408
runAsyncHandler(setUp.bind(null, args));
@@ -432,6 +439,7 @@ async function doSetUp({
432439
'container-session-data-folder': containerSessionDataFolder,
433440
'include-configuration': includeConfig,
434441
'include-merged-configuration': includeMergedConfig,
442+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
435443
}: SetUpArgs) {
436444

437445
const disposables: (() => Promise<unknown> | undefined)[] = [];
@@ -482,6 +490,7 @@ async function doSetUp({
482490
installCommand: dotfilesInstallCommand,
483491
targetPath: dotfilesTargetPath,
484492
},
493+
allowedCrossOriginAuthHosts,
485494
}, disposables);
486495

487496
const { common } = params;
@@ -573,7 +582,7 @@ function buildOptions(y: Argv) {
573582
});
574583
}
575584

576-
type BuildArgs = UnpackArgv<ReturnType<typeof buildOptions>>;
585+
type BuildArgs = UnpackArgv<ReturnType<typeof buildOptions>> & OciAuthArgs;
577586

578587
function buildHandler(args: BuildArgs) {
579588
runAsyncHandler(build.bind(null, args));
@@ -614,6 +623,7 @@ async function doBuild({
614623
'no-lockfile': noLockfile,
615624
'frozen-lockfile': frozenLockfile,
616625
'omit-syntax-directive': omitSyntaxDirective,
626+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
617627
}: BuildArgs) {
618628
warnDeprecatedLockfileFlags(experimentalLockfile, experimentalFrozenLockfile);
619629
const effectiveFrozenLockfile = frozenLockfile || experimentalFrozenLockfile;
@@ -667,6 +677,7 @@ async function doBuild({
667677
noLockfile,
668678
frozenLockfile: effectiveFrozenLockfile,
669679
omitSyntaxDirective,
680+
allowedCrossOriginAuthHosts,
670681
}, disposables);
671682

672683
const { common, dockerComposeCLI } = params;
@@ -842,7 +853,7 @@ function runUserCommandsOptions(y: Argv) {
842853
});
843854
}
844855

845-
type RunUserCommandsArgs = UnpackArgv<ReturnType<typeof runUserCommandsOptions>>;
856+
type RunUserCommandsArgs = UnpackArgv<ReturnType<typeof runUserCommandsOptions>> & OciAuthArgs;
846857

847858
function runUserCommandsHandler(args: RunUserCommandsArgs) {
848859
runAsyncHandler(runUserCommands.bind(null, args));
@@ -1035,7 +1046,7 @@ function readConfigurationOptions(y: Argv) {
10351046
});
10361047
}
10371048

1038-
type ReadConfigurationArgs = UnpackArgv<ReturnType<typeof readConfigurationOptions>>;
1049+
type ReadConfigurationArgs = UnpackArgv<ReturnType<typeof readConfigurationOptions>> & OciAuthArgs;
10391050

10401051
function readConfigurationHandler(args: ReadConfigurationArgs) {
10411052
runAsyncHandler(readConfiguration.bind(null, args));
@@ -1060,6 +1071,7 @@ async function readConfiguration({
10601071
'include-merged-configuration': includeMergedConfig,
10611072
'additional-features': additionalFeaturesJson,
10621073
'skip-feature-auto-mapping': skipFeatureAutoMapping,
1074+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
10631075
}: ReadConfigurationArgs) {
10641076
const disposables: (() => Promise<unknown> | undefined)[] = [];
10651077
const dispose = async () => {
@@ -1116,7 +1128,8 @@ async function readConfiguration({
11161128
env: cliHost.env,
11171129
output,
11181130
buildPlatformInfo,
1119-
targetPlatformInfo: buildPlatformInfo
1131+
targetPlatformInfo: buildPlatformInfo,
1132+
allowedCrossOriginAuthHosts,
11201133
};
11211134
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
11221135
if (container) {
@@ -1174,7 +1187,7 @@ function outdatedOptions(y: Argv) {
11741187
});
11751188
}
11761189

1177-
type OutdatedArgs = UnpackArgv<ReturnType<typeof outdatedOptions>>;
1190+
type OutdatedArgs = UnpackArgv<ReturnType<typeof outdatedOptions>> & OciAuthArgs;
11781191

11791192
function outdatedHandler(args: OutdatedArgs) {
11801193
runAsyncHandler(outdated.bind(null, args));
@@ -1189,6 +1202,7 @@ async function outdated({
11891202
'log-format': logFormat,
11901203
'terminal-rows': terminalRows,
11911204
'terminal-columns': terminalColumns,
1205+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
11921206
}: OutdatedArgs) {
11931207
const disposables: (() => Promise<unknown> | undefined)[] = [];
11941208
const dispose = async () => {
@@ -1225,6 +1239,7 @@ async function outdated({
12251239
env: cliHost.env,
12261240
skipFeatureAutoMapping: false,
12271241
platform: cliHost.platform,
1242+
allowedCrossOriginAuthHosts,
12281243
};
12291244

12301245
const outdated = await loadVersionInfo(params, configs.config.config);

‎src/spec-node/featureUtils.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ export async function readFeaturesConfig(params: DockerCLIParameters, pkg: Packa
99
const { cwd, env, platform } = cliHost;
1010
const featuresTmpFolder = await createFeaturesTempFolder({ cliHost, package: pkg });
1111
const cacheFolder = await getCacheFolder(cliHost);
12-
return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform, noLockfile: true }, featuresTmpFolder, config, additionalFeatures);
12+
return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform, noLockfile: true, allowedCrossOriginAuthHosts: params.allowedCrossOriginAuthHosts }, featuresTmpFolder, config, additionalFeatures);
1313
}

‎src/spec-node/featuresCLI/info.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { OCIManifest, OCIRef, fetchOCIManifestIfExists, getPublishedTags, getRef
33
import { Log, LogLevel, mapLogLevel } from '../../spec-utils/log';
44
import { getPackageConfig } from '../../spec-utils/product';
55
import { createLog } from '../devContainers';
6-
import { UnpackArgv } from '../devContainersSpecCLI';
6+
import { OciAuthArgs, UnpackArgv } from '../devContainersSpecCLI';
77
import { buildDependencyGraph, generateMermaidDiagram } from '../../spec-configuration/containerFeaturesOrder';
88
import { DevContainerFeature } from '../../spec-configuration/configuration';
99
import { processFeatureIdentifier } from '../../spec-configuration/containerFeaturesConfiguration';
@@ -19,7 +19,7 @@ export function featuresInfoOptions(y: Argv) {
1919
.positional('feature', { type: 'string', demandOption: true, description: 'Feature Identifier' });
2020
}
2121

22-
export type FeaturesInfoArgs = UnpackArgv<ReturnType<typeof featuresInfoOptions>>;
22+
export type FeaturesInfoArgs = UnpackArgv<ReturnType<typeof featuresInfoOptions>> & OciAuthArgs;
2323

2424
export function featuresInfoHandler(args: FeaturesInfoArgs) {
2525
runAsyncHandler(featuresInfo.bind(null, args));
@@ -36,6 +36,7 @@ async function featuresInfo({
3636
'feature': featureId,
3737
'log-level': inputLogLevel,
3838
'output-format': outputFormat,
39+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
3940
}: FeaturesInfoArgs) {
4041
const disposables: (() => Promise<unknown> | undefined)[] = [];
4142
const dispose = async () => {
@@ -51,7 +52,7 @@ async function featuresInfo({
5152
terminalDimensions: undefined,
5253
}, pkg, new Date(), disposables, true);
5354

54-
const params = { output, env: process.env, outputFormat };
55+
const params = { output, env: process.env, outputFormat, allowedCrossOriginAuthHosts };
5556

5657
const jsonOutput: InfoJsonOutput = {};
5758

‎src/spec-node/featuresCLI/publish.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { LogLevel, mapLogLevel } from '../../spec-utils/log';
55
import { rmLocal } from '../../spec-utils/pfs';
66
import { getPackageConfig } from '../../spec-utils/product';
77
import { createLog } from '../devContainers';
8-
import { UnpackArgv } from '../devContainersSpecCLI';
8+
import { OciAuthArgs, UnpackArgv } from '../devContainersSpecCLI';
99
import { doFeaturesPackageCommand } from './packageCommandImpl';
1010
import { getCLIHost } from '../../spec-common/cliHost';
1111
import { loadNativeModule } from '../../spec-common/commonUtils';
@@ -21,7 +21,7 @@ export function featuresPublishOptions(y: Argv) {
2121
return publishOptions(y, 'feature');
2222
}
2323

24-
export type FeaturesPublishArgs = UnpackArgv<ReturnType<typeof featuresPublishOptions>>;
24+
export type FeaturesPublishArgs = UnpackArgv<ReturnType<typeof featuresPublishOptions>> & OciAuthArgs;
2525

2626
export function featuresPublishHandler(args: FeaturesPublishArgs) {
2727
runAsyncHandler(featuresPublish.bind(null, args));
@@ -31,7 +31,8 @@ async function featuresPublish({
3131
'target': targetFolder,
3232
'log-level': inputLogLevel,
3333
'registry': registry,
34-
'namespace': namespace
34+
'namespace': namespace,
35+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
3536
}: FeaturesPublishArgs) {
3637
const disposables: (() => Promise<unknown> | undefined)[] = [];
3738
const dispose = async () => {
@@ -49,7 +50,7 @@ async function featuresPublish({
4950
terminalDimensions: undefined,
5051
}, pkg, new Date(), disposables);
5152

52-
const params = { output, env: process.env };
53+
const params = { output, env: process.env, allowedCrossOriginAuthHosts };
5354

5455
// Package features
5556
const outputDir = path.join(os.tmpdir(), `/features-output-${Date.now()}`);

‎src/spec-node/featuresCLI/resolveDependencies.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Argv } from 'yargs';
33
import { LogLevel, mapLogLevel } from '../../spec-utils/log';
44
import { getPackageConfig } from '../../spec-utils/product';
55
import { createLog } from '../devContainers';
6-
import { UnpackArgv } from '../devContainersSpecCLI';
6+
import { OciAuthArgs, UnpackArgv } from '../devContainersSpecCLI';
77
import { isLocalFile } from '../../spec-utils/pfs';
88
import { DevContainerFeature } from '../../spec-configuration/configuration';
99
import { buildDependencyGraph, computeDependsOnInstallationOrder, generateMermaidDiagram } from '../../spec-configuration/containerFeaturesOrder';
@@ -34,7 +34,7 @@ export function featuresResolveDependenciesOptions(y: Argv) {
3434
});
3535
}
3636

37-
export type featuresResolveDependenciesArgs = UnpackArgv<ReturnType<typeof featuresResolveDependenciesOptions>>;
37+
export type featuresResolveDependenciesArgs = UnpackArgv<ReturnType<typeof featuresResolveDependenciesOptions>> & OciAuthArgs;
3838

3939
export function featuresResolveDependenciesHandler(args: featuresResolveDependenciesArgs) {
4040
runAsyncHandler(featuresResolveDependencies.bind(null, args));
@@ -43,6 +43,7 @@ export function featuresResolveDependenciesHandler(args: featuresResolveDependen
4343
async function featuresResolveDependencies({
4444
'workspace-folder': workspaceFolderArg,
4545
'log-level': inputLogLevel,
46+
'allow-cross-origin-auth-host': allowedCrossOriginAuthHosts,
4647
}: featuresResolveDependenciesArgs) {
4748
const disposables: (() => Promise<unknown> | undefined)[] = [];
4849
const dispose = async () => {
@@ -73,6 +74,7 @@ async function featuresResolveDependencies({
7374
const params = {
7475
output,
7576
env: process.env,
77+
allowedCrossOriginAuthHosts,
7678
};
7779

7880
const cwd = workspaceFolder || process.cwd();

0 commit comments

Comments
 (0)