AK/SK 别进聊天记录:华为云多账号凭证的三层隔离与 hcloud 实战坑位
脚本化操作云平台,第一个绕不开的问题不是接口怎么调,而是密钥放在哪。贴进聊天框、写进 crontab、塞进环境变量,三种做法都在扩大泄露面。这篇讲我在一台 ARM 服务器上给华为云做凭证分层的过程,目标是:日常脚本手里永远只有一次性凭证,长期密钥只有一个 600 权限的文件能读到。
一、三层边界
先把边界划清楚,后面每个坑都是某一层没守住导致的:
- 第一层 · 账号密钥:主账号 AK/SK,唯一合法存放点是
~/.secrets/*.env,权限 600,不 source 到交互 shell,只被需要它的脚本读取。 - 第二层 · CLI profile:hcloud 的 profile 把账号、密钥、区域、超时绑在一起,切账号不用改环境变量,也就不会出现「A 账号的密钥打 B 账号的接口」。
- 第三层 · 临时凭证:用 IAM 委托换 STS token,有效期一天,落盘到临时目录,跑完删除。业务脚本只认这一层。
这样一层层下来,能泄露的东西从「永久密钥」变成「24 小时的临时 token」,而且 token 只能干委托里授权的事。
二、坑一:hcloud 根本不读环境变量
直觉做法是 export HUAWEICLOUD_SDK_AK=... 然后调 CLI。实测直接 Unauthorized——CLI 走的是自己的配置体系,环境变量只对 SDK 生效。两条路:要么每次显式传参,要么写进 profile。
# 方式一:显式传参(适合一次性调用,不留痕)
hcloud IAM KeystoneListAuthDomains --cli-access-key="$AK" --cli-secret-key="$SK" --cli-region=cn-north-4
# 方式二:写进 profile(适合反复调用)
hcloud configure set --cli-access-key="$AK" --cli-secret-key="$SK" --cli-domain-id="$DOMAIN" --cli-region=cn-north-4 --cli-profile=default
注意方式二的收尾:配置会落在 ~/.hcloud/config.json 明文里。用完必须把 accessKeyId / secretAccessKey 清空。这里有个小坑——hcloud configure set --cli-access-key="" 会报参数错误,清不掉,只能改 JSON:
python3 - <<'EOF'
import json, os
p = os.path.expanduser('~/.hcloud/config.json')
d = json.load(open(p))
for prof in d.values():
if isinstance(prof, dict):
prof['accessKeyId'] = ''
prof['secretAccessKey'] = ''
json.dump(d, open(p, 'w'), indent=1)
EOF
三、坑二:默认超时短到必失败
IAM 和 STS 这类鉴权接口跨区域调用经常 10~30 秒才回。CLI 默认的读超时只有几秒,表现是 API calling timed out,而脚本里再包一层解析,就会变成「解析 hcloud 输出失败(无 JSON 内容)」——看起来像返回格式问题,实际是根本没等到响应。
hcloud configure set --cli-read-timeout=90 --cli-connect-timeout=20
同样的错我在 Node 脚本里也踩过一次:子进程默认超时 30 秒,而真实调用要 10~30 秒,边缘概率直接抛 spawnSync ETIMEDOUT。所以凡是跨公网的鉴权调用,超时一律按 90 秒配,别用默认值。
四、坑三:委托 409 不等于可用
临时凭证要靠一个「委托」(agency)来换。第一次跑会报「委托不存在」,因为平台不会自动建;自己建一次,24 小时过期。第二天再跑,创建接口回 409 已存在,看着像成功,但换 token 时又报找不到——因为同名委托的 expire_time 已经过了。
# 先查过期时间,别直接建
hcloud IAM ListAgencies --cli-region=cn-north-4 | jq -r '.agencies[] | "\(.id) \(.name) \(.expire_time)"'
# 过期就删掉重建
hcloud IAM DeleteAgency --agency_id="$OLD_ID" --cli-region=cn-north-4
hcloud IAM CreateAgency --agency.name=SELF_VERIFY --agency.domain_id="$DOMAIN" --agency.trust_domain_id="$DOMAIN" --agency.duration=ONEDAY --cli-region=cn-north-4
判定标准写清楚:409 只说明名字被占用,能不能用只看 expire_time 是否在未来。这一条让排障从「玄学」变成一次查询。
五、第三层落地:换 token、用完就删
拿到委托后换 STS,把三件套(ak / sk / token)写成 0600 的临时文件,交给业务脚本;脚本读到内存后立刻删文件。关键是 finally 里删——中途抛异常也要删,否则临时凭证就变成长期凭证躺在磁盘上。
import json, os, subprocess, tempfile
creds = os.path.join(tempfile.mkdtemp(), 'sts.json')
try:
subprocess.run([
'python3', 'gen_sts.py',
'--account', domain, '--region', 'cn-north-4',
'--creds-out', creds,
], check=True)
d = json.load(open(creds))
ak, sk, tok = d['accessKeyId'], d['secretAccessKey'], d['securityToken']
# ... 交给业务调用
finally:
if os.path.exists(creds):
os.remove(creds)
验证方式别靠「没报错」:用临时凭证调一个只读接口,看 HTTP 状态码。
curl -s -o /dev/null -w '%{http_code}
' -H "X-Auth-Token: $TOKEN" "https://iam.myhuaweicloud.com/v3/auth/projects"
200 才算通;401 说明 token 拼错或过期,403 说明委托里没这条权限——三种状态对应三个完全不同的修法,比看日志猜快得多。
六、为什么值得做
这套东西第一次搭要花一两个小时,之后每次跑多三行代码。换来的是:脚本仓库里没有明文密钥,服务器上只有一个 600 文件,临时凭证的权限被委托卡死、寿命只有一天。真要出事,暴露窗口从「永久」压缩到「至多 24 小时,且只能做委托允许的那几件事」。
顺带一个副产品:因为凭证要手动换,反而逼着把「谁在调什么接口」梳理清楚了。这比任何密钥管理系统都管用。
- 点赞
- 收藏
- 关注作者
评论(0)