将私有仓库克隆到 sandbox,同时将 access token 留在 Worker 中。如果克隆 URL 包含 token,sandbox 中的每条命令都可以读取它。相反,Durable Object 会拦截 Git 发往 github.com 的 HTTPS 请求。Worker 中的 entrypoint 接收每个请求、检查它并添加 token。
- 带有 Durable Object 的 Worker,该 Durable Object 使用 Durable Object 调度策略 启动 container。要创建一个,请参阅 运行 Linux 命令。
- 对仓库内容具有读取权限的 GitHub access token。
当您运行 wrangler deploy 时,必须在本地运行 Docker。对于大多数人来说,安装 Docker 的最佳方式是按照安装 Docker Desktop 的文档 ↗进行操作。其他工具(如 Colima ↗)也可能有效。
您可以通过在终端中运行 docker info 命令来检查 Docker 是否正常运行。如果 Docker 正在运行,该命令将成功执行。如果 Docker 未运行,docker info 命令将挂起或返回包含 "Cannot connect to the Docker daemon" 消息的错误。
-
在项目根目录创建安装 Git 的
Dockerfile:Dockerfiledockerfile FROM node:24-trixie-slim RUN apt-get update \ && apt-get install --yes --no-install-recommends ca-certificates git \ && rm -rf /var/lib/apt/lists/* CMD ["sleep", "infinity"] -
在
wrangler.jsonc中命名仓库,将 token 声明为 secret,并将Dockerfile构建为具名镜像。将<OWNER>/<REPOSITORY>替换为你的仓库:{ "vars": { "REPOSITORY": "<OWNER>/<REPOSITORY>", }, "secrets": { "required": ["GITHUB_TOKEN"], }, "containers": [ { "class_name": "MyContainer", "scheduling_policy": "durable_object", "images": { "git": { "dockerfile": "./Dockerfile", }, }, }, ], }[vars] REPOSITORY = "<OWNER>/<REPOSITORY>" [secrets] required = [ "GITHUB_TOKEN" ] [[containers]] class_name = "MyContainer" scheduling_policy = "durable_object" [containers.images.git] dockerfile = "./Dockerfile"npx wrangler typesyarn wrangler typespnpm wrangler types -
Wrangler 提示时存储 token:
npx wrangler secret put GITHUB_TOKENyarn wrangler secret put GITHUB_TOKENpnpm wrangler secret put GITHUB_TOKEN -
向 Worker 添加 entrypoint,只接受获取该仓库的请求,并添加 token:
src/index.jsjs import { WorkerEntrypoint } from "cloudflare:workers"; export class GitGateway extends WorkerEntrypoint { async fetch(request) { const url = new URL(request.url); const repository = `/${this.env.REPOSITORY}.git`; const fetchesRepository = (request.method === "GET" && url.pathname === `${repository}/info/refs` && url.search === "?service=git-upload-pack") || (request.method === "POST" && url.pathname === `${repository}/git-upload-pack`); if (url.hostname !== "github.com" || !fetchesRepository) { return new Response("Forbidden", { status: 403 }); } const headers = new Headers(request.headers); const credentials = btoa(`x-access-token:${this.env.GITHUB_TOKEN}`); headers.set("Authorization", `Basic ${credentials}`); return fetch(new Request(request, { headers })); } }src/index.tsts import { WorkerEntrypoint } from "cloudflare:workers"; export class GitGateway extends WorkerEntrypoint<Env> { async fetch(request: Request): Promise<Response> { const url = new URL(request.url); const repository = `/${this.env.REPOSITORY}.git`; const fetchesRepository = (request.method === "GET" && url.pathname === `${repository}/info/refs` && url.search === "?service=git-upload-pack") || (request.method === "POST" && url.pathname === `${repository}/git-upload-pack`); if (url.hostname !== "github.com" || !fetchesRepository) { return new Response("Forbidden", { status: 403 }); } const headers = new Headers(request.headers); const credentials = btoa(`x-access-token:${this.env.GITHUB_TOKEN}`); headers.set("Authorization", `Basic ${credentials}`); return fetch(new Request(request, { headers })); } }git-upload-pack提供克隆和 fetch。entrypoint 只允许一个仓库使用它,并拒绝所有其他请求。即使 token 允许,sandbox 中的代码也无法通过git-receive-pack推送或读取其他仓库。 -
向 Durable Object 添加拦截
github.com并克隆仓库的方法:src/index.tsts export class MyContainer extends DurableObject<Env> { // ... async clone(): Promise<{ exitCode: number; stderr: string }> { const container = this.ctx.container; if (!container) { throw new Error("No container is configured"); } await container.interceptOutboundHttps( "github.com", this.ctx.exports.GitGateway, ); if (!container.running) { container.start({ image: container.images.git, // The container can reach only the hostnames you intercept enableInternet: false, }); // Keep the clone for later requests await container.setInactivityTimeout(10 * 60 * 1000); } const process = await container.exec( [ "git", "clone", "--depth=1", `https://github.com/${this.env.REPOSITORY}.git`, "/workspace", ], { env: { // The intercept terminates TLS with a certificate that the container // CA certificate signs. Git reads the CA certificate from this path GIT_SSL_CAINFO: "/etc/cloudflare/certs/cloudflare-containers-ca.crt", }, }, ); const output = await process.output(); return { exitCode: output.exitCode, // Git prints its progress to standard error stderr: new TextDecoder().decode(output.stderr), }; } } -
向 Worker 添加克隆仓库的路由:
src/index.tsts if (url.pathname === "/clone" && request.method === "POST") { const sandbox = env.MY_CONTAINER.getByName("repo"); const { exitCode, stderr } = await sandbox.clone(); return new Response(stderr, { status: exitCode === 0 ? 200 : 500 }); }该路由使用独立的沙盒名称。已运行的实例会保留启动时的镜像和 Internet 访问设置,因此该实例不会包含 Git。
Git 失败时,该路由会以
500以及来自 Git 的错误进行响应。sandbox 继续运行时,第二次请求会以这种方式失败,因为/workspace已经保存了克隆。请先认证调用方,以免其他人通过 sandbox 读取仓库。更多信息请参阅 Sandbox 安全。
-
部署你的 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy -
向 Wrangler 打印的
workers.devURL 上的/clone发送POST请求:curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/clone --request POSTCloning into '/workspace'...代码仓库位于
/workspace中,git remote get-url origin会输出不含令牌的 URL。从沙盒执行git push会失败并返回403。克隆内容会保留到实例停止为止。更多信息请参阅沙盒生命周期。
- 运行 Git 仓库中的测试:安装并测试已克隆的仓库。
- 从 sandbox 调用经过认证的 API:API 的相同模式。
interceptOutboundHttps