Skip to content

凭据

Cornus 可以向正在运行的工作负载提供密钥: 云凭据、LLM API 密钥或其他任何内容,同时密钥绝不会进入镜像、部署规范或 Pod 规范。凭据会在你的机器上签发 (使用你本地的凭据),通过实时 deploy-attach 连接经服务器中继,再由每个 Pod 的 caretaker 边车交付到容器中: 按需获取、按 TTL 缓存,并在临近过期时刷新。其配套功能是将工作负载的出站流量通过调用方路由,即客户端侧出站流量

工作原理

它在部署规范中声明为 credentials: 块,或在 Compose service 上声明为 x-cornus-credentials: (字段完全相同,参见下文的从 Compose 文件) 。它由 kubernetes 后端实现,客户端须在工作负载整个生命周期内保持会话以响应获取请求,因此 cornus deploy --detach 和主机后端会拒绝它。sources: 下的每个条目会命名一个客户端侧后端以生成密钥,并命名一个或多个将其呈现给容器的交付方式

只有后端名称和不含密钥的 config 会传到服务器;密钥由后端在获取时生成。

源后端

每个后端都从调用方自身的环境中签发凭据。

backend签发来源说明
static字面 config 值 (或文件)
execconfig.command 的标准输出JSON,或 config.key 下单个 raw
env客户端环境变量 (config.var)例如 ANTHROPIC_API_KEY
aws-sts通过 STS 获取的短期 AWS 凭据,使用你的 AWS 凭据链需要带 credaws tag 的二进制文件;模式包括 auto / assume-role / session-token / passthrough
anthropic / claude-code / codex你的本地 LLM 登录临近过期时重新读取短期 token
github-cli你本地的 gh auth login运行 gh auth token;GitHub Enterprise 用 hostname,选择账号用 user

交付类型

deliveries[].kind 默认为 endpoint

  • endpoint: caretaker 从回环 HTTP 端点提供凭据。provider: generic (默认值) 提供原生协议 (GET /credentials/<name> 返回 {"values":{...},"expiration":"..."}),并通过 CORNUS_CREDENTIALS_URL / CORNUS_CREDENTIAL_<NAME>_URL 向应用公布。provider: aws-imds 会以未修改的 AWS SDK 所期望的格式渲染凭据,见下方从 AWS STS 获取凭据。注入认证信息的 provider (anthropic-proxyopenai-proxygithub-proxy) 更进一步,自行持有凭据,因此容器根本不会收到它。
  • file: 将内容写入共享卷中的 path:format: 可为 json (默认)、env (KEY=VALUE 行)、raw (单个值) 或 aws-credentials (ini profile)。以 0600 权限写入。
  • env: 向应用容器注入 envVar:。该值在部署时获取一次,并存储在由 secretKeyRef 引用的 Kubernetes Secret 中 (因此不是 pod-spec 字面量),但它是静态的 (不会刷新) 且存在 etcd 中。对于短期或绝不应实体化的密钥,应优先使用 endpoint / file

信任

密钥会通过实时会话按每次获取响应,绝不会包含在规范或线路控制帧中。工作负载只能获取其自身部署会话所声明的凭据名称: 会话 id 是不可猜测的能力令牌,会在服务器中继处检查一次、在 caretaker 中再次检查。认证代理会在注入真实凭据前移除客户端提供的认证信息,因此工作负载既无法读取原始密钥,也无法伪造它。

另请参阅: deploy spec

将凭据代理给工作负载,而不写入镜像

声明 credentials: block;secret 在您的机器上签发并由 caretaker 交付,绝不进入镜像、spec 或 pod spec。

