跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

克隆私有仓库

最后更新 查看 MarkdownAgent 设置

将私有仓库克隆到 sandbox,同时将 access token 留在 Worker 中。如果克隆 URL 包含 token,sandbox 中的每条命令都可以读取它。相反,Durable Object 会拦截 Git 发往 github.com 的 HTTPS 请求。Worker 中的 entrypoint 接收每个请求、检查它并添加 token。

前提条件

当您运行 wrangler deploy 时,必须在本地运行 Docker。对于大多数人来说,安装 Docker 的最佳方式是按照安装 Docker Desktop 的文档 ↗进行操作。其他工具(如 Colima ↗)也可能有效。

您可以通过在终端中运行 docker info 命令来检查 Docker 是否正常运行。如果 Docker 正在运行,该命令将成功执行。如果 Docker 未运行,docker info 命令将挂起或返回包含 "Cannot connect to the Docker daemon" 消息的错误。

克隆仓库

  1. 在项目根目录创建安装 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"]
  2. 在 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 types
  3. Wrangler 提示时存储 token:

    npx wrangler secret put GITHUB_TOKEN
  4. 向 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 推送或读取其他仓库。

  5. 向 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),
            };
        }
    }
  6. 向 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 安全。

  7. 部署你的 Worker:

    npx wrangler deploy
  8. 向 Wrangler 打印的 workers.dev URL 上的 /clone 发送 POST 请求:

    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/clone --request POST
    Cloning into '/workspace'...

    代码仓库位于 /workspace 中,git remote get-url origin 会输出不含令牌的 URL。从沙盒执行 git push 会失败并返回 403。克隆内容会保留到实例停止为止。更多信息请参阅沙盒生命周期。

相关资源

这篇文档对您有帮助吗?