别再用主账号 AK 了:用 RAM 子账号把权限拆开

举报
yd_42677603 发表于 2026/11/01 10:47:25 2026/11/01
【摘要】 主账号 AK 一旦泄露,攻击者能操作你名下所有资源。这篇把 RAM 拆权的完整流程走一遍:创建子用户、建用户组、写最小权限策略、绑定策略、签发 AK、查用量、清理下线,每一步都给出可复制的 Python SDK 调用,并说明字段名与返回值的坑。

一个真实的教训

去年我在管理华为云资源时,图省事直接用了账号的 AK/SK 写进本地脚本。后来这个 AK 因为一次误操作被写进了某个会同步到第三方的地方,等我发现去控制台处理时,那把 AK 已经躺在我根本不知道的地方了。

处理结果还算体面——控制台里作废旧 AK、重新签发。但这次之后我做了一件事:把所有脚本里用的 AK 全换成了 RAM 子账号的 AK,并按用途拆成了几把,每把只给必要的权限。

这篇写的是这套做法怎么落地,以及华为云 RAM 里几个容易搞混的概念。

一、为什么要拆

先说清楚风险到底在哪。

主账号(或者说账号本身)的 AK 是全能钥匙:它能操作你名下所有资源,包括删除服务器、修改账单、创建新的子账号、给别的账号授权。它一旦泄露,攻击者能做的事情远超"跑跑脚本"的范围。

而 RAM 子账号的 AK 只能做你显式授权的事。哪怕这把钥匙被完整拿走,攻击者能造成的破坏也被框在你能接受的范围内。

实际收益有这么几层:

  • 爆炸半径可控。跑测试脚本用的 AK,只给测试资源的权限;跑部署脚本的 AK,给发布相关的权限。就算前者泄露,也动不了生产数据库。
  • 能单独作废。某台机器上的脚本需要撤权,直接删掉那把 AK 的 access key id 就行,不用动账号本身。
  • 留痕可审计。子账号的操作在 CES/CTS 里能按身份区分开,出问题时知道是谁干的、哪个脚本干的。
  • 能轮换。一把子账号 AK 用在若干台机器上,轮换时你会真的需要一个"更新所有机器"的流程;而主账号 AK 的轮换是件让人望而生畏的苦差。

二、IAM 模型的四个概念

华为云的 RAM(Resource Access Management)里,这四个概念必须分清楚:

概念 是什么 类比
用户(User) 一个独立的身份,有自己的登录凭证 一个员工
用户组(Group) 一组用户的容器,方便批量授权 一个部门
策略(Policy) 一段 JSON,描述"能对哪些资源做什么操作" 岗位权限说明
委托(Agency) 让其他账号(或服务)以你的身份操作 外部承包商的临时授权

委托和用户最容易混。区别在于:

  • 用户是账号内部的身份,你自己管理它的生命周期。
  • 委托是把权限临时借给别的账号(比如让对象存储服务代表你访问你的桶),或者让服务角色(Service role)代表你操作。

日常脚本场景用用户就够了。

三、创建用户

先装 SDK:

pip3 install huaweicloudsdkiam -i https://pypi.org/simple

huaweicloudsdkiam 有多个版本目录,用户管理相关接口在 v5:

from huaweicloudsdkcore.auth.credentials import BasicCredentials
from huaweicloudsdkiam.v5 import IamClient
from huaweicloudsdkiam.v5.region.iam_region import IamRegion

credentials = BasicCredentials(ak=AK, sk=SK, project_id=PROJECT_ID)

client = IamClient.new_builder() \
    .with_credentials(credentials) \
    .with_region(IamRegion.value_of("cn-north-4")) \
    .build()

注意 IAM 是全局服务,控制台在华北-北京四,但接口调用时用哪个区域都行(SDK 会自动路由)。

创建用户:

from huaweicloudsdkiam.v5 import CreateUserV5Request
from huaweicloudsdkiam.v5.model import CreateUserReqBody

body = CreateUserReqBody(
    name="deploy-bot",
    description="部署脚本专用子账号",
    enabled=True
)

request = CreateUserV5Request(body=body)
response = client.create_user_v5(request)
user_id = response.user.id

