use registry hosts for scoped credentials

Parse registry URLs so scoped ECR credentials use the same config directory
for login, logout, and Buildx lookup. Preserve explicit default ports.

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2026-10-05 13:58:27 +02:00
parent d3b9cdfa58
commit 4e12eabf92
3 changed files with 56 additions and 3 deletions
+21 -1
View File
@@ -3,7 +3,7 @@ import * as path from 'path';
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js'; import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
import {getAuthList, getInputs} from '../src/context.js'; import {getAuthList, getInputs, scopeToConfigDir} from '../src/context.js';
afterEach(() => { afterEach(() => {
vi.restoreAllMocks(); vi.restoreAllMocks();
@@ -188,3 +188,23 @@ test.each([
}); });
expect(auth.ecr).toBe(expected); expect(auth.ecr).toBe(expected);
}); });
// prettier-ignore
test.each([
['ghcr.io', 'ghcr.io'],
['ghcr.io/', 'ghcr.io'],
['https://ghcr.io', 'ghcr.io'],
['https://ghcr.io/', 'ghcr.io'],
['https://ghcr.io/v2/', 'ghcr.io'],
['http://localhost:5000', 'localhost:5000'],
['http://localhost:80', 'localhost:80'],
['https://ghcr.io:443', 'ghcr.io:443'],
['https://012345678910.dkr.ecr.eu-west-3.amazonaws.com', '012345678910.dkr.ecr.eu-west-3.amazonaws.com'],
['https://docker.io', 'registry-1.docker.io']
])('scopeToConfigDir uses the registry host for %s', (registry, host) => {
expect(scopeToConfigDir(registry, 'myorg/myimage@push')).toBe(path.join(Buildx.configDir, 'config', host, 'myorg', 'myimage') + '@push');
});
test('scopeToConfigDir rejects a URL whose host escapes the config directory', () => {
expect(() => scopeToConfigDir('https://../', '@push')).toThrow(/Invalid registry/);
});
+31 -1
View File
@@ -1,8 +1,12 @@
import {afterEach, expect, test, vi} from 'vitest'; import {afterEach, expect, test, vi} from 'vitest';
import * as path from 'path';
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js'; import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js';
import {login, loginStandard, logout} from '../src/docker.js'; import * as aws from '../src/aws.js';
import {getAuthList} from '../src/context.js';
import {login, loginECR, loginStandard, logout} from '../src/docker.js';
import * as dockerhub from '../src/dockerhub.js'; import * as dockerhub from '../src/dockerhub.js';
afterEach(() => { afterEach(() => {
@@ -92,3 +96,29 @@ test('logout calls exec', async () => {
ignoreReturnCode: true ignoreReturnCode: true
}); });
}); });
test('loginECR and logout use the config dir of the registry host', async () => {
const execSpy = vi.spyOn(Docker, 'getExecOutput').mockResolvedValue({
exitCode: 0,
stdout: '',
stderr: ''
});
const registry = '012345678910.dkr.ecr.eu-west-3.amazonaws.com';
vi.spyOn(aws, 'getRegistriesData').mockResolvedValue([
{
registry: `https://${registry}`,
username: 'AWS',
password: 'world'
}
]);
const scope = 'myapp@push';
const [auth] = getAuthList({registry, username: 'CAFEBABE', password: 'DEADBEEF', scope, ecr: 'auto', logout: true, registryAuth: ''});
await loginECR(auth.registry, auth.username, auth.password, auth.scope);
await logout(auth.registry, auth.configDir);
const expectedConfigDir = path.join(Buildx.configDir, 'config', registry, 'myapp@push');
expect(execSpy).toHaveBeenCalledTimes(2);
expect(execSpy.mock.calls[0][1]?.env?.DOCKER_CONFIG).toBe(expectedConfigDir);
expect(execSpy.mock.calls[1][1]?.env?.DOCKER_CONFIG).toBe(expectedConfigDir);
});
+4 -1
View File
@@ -77,8 +77,11 @@ export function scopeToConfigDir(registry: string, scope?: string): string {
if (scopeDisabled() || !scope || scope === '') { if (scopeDisabled() || !scope || scope === '') {
return ''; return '';
} }
const schemeEnd = registry.indexOf('://');
// A neutral scheme preserves explicit ports, including HTTP(S) default ports.
const registryHost = schemeEnd === -1 ? registry : new URL(`registry://${registry.slice(schemeEnd + 3)}`).host;
const configRoot = path.resolve(Buildx.configDir, 'config'); const configRoot = path.resolve(Buildx.configDir, 'config');
const registryDir = path.resolve(configRoot, registry === 'docker.io' ? 'registry-1.docker.io' : registry); const registryDir = path.resolve(configRoot, registryHost === 'docker.io' ? 'registry-1.docker.io' : registryHost);
if (!isChildPath(configRoot, registryDir)) { if (!isChildPath(configRoot, registryDir)) {
throw new Error(`Invalid registry '${registry}': resolved config path escapes the Buildx config directory`); throw new Error(`Invalid registry '${registry}': resolved config path escapes the Buildx config directory`);
} }