1 Commits

Author SHA1 Message Date
ciiiii
8574d1c619 Fix registry auth 2024-06-13 04:27:03 +08:00
5 changed files with 55 additions and 138 deletions

View File

@@ -13,15 +13,15 @@ jobs:
runs-on: ubuntu-latest
name: Build & Deploy
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
uses: cloudflare/wrangler-action@v3
env:
CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN || 'libcuda.so' }}
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{secrets.CF_ACCOUNT_ID}}
vars:
CUSTOM_DOMAIN
command: deploy --env production --minify src/index.js
environment: production
env:
CF_ACCOUNT_ID: ${{secrets.CF_ACCOUNT_ID}}

View File

@@ -1,25 +0,0 @@
name: Deploy to Cloudflare Workers(Staging)
on:
pull_request_target:
paths-ignore:
- '**.md'
repository_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
name: Build & Deploy
steps:
- uses: actions/checkout@v4
- name: Publish
uses: cloudflare/wrangler-action@v3
env:
CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN || 'libcuda.so' }}
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{secrets.CF_ACCOUNT_ID}}
vars:
CUSTOM_DOMAIN
command: deploy --env staging --minify src/index.js
environment: staging

View File

@@ -8,13 +8,13 @@
## Deploy
1. click the "Deploy With Workers" button
2. follow the instructions to fork and deploy
3. update routes as you requirement
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
[![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
## Config tutorial
1. use cloudflare worker host: only support proxy one registry
```javascript

View File

@@ -3,21 +3,14 @@ addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
const dockerHub = "https://registry-1.docker.io";
const routes = {
// 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,
"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",
};
function routeByHosts(host) {
@@ -43,7 +36,6 @@ async function handleRequest(request) {
}
);
}
const isDockerHub = upstream == dockerHub;
const authorization = request.headers.get("Authorization");
if (url.pathname == "/v2/") {
const newUrl = new URL(upstream + "/v2/");
@@ -58,9 +50,24 @@ async function handleRequest(request) {
redirect: "follow",
});
if (resp.status === 401) {
return responseUnauthorized(url);
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,
});
} else {
return resp;
}
return resp;
}
// get token
if (url.pathname == "/v2/auth") {
@@ -77,41 +84,16 @@ async function handleRequest(request) {
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);
}
return await fetchToken(wwwAuthenticate, url.searchParams, authorization);
}
// foward requests
const newUrl = new URL(upstream + url.pathname);
const newReq = new Request(newUrl, {
method: request.method,
headers: request.headers,
redirect: "manual",
redirect: "follow",
});
const resp = await fetch(newReq);
if (resp.status == 401) {
return responseUnauthorized(url);
}
return resp;
return await fetch(newReq);
}
function parseAuthenticate(authenticateStr) {
@@ -119,7 +101,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 {
@@ -128,36 +110,17 @@ function parseAuthenticate(authenticateStr) {
};
}
async function fetchToken(wwwAuthenticate, scope, authorization) {
async function fetchToken(wwwAuthenticate, searchParams, authorization) {
const url = new URL(wwwAuthenticate.realm);
if (wwwAuthenticate.service.length) {
url.searchParams.set("service", wwwAuthenticate.service);
}
if (scope) {
url.searchParams.set("scope", scope);
if (searchParams.get("scope")) {
url.searchParams.set("scope", searchParams.get("scope"));
}
const headers = new Headers();
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,39 +1,18 @@
name = "cloudflare-docker-proxy"
compatibility_date = "2023-12-01"
workers_dev = true
main = "src/index.js"
compatibility_date = "2021-12-07"
[dev]
ip = "0.0.0.0"
port = 8787
local_protocol = "http"
local_protocol="http"
upstream_protocol="https"
[env.vars]
CUSTOM_DOMAIN = "libcuda.so"
[vars]
MODE="production"
TARGET_UPSTREAM=""
[env.dev.vars]
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 = ""
MODE="debug"
TARGET_UPSTREAM="https://registry-1.docker.io"