几个字段的坑:

  • body 模型名不叫 CreateUserV5RequestBody,而是 CreateUserReqBody。IAM v5 的 SDK 里 Request 后缀和 ReqBody 后缀混用,import 前最好先确认一下:

```python

import huaweicloudsdkiam.v5.model as m

print([n for n in dir(m) if 'CreateUser' in n])

```

  • enabled=True 才有意义。默认创建出来可能是禁用的,禁用的用户连 AK 都签发不了。
  • 返回值里是 response.user.id,这个 id 后面签发 AK、加组、授权都要用,别用 name 当标识。

四、创建用户组并加用户

一个用户一个角色会很乱。用组来批量管理:

from huaweicloudsdkiam.v5 import CreateGroupV5Request, AddUserToGroupV5Request
from huaweicloudsdkiam.v5.model import CreateGroupReqBody, AddUserToGroupReqBody

group_body = CreateGroupReqBody(
    group_name="deployers",
    description="部署相关子账号"
)
group = client.create_group_v5(CreateGroupV5Request(body=group_body))
group_id = group.group.id

client.add_user_to_group_v5(
    AddUserToGroupV5Request(
        group_id=group_id,
        body=AddUserToGroupReqBody(user_id=user_id)
    )
)

同样是 CreateGroupReqBody 而不是 CreateGroupV5RequestBody。

五、写策略

这是最需要精确的部分。策略是一段 JSON,结构固定:

{
  "Version": "1.1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecs:server:list",
        "ecs:server:show"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

各字段的含义:

  • Version:写 "1.1"。
  • Statement:数组,一个策略可以有多条。
  • Effect:"Allow" 或 "Deny"。华为云支持显式 Deny,且 Deny 优先级高于 Allow——用户同时匹配到一条 Allow 和一条 Deny 时,结果是拒绝。这点和 AWS 一致。
  • Action:操作标识,格式是 服务:资源:动作,比如 ecs:server:list、vpc:securityGroups:update。也可以用通配符 ecs:*:*。
  • Resource:作用范围。* 表示全部;如果要限定,就写资源 ARN 或资源 ID。

Resource 到底该写多细,取决于你想要什么粒度。 三种常见写法:

// 最宽松:所有实例的所有操作
"Resource": ["*"], "Action": ["ecs:*:*"]

// 中等:所有实例,但只读
"Resource": ["*"], "Action": ["ecs:server:list", "ecs:server:show"]

// 最严格:只对指定实例
"Resource": ["*"], "Action": ["ecs:server:start", "ecs:server:stop"]

对 ECS 这类实例级资源,SDK 层面把 Resource 写成 "*" + 精确 Action 是常见做法。真正需要限定到某个实例时,Resource 要填该实例的 ARN,形如:

arn:cn-east-3:iam:xxxxxxxx:instance/i-yyyyyyyyyyyyy

写策略:

from huaweicloudsdkiam.v5 import CreatePolicyV5Request
from huaweicloudsdkiam.v5.model import CreatePolicyReqBody, PolicyDocument

policy_doc = {
    "Version": "1.1",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecs:server:list",
                "ecs:server:show"
            ],
            "Resource": ["*"]
        }
    ]
}

body = CreatePolicyReqBody(
    policy_name="ecs-readonly",
    path="/custom/ecs-readonly/",
    policy_document=PolicyDocument(**policy_doc),
    description="只读查看 ECS 实例"
)
policy = client.create_policy_v5(CreatePolicyV5Request(body=body))
policy_id = policy.policy.id

关于 PolicyDocument 这个类型:它的 openapi_types 是空的,意思就是"接受任意 JSON"。直接传 dict 就行,不用包 PolicyDocument(...):

policy_document=policy_doc

包一层也不报错,但没必要。

path 参数决定策略在控制台里的归类,/custom/ 开头表示自定义策略。这个字段不填也能创建,但控制台里会归到默认位置。

六、把策略绑给用户或组

from huaweicloudsdkiam.v5 import AttachUserPolicyV5Request, AttachGroupPolicyV5Request
from huaweicloudsdkiam.v5.model import AttachUserPolicyReqBody

# 绑给用户
client.attach_user_policy_v5(
    AttachUserPolicyV5Request(
        policy_id=policy_id,
        body=AttachUserPolicyReqBody(user_id=user_id)
    )
)

