20 Commits

Author SHA1 Message Date
ciiiii
001a9fdd51 Redirect for index route 2025-06-01 16:13:12 +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
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
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
Yisheng Cai
3e8acaf2ba Support registry login (#14)
* Fix dev env

* Support registry login

* Update README
2024-06-13 04:05:23 +08:00
Yisheng Cai
55c5a48e19 Fix link in README.md 2024-01-29 21:45:34 +08:00
ciiiii
5c2126259e Specify entrypoint 2023-03-05 23:28:39 +08:00
Yisheng Cai
9ae8f6464f Fix registry no need auth (#3)
* Migrate to wrangler2

* Support registry no need auth
2023-03-05 23:26:15 +08:00
8 changed files with 3954 additions and 193 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

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
### cloudflare worker ###
worker
.wrangler
# Created by https://www.toptal.com/developers/gitignore/api/osx
# Edit at https://www.toptal.com/developers/gitignore?templates=osx

View File

@@ -2,16 +2,19 @@
![deploy](https://github.com/ciiiii/cloudflare-docker-proxy/actions/workflows/deploy.yaml/badge.svg)
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](github.com/ciiiii/cloudflare-helm-proxy).
## Deploy
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/ciiiii/cloudflare-docker-proxy)
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
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](https://github.com/ciiiii/cloudflare-helm-proxy).
## Config tutorial
## Deploy
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)
## 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

@@ -8,11 +8,13 @@
"devDependencies": {
"prettier": "^2.4.1",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
"webpack-cli": "^4.9.1",
"wrangler": "^3.36.0"
},
"scripts": {
"format": "prettier --write '**/*.{js,css,json,md}'",
"build": "webpack"
"build": "webpack",
"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,6 +32,9 @@ function routeByHosts(host) {
async function handleRequest(request) {
const url = new URL(request.url);
if (url.pathname == "/") {
return Response.redirect(url.protocol + "//" + url.host + "/v2/", 301);
}
const upstream = routeByHosts(url.hostname);
if (upstream === "") {
return new Response(
@@ -36,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") {
@@ -80,16 +80,51 @@ async function handleRequest(request) {
return resp;
}
const wwwAuthenticate = parseAuthenticate(authenticateStr);
return await fetchToken(wwwAuthenticate, url.searchParams);
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) {
@@ -97,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 {
@@ -106,13 +141,36 @@ function parseAuthenticate(authenticateStr) {
};
}
async function fetchToken(wwwAuthenticate, searchParams) {
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);
}
return await fetch(url, { method: "GET", 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,39 @@
name = "cloudflare-docker-proxy"
workers_dev = true
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 = "libcuda.so"
[env.dev.vars]
MODE="debug"
LOCAL_ADDRESS="http://192.168.10.102:8787"
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"
# routes = [
# { pattern = "docker.libcuda.so", custom_domain = true },
# { pattern = "quay.libcuda.so", custom_domain = true },
# { pattern = "gcr.libcuda.so", custom_domain = true },
# { pattern = "k8s-gcr.libcuda.so", custom_domain = true },
# { pattern = "k8s.libcuda.so", custom_domain = true },
# { pattern = "ghcr.libcuda.so", custom_domain = true },
# { pattern = "cloudsmith.libcuda.so", custom_domain = true },
# ]
[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 = ""

910
yarn.lock

File diff suppressed because it is too large Load Diff