2 Commits

Author SHA1 Message Date
ciiiii
42ab9cb5bc Support registry no need auth 2023-03-05 23:25:11 +08:00
ciiiii
433eb86b4d Migrate to wrangler2 2023-03-05 23:25:08 +08:00
8 changed files with 192 additions and 3962 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,29 +1,17 @@
# cloudflare-docker-proxy # cloudflare-docker-proxy
> ### ⚠️ **Important Notice**
> <span style="color:#d73a49;font-weight:bold">Docker Hub is rate-limiting Cloudflare Worker IPs, causing frequent <code>429</code> errors.</span>
> <span style="color:#d73a49;font-weight:bold">This project is currently NOT recommended for production use.</span>
Due to the current instability, this project is not recommended for production use.
We will provide updates as soon as more information becomes available.
![deploy](https://github.com/ciiiii/cloudflare-docker-proxy/actions/workflows/deploy.yaml/badge.svg) ![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) > If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](github.com/ciiiii/cloudflare-helm-proxy).
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](https://github.com/ciiiii/cloudflare-helm-proxy).
## Deploy ## 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) [![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. 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
## Config tutorial
1. use cloudflare worker host: only support proxy one registry 1. use cloudflare worker host: only support proxy one registry
```javascript ```javascript

3023
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,11 @@
"devDependencies": { "devDependencies": {
"prettier": "^2.4.1", "prettier": "^2.4.1",
"webpack": "^5.65.0", "webpack": "^5.65.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1"
"wrangler": "^3.36.0"
}, },
"scripts": { "scripts": {
"format": "prettier --write '**/*.{js,css,json,md}'", "format": "prettier --write '**/*.{js,css,json,md}'",
"build": "webpack", "build": "webpack"
"dev": "npx wrangler dev src/index.js --env dev"
}, },
"license": "MIT", "license": "MIT",
"main": "src/index.js" "main": "src/index.js"

View File

@@ -3,21 +3,14 @@ addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request)); event.respondWith(handleRequest(event.request));
}); });
const dockerHub = "https://registry-1.docker.io";
const routes = { const routes = {
// production "docker.libcuda.so": "https://registry-1.docker.io",
["docker." + CUSTOM_DOMAIN]: dockerHub, "quay.libcuda.so": "https://quay.io",
["quay." + CUSTOM_DOMAIN]: "https://quay.io", "gcr.libcuda.so": "https://gcr.io",
["gcr." + CUSTOM_DOMAIN]: "https://gcr.io", "k8s-gcr.libcuda.so": "https://k8s.gcr.io",
["k8s-gcr." + CUSTOM_DOMAIN]: "https://k8s.gcr.io", "k8s.libcuda.so": "https://registry.k8s.io",
["k8s." + CUSTOM_DOMAIN]: "https://registry.k8s.io", "ghcr.libcuda.so": "https://ghcr.io",
["ghcr." + CUSTOM_DOMAIN]: "https://ghcr.io", "cloudsmith.libcuda.so": "https://docker.cloudsmith.io",
["cloudsmith." + CUSTOM_DOMAIN]: "https://docker.cloudsmith.io",
["ecr." + CUSTOM_DOMAIN]: "https://public.ecr.aws",
// staging
["docker-staging." + CUSTOM_DOMAIN]: dockerHub,
}; };
function routeByHosts(host) { function routeByHosts(host) {
@@ -32,9 +25,6 @@ function routeByHosts(host) {
async function handleRequest(request) { async function handleRequest(request) {
const url = new URL(request.url); const url = new URL(request.url);
if (url.pathname == "/") {
return Response.redirect(url.protocol + "//" + url.host + "/v2/", 301);
}
const upstream = routeByHosts(url.hostname); const upstream = routeByHosts(url.hostname);
if (upstream === "") { if (upstream === "") {
return new Response( return new Response(
@@ -46,24 +36,34 @@ async function handleRequest(request) {
} }
); );
} }
const isDockerHub = upstream == dockerHub; // check if need to authenticate
const authorization = request.headers.get("Authorization");
if (url.pathname == "/v2/") { if (url.pathname == "/v2/") {
const newUrl = new URL(upstream + "/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(), { const resp = await fetch(newUrl.toString(), {
method: "GET", method: "GET",
headers: headers,
redirect: "follow", redirect: "follow",
}); });
if (resp.status === 401) { if (resp.status === 200) {
return responseUnauthorized(url); } 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;
} }
return resp;
} }
// get token // get token
if (url.pathname == "/v2/auth") { if (url.pathname == "/v2/auth") {
@@ -80,51 +80,16 @@ async function handleRequest(request) {
return resp; return resp;
} }
const wwwAuthenticate = parseAuthenticate(authenticateStr); const wwwAuthenticate = parseAuthenticate(authenticateStr);
let scope = url.searchParams.get("scope"); return await fetchToken(wwwAuthenticate, url.searchParams);
// 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 // foward requests
const newUrl = new URL(upstream + url.pathname); const newUrl = new URL(upstream + url.pathname);
const newReq = new Request(newUrl, { const newReq = new Request(newUrl, {
method: request.method, method: request.method,
headers: request.headers, headers: request.headers,
// don't follow redirect to dockerhub blob upstream redirect: "follow",
redirect: isDockerHub ? "manual" : "follow",
}); });
const resp = await fetch(newReq); return 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) { function parseAuthenticate(authenticateStr) {
@@ -132,7 +97,7 @@ function parseAuthenticate(authenticateStr) {
// match strings after =" and before " // match strings after =" and before "
const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g; const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g;
const matches = authenticateStr.match(re); 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}`); throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`);
} }
return { return {
@@ -141,36 +106,13 @@ function parseAuthenticate(authenticateStr) {
}; };
} }
async function fetchToken(wwwAuthenticate, scope, authorization) { async function fetchToken(wwwAuthenticate, searchParams) {
const url = new URL(wwwAuthenticate.realm); const url = new URL(wwwAuthenticate.realm);
if (wwwAuthenticate.service.length) { if (wwwAuthenticate.service.length) {
url.searchParams.set("service", wwwAuthenticate.service); url.searchParams.set("service", wwwAuthenticate.service);
} }
if (scope) { if (searchParams.get("scope")) {
url.searchParams.set("scope", scope); url.searchParams.set("scope", searchParams.get("scope"));
} }
const headers = new Headers(); return await fetch(url, { method: "GET", 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,39 +1,19 @@
name = "cloudflare-docker-proxy" name = "cloudflare-docker-proxy"
compatibility_date = "2023-12-01" workers_dev = true
compatibility_date = "2021-12-07"
[dev] [dev]
ip = "0.0.0.0" ip = "0.0.0.0"
port = 8787 port = 8787
local_protocol = "http" local_protocol="http"
upstream_protocol="https"
[env.vars] [vars]
CUSTOM_DOMAIN = "libcuda.so" MODE="production"
LOCAL_ADDRESS=""
TARGET_UPSTREAM=""
[env.dev.vars] [env.dev.vars]
MODE = "debug" MODE="debug"
TARGET_UPSTREAM = "https://registry-1.docker.io" LOCAL_ADDRESS="http://192.168.10.102:8787"
CUSTOM_DOMAIN = "exmaple.com" TARGET_UPSTREAM="https://registry-1.docker.io"
[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