# 或者绑给组(推荐,批量维护更省事)
client.attach_group_policy_v5(
    AttachGroupPolicyV5Request(
        policy_id=policy_id,
        body=AttachGroupPolicyReqBody(group_id=group_id)
    )
)

绑给组而不是绑给用户,理由很直接:人员流动时只需要把人移出组,权限跟着组走,不用逐个用户重新授权。

七、签发 AK

一切就绪,给用户签发访问密钥:

from huaweicloudsdkiam.v5 import CreateAccessKeyV5Request

response = client.create_access_key_v5(
    CreateAccessKeyV5Request(user_id=user_id)
)
print(response.access_key.access_key_id)
print(response.access_key.secret_access_key)

secret_access_key 只在这一次响应里返回,之后再也查不到了。 这是设计如此,不是 bug。所以:

# 拿到立刻落盘或交给部署系统,别指望能再查一次
ak = response.access_key.access_key_id
sk = response.access_key.secret_access_key

如果丢了,只能重新签发一把新的,然后作废旧的。

一个用户默认最多两把 AK,签发第三把会报错。轮换时可以先签新的、验证能用、再删旧的。

八、查看和清理

from huaweicloudsdkiam.v5 import ListAccessKeysV5Request, ShowAccessKeyLastUsedV5Request

# 列出某用户的所有 AK
keys = client.list_access_keys_v5(ListAccessKeysV5Request(user_id=user_id))
for k in keys.access_keys:
    print(k.access_key_id, k.status, k.created_at)

# 看某把 AK 最后一次使用时间
last = client.show_access_key_last_used_v5(
    ShowAccessKeyLastUsedV5Request(user_id=user_id, access_key_id=ak_id)
)
print(last.last_used_at)

last_used_at 这个字段是排查 AK 泄露最实用的工具——如果一把你以为是闲置的 AK 最近有使用记录,就值得警觉。

删除:

from huaweicloudsdkiam.v5 import DeleteAccessKeyV5Request
client.delete_access_key_v5(
    DeleteAccessKeyV5Request(user_id=user_id, access_key_id=ak_id)
)

九、几条实践经验

按用途拆 AK,别按人拆。 我的划分方式是按"用途 + 环境":部署用一套、巡检用一套、数据同步用一套。同一个用途的多台机器共用一把,轮换时有明确的目标。

先只读,后读写。 新建的子账号先只给 list/show 类权限,跑通了再加写操作。绝大多数运维脚本其实只需要读权限。

把 Deny 当护栏用。 除了 Allow,可以再给一条显式 Deny 保护高危操作:

{
  "Effect": "Deny",
  "Action": ["ecs:server:delete", "iam:*:delete"],
  "Resource": ["*"]
}

这条策略和上面的 Allow 绑给同一个用户,Deny 优先级更高,即便 Allow 里写了删除也会被拦住。

别把 AK 写进代码仓库。 放在环境变量或者 CI 的加密变量里。本地的话可以用 ~/.config/ 下权限 600 的文件,加载时立刻检查文件权限:

stat -f '%Sp %N' ~/.config/huaweicloud/ak

macOS 上看到 -rw------- 才是对的。

定期用 API 巡检。 列出所有子账号的 AK 和最后使用时间,把"三个月没用过"的清理掉。这件事手动做一次很烦,写成脚本跑一次就很轻松。

小结

用 RAM 子账号替代主账号 AK,本质是把"一把万能钥匙"换成"一把把用途明确的钥匙"。改造成本不高——多建几个用户和组、把手里的 AK 换掉——但收益是实打实的:泄露了损失可控、能单独撤权、能审计、能轻松轮换。

具体落地顺序我建议这样:

  1. 梳理现有脚本用到的 AK 和它们实际需要什么操作
  2. 建对应的用户组,每组配一份精确到 Action 的策略
  3. 建用户、加组、签发 AK
  4. 逐个把机器上的旧 AK 换成新的
  5. 作废主账号 AK——这一步做完,前面所有的风险才真正降下来
  6. 挂一个定期巡检脚本,列出所有 AK 的最后使用时间

第 5 步做完的那一刻,你会意识到之前一直在裸奔。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。