mirror of
https://github.com/ciiiii/cloudflare-docker-proxy.git
synced 2025-12-06 14:42:51 +08:00
Compare commits
20 Commits
yscai/fix-
...
yscai/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
001a9fdd51 | ||
|
|
dfac79db55 | ||
|
|
e176bc4b29 | ||
|
|
47001590eb | ||
|
|
703fae4e63 | ||
|
|
24d7c9fc90 | ||
|
|
696009dd69 | ||
|
|
1bc56391bb | ||
|
|
aa61ad58cf | ||
|
|
d82c47d53a | ||
|
|
74b03d2aaf | ||
|
|
8df9982c2b | ||
|
|
d1d3bc252c | ||
|
|
00b8c83650 | ||
|
|
7fbc589095 | ||
|
|
109f2d4d41 | ||
|
|
3e8acaf2ba | ||
|
|
55c5a48e19 | ||
|
|
5c2126259e | ||
|
|
9ae8f6464f |
18
.github/workflows/deploy.yaml
vendored
18
.github/workflows/deploy.yaml
vendored
@@ -13,15 +13,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build & Deploy
|
name: Build & Deploy
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- 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@1.2.0
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
env:
|
||||||
|
CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN || 'libcuda.so' }}
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
env:
|
accountId: ${{secrets.CF_ACCOUNT_ID}}
|
||||||
CF_ACCOUNT_ID: ${{secrets.CF_ACCOUNT_ID}}
|
vars:
|
||||||
|
CUSTOM_DOMAIN
|
||||||
|
command: deploy --env production --minify src/index.js
|
||||||
|
environment: production
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
### 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
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -2,16 +2,19 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](github.com/ciiiii/cloudflare-helm-proxy).
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/ciiiii/cloudflare-docker-proxy)
|
[](https://deploy.workers.cloudflare.com/?url=https://github.com/ciiiii/cloudflare-docker-proxy)
|
||||||
|
|
||||||
1. fork this project
|
> If you're looking for proxy for helm, maybe you can try [cloudflare-helm-proxy](https://github.com/ciiiii/cloudflare-helm-proxy).
|
||||||
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
|
## Deploy
|
||||||
|
|
||||||
|
1. click the "Deploy With Workers" button
|
||||||
|
2. follow the instructions to fork and deploy
|
||||||
|
3. update routes as you requirement
|
||||||
|
|
||||||
|
[](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
|
1. use cloudflare worker host: only support proxy one registry
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
3023
package-lock.json
generated
Normal file
3023
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,13 @@
|
|||||||
"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"
|
||||||
|
|||||||
140
src/index.js
140
src/index.js
@@ -3,14 +3,21 @@ addEventListener("fetch", (event) => {
|
|||||||
event.respondWith(handleRequest(event.request));
|
event.respondWith(handleRequest(event.request));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dockerHub = "https://registry-1.docker.io";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
"docker.libcuda.so": "https://registry-1.docker.io",
|
// production
|
||||||
"quay.libcuda.so": "https://quay.io",
|
["docker." + CUSTOM_DOMAIN]: dockerHub,
|
||||||
"gcr.libcuda.so": "https://gcr.io",
|
["quay." + CUSTOM_DOMAIN]: "https://quay.io",
|
||||||
"k8s-gcr.libcuda.so": "https://k8s.gcr.io",
|
["gcr." + CUSTOM_DOMAIN]: "https://gcr.io",
|
||||||
"k8s.libcuda.so": "https://registry.k8s.io",
|
["k8s-gcr." + CUSTOM_DOMAIN]: "https://k8s.gcr.io",
|
||||||
"ghcr.libcuda.so": "https://ghcr.io",
|
["k8s." + CUSTOM_DOMAIN]: "https://registry.k8s.io",
|
||||||
"cloudsmith.libcuda.so": "https://docker.cloudsmith.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) {
|
function routeByHosts(host) {
|
||||||
@@ -25,32 +32,40 @@ 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 == "/v2/") {
|
if (url.pathname == "/") {
|
||||||
const headers = new Headers();
|
return Response.redirect(url.protocol + "//" + url.host + "/v2/", 301);
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const upstream = routeByHosts(url.hostname);
|
const upstream = routeByHosts(url.hostname);
|
||||||
if (upstream === "") {
|
if (upstream === "") {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
routes: routes,
|
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) {
|
||||||
|
return responseUnauthorized(url);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
// get token
|
||||||
if (url.pathname == "/v2/auth") {
|
if (url.pathname == "/v2/auth") {
|
||||||
const newUrl = new URL(upstream + "/v2/");
|
const newUrl = new URL(upstream + "/v2/");
|
||||||
const resp = await fetch(newUrl.toString(), {
|
const resp = await fetch(newUrl.toString(), {
|
||||||
@@ -65,15 +80,51 @@ async function handleRequest(request) {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
const wwwAuthenticate = parseAuthenticate(authenticateStr);
|
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 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,
|
||||||
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) {
|
function parseAuthenticate(authenticateStr) {
|
||||||
@@ -81,7 +132,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 {
|
||||||
@@ -90,13 +141,36 @@ function parseAuthenticate(authenticateStr) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchToken(wwwAuthenticate, searchParams) {
|
async function fetchToken(wwwAuthenticate, scope, authorization) {
|
||||||
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 (searchParams.get("scope")) {
|
if (scope) {
|
||||||
url.searchParams.set("scope", searchParams.get("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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,39 @@
|
|||||||
name = "cloudflare-docker-proxy"
|
name = "cloudflare-docker-proxy"
|
||||||
type = "webpack"
|
compatibility_date = "2023-12-01"
|
||||||
|
|
||||||
account_id = ""
|
|
||||||
workers_dev = true
|
|
||||||
route = ""
|
|
||||||
zone_id = ""
|
|
||||||
|
|
||||||
webpack_config = "webpack.config.js"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[vars]
|
[env.vars]
|
||||||
MODE="production"
|
CUSTOM_DOMAIN = "libcuda.so"
|
||||||
LOCAL_ADDRESS=""
|
|
||||||
TARGET_UPSTREAM=""
|
|
||||||
|
|
||||||
[env.dev.vars]
|
[env.dev.vars]
|
||||||
MODE="debug"
|
MODE = "debug"
|
||||||
LOCAL_ADDRESS="http://192.168.50.160:8787"
|
TARGET_UPSTREAM = "https://registry-1.docker.io"
|
||||||
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 = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user