yaml
name: app
image: localhost:5000/app:v1
credentials:
  sources:
    - name: db
      backend: static                              # 在客户端生成密钥
      config: { username: app, password: s3cret }  # 供其他后端使用的非密钥配置
      deliveries:
        - { kind: endpoint, provider: generic }        # GET $CORNUS_CREDENTIALS_URL -> JSON
        - { kind: file, path: /creds/db.json, format: json }
  • kubernetes 后端上通过前台 cornus deploy --server session 实现 (客户端会在工作负载存续期响应 fetch,因此 --detach 会拒绝它)。
  • deliveries[].kind 可为 endpoint (默认)、fileenv;工作负载只能 fetch 自己 session 声明的凭据名称。

另请参阅: deploy spec

代理 LLM API 或向工作负载注入 API key

anthropic-proxyopenai-proxy 端点提供方比单纯提供凭据更进一步: caretaker 运行一个指向供应商 API 的回环反向代理,并自行注入认证请求头,因此工作负载调用 LLM 时无需持有自己的密钥。它会在应用上设置 ANTHROPIC_BASE_URL / OPENAI_BASE_URL,同时设置一个占位ANTHROPIC_API_KEY / OPENAI_API_KEY (因为即使真正持有凭据的是代理,SDK 和 CLI 在缺少各自的 key 环境变量时仍会拒绝启动) ,去除客户端发送的任何认证信息,并在每个请求中添加真实凭据。因此,编码代理工作负载可以使用你自己的 Claude Code / Codex 登录,而密钥从不进入容器。

