ACK RRSA 跨账号免密访问
2026/11/09 kubernetes
Pod 里要读阿里云的资源,比如 SLS 日志,最省事的做法是把 AccessKey 塞进 Secret。但 AK 是长期有效的,泄露了很难察觉,跨账号的时候更麻烦:每个账号都要维护一份。
ACK 提供的 RRSA(RAM Roles for Service Accounts)可以解决这个问题:Pod 不保存任何 AK,只靠 Kubernetes 签发的 OIDC Token 换取临时凭证。
RRSA 是怎么工作的?
开启 RRSA 后,集群会成为一个 OIDC Provider。绑定了 RAM 角色的 ServiceAccount,它对应的 Pod 会被注入 OIDC Token 文件,以及角色和 Provider 的 ARN(通常是 ALIBABA_CLOUD_ROLE_ARN、ALIBABA_CLOUD_OIDC_PROVIDER_ARN、ALIBABA_CLOUD_OIDC_TOKEN_FILE 这几个环境变量)。
SDK 读到这些信息后,拿 Token 去调用 STS 的 AssumeRoleWithOIDC,换回一份有时效的 STS 凭证。整个过程应用代码基本无感。
第一步:配置 Role A 并投射 OIDC Token
跨账号之前,先要保证 Pod 能访问本账号的资源。这需要配三样东西:Role A 的信任策略、Role A 的权限策略,以及 Deployment 里的 Token 投射。
Role A 需要信任什么?
Role A 要信任集群的 OIDC Provider,并用 Condition 限定到具体的 ServiceAccount。不加 oidc:sub 的话,集群里任何 ServiceAccount 都能扮演这个角色。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"Federated": ["acs:ram::<账号A-UID>:oidc-provider/ack-rrsa-<cluster-id>"]
},
"Condition": {
"StringEquals": {
"oidc:aud": "sts.aliyuncs.com",
"oidc:iss": "<集群的 OIDC Issuer URL>",
"oidc:sub": "system:serviceaccount:<namespace>:<serviceaccount>"
}
}
}
]
}
Provider 的 ARN 和 Issuer URL 可以在 ACK 控制台开启 RRSA 之后看到。
Role A 需要什么权限?
Role A 本身要有读取本账号 SLS 的权限,同时要允许它去扮演账号 B 的 Role B:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["log:GetProject", "log:GetLogStore", "log:GetLogStoreLogs", "log:ListLogStores"],
"Resource": "acs:log:*:<账号A-UID>:project/<project>/*"
},
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "acs:ram::<账号B-UID>:role/<role-b>"
}
]
}
上面的 SLS Action 只是读日志的最小示例,按实际需要增减。
Deployment 如何投射 OIDC Token?
Token 是通过 Kubernetes 的 projected volume 投射的,audience 必须和信任策略里的 oidc:aud 一致:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sls-reader
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: sls-reader
template:
metadata:
labels:
app: sls-reader
spec:
serviceAccountName: sls-reader
containers:
- name: app
image: <your-image>
env:
- name: ALIBABA_CLOUD_ROLE_ARN
value: acs:ram::<账号A-UID>:role/<role-a>
- name: ALIBABA_CLOUD_OIDC_PROVIDER_ARN
value: acs:ram::<账号A-UID>:oidc-provider/ack-rrsa-<cluster-id>
- name: ALIBABA_CLOUD_OIDC_TOKEN_FILE
value: /var/run/secrets/ack.alibabacloud.com/rrsa-tokens/token
volumeMounts:
- name: rrsa-token
mountPath: /var/run/secrets/ack.alibabacloud.com/rrsa-tokens
readOnly: true
volumes:
- name: rrsa-token
projected:
sources:
- serviceAccountToken:
audience: sts.aliyuncs.com
expirationSeconds: 3600
path: token
serviceAccountName 要和信任策略里 oidc:sub 的 ServiceAccount 对上。ACK 也提供了 ack-pod-identity-webhook,可以按命名空间标签和 ServiceAccount 注解自动注入上面的环境变量和 Volume,不用每个 Deployment 手写。具体用法见 官方文档。
Pod 里的 SDK 读到这三个变量后,就会自动完成 AssumeRoleWithOIDC,拿到 Role A 的临时凭证。
第二步:配置 Role B,跨账号访问账号 B 的 SLS
Pod 拿到 Role A 的凭证以后,访问账号 B 要再扮演一次角色,也就是“链式扮演角色”:
- Pod 读取 OIDC Token,调用 STS 换取账号 A 下 Role A 的临时凭证。
- 用 Role A 的凭证访问账号 A 自己的 SLS。
- 用 Role A 的凭证再请求 STS,扮演账号 B 下的 Role B,拿到 Role B 的临时凭证。
- 切换为 Role B 的凭证,读取账号 B 的 SLS。
前两步已经在第一步里配好了。第 3 步要成功,需要两边同时放行:Role A 一侧允许去扮演 Role B,Role B 一侧允许被 Role A 扮演。
Role A 一侧
Role A 的权限策略里要有对 Role B 的 sts:AssumeRole,这条在第一步已经加好了:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "acs:ram::<账号B-UID>:role/<role-b>"
}
Role B 需要信任什么?
在账号 B 里创建 Role B,信任策略只信任账号 A 的 Role A,不要信任整个账号 A:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"RAM": ["acs:ram::<账号A-UID>:role/<role-a>"]
}
}
]
}
如果 Principal 写成 acs:ram::<账号A-UID>:root,账号 A 下被授权了 sts:AssumeRole 的任何用户或角色都能扮演 Role B,范围比实际需要大得多。
Role B 需要什么权限?
Role B 的权限策略和第一步类似,只是 Resource 换成账号 B 的 SLS Project:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["log:GetProject", "log:GetLogStore", "log:GetLogStoreLogs", "log:ListLogStores"],
"Resource": "acs:log:*:<账号B-UID>:project/<project>/*"
}
]
}
账号 B 这边不需要知道集群、OIDC Provider 或 ServiceAccount 的存在,只和 Role A 打交道。
应用侧要做什么?
Deployment 不用再改。应用拿到 Role A 的凭证后,用它请求 STS 的 AssumeRole,参数里的 RoleArn 填 Role B 的 ARN,返回的就是 Role B 的临时凭证。这一步需要在应用里显式调用,阿里云 Credentials 库默认的凭证链只会处理第一次 OIDC 换取。
为什么这样设计?
- 没有长期密钥:全程使用短时效的 STS 凭证,不需要保存明文 AK。
- 身份隔离到 ServiceAccount:凭证绑定的是 ServiceAccount 和命名空间,而不是整个集群或节点,一个业务拿不到另一个业务的权限。
- 职责清楚:账号 B 只需要信任 Role A,不关心集群里有多少 Pod、怎么部署,跨账号的配置和集群本身是解耦的。
总结
RRSA 把“Pod 是谁”交给 OIDC Token 证明,把“能做什么”交给 RAM 角色的策略。跨账号时再多套一层 Role B,就能做到全程没有 AK。
TODO:上面的策略和 YAML 是按文档整理的模板,尚未在实际集群验证。应用侧调用
AssumeRole的具体写法也待补充。