能力令牌
前提:服务已经装好,身份已经建进服务。这一页解决的是"调用签名的人不是 Agent 系统账号"的情况——比如整台机器只有一个人在用,Claude Code、脚本和终端都跑在管理员账号下。
为什么需要它
受保护数据面按内核 UID 认人:sign.sock 只接受登记过的 Agent 主体(macOS 预览里是 _anpagent,uid 601),管理员账号按设计连这个目录都进不去。让管理员账号直接签名有两条路:
- 整个 Agent 换到 Agent 账号下运行。这是边界成立的前提,能换就换;
- 管理员给这个账号签一张能力令牌。调用方仍是管理员账号,服务端按令牌里的主体和 DID 记授权。
能力令牌只回答"你是谁、只能碰哪个 DID",不回答"这份代码可不可信"。同一 UID 下的其他进程能读到同一个令牌文件,所以它不是进程间隔离,也不替代换账号。
两层授权
| 层 | 回答 | 生命周期 |
|---|---|---|
| 能力令牌 | 你是谁、只能碰哪个 DID | 每 Agent、每 DID 一张;跨重启保留;可单独撤销 |
| grant | 能签哪个操作、什么目标、多久、几次 | 按操作发;重启后作废,需要重新解锁与刷新 |
两者都过才签。令牌先于配额裁决:DID 不匹配时返回 CAPABILITY_SCOPE_DENIED,不会先扣掉一次。
1. 开启能力入口(默认关)
features.agent_capability_signing 默认 false:目录不建、socket 不监听、请求不可能命中。打开要换监听集合,必须重启一次;这次重启会锁住所有钥匙并作废全部 grant,所以把它和安装/接管放在同一个维护窗口里。
Linux 受保护部署:
./install.sh --upgrade --enable-agent-capabilitymacOS 本机预览:
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 --applymacOS 上不要指望 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. 签发令牌
管理员账号执行:
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,数据库、审计和日志里都没有令牌。回包丢了就重新签一张——服务端不保留可恢复的明文。
撤销:
anpio-key admin --config /Library/ANPIOKey/service.json \
capability revoke --capability-id <id>撤销是幂等的,下一条请求立即失败。到期与撤销走和 grant 相同的维护扫描,不新增后台任务。
3. 用它签名
底层命令是 anpio-key data。请求文件仍是裸的 DataRequest,CLI 自己套信封:
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,之后每条签名命令都自动带上:
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。
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 仍然决定操作、目标、期限和次数。