接入 AI service
做完后,AI service 带证书以渠道身份换取委托令牌,代表发消息的人调产品 tools。
| 环境 | 登录地址 | 带证书调用的接口地址 |
|---|---|---|
local | https://id.local.globalbes.sg | https://id.local.globalbes.sg:19443 |
test | https://id.test.globalbes.sg | https://id.test.globalbes.sg:9443 |
staging | 待定 | 待定 |
prod | 待定 | 待定 |
Sign in to send requests to see which of these steps are done. Sign in
生成私钥和证书请求
在哪里做:AI service 服务器
跑一条 openssl 命令,得到私钥 ai-service-key.pem 和证书请求 ai-service.csr。证书请求拿去签发;私钥留在 AI 服务器上,不发给任何人。
CERT_DIR="$HOME/bes-certs/ai" mkdir -p "$CERT_DIR" SERVICE_ACCOUNT_ID=$(uuidgen | tr A-Z a-z) openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -subj "/CN=AI" \ -addext "subjectAltName=URI:urn:bes:service:$SERVICE_ACCOUNT_ID" \ -addext "basicConstraints=critical,CA:FALSE" \ -addext "keyUsage=critical,digitalSignature" \ -addext "extendedKeyUsage=clientAuth" \ -keyout "$CERT_DIR/ai-service-key.pem" -out "$CERT_DIR/ai-service.csr"
SERVICE_ACCOUNT_ID每次新生成,写进证书唯一的 URI SANurn:bes:service:<uuid>;登记平台账号后它就是账号的service_account_id。- 私钥
ai-service-key.pem放进 AI 部署的密钥存储。
签发证书
在哪里做:客户端 CA 所在的机器
用客户端 CA 给证书请求签名,得到证书 ai-service.pem。证书拷回 AI 服务器,和私钥一起调 Control;登记平台账号时也要用它。
前提:OpenSSL 3.0 及以上,openssl version 显示 OpenSSL 3.x。Mac 自带的 /usr/bin/openssl 是 LibreSSL,不认 -copy_extensions:先执行 brew install openssl@3,再执行 export PATH="$(brew --prefix openssl@3)/bin:$PATH"。
本环境(test)
CA_DIR:待定,test 环境的客户端 CA 位置未定。- 签发前把
ai-service.csr拷到客户端 CA 所在机器的~/bes-certs/ai/;签发后把ai-service.pem拷回 AI 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/ai" mkdir -p "$CERT_DIR" CA_DIR="待定" openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/ai-service.csr" \ -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \ -CAserial "$CERT_DIR/ai-service.srl" -CAcreateserial -copy_extensions copy \ -out "$CERT_DIR/ai-service.pem"
其他环境
local
本地的客户端 CA 在本机,证书请求不用拷,在本机执行:
CERT_DIR="$HOME/bes-certs/ai"
mkdir -p "$CERT_DIR"
CA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/bes/local/stack/secrets/control"
openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/ai-service.csr" \
-CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \
-CAserial "$CERT_DIR/ai-service.srl" -CAcreateserial -copy_extensions copy \
-out "$CERT_DIR/ai-service.pem"
staging
CA_DIR:待定,staging 环境的客户端 CA 位置未定。- 签发前把
ai-service.csr拷到客户端 CA 所在机器的~/bes-certs/ai/;签发后把ai-service.pem拷回 AI 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/ai" mkdir -p "$CERT_DIR" CA_DIR="待定" openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/ai-service.csr" \ -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \ -CAserial "$CERT_DIR/ai-service.srl" -CAcreateserial -copy_extensions copy \ -out "$CERT_DIR/ai-service.pem"
prod
CA_DIR:待定,prod 环境的客户端 CA 位置未定。- 签发前把
ai-service.csr拷到客户端 CA 所在机器的~/bes-certs/ai/;签发后把ai-service.pem拷回 AI 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/ai" mkdir -p "$CERT_DIR" CA_DIR="待定" openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/ai-service.csr" \ -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \ -CAserial "$CERT_DIR/ai-service.srl" -CAcreateserial -copy_extensions copy \ -out "$CERT_DIR/ai-service.pem"
登记平台账号
在哪里做:Control 服务器
执行 bes-control seed-trusted-caller,得到 AI 的平台账号,状态 active。之后 AI 带这张证书调查询接口,Control 按这个账号放行。
- 账号带 Control 的
ai-runtime角色(role),命令输出应为role=ai-runtime;列表不返回角色码,自动检查按显示名、product_id: null和状态判断。 --display-name写AI:测试台按这个名字找 AI 的平台账号。- 重复执行保留同一个账号。
本环境(test)
- 在 Control 服务器上执行
bes-control的方式:待定,test 环境的 Control 部署方式未定。 ai-service.pem拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role ai-runtime --certificate "待定/ai-service.pem" --display-name AI
其他环境
local
本地 Control 在容器 control-api 里,先把证书拷进 ~/.local/share/bes/local/stack/secrets/control/,每次执行时它被复制成容器里的 /run/bes/control/ai-service.pem。在本机执行:
BES_INFRA="$HOME/Projects/bes-infra" # bes-infra 仓的路径
cp "$HOME/bes-certs/ai/ai-service.pem" "${XDG_DATA_HOME:-$HOME/.local/share}/bes/local/stack/secrets/control/ai-service.pem"
"$BES_INFRA/envs/local/dev" stack compose exec -T control-api /bin/sh /opt/bes/stack-entrypoint.sh \
bes-control seed-trusted-caller --role ai-runtime --certificate /run/bes/control/ai-service.pem --display-name AI
staging
- 在 Control 服务器上执行
bes-control的方式:待定,staging 环境的 Control 部署方式未定。 ai-service.pem拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role ai-runtime --certificate "待定/ai-service.pem" --display-name AI
prod
- 在 Control 服务器上执行
bes-control的方式:待定,prod 环境的 Control 部署方式未定。 ai-service.pem拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role ai-runtime --certificate "待定/ai-service.pem" --display-name AI --confirm-production
做完这一部分,手里有:AI 的证书、私钥和平台账号。每次换取委托令牌时带证书和私钥。
为发消息的人换委托令牌
在哪里做:AI service 代码
AI service 每次调产品 tools 之前,以自己的 ai-runtime 平台账号,代表 Marcus Lim 向 BIM 换取一张五分钟以内的委托令牌。
POSTDelegation Exchange程序里发这个请求(id 用前面步骤返回的):
带:证书
请求:
POST https://id.test.globalbes.sg:9443/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token_type=urn%3Abes%3Aparams%3Aoauth%3Atoken-type%3Achannel-identity&subject_token=whatsapp%3A6591234567&tenant_id=7c0e2a5e-3b1f-4d6a-9a53-1f2f6d0c8a11&project_id=e2f7a1c4-0d35-4b8e-a6c9-7d18f4b2a903&audience=b41d9a70-6c2e-4a0f-8e7d-52a9c3f0d614&scope=document.read
返回:
HTTP/1.1 200 OK
{
"access_token": "<delegation_token>",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 300,
"scope": "document.read"
}
- 样例使用 Marcus Lim 的 WhatsApp 地址
6591234567和 BIM 产品;该地址须已在 Control 验证,且人、成员、项目与业务授权均有效。 - 同一租户、项目须有闸门产品的有效授权;Control 设置
channel_exchange_gate_product_code默认是ai-service。 - BIM 的 active 服务账号对应
svc-<service_account_id>客户端须登记deployment_version和document.readscope;最终 scope 还受 Marcus 的当前产品权限限制。 - 每次调产品 tools 前现换,用完即弃,不缓存令牌,不传
context_id,不需要用户令牌。channel_assurance说明身份怎么建立;是否按它限制动作由产品决定。 - 群到项目的绑定由 会话服务 判断,不推进 Control 的
binding_version。
带令牌调产品 tools
在哪里做:AI service 代码
将新换取的委托令牌原样放进 Authorization: Bearer 调产品 tools,用完即弃。
- 将委托令牌原样放在
Authorization: Bearer <delegation_token>,交给对应目标产品的 tools 接口。 - 每次调用前都换一张新令牌,用完即弃,
exp - iat不超过 300 秒。 - 撤权在下一次换令牌时生效;Control 按当时的人、绑定、成员关系和授权状态校验。
做完这一部分,手里有:AI service 每次调产品 tools 前取得一张新令牌,带令牌调用后即丢弃。
展示上下文名称
在哪里做:AI service 代码
显示 Control 返回的租户与项目名称;租户级项目名为 null。
身份上下文的显示名称来自 identity.resolve:每项包含 tenant_name 与 project_name;租户级 project_name 为 null。示例:{product_id: "<产品 UUID>", tenant_id: "<租户 UUID>", tenant_name: "Acme Construction", project_name: null}。
做完这一部分,手里有:按 Control 当前上下文展示租户与项目名称。