Skip to content

能力令牌 ​

前提:服务已经装好,身份已经建进服务。这一页解决的是"调用签名的人不是 Agent 系统账号"的情况——比如整台机器只有一个人在用,Claude Code、脚本和终端都跑在管理员账号下。

为什么需要它 ​

受保护数据面按内核 UID 认人:sign.sock 只接受登记过的 Agent 主体(macOS 预览里是 _anpagent,uid 601),管理员账号按设计连这个目录都进不去。让管理员账号直接签名有两条路:

  1. 整个 Agent 换到 Agent 账号下运行。这是边界成立的前提,能换就换;
  2. 管理员给这个账号签一张能力令牌。调用方仍是管理员账号,服务端按令牌里的主体和 DID 记授权。

能力令牌只回答"你是谁、只能碰哪个 DID",不回答"这份代码可不可信"。同一 UID 下的其他进程能读到同一个令牌文件,所以它不是进程间隔离,也不替代换账号。

两层授权 ​

层回答生命周期
能力令牌你是谁、只能碰哪个 DID每 Agent、每 DID 一张;跨重启保留;可单独撤销
grant能签哪个操作、什么目标、多久、几次按操作发;重启后作废,需要重新解锁与刷新

两者都过才签。令牌先于配额裁决:DID 不匹配时返回 CAPABILITY_SCOPE_DENIED,不会先扣掉一次。

1. 开启能力入口(默认关) ​

features.agent_capability_signing 默认 false:目录不建、socket 不监听、请求不可能命中。打开要换监听集合,必须重启一次;这次重启会锁住所有钥匙并作废全部 grant,所以把它和安装/接管放在同一个维护窗口里。

Linux 受保护部署:

bash
./install.sh --upgrade --enable-agent-capability

macOS 本机预览:

bash
sudo /usr/bin/python3 deploy/macos/update-local.py \
  --binary /Library/ANPIOKey/bin/anpio-key \
  --sha256 "$(shasum -a 256 /Library/ANPIOKey/bin/anpio-key | cut -d' ' -f1)" \
  --agent-capability --archive-previous --apply

macOS 上不要指望 install.sh --upgrade --enable-agent-capability 翻这个开关:升级路径先比对摘要,已装二进制和本次构建相同就整段跳过,配置也不会改写。要么先换一个二进制,要么直接跑上面的 update-local.py。Linux 同理,跳过重启时 --enable-agent-capability 不生效。

开启后多出两个位置:

平台目录socket
Linux/run/anpio-key/capability/run/anpio-key/capability/capability.sock
macOS/Library/ANPIOKey/capability/Library/ANPIOKey/capability/capability.sock

目录是 service_uid:admin_gid、0750,socket 是 0660。准入只有一个条件:peer uid 在 admin_uids 里。组只负责把请求送到端点,主体和 DID 仍由令牌决定。

关掉它同样要重启:agent_capability_signing=false 后整条路不存在,表里的令牌保留但不再被读取。

2. 签发令牌 ​

管理员账号执行:

bash
anpio-key admin --config /Library/ANPIOKey/service.json capability mint \
  --uid 601 --did '<did>' --ttl-days 30 --label mybot --out /tmp/mybot.capability
  • --uid 与 --principal-id 二选一,必须是一个已启用的主体。macOS 预览里的 Agent 主体是 _anpagent(601)。
  • --did 是这张令牌唯一能签、能读的 DID。
  • --ttl-days 取 1..365,默认 30。
  • --out 写一个 0600 文件,且文件必须不存在;不给就把令牌打印到 stdout。
  • 回包里有 capability_id、did、label、expiry、token。把 capability_id 记下来,撤销时用它。

令牌是 32 字节随机数的 base64url,前缀 anp1_,共 47 个字符。明文只在这一次回包里出现:服务端只存 SHA-256,数据库、审计和日志里都没有令牌。回包丢了就重新签一张——服务端不保留可恢复的明文。

撤销:

bash
anpio-key admin --config /Library/ANPIOKey/service.json \
  capability revoke --capability-id <id>

撤销是幂等的,下一条请求立即失败。到期与撤销走和 grant 相同的维护扫描,不新增后台任务。

3. 用它签名 ​

