CF-Workers-docker.io/_worker.js

159 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const dockerHub = "https://registry-1.docker.io"; // Docker Hub 的 URL
// 定义路由表
const routes = {
// 生产环境
"quay": "https://quay.io",
"gcr": "https://gcr.io",
"k8s-gcr": "https://k8s.gcr.io",
"k8s": "https://registry.k8s.io",
"ghcr": "https://ghcr.io",
"cloudsmith": "https://docker.cloudsmith.io",
// 测试环境
"test": dockerHub,
};
// 根据主机名选择对应的上游地址
function routeByHosts(host) {
if (host in routes) {
return routes[host];
}
if (MODE == "debug") {
return TARGET_UPSTREAM; // 调试模式返回目标上游
}
return ""; // 如果没有匹配到,返回空字符串
}
export default {
// 处理请求
async fetch(request, env, ctx) {
const url = new URL(request.url); // 解析请求 URL
const hostName = url.hostname.split('.')[0]; // 获取主机名的第一部分
console.log(hostName);
const upstream = routeByHosts(hostName); // 获取上游地址
if (upstream === "") {
// 如果没有匹配到上游地址,返回 404
return new Response(
JSON.stringify({
routes: routes,
}),{
status: 404,
});
}
const isDockerHub = upstream == dockerHub; // 检查是否是 Docker Hub
const authorization = request.headers.get("Authorization"); // 获取请求头中的授权信息
if (url.pathname == "/v2/") {
const newUrl = new URL(upstream + "/v2/");
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization); // 如果有授权信息,设置授权头
}
// 检查是否需要认证
const resp = await fetch(newUrl.toString(), {
method: "GET",
headers: headers,
redirect: "follow",
});
if (resp.status === 401) {
if (MODE == "debug") {
headers.set(
"Www-Authenticate",
`Bearer realm="http://${url.host}/v2/auth",service="cloudflare-docker-proxy"`
);
} else {
headers.set(
"Www-Authenticate",
`Bearer realm="https://${url.hostname}/v2/auth",service="cloudflare-docker-proxy"`
);
}
return new Response(JSON.stringify({ message: "UNAUTHORIZED" }), {
status: 401,
headers: headers,
});
} else {
return resp; // 返回上游响应
}
}
// 获取 token
if (url.pathname == "/v2/auth") {
const newUrl = new URL(upstream + "/v2/");
const resp = await fetch(newUrl.toString(), {
method: "GET",
redirect: "follow",
});
if (resp.status !== 401) {
return resp;
}
const authenticateStr = resp.headers.get("WWW-Authenticate");
if (authenticateStr === null) {
return resp;
}
const wwwAuthenticate = parseAuthenticate(authenticateStr);
let scope = url.searchParams.get("scope");
// 自动补全 DockerHub 的 library 镜像
// 例如repository:busybox:pull => repository:library/busybox:pull
if (scope && isDockerHub) {
let scopeParts = scope.split(":");
if (scopeParts.length == 3 && !scopeParts[1].includes("/")) {
scopeParts[1] = "library/" + scopeParts[1];
scope = scopeParts.join(":");
}
}
return await fetchToken(wwwAuthenticate, scope, authorization);
}
// 重定向 DockerHub 的 library 镜像
// 例如:/v2/busybox/manifests/latest => /v2/library/busybox/manifests/latest
if (isDockerHub) {
const pathParts = url.pathname.split("/");
if (pathParts.length == 5) {
pathParts.splice(2, 0, "library");
let redirectUrl = new URL(url);
redirectUrl.pathname = pathParts.join("/");
return Response.redirect(redirectUrl.toString(), 301);
}
}
// 转发请求
const newUrl = new URL(upstream + url.pathname);
const newReq = new Request(newUrl, {
method: request.method,
headers: request.headers,
redirect: "follow",
});
return await fetch(newReq);
}
};
// 解析 Www-Authenticate 头
function parseAuthenticate(authenticateStr) {
// 示例Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io"
// 匹配 =" 后的字符串,直到 " 结束
const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g;
const matches = authenticateStr.match(re);
if (matches == null || matches.length < 2) {
throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`);
}
return {
realm: matches[0],
service: matches[1],
};
}
// 获取 token
async function fetchToken(wwwAuthenticate, scope, authorization) {
const url = new URL(wwwAuthenticate.realm);
if (wwwAuthenticate.service.length) {
url.searchParams.set("service", wwwAuthenticate.service);
}
if (scope) {
url.searchParams.set("scope", scope);
}
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization);
}
return await fetch(url, { method: "GET", headers: headers });
}