yaml
credentials:
  sources:
    - name: claude
      backend: claude-code                  # 或: anthropic / env (config.var: ANTHROPIC_API_KEY)
      deliveries:
        - kind: endpoint
          provider: anthropic-proxy         # 设置 ANTHROPIC_BASE_URL 并注入请求头
          # upstream: https://my-gateway    # 可选: Azure OpenAI、本地网关或 mock
  • upstream 使代理指向任意兼容 Anthropic 或 OpenAI 的网关,而不是供应商默认端点 (https://api.anthropic.com / https://api.openai.com)。
  • 如需注入普通 env var,请将 backend: envconfig.varenv kind delivery 结合使用 (static,保存在 Kubernetes Secret 中;短生命周期 secret 优先使用 endpoint / file)。

API 密钥和 OAuth token

代理会透明处理两种凭据格式,因此无需改变工作负载,既可使用普通 API 密钥,也可使用 OAuth 登录 token:

  • API 密钥会在供应商的常规密钥请求头中发送 (Anthropic 使用 x-api-key)。
  • OAuth token,例如通过 claude / ant auth login 登录获取的 sk-ant-oat... token,会作为 Authorization: Bearer <token> 发送,并带有 Anthropic API 对 OAuth bearer token 所需的 anthropic-beta: oauth-2025-04-20 请求头。代理按以下顺序选取凭据值: oauth_token (强制 OAuth)、api_key (强制 API-key),否则使用 value / token

anthropic / claude-code / codex 源后端会读取你的本地登录存储,并在短期 OAuth access token 临近过期时刷新它 (codex 读取 ChatGPT 登录的 tokens.access_token,必要时回退到 API 密钥),因此长时间运行的代理无需你重新认证便可继续工作,同时 token 仍不会进入容器。

另请参阅: deploy spec

从 AWS STS 获取凭据

从您自身 AWS credential chain 签发短期 AWS credential,并以 SDK 预期形式提供。

yaml
credentials:
  sources:
    - name: aws
      backend: aws-sts
      config: { role_arn: arn:aws:iam::123456789012:role/app, region: us-east-1 }
      deliveries:
        - { kind: endpoint, provider: aws-imds, wellKnown: true }
        - { kind: file, path: /root/.aws/credentials, format: aws-credentials }
  • aws-sts 通过 STS 使用您的 AWS credential chain;需要带 credaws tag 的 binary,支持 auto / assume-role / session-token / passthrough 模式。

aws-imds 端点提供方会将代理的凭据渲染为 AWS SDK 已会查找的格式,因此未修改的 SDK 无需代码或应用改动即可获取它。该适配器是纯 HTTP,自身不依赖 AWS SDK,并通过一个端点响应两种格式:

  • ECS 容器凭据: GET /creds 返回 {AccessKeyId, SecretAccessKey, Token, Expiration}
  • EC2 IMDSv2: 先 PUT /latest/api/token,然后 GET /latest/meta-data/iam/security-credentials/<role> (列表公布一个合成角色 cornus)。IMDSv1 客户端只需跳过 token 步骤。

SDK 如何访问它取决于 wellKnown:

wellKnown绑定SDK 的发现方式所需条件
false (默认)回环地址Cornus 注入 AWS_CONTAINER_CREDENTIALS_FULL_URI=http://<loopback>/creds,这是 AWS SDK 遵从的标准 ECS 凭据环境变量。无额外要求
truePod netns 中的链路本地地址 169.254.169.254:80SDK 内建的 IMDSv2 路径:** 完全不需要环境变量**,与真实 EC2 实例一致。caretaker 需要 NET_ADMIN

这是一种交付适配器,并非需要运行的通用元数据服务: 它仅为工作负载的会话提供这一个代理凭据。GCP / Azure 元数据适配器也可通过同一机制接入。

另请参阅: deploy spec

用你自己的 gh 登录给工作负载 GitHub API 访问权限

github-cli 源在你的机器上运行 gh auth tokengithub-proxy 端点 provider 把这个 token 注入到工作负载的 GitHub REST API 调用中,于是容器在完全不持有 token 的情况下发出已认证的请求。

yaml
credentials:
  sources:
    - name: gh
      backend: github-cli
      ttl: 1h                                # gh 不报告过期时间,见下文
      deliveries:
        - kind: endpoint
          provider: github-proxy             # 设置 GITHUB_API_URL 并注入请求头
          # upstream: https://ghe.corp/api/v3   # GitHub Enterprise Server

token 从 gh 实际保存它的地方读取——在多数机器上是操作系统的密钥环,因此在直接读取 ~/.config/gh/hosts.yml 行不通的场景下这种方式仍然有效。gh 同样尊重 GH_TOKEN / GITHUB_TOKEN (其他主机则是 GH_ENTERPRISE_TOKEN) ,所以同一份 spec 在 CI 中也无需改动。配置键: hostname (GitHub Enterprise) 、user (在多个已登录账号间选择) 、commandtimeoutkey

如果 gh 不在你的 PATH 中,或者以别的名字安装,请在持有 deploy session 的机器上设置 CORNUS_GH_BIN——token 是在那台机器上签发的,因此这个变量也在那里读取。它的用途是在不修改共享 spec 的前提下适配某一台机器;显式的 config.command 是写 spec 的人做出的刻意选择 (比如一个不容绕过的 wrapper) ,因此优先级更高。优先级顺序: config.command,然后 CORNUS_GH_BIN,最后 gh

sh
CORNUS_GH_BIN=/opt/homebrew/bin/gh cornus deploy --server -f app.yaml

请显式设置 ttl:gh auth token 不报告过期时间,因此默认的 5 分钟会让每个副本每 5 分钟就重新运行一次 gh,并可能触碰你的密钥环。对于没有过期时间的 token,1 小时足够了。

这是 REST API,不是 git

git clonegit push 不会经过这个代理: git over HTTPS 直接连往 github.com:443,不受影响。gh CLI 本身也无法配合它工作——gh 接受的是主机名而非 base URL,并且始终使用 HTTPS,无法指向明文的回环 sidecar。这两种情况都应改为直接交付 token 本身,并接受它会进入容器:

yaml
deliveries:
  - { kind: endpoint }                                   # GET $CORNUS_CREDENTIALS_URL -> JSON
  - { kind: file, path: /run/secrets/gh-token, format: raw }

让你的客户端指向代理

只有 @actions/github 会自行读取 GITHUB_API_URL。其他客户端都需要加一行:

客户端
Octokit (JS)new Octokit({ baseUrl: process.env.GITHUB_API_URL })
PyGithubGithub(base_url=os.environ["GITHUB_API_URL"])
go-githubc := github.NewClient(nil); c.BaseURL, _ = url.Parse(os.Getenv("GITHUB_API_URL") + "/")

go-github 请直接设置 BaseURL,并且带结尾斜杠——不要用 WithEnterpriseURLs,它会给任何看起来不像 api.github.com 的主机追加 api/v3/,于是回环代理地址会变成 https://api.github.com/api/v3/... 并返回 404。

在默认 upstream 下,GITHUB_GRAPHQL_URL 会与 GITHUB_API_URL 一同设置。对 GitHub Enterprise 的 upstream 则被刻意省略: GHES 在 /api/v3 下提供 REST,而 GraphQL 在同级的 /api/graphql 下,单个代理无法同时到达;公布一个错误的 URL 只会让客户端不带凭据地直连 GHES。

容器里不会设置 GITHUB_TOKEN,这是刻意的: 占位值会被 gh、git 凭据 helper 以及任何直接调用 api.github.com 的脚本取用,从而把"没有凭据"变成一个远离根因的、令人困惑的 401。如果某个客户端坚持要求该变量存在,请自行在 spec 的 env: 中设一个哑值;代理会去除客户端发送的任何内容。

两个需要注意的点

hostnameupstream 保持一致。 源的 hostname 在你的机器上解析,交付的 upstream 在部署路径上解析,没有任何机制检查两者是否匹配。把 hostname: ghe.corpgithub-cli 源与默认 upstream 搭配,会把一份有效的 GitHub Enterprise 凭据发往 api.github.com。这两项务必成对配置。

注意 scope。 gh auth login 的 token 通常带有 repo (对你能访问的所有私有仓库的读写权限) 、read:org,往往还有 workflow,而 pod 中运行的任何东西都能通过回环代理以你的身份行事,且无法收窄。这比一个 LLM key 的影响范围大得多。失控的循环还会消耗你自己的速率限额。除非是你完全信任的工作负载,否则请签发 fine-grained PAT 并改用 static / env / exec 交付。

未覆盖的部分: uploads.github.com (release 资源) 是另一个主机;响应正文中的绝对 URL 不会被重写 (LinkLocation 请求头会被重写,因此分页和重定向仍留在代理上) ;私有 CA 后面的 GitHub Enterprise 实例需要把该 CA 放进 caretaker 镜像或通过 SSL_CERT_FILE 提供。

另请参阅: deploy spec

从 Compose 文件

Compose service 用 x-cornus-credentials: 声明同一个块,于是整个 stack——agent、它的数据库、它的缓存——用一条 cornus compose up 就能起来,而 agent 依然搭乘你自己的登录。

yaml
services:
  agent:
    image: localhost:5000/agent:v1
    x-cornus-credentials:
      - name: claude
        backend: claude-code
        deliveries:
          - { kind: endpoint, provider: anthropic-proxy }
  db:
    image: postgres:16
  • 该块既可以是上面这样的裸 source 列表,也可以是 spec 的对象形式 (用 sources: 承载同一个列表) ——spec 中的块可以原样粘贴过来。
  • 交付字段采用 Compose 的 snake_case 拼写 (well_knownenv_varvalue_key) ;spec 的 camelCase 拼写同样有效。两者之外的 key 会报错,而不是被悄悄忽略。
  • 声明该块的 service 会在其整个生命周期内持有一个 deploy-attach session。在 cornus compose up -d 下由项目的后台 agent 持有——因此与 cornus deploy --detach 不同,分离模式的 compose up 支持凭据。 up 那一行会说明原因 (brokering credentials) 。
  • 主机后端没有实现凭据交付: 它们要么拒绝该部署,要么发出警告并忽略这个 block。
  • 项目级的 x-cornus-credentials: block 为每个未自行声明的 service 提供默认值,service 级 block 会整体覆盖它。每个继承它的 service 各自持有一个 session。

另请参阅: cornus compose

Released under the Apache-2.0 License.