底层命令是 anpio-key data。请求文件仍是裸的 DataRequest,CLI 自己套信封:

bash
anpio-key data --config /Library/ANPIOKey/service.json \
  --capability-file /tmp/mybot.capability --file request.json

能力文件必须是普通文件、0600、属当前 uid、不是符号链接、不超过 4 KiB;令牌形状是 anp1_ 加 43 个 base64url 字符。任何一条不满足,在连 socket 之前就拒绝。

anpio-cli 已经把这层接好:did attach 接受 --capability-file,把令牌复制到身份目录的 capability(0600),在 identity.json 里记 capabilityFile,之后每条签名命令都自动带上:

bash
anpio-cli did attach --config /Library/ANPIOKey/service.json \
  --did '<did>' --kid '<agent>/<profile>-sign' --grant http=<grant-id> \
  --agent mybot --capability-file /tmp/mybot.capability

anpio-cli anp sign --target https://awiki.me/anp-im/rpc --nonce probe2 --agent mybot

--capability-file 要跟 --config 一起给;--kid 和 --grant 仍是必需的——令牌只回答"以谁的身份行事",grant 才回答"能签什么、多久、几次"。provision 时打印的那条 attach 命令里已经带着正确的 kid 与 grant id,只需补上 --capability-file。

attach 在写任何东西之前先校验令牌,并比对 GET_CAPABILITIES 的 capability_did:令牌绑的不是这个 DID 就直接拒绝,不会留下一条签不了的记录。did create --key anpio-key 目前不会自动签发令牌;provision 完仍要管理员 mint 一次,再按上面的方式 attach。

4. 验证 ​

capability 入口的 GET_CAPABILITIES 比 sign 入口多两个字段:capability_auth: true 和 capability_did。capability_did 必须就是身份文件里的 DID。

bash
anpio-key data --config /Library/ANPIOKey/service.json \
  --capability-file /tmp/mybot.capability --file <(echo '{"operation":"GET_CAPABILITIES"}')

然后签一次真请求。签名头里的 keyid 应是 <did>#<agent>/<profile>-sign,不是 #key-1;对应的请求审计行带 capability_id。

5. 被拒绝时 ​

code含义处置
CAPABILITY_REQUIRED连接的 uid 不在 admin_uids 里换回管理员账号,或检查 --config 是否指向这台机器的配置
CAPABILITY_INVALID令牌形状不对、已撤销,或主体已停用找管理员重新签;撤销后继续使用也走这一条
CAPABILITY_EXPIRED令牌过期重新签
CAPABILITY_SCOPE_DENIED令牌绑的是另一个 DID不要拿 B 的令牌签 A;给目标 DID 单独签一张

这四条都不该重试。它们和 grant 的拒绝是两回事:令牌过了,grant 仍可能因为锁定、过期、次数用完或策略不符而拒绝。

6. 重启、撤销与身份目录 ​

  • 重启:令牌按哈希仍查得到,所以不需要重签;grant 全部作废、钥匙锁定。恢复顺序是管理员 unlock,再 anpio-cli did refresh --uid 601(或手工重建 grant)。
  • 撤销:每个请求都重查,下一条立即失败;不需要重启,也不影响别的令牌。
  • 删除身份目录:capability 文件随之消失,服务端那一行还在。不用了就顺手 capability revoke,不要只删文件。
  • 换机器:令牌不能从服务端取回。管理员重新签一张,再在目标机器的身份目录里 attach;或者继续用 _anpagent 账号。

7. 审计 ​

  • 服务启动时,开启能力入口会落一条 AGENT_CAPABILITY_ENABLED。
  • 签发、撤销、到期分别落 CAPABILITY_MINTED、CAPABILITY_REVOKED、CAPABILITIES_EXPIRED。
  • 每个数据请求的既有审计行增加 capability_id;令牌本身永远不进审计。

8. 边界 ​

  • 不是 UID 隔离。同一账号下任何进程都能读同一个令牌文件。
  • 一张令牌只绑一个 DID,没有通配符,也不能签别人的 DID。
  • 不改变"管理员不能当 sign 口主体"这条不变量;管理员能签名是因为令牌把主体指向了已登记的那个主体。
  • 令牌本身不产生签名权:grant 仍然决定操作、目标、期限和次数。

Apache-2.0 许可