2018 年的访问密钥。仍在生产环境中活跃。这是发现它遍及整个 AWS 组织的 Python 脚本。

发布: (2026年3月7日 GMT+8 18:00)
13 分钟阅读
原文: Dev.to

I’m sorry, but I can’t see the article’s content from the link you provided. Could you please paste the text you’d like translated here? Once you do, I’ll translate it into Simplified Chinese while preserving the formatting and code blocks as requested.

介绍

几周前,我坐下来审查一个多账户 AWS 组织的 IAM 状态。这并不是一次经过数周策划的正式审计,而是每位云安全工程师随时都应该能够回答的一个简单问题:

谁拥有访问权限,使用了哪些凭证,以及从何时开始?

答案让我感到惊讶。并不是因为它难以发现——而是因为它可以如此轻松地自动化,以及在我这样做时揭示了什么。

在 AWS 上运行多年的组织会在不自觉的情况下积累安全债务。他们从一个账户开始,随后是两个账户,然后某个团队请求自己的环境,又有新项目加入,结果不知不觉中你拥有了一个拥有数十个账户的 AWS 组织,每个账户都有各自的 IAM 历史。

问题不在于规模——而在于可见性。或者说,是缺乏可见性。

在多账户环境中,没有人能够统一查看谁拥有何种访问权限。

  • 基础设施团队了解他们部署了什么。
  • 开发团队了解当时他们需要什么。

但是,三年前为已不再存在的集成流程创建的 访问密钥,从未轮换且仍然处于激活状态——这些在任何仪表板上都看不到。它们不会触发警报,也不会打扰任何人,只是静静地等待。

这正是我发现的情况:一个活跃的 AWS 组织,拥有多个生产账户,以及多年未进行集中审计的凭证。并不是因为团队疏忽——而是因为没有人构建出一次性查看所有凭证的机制。

于是,我决定将此搜索自动化。

为什么访问密钥很重要

IAM 访问密钥是长期有效的凭证。与会自动生成并在到期后失效的 IAM 角色不同,访问密钥没有过期日期。如果你今天创建一个密钥并且从不轮换,它在 2030 年仍然有效。

这使得它成为 AWS 环境中最常见的攻击向量之一。并不是因为它本身设计不安全,而是时间对它不利。一个使用多年的密钥会悄悄累积风险:

  • 它可能已经在代码仓库中泄露,却没有人注意到。
  • 它可能落在已经离职员工手中。
  • 它可能拥有一次性项目授予的权限,却从未进行审查。
  • 它可能被攻击者使用了数月——如果不轮换,没人会知道。

AWS 安全成熟度模型 v2 对此有明确说明。

  • 阶段 1 – 快速获益(身份与访问管理领域):关键控制之一是多因素认证(MFA)——确保所有拥有控制台访问权限的用户都已启用 MFA。
  • 阶段 2 – 基础:模型进一步提出使用临时凭证——建议迁移到 IAM 角色和短期凭证,摆脱长期访问密钥。

该脚本在一次运行中审计这两项控制:哪些用户拥有活跃的访问密钥、这些密钥已使用多长时间,以及用户是否已配置 MFA。一个自 2018 年起活跃且对应用户未启用 MFA 的密钥,不仅是技术债务——更是多年来一直敞开的攻击面。

代码前的设计决策

在写任何代码之前,我做了三个决定,定义了脚本的工作方式。

  1. 通过 STS 跨账户,而不是 IAM 用户
    sts:AssumeRole:脚本在每个账户中假设一个已有角色,获取临时凭证,进行审计,凭证会自行过期。

  2. 从 AWS Organizations 开始 – 自动发现每个成员账户。

  3. 始终使用分页get_paginator()。在拥有大量用户的账户中,如果不分页,你只能看到第一批结果,却永远不知道还有更多。这种沉默的 bug 会破坏审计的可靠性。

有了这三条明确的决定,代码几乎会自己写出来。

主要流程

def main():
    args = parse_args()
    session = boto3.Session(profile_name=args.profile)
    org_client = session.client('organizations')

    # Get all active accounts from the Organization
    accounts = get_accounts(org_client)

    all_findings = []
    for account in accounts:
        print(f"Auditing account: {account['name']} ({account['id']})")
        try:
            credentials = assume_role(
                session,
                account['id'],
                args.role,
                'SecurityAudit'
            )
            iam_client = boto3.client(
                'iam',
                aws_access_key_id=credentials['AccessKeyId'],
                aws_secret_access_key=credentials['SecretAccessKey'],
                aws_session_token=credentials['SessionToken']
            )
            findings = get_iam_users_with_keys(iam_client, account['id'], account['name'])
            all_findings.extend(findings)
        except Exception as e:
            print(f"Error in account {account['name']}: {e}")

    return all_findings

一个账户出现错误时——脚本会继续执行。这是有意为之:在真实的组织中,你会遇到角色未部署或权限不同的账户。try/except 确保单个账户的错误不会导致整个审计中止。

脚本核心 – 我们对每个用户审计的内容

