fix(proxy): tighten protocol routing and stabilize tests

This commit is contained in:
xixu-me committed 2026-03-06 19:41:14 +08:00
1 parent d983717fee
commit 71e239060c
9 files changed
+262 -149

No files matched your search

+67 -47
View File
@@ -124,6 +124,66 @@ export function getScopeFromUrl(url, effectivePath, platform) {
return scope;
}
/**
* Normalizes Docker Hub official images to the canonical library namespace.
* @param {string} platformKey
* @param {string} repoPath
* @returns {string} Normalized upstream repository path.
*/
function normalizeRepoPath(platformKey, repoPath) {
if (platformKey === 'cr-docker' && repoPath && !repoPath.includes('/')) {
return `library/${repoPath}`;
}
return repoPath;
}
/**
* Resolves the target registry and scope for Docker auth proxy requests.
* @param {URL} url
* @param {{ [key: string]: string }} platforms
* @returns {{ platformKey: string, upstreamScope: string }} Resolved auth target info.
*/
function resolveDockerAuthTarget(url, platforms) {
const scope = url.searchParams.get('scope') || '';
const pathMatch = url.pathname.match(/^\/cr\/([^/]+)\/v2\/auth\/?$/);
let platformKey = pathMatch ? `cr-${pathMatch[1]}` : '';
let repoPath = '';
let upstreamScope = scope;
if (scope) {
const parts = scope.split(':');
if (parts.length >= 3 && parts[0] === 'repository') {
const [, fullRepoPath] = parts;
if (fullRepoPath.startsWith('cr/')) {
for (const key of SORTED_PLATFORMS) {
if (!key.startsWith('cr-')) continue;
const prefix = key.replace(/-/g, '/');
if (fullRepoPath.startsWith(`${prefix}/`)) {
platformKey = key;
repoPath = fullRepoPath.slice(prefix.length + 1);
break;
}
}
} else {
repoPath = fullRepoPath;
}
repoPath = normalizeRepoPath(platformKey, repoPath);
upstreamScope = repoPath ? `repository:${repoPath}:${parts.slice(2).join(':')}` : scope;
}
}
if (!platformKey || !platforms[platformKey]) {
throw new Error('Unsupported registry platform in scope');
}
return { platformKey, upstreamScope };
}
/**
* Creates an unauthorized (401) response for container registry authentication.
*
@@ -162,46 +222,14 @@ export function responseUnauthorized(url) {
* @returns {Promise<Response>} The response (token or error)
*/
export async function handleDockerAuth(request, url, config) {
const scope = url.searchParams.get('scope');
if (!scope) {
return createErrorResponse('Missing scope parameter', 400);
let target;
try {
target = resolveDockerAuthTarget(url, config.PLATFORMS);
} catch (error) {
return createErrorResponse(error instanceof Error ? error.message : String(error), 400);
}
// Parse scope to find the target platform and repository
// Format: repository:cr/docker/library/ubuntu:pull
// We need to extract 'cr/docker' as the platform
const parts = scope.split(':');
if (parts.length < 3 || parts[0] !== 'repository') {
// If not a repository scope, or invalid format, we can't easily proxy it
return createErrorResponse('Invalid scope format', 400);
}
const [, fullRepoPath] = parts; // e.g., cr/docker/library/ubuntu
let platformKey = '';
let repoPath = '';
// Find the platform from the start of the repo path
// Try to match 'cr/docker', 'cr/ghcr', etc.
// We need to find which platform prefix matches the start of fullRepoPath
// Uses global SORTED_PLATFORMS which is imported
for (const key of SORTED_PLATFORMS) {
if (!key.startsWith('cr-')) continue;
// Convert key cr-docker to cr/docker for matching
const prefix = key.replace(/-/g, '/');
if (fullRepoPath.startsWith(`${prefix}/`)) {
platformKey = key;
repoPath = fullRepoPath.slice(prefix.length + 1); // +1 for the slash
break;
}
}
if (!platformKey || !config.PLATFORMS[platformKey]) {
return createErrorResponse('Unsupported registry platform in scope', 400);
}
const upstreamUrl = config.PLATFORMS[platformKey];
const upstreamUrl = config.PLATFORMS[target.platformKey];
const authorization = request.headers.get('Authorization');
// 1. Fetch the upstream root (v2) to get the proper realm and service
@@ -225,14 +253,6 @@ export async function handleDockerAuth(request, url, config) {
const wwwAuthenticate = parseAuthenticate(authenticateStr);
// 2. Construct the new scope for the upstream registry
// We replace our prefixed path with the actual repo path
// e.g. repository:cr/docker/library/ubuntu:pull -> repository:library/ubuntu:pull
// However, we also need to respect the service name if possible,
// but usually we just need to fix the repository part of the scope.
const newScope = `repository:${repoPath}:${parts.slice(2).join(':')}`;
// 3. Fetch the token from the upstream realm
return await fetchToken(wwwAuthenticate, newScope, authorization || '');
return await fetchToken(wwwAuthenticate, target.upstreamScope, authorization || '');
}