23 Commits

Author SHA1 Message Date
c3166167eb merge upstream 2025-08-07 10:04:53 +08:00
Yisheng Cai
c595a14166 Redirect for index route (#121)
Some checks failed
Deploy to Cloudflare Workers / Build & Deploy (push) Has been cancelled
2025-06-01 16:13:44 +08:00
9009e8acd5 merge upstream 2025-03-05 09:52:29 +08:00
ciiiii
dfac79db55 Handle dockerhub blob redirect
Some checks failed
Deploy to Cloudflare Workers / Build & Deploy (push) Has been cancelled
2025-02-16 18:14:20 -06:00
ciiiii
e176bc4b29 Remove stage deployment 2025-02-16 12:35:01 -06:00
Yisheng Cai
47001590eb Change redirect mode from follow to manual (#104) 2025-02-17 02:30:59 +08:00
8cf519fc3e update wragler.toml 2024-10-28 10:31:05 +08:00
5a29e9a6ea Merge branch 'GitHub-master' 2024-10-28 10:26:44 +08:00
shxyke
703fae4e63 chore: Update deployment configuration for custom domain (#66)
Some checks failed
Deploy to Cloudflare Workers / Build & Deploy (push) Has been cancelled
2024-10-08 11:31:11 +08:00
简简aw
24d7c9fc90 Fix headers variable initialization (#71) 2024-10-08 11:30:07 +08:00
Yisheng Cai
696009dd69 Trigger staging deploy with pull_request_target (#24)
* Trigger with pull_request_target

* Fix event type

* Remove conditions
2024-10-08 11:27:23 +08:00
STARRY-S
1bc56391bb Fix containerd unauthorized response header (#63) 2024-10-08 11:24:49 +08:00
533e46e0dc 更新 src/index.js 2024-09-18 13:06:08 +08:00
0dc7dbd355 更新 README.md 2024-09-18 12:31:48 +08:00
9bb87fe8ef 更新 wrangler.toml
use lolicon.in
2024-09-18 12:28:39 +08:00
Yisheng Cai
aa61ad58cf Fix routes 2024-06-25 03:14:44 +08:00
Yisheng Cai
d82c47d53a Add public ecr registry 2024-06-25 03:08:08 +08:00
Yisheng Cai
74b03d2aaf Update README.md 2024-06-24 00:39:28 +08:00
Yisheng Cai
8df9982c2b Remove routes from config (#39) 2024-06-21 16:59:10 +08:00
意琦行
d1d3bc252c fix: domain typo (#23) 2024-06-21 16:52:51 +08:00
Yisheng Cai
00b8c83650 Deploy staging env (#21)
* Deploy staging env

* Fix ci

* Fix staging configuration

* Fix production configuration
2024-06-14 02:33:52 +08:00
Yisheng Cai
7fbc589095 Fix DockerHub library images default pulling case (#20) 2024-06-14 01:46:36 +08:00
Yisheng Cai
109f2d4d41 Fix registry auth (#15) 2024-06-13 04:28:56 +08:00
7 changed files with 3663 additions and 583 deletions

View File

@@ -13,15 +13,15 @@ jobs:
runs-on: ubuntu-latest
name: Build & Deploy
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: npm install
- uses: actions/checkout@v4
- name: Publish
uses: cloudflare/wrangler-action@2.0.0
uses: cloudflare/wrangler-action@v3
env:
CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN || 'libcuda.so' }}
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
env:
CF_ACCOUNT_ID: ${{secrets.CF_ACCOUNT_ID}}
accountId: ${{secrets.CF_ACCOUNT_ID}}
vars:
CUSTOM_DOMAIN
command: deploy --env production --minify src/index.js
environment: production

View File

@@ -2,19 +2,19 @@
![deploy](https://github.com/ciiiii/cloudflare-docker-proxy/actions/workflows/deploy.yaml/badge.svg)
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/ciiiii/cloudflare-docker-proxy)
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://git.yinr.cc/Yinr/cloudflare-docker-proxy.git)
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](https://github.com/ciiiii/cloudflare-helm-proxy).
## Deploy
1. fork this project
2. modify the link of the above button to your fork url
3. click the button, you will be redirected to the deploy page
1. click the "Deploy With Workers" button
2. follow the instructions to fork and deploy
3. update routes as you requirement
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/ciiiii/cloudflare-docker-proxy)
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://git.yinr.cc/Yinr/cloudflare-docker-proxy.git)
## Config tutorial
## Routes configuration tutorial
1. use cloudflare worker host: only support proxy one registry
```javascript

3023
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
"scripts": {
"format": "prettier --write '**/*.{js,css,json,md}'",
"build": "webpack",
"dev": "wrangler dev src/index.js --env dev"
"dev": "npx wrangler dev src/index.js --env dev"
},
"license": "MIT",
"main": "src/index.js"

View File

@@ -3,14 +3,21 @@ addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
const dockerHub = "https://registry-1.docker.io";
const routes = {
"docker.libcuda.so": "https://registry-1.docker.io",
"quay.libcuda.so": "https://quay.io",
"gcr.libcuda.so": "https://gcr.io",
"k8s-gcr.libcuda.so": "https://k8s.gcr.io",
"k8s.libcuda.so": "https://registry.k8s.io",
"ghcr.libcuda.so": "https://ghcr.io",
"cloudsmith.libcuda.so": "https://docker.cloudsmith.io",
// production
["docker." + CUSTOM_DOMAIN]: dockerHub,
["quay." + CUSTOM_DOMAIN]: "https://quay.io",
["gcr." + CUSTOM_DOMAIN]: "https://gcr.io",
["k8s-gcr." + CUSTOM_DOMAIN]: "https://k8s.gcr.io",
["k8s." + CUSTOM_DOMAIN]: "https://registry.k8s.io",
["ghcr." + CUSTOM_DOMAIN]: "https://ghcr.io",
["cloudsmith." + CUSTOM_DOMAIN]: "https://docker.cloudsmith.io",
["ecr." + CUSTOM_DOMAIN]: "https://public.ecr.aws",
// staging
["docker-staging." + CUSTOM_DOMAIN]: dockerHub,
};
function routeByHosts(host) {
@@ -25,26 +32,8 @@ function routeByHosts(host) {
async function handleRequest(request) {
const url = new URL(request.url);
const authorization = request.headers.get("Authorization");
if (url.pathname == "/v2/") {
if (authorization === null || authorization === "") {
const headers = new Headers();
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,
});
}
if (url.pathname == "/") {
return Response.redirect(url.protocol + "//" + url.host + "/v2/", 301);
}
const upstream = routeByHosts(url.hostname);
if (upstream === "") {
@@ -57,34 +46,24 @@ async function handleRequest(request) {
}
);
}
// check if need to authenticate
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 === 200) {
} else if (resp.status === 401) {
const headers = new Headers();
if (MODE == "debug") {
headers.set(
"Www-Authenticate",
`Bearer realm="${LOCAL_ADDRESS}/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;
if (resp.status === 401) {
return responseUnauthorized(url);
}
return resp;
}
// get token
if (url.pathname == "/v2/auth") {
@@ -101,16 +80,51 @@ async function handleRequest(request) {
return resp;
}
const wwwAuthenticate = parseAuthenticate(authenticateStr);
return await fetchToken(wwwAuthenticate, url.searchParams, authorization);
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",
// don't follow redirect to dockerhub blob upstream
redirect: isDockerHub ? "manual" : "follow",
});
return await fetch(newReq);
const resp = await fetch(newReq);
if (resp.status == 401) {
return responseUnauthorized(url);
}
// handle dockerhub blob redirect manually
if (isDockerHub && resp.status == 307) {
const location = new URL(resp.headers.get("Location"));
const redirectResp = await fetch(location.toString(), {
method: "GET",
redirect: "follow",
});
return redirectResp;
}
return resp;
}
function parseAuthenticate(authenticateStr) {
@@ -118,7 +132,7 @@ function parseAuthenticate(authenticateStr) {
// match strings after =" and before "
const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g;
const matches = authenticateStr.match(re);
if (matches === null || matches.length < 2) {
if (matches == null || matches.length < 2) {
throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`);
}
return {
@@ -127,17 +141,36 @@ function parseAuthenticate(authenticateStr) {
};
}
async function fetchToken(wwwAuthenticate, searchParams, authorization) {
async function fetchToken(wwwAuthenticate, scope, authorization) {
const url = new URL(wwwAuthenticate.realm);
if (wwwAuthenticate.service.length) {
url.searchParams.set("service", wwwAuthenticate.service);
}
if (searchParams.get("scope")) {
url.searchParams.set("scope", searchParams.get("scope"));
if (scope) {
url.searchParams.set("scope", scope);
}
headers = new Headers();
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization);
}
return await fetch(url, { method: "GET", headers: headers });
}
function responseUnauthorized(url) {
const headers = new Headers();
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,
});
}

View File

@@ -1,19 +1,30 @@
name = "cloudflare-docker-proxy"
workers_dev = true
main = "src/index.js"
compatibility_date = "2021-12-07"
compatibility_date = "2023-12-01"
[dev]
ip = "0.0.0.0"
port = 8787
local_protocol="http"
upstream_protocol="https"
local_protocol = "http"
[vars]
MODE="production"
LOCAL_ADDRESS=""
TARGET_UPSTREAM=""
[env.vars]
CUSTOM_DOMAIN = "lolicon.in"
[env.dev.vars]
MODE="debug"
TARGET_UPSTREAM="https://registry-1.docker.io"
MODE = "debug"
TARGET_UPSTREAM = "https://registry-1.docker.io"
CUSTOM_DOMAIN = "exmaple.com"
[env.production]
name = "cloudflare-docker-proxy"
[env.production.vars]
MODE = "production"
TARGET_UPSTREAM = ""
[env.staging]
name = "cloudflare-docker-proxy-staging"
# route = { pattern = "docker-staging.libcuda.so", custom_domain = true }
[env.staging.vars]
MODE = "staging"
TARGET_UPSTREAM = ""

1013
yarn.lock

File diff suppressed because it is too large Load Diff