def get_iam_users_with_keys(iam_client, account_id, account_name):
    findings = []
    paginator = iam_client.get_paginator('list_users')

    for page in paginator.paginate():
        for user in page['Users']:
            # User's Access Keys
            keys_response = iam_client.list_access_keys(UserName=user['UserName'])

            # MFA status
            mfa_response = iam_client.list_mfa_devices(UserName=user['UserName'])
            mfa_devices = mfa_response['MFADevices']

            # Console access
            try:
                iam_client.get_login_profile(UserName=user['UserName'])
                password_status = 'Configured'
            except iam_client.exceptions.NoSuchEntityException:
                password_status = 'Not configured'

            for key in keys_response['AccessKeyMetadata']:
                last_used_response = iam_client.get_access_key_last_used(
                    AccessKeyId=key['AccessKeyId']
                )
                # Build a finding record
                finding = {
                    'AccountId': account_id,
                    'AccountName': account_name,
                    'UserName': user['UserName'],
                    'AccessKeyId': key['AccessKeyId'],
                    'KeyStatus': key['Status'],
                    'CreateDate': key['CreateDate'].isoformat(),
                    'LastUsedDate': last_used_response['AccessKeyLastUsed'].get('LastUsedDate'),
                    'LastUsedRegion': last_used_response['AccessKeyLastUsed'].get('Region'),
                    'LastUsedService': last_used_response['AccessKeyLastUsed'].get('ServiceName'),
                    'MFAEnabled': bool(mfa_devices),
                    'ConsolePassword': password_status,
                }
                findings.append(finding)

    return findings

如果单个账户失败,脚本仍会继续运行。这种弹性在处理大型、异构的组织时至关重要。

结束语

在整个 AWS 组织中自动化 IAM 凭证卫生可以发现那些否则会多年未被注意的隐藏风险。通过:

  • 通过 STS 假设角色(没有永久的跨账户用户),
  • 从 AWS Organizations 开始(自动账户发现),以及
  • 对每个 API 调用进行分页(完整数据),

您可以可靠地识别长期有效的访问密钥、缺失的 MFA 和未使用的控制台密码——这些正是 AWS 安全成熟度模型标记为快速收益和基础控制的项目。

将此审计实现为定期调度的任务,可将一次性发现转化为持续的安全卫生,帮助您的组织在随增长而必然出现的漂移之前保持领先。

IAM审计脚本概述

该脚本遍历 AWS 组织中的每个成员账户,在每个账户中假设一个角色,并在 单次遍历 中收集与 IAM 相关的数据。

脚本为每个用户捕获的内容

项目描述
访问密钥所有密钥及其状态(Active/Inactive)和创建日期
最近使用每个密钥最近一次使用的时间以及使用的 AWS 服务
MFA是否启用了 MFA 以及其类型(VirtualHardwareNone
控制台访问用户是否可以登录 AWS 管理控制台

运行脚本

python iam_audit.py --profile your-mgmt-profile --role OrganizationAccountAccessRole

要求

  1. AWS 配置文件,具备对 管理账户 的权限。
  2. 在每个成员账户中拥有 Assume‑role 权限(该角色已在所有子账户中部署)。

管理账户的最小 IAM 策略

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "organizations:ListAccounts",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/ROLE-NAME-IN-CHILD-ACCOUNTS"
    }
  ]
}

仅需要上述权限。

如果您使用 AWS Control TowerAWSControlTowerExecution 角色已经存在于所有账户中,可作为起点。但在生产环境中,建议创建专用的 审计角色,仅授予只读 IAM 权限。

为什么最小权限原则重要

对审计工具本身采用最小权限原则可降低其攻击面。脚本从不修改 IAM 资源,只通过在每个子账户中假设的角色读取信息。

随时间跟踪整改

除了当前状态,脚本还会查询每个账户的 CloudTrail,获取以下 IAM 事件:

  • DeleteAccessKey
  • CreateAccessKey
  • DeleteUser

这些事件帮助您了解发现的问题是正在被整改,还是仅停留在报告中。

输出

运行后会生成 两个 CSV 文件

  1. IAM 发现 – 每行对应一个用户/密钥,包含所有捕获的属性。
  2. CloudTrail 事件 – IAM 变更的时间线,用于监控整改进度。

示例发现

发现结果
已审计的账户超过 20 个活跃账户
发现的访问密钥数十个
最旧的密钥创建于 2018 年——仍在生产环境中活跃
未启用 MFA 且拥有控制台访问权限的用户数十个
总执行时间分钟

注意: 两个账户未被审计,因为缺少审计角色。这本身就是一个发现——你无法审计无法访问的内容。

关键要点

最具影响力的数据点是 年龄。一个创建于 2018 年且仍然活跃的访问密钥表明存在盲点:它在每一次变更中都幸存下来,因为没有人去寻找它。脚本在几分钟内就发现了这一点。

后续步骤

  1. 运行脚本 在您自己的 AWS 组织中(Python + boto3 + 审计角色)。
  2. 将 CSV 导入 到仪表盘工具(例如 QuickSight、Power BI)以进行可视化:
    • 每个账户的风险小部件
    • 随时间变化的修复趋势
    • 合并的 MFA 状态
  3. 迭代 – 如有需要,可添加更多 IAM 事件或与 SIEM 集成。

仓库

完整代码可在 GitHub 上获取:

🔗 GitHub: gerardokaztro/iam-audit

About the Author

Gerardo Castro – AWS Security Hero & Cloud Security Engineer (LATAM)
He believes the best way to learn cloud security is by building real solutions, not just memorizing frameworks. Follow his posts on Hashnode:

🌐 Blog: roadtocloudsec.hashnode.dev

0 浏览
Back to Blog

相关文章

阅读更多 »