Skip to content

Instantly share code, notes, and snippets.

@thesadabc
Created June 24, 2024 06:33
Show Gist options
  • Save thesadabc/5c8e00793cc28c1b8f7c1d570abdf392 to your computer and use it in GitHub Desktop.
Save thesadabc/5c8e00793cc28c1b8f7c1d570abdf392 to your computer and use it in GitHub Desktop.
Cloudflare Workers上运行的Docker镜像代理服务。Docker proxy for Cloudflare Workers.
/**
* 代码改自 https://github.com/ciiiii/cloudflare-docker-proxy/tree/master
* 到 https://dash.cloudflare.com/?to=/:account/workers-and-pages 创建worker
* 将本代码修改参数后贴到worker里,点击部署即可使用
**/
addEventListener("fetch", (event) => {
event.passThroughOnException();
event.respondWith(handleRequest(event.request));
});
const dockerHub = "https://registry-1.docker.io";
const routes = {
// 这里要改成你用来访问的域名,可以用它
"dhub.xjp.in": dockerHub,
};
function routeByHosts(host) {
if (host in routes) {
return routes[host];
}
return "";
}
async function handleRequest(request) {
const url = new URL(request.url);
const upstream = routeByHosts(url.hostname);
if (upstream === "") {
return new Response(
JSON.stringify({
routes: routes,
}),
{
status: 404,
}
);
}
const isDockerHub = upstream == dockerHub;
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);
}
// check if need to authenticate
const resp = await fetch(newUrl.toString(), {
method: "GET",
headers: headers,
redirect: "follow",
});
if (resp.status === 401) {
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;
}
}
// get 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");
// autocomplete repo part into scope for DockerHub library images
// Example: 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);
}
// redirect for DockerHub library images
// Example: /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");
const redirectUrl = new URL(url);
redirectUrl.pathname = pathParts.join("/");
return Response.redirect(redirectUrl, 301);
}
}
// foward requests
const newUrl = new URL(upstream + url.pathname);
const newReq = new Request(newUrl, {
method: request.method,
headers: request.headers,
redirect: "follow",
});
return await fetch(newReq);
}
function parseAuthenticate(authenticateStr) {
// sample: Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io"
// match strings after =" and before "
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],
};
}
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 });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment