简介这份《AWS云上安全最佳实践》PDF面向云计算运维、安全工程师及架构师系统梳理AWS平台的安全防护体系帮助读者建立从身份权限到数据加密的完整认知。内容围绕IAM最小权限与MFA、静态与传输数据加密、KMS密钥集中管理、VPC纵深防御、WAF与PrivateLink私有连接、GuardDuty威胁检测、CloudTrail日志审计等核心主题展开并延伸至零信任架构、CAF安全能力模型与合规审计策略适合作为企业上云安全落地的参考手册。资源包内含1个PDF文件大小约15.34MB页面结构清晰便于按身份、数据、网络、基础设施、日志监控等模块查阅。目前已有794人学习下载可帮助读者快速对照AWS安全服务矩阵理解分层防护与自动化合规检查的实践思路。1. 云上安全不是堆服务从一份最佳实践文档说起很多团队上云之后的第一反应是“买安全产品”——WAF、堡垒机、密钥管理、态势感知能开的全开账单翻了三倍结果一次模拟红队演练还是被打穿了。问题不在工具不够而在于没有一条从身份到数据、从网络到审计的完整链路。我见过一个典型场景某公司把数据库放进了私有子网安全组只开了内网端口看起来固若金汤但一台被攻陷的跳板机上明文存着数据库连接串攻击者横向一跳就拿到了全量数据。这就是典型的“单点加固、全局失守”。这份《AWS云上安全最佳实践》要解决的核心问题就是帮你把零散的安全配置串成一条可验证、可审计、可复现的纵深防御链路。它适合正在做云上架构评审的工程师、负责等保或合规落地的安全同学以及那些“知道该做什么但不知道从哪下手”的运维负责人。接下来的内容不堆概念而是按身份、网络、数据、检测、响应五个层面把每一步的配置、参数和踩坑点讲清楚让你能直接对照自己的账号做一遍体检。2. 身份与权限把最小权限从口号变成 IAM 策略云上安全的第一道门永远是身份。绝大多数云上入侵事件的起点不是漏洞利用而是凭证泄露或权限过大。IAM 不是“建个用户给个 AdministratorAccess 就完事”的东西它是一套需要持续收敛的策略体系。2.1 为什么 root 账号必须锁进保险柜root 账号拥有不可限制的完全控制权任何附加的 SCP 或权限边界对它都无效。常见做法是创建之初就绑定一个专用的物理 MFA 设备然后把访问密钥全部删除日常运维一律走 IAM 角色或联合身份。我一般会额外做一件事——在 CloudTrail 里对 root 登录事件单独设一条告警因为 root 登录本身就是异常信号。具体操作上先确认 root 没有活跃的访问密钥# 列出 root 账号的访问密钥状态需要以 root 身份执行 aws iam get-account-summary --query SummaryMap.AccountAccessKeysPresent # 返回 0 表示没有活跃密钥返回 1 或更多说明存在密钥需要立即删除这个命令查的是账号摘要里的AccountAccessKeysPresent字段它统计的是 root 账号下活跃的访问密钥数量。参数不需要额外指定走默认凭证链即可。如果返回值大于 0去 IAM 控制台的“安全凭证”页面把密钥删掉然后给 root 绑定 MFA。注意删除密钥不会影响任何使用 IAM 角色的服务但如果有脚本硬编码了 root 密钥删之前先搜一遍代码仓库。2.2 用权限边界给“临时管理员”上锁很多团队会给开发人员临时开管理员权限来排查问题但忘了回收。权限边界Permissions Boundary是解决这个问题的利器它定义了一个 IAM 实体所能拥有的最大权限上限即使附加了 AdministratorAccess 策略实际生效的也只是边界与策略的交集。下面这段策略创建了一个边界只允许 S3、CloudWatch 和 EC2 只读操作然后把它绑到一个用户上{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: [ s3:GetObject, s3:ListBucket, cloudwatch:GetMetricData, cloudwatch:ListMetrics, ec2:DescribeInstances, ec2:DescribeSecurityGroups ], Resource: * } ] }把这段保存为boundary.json然后执行# 创建权限边界策略 aws iam create-policy \ --policy-name DevReadOnlyBoundary \ --policy-document file://boundary.json # 将边界绑定到指定用户替换 ACCOUNT_ID 和 USER_NAME aws iam put-user-permissions-boundary \ --user-name USER_NAME \ --permissions-boundary arn:aws:iam::ACCOUNT_ID:policy/DevReadOnlyBoundaryput-user-permissions-boundary这个命令的关键参数是--permissions-boundary它接受一个策略 ARN。绑定之后该用户即使被附加了AdministratorAccess也只能执行边界里列出的只读操作。这里有个容易翻车的点权限边界只对 IAM 用户和角色生效对 root 无效而且边界本身不授予权限必须同时有身份策略和边界策略的交集才会放行。我见过有人只绑了边界没加身份策略然后抱怨“怎么什么权限都没有”——这是正常的边界是天花板不是地板。2.3 角色信任策略里的 ExternalId 到底防什么跨账号访问时信任策略里的ExternalId经常被当成可选字段忽略掉。它的作用是防止“混淆代理”问题当第三方服务需要访问你的账号资源时如果没有 ExternalId任何知道角色 ARN 的人都能诱导该服务去访问你的资源。加上一个只有你和对方知道的随机字符串就能把这种攻击面堵死。信任策略的写法如下{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { AWS: arn:aws:iam::THIRD_PARTY_ACCOUNT:root }, Action: sts:AssumeRole, Condition: { StringEquals: { sts:ExternalId: your-unique-random-string-here } } } ] }Condition里的sts:ExternalId就是那个共享密钥。生成方式建议用openssl rand -hex 16不要用可猜测的单词。注意ExternalId 不是加密凭证它只是防混淆的标识所以不需要定期轮换但也不能泄露到公开仓库里。如果对方服务不支持 ExternalId那就要重新评估这个集成的安全模型了。3. 网络隔离安全组、NACL 与私有子网的三层配合网络层的安全经常被简化成“安全组开端口”但真正有效的隔离需要安全组、网络 ACL 和子网路由三者协同。安全组是有状态的实例级防火墙NACL 是无状态的子网级防火墙而私有子网的路由表决定了流量能不能出去。三者配合不好就会出现“安全组很严但数据照样泄露”的情况。3.1 安全组引用安全组比 CIDR 更可靠的授权方式很多人在安全组规则里写0.0.0.0/0或者整个 VPC 的 CIDR 来放行流量这等于把内网所有实例都信任了。更好的做法是让安全组互相引用数据库安全组的入站规则只允许应用安全组的成员访问而不是允许某个 IP 段。# 创建数据库安全组入站只允许应用安全组 aws ec2 authorize-security-group-ingress \ --group-id sg-数据库安全组ID \ --protocol tcp \ --port 3306 \ --source-group sg-应用安全组ID--source-group参数接受另一个安全组的 ID而不是 CIDR。这样只有绑定了该应用安全组的实例才能访问数据库端口。好处是当应用实例扩缩容时不需要改任何 IP 规则。注意安全组引用安全组只在同一个 VPC 内生效对等连接 VPC 需要额外配置跨 VPC 还是得用 CIDR 或前缀列表。3.2 私有子网不是“没有路由”就行把实例放进私有子网只是第一步关键是路由表里不能有指向互联网网关的默认路由。但很多架构需要私有子网里的实例访问外网下载补丁于是加了 NAT 网关。这时候要注意NAT 网关只允许出站发起外部无法主动连入这是对的但如果安全组入站开了0.0.0.0/0的 SSH那 NAT 也救不了你。检查私有子网路由表的命令# 列出指定路由表的所有路由 aws ec2 describe-route-tables \ --route-table-ids rtb-路由表ID \ --query RouteTables[].Routes[].{Dest:DestinationCidrBlock,GW:GatewayId,NAT:NatGatewayId}输出里如果看到Dest是0.0.0.0/0且GW是igw-开头说明这个“私有子网”其实有公网路由需要立刻修正。NAT字段显示nat-开头才是正确的出站路径。我一般会把这个检查写进日常巡检脚本因为子网类型被误改的情况并不少见。3.3 VPC 流日志的采样率与成本平衡VPC 流日志是网络层排错和取证的核心数据源但全量采集的成本可能很高。常见做法是把采样率设成 1全量用于关键子网其他子网设成 0.1 或更低。流日志本身不影响性能但会往 CloudWatch Logs 或 S3 写数据费用主要来自存储和查询。# 为指定子网创建流日志输出到 CloudWatch Logs采样率 50% aws ec2 create-flow-logs \ --resource-type Subnet \ --resource-ids subnet-子网ID \ --traffic-type ALL \ --log-destination-type cloud-watch-logs \ --log-group-name /vpc/flowlogs/critical-subnet \ --max-aggregation-interval 60 \ --traffic-type ALL--max-aggregation-interval默认是 600 秒设成 60 秒能更快看到流量变化但日志量会增大。--traffic-type选ALL会同时记录接受和拒绝的流量排错时有用但长期运行建议改成REJECT只记录被拒绝的流量降低噪音和成本。注意流日志不记录 DNS 查询、DHCP 和实例元数据流量排查这类问题得另找工具。4. 数据保护KMS 密钥策略与 S3 桶策略的联动数据是攻击者的最终目标加密是最后一道防线。但加密不是“勾选启用”就完事KMS 密钥策略、S3 桶策略和 IAM 策略三者之间的权限关系经常把人绕晕。一个常见的翻车场景是S3 桶开了默认加密但密钥策略允许kms:*给所有账号结果任何有 KMS 权限的人都能解密数据。4.1 KMS 密钥策略的最小化写法KMS 密钥策略是密钥的“门禁”它独立于 IAM 策略。默认密钥策略允许账号 root 拥有全部权限这通常太宽了。下面是一个收敛后的密钥策略示例只允许特定角色进行加密解密密钥管理员单独指定{ Version: 2012-10-17, Statement: [ { Sid: AllowKeyAdmin, Effect: Allow, Principal: { AWS: arn:aws:iam::ACCOUNT_ID:role/KeyAdminRole }, Action: [ kms:Create*, kms:Describe*, kms:Enable*, kms:List*, kms:Put*, kms:Update*, kms:Revoke*, kms:Disable*, kms:Get*, kms:Delete*, kms:TagResource, kms:UntagResource ], Resource: * }, { Sid: AllowDataAccess, Effect: Allow, Principal: { AWS: arn:aws:iam::ACCOUNT_ID:role/AppRole }, Action: [ kms:Encrypt, kms:Decrypt, kms:ReEncrypt*, kms:GenerateDataKey*, kms:DescribeKey ], Resource: * } ] }第一个语句给密钥管理员角色管理权限第二个语句给应用角色加密解密权限。注意kms:DescribeKey必须包含在数据访问语句里否则应用连密钥元数据都读不到。另外密钥策略里不要用Principal: *那等于把密钥公开。如果确实需要跨账号访问用AWS: arn:aws:iam::OTHER_ACCOUNT:root加上条件键限制。4.2 S3 桶策略拒绝非加密上传S3 的默认加密只保证静态数据被加密但不阻止用户上传未加密的对象。要强制加密需要在桶策略里加一条 Deny 语句拒绝没有加密头或没有指定 KMS 密钥的 PutObject 请求{ Version: 2012-10-17, Statement: [ { Sid: DenyUnencryptedUploads, Effect: Deny, Principal: *, Action: s3:PutObject, Resource: arn:aws:s3:::your-bucket-name/*, Condition: { StringNotEquals: { s3:x-amz-server-side-encryption: aws:kms } } }, { Sid: DenyWrongKMSKey, Effect: Deny, Principal: *, Action: s3:PutObject, Resource: arn:aws:s3:::your-bucket-name/*, Condition: { StringNotEquals: { s3:x-amz-server-side-encryption-aws-kms-key-id: arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID } } } ] }第一条拒绝所有没有aws:kms加密头的上传第二条拒绝使用了错误 KMS 密钥的上传。StringNotEquals这个条件操作符是关键它表示“当这个头不等于指定值时拒绝”。注意如果客户端用的是 SSE-S3AES256而不是 SSE-KMS第一条就会拒绝因为s3:x-amz-server-side-encryption的值是AES256而不是aws:kms。这个策略对桶拥有者自己也生效所以测试时先用一个测试桶验证。4.3 用 S3 访问分析器找出公开桶S3 桶意外公开是数据泄露的头号原因。访问分析器Access Analyzer能自动识别被外部账号或公开访问的桶、角色、KMS 密钥等资源。开启方式很简单# 创建访问分析器范围限定在当前账号 aws accessanalyzer create-analyzer \ --analyzer-name account-analyzer \ --type ACCOUNT \ --tags EnvironmentProduction--type ACCOUNT表示分析当前账号内的资源如果要分析整个组织用ORGANIZATION并指定组织 ID。创建后分析器会自动扫描并生成发现结果。查看结果的命令aws accessanalyzer list-findings \ --analyzer-arn arn:aws:access-analyzer:REGION:ACCOUNT_ID:analyzer/account-analyzer \ --filter {status:{eq:[ACTIVE]}}--filter参数用来筛选活跃的发现项。每个发现项会告诉你哪个资源、什么访问类型、来自哪个外部主体。我一般会把这个命令的输出接入告警一旦出现新的 ACTIVE 发现就通知安全群。注意访问分析器不是实时的扫描有延迟所以不能替代桶策略的预防性控制它是检测层的手段。5. 检测与响应CloudTrail、GuardDuty 和 Config 的联动排查安全事件发生时最怕的是“日志没开”或者“日志开了但没人看”。检测层的三件套是 CloudTrailAPI 审计、GuardDuty威胁检测和 Config配置合规。它们各自独立但联动起来才能形成闭环。5.1 CloudTrail 多区域跟踪与日志完整性校验默认的 CloudTrail 事件历史只保留 90 天而且不包含数据事件。生产账号必须创建多区域跟踪并把日志写到专用的 S3 桶开启日志文件验证。# 创建多区域跟踪包含管理事件和 S3 数据事件 aws cloudtrail create-trail \ --name org-trail \ --s3-bucket-name your-cloudtrail-bucket \ --is-multi-region-trail \ --enable-log-file-validation \ --include-global-service-events # 启动跟踪 aws cloudtrail start-logging --name org-trail--enable-log-file-validation会为每个日志文件生成一个摘要文件用来验证日志是否被篡改。--include-global-service-events确保 IAM、STS 等全局服务的事件被记录。注意S3 数据事件默认不记录需要额外配置--advanced-event-selectors来指定要记录哪些桶的读写操作否则数据泄露时你只能看到管理事件看不到谁读了对象。5.2 GuardDuty 发现结果的严重级别与处置优先级GuardDuty 的发现结果分 Low、Medium、High 三个级别但不是所有 High 都需要立即响应。比如UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration是 High意味着实例凭证可能泄露到外部必须立刻隔离实例并轮换角色凭证。而Recon:EC2/Portscan可能只是外部扫描优先级可以稍低。查看当前活跃发现的命令# 列出 GuardDuty 检测器 ID aws guardduty list-detectors # 列出该检测器下的发现结果 ID aws guardduty list-findings --detector-id DETECTOR_ID # 获取具体发现详情 aws guardduty get-findings \ --detector-id DETECTOR_ID \ --finding-ids FINDING_ID_1 FINDING_ID_2get-findings返回的 JSON 里包含Severity、Type、Resource和Service等字段。我一般会按Type分类写处置剧本凭证泄露类先隔离再轮换端口扫描类先确认安全组是否放行了不必要的端口。注意GuardDuty 的发现结果保留 90 天超过后自动删除所以如果需要长期留存得用 EventBridge 把发现结果转发到 S3 或 SIEM。5.3 Config 规则自动修复以 S3 公共访问为例Config 能持续评估资源配置是否符合规则并支持自动修复。以“S3 桶不允许公共读”为例先创建 Config 规则再绑定一个 SSM 自动化文档来执行修复。# 创建 Config 规则检测 S3 桶的公共读权限 aws configservice put-config-rule \ --config-rule { ConfigRuleName: s3-bucket-public-read-prohibited, Source: { Owner: AWS, SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED } }这条规则是 AWS 托管规则不需要自己写 Lambda。创建后Config 会评估所有 S3 桶的公共访问设置。如果发现不合规可以在 Config 控制台手动触发修复或者配置 SSM 自动化。自动修复的文档需要自己写或者用现成的AWS-DisableS3BucketPublicReadWrite。注意自动修复有风险——如果误判了某个必须公开的桶修复动作会直接改桶策略所以建议先跑一段时间的“仅检测”模式确认没有误报再开启自动修复。6. 避坑与排查五个真实踩坑记录安全配置的坑往往不是“不知道”而是“以为配了其实没生效”。下面这五条是我和身边同事实际遇到过的每条按现象、原因、解决来写。现象一S3 桶策略明明写了 Deny 未加密上传但测试时还是传上去了。原因桶策略的 Deny 只对通过 S3 API 的上传生效如果用的是 S3 控制台的“上传”按钮控制台会自动加上加密头所以看起来“没拦住”。但用 CLI 不带加密参数上传时Deny 就会生效。解决测试时用aws s3 cp不带--sse参数验证确认返回AccessDenied。现象二安全组引用了另一个安全组但流量还是不通。原因安全组引用只在同一个 VPC 内生效如果两个安全组在不同的 VPC比如通过对等连接引用规则不会匹配。解决跨 VPC 场景改用 CIDR 或前缀列表并确认对等连接的路由表已正确配置。现象三CloudTrail 日志里找不到某个 API 调用。原因CloudTrail 默认只记录管理事件S3 的 GetObject、PutObject 属于数据事件需要单独开启。另外如果调用的是被拒绝的请求管理事件里会记录AccessDenied但数据事件默认不记录。解决在跟踪配置里添加数据事件选择器指定要监控的桶或 Lambda 函数。现象四KMS 密钥被禁用后S3 里的加密对象全部无法读取。原因S3 的 SSE-KMS 加密依赖 KMS 密钥来解密数据密钥密钥被禁用或删除后所有用该密钥加密的对象都变成“黑匣子”。解决密钥删除有 7 到 30 天的等待期期间可以取消删除。日常操作中禁用密钥前先确认没有生产数据依赖它或者用密钥别名做轮换而不是直接禁用旧密钥。现象五GuardDuty 开了但从来没收到过告警。原因GuardDuty 的发现结果默认只写在 GuardDuty 控制台里不会主动推送。需要配置 EventBridge 规则把发现结果转发到 SNS 或 Slack。解决创建 EventBridge 规则事件源选aws.guardduty目标选 SNS 主题然后在 SNS 里订阅邮件或 webhook。注意EventBridge 规则只匹配GuardDuty Finding事件类型别选错了。7. 用 Security Hub 做一次跨账号安全评分如果你管理多个账号逐个登录检查安全配置是不现实的。Security Hub 能把 GuardDuty、Config、Inspector 等服务的发现结果聚合到一个视图里并给出一个 0 到 100 的安全评分。这个评分不是绝对标准但能帮你快速定位最薄弱的环节。7.1 启用 Security Hub 并关联组织账号在管理账号里启用 Security Hub 后可以自动把成员账号纳入。前提是已经用 Organizations 管理账号体系。# 在管理账号启用 Security Hub aws securityhub enable-security-hub --enable-default-standards # 查看当前安全评分 aws securityhub get-findings \ --filters {RecordState:[{Value:ACTIVE,Comparison:EQUALS}]} \ --query Findings[].{Severity:Severity.Label,Title:Title,Score:Compliance.Status}--enable-default-standards会启用 AWS 基础安全最佳实践标准AWS Foundational Security Best Practices。这个标准包含 200 多条控制项覆盖 IAM、S3、EC2、RDS 等服务。get-findings返回的每条发现都有Severity.Label和Compliance.Status可以按严重级别排序处理。7.2 用自定义洞察聚焦高风险项Security Hub 的默认视图信息量很大容易看花眼。自定义洞察Insight可以按条件过滤比如“所有严重级别为 Critical 且状态为 Failed 的 S3 相关发现”。# 创建自定义洞察 aws securityhub create-insight \ --name Critical-S3-Failures \ --filters { SeverityLabel: [{Value: CRITICAL, Comparison: EQUALS}], ResourceType: [{Value: AwsS3Bucket, Comparison: EQUALS}], ComplianceStatus: [{Value: FAILED, Comparison: EQUALS}] } \ --group-by-attribute ResourceId--filters里的三个条件分别过滤严重级别、资源类型和合规状态。--group-by-attribute按资源 ID 分组方便看到同一个桶上的多个失败项。创建后在 Security Hub 控制台的“洞察”页面就能看到这个自定义视图。我一般会为每个核心服务S3、IAM、EC2各建一个洞察每周过一遍 Critical 和 High 的失败项。7.3 把评分接入日常巡检的脚本写法Security Hub 的评分可以通过 API 获取写进巡检脚本里每天跑一次比人工登录看要靠谱。import boto3 import json def get_security_score(): client boto3.client(securityhub, region_nameus-east-1) # 获取所有活跃发现 paginator client.get_paginator(get_findings) findings [] for page in paginator.paginate( Filters{RecordState: [{Value: ACTIVE, Comparison: EQUALS}]} ): findings.extend(page[Findings]) # 按严重级别统计 severity_count {} for f in findings: label f[Severity][Label] severity_count[label] severity_count.get(label, 0) 1 # 计算加权评分Critical 权重最高 weights {CRITICAL: 10, HIGH: 5, MEDIUM: 2, LOW: 1, INFORMATIONAL: 0} total_weight sum(severity_count.get(k, 0) * v for k, v in weights.items()) max_score 100 score max(0, max_score - total_weight) return { score: score, severity_breakdown: severity_count, total_findings: len(findings) } if __name__ __main__: result get_security_score() print(json.dumps(result, indent2))这段脚本用get_paginator自动处理分页避免一次请求返回不全。weights字典给不同严重级别赋权Critical 的权重是 10意味着一个 Critical 发现扣 10 分。这个评分模型是我自己用的简化版不是 AWS 官方算法但足够反映趋势。你可以把score写进 CloudWatch 自定义指标设一个阈值告警比如低于 80 分就通知安全负责人。注意get_findings默认只返回最近 90 天的活跃发现而且每次最多 100 条分页器会自动处理。如果账号里发现数量很大这个脚本的执行时间可能超过 Lambda 的 15 分钟限制建议拆成多个区域并行跑或者用 Step Functions 编排。7.4 一个让我后悔没早做的习惯我最后悔的一件事是早期没有把 Security Hub 的评分和 CloudTrail 的异常事件关联起来看。有一次评分从 92 掉到 78我只顾着处理 Config 的合规失败项没注意到同一天 CloudTrail 里有一条来自陌生 IP 的AssumeRole调用。后来复盘发现攻击者先用泄露的凭证创建了一个新用户然后关掉了几个 Config 规则来降低评分制造噪音掩盖真正的入侵行为。如果当时把评分下降和 API 异常调用放在同一个仪表盘上看响应会快很多。现在的习惯是每天早上花五分钟看三个东西——Security Hub 的评分变化、GuardDuty 的新增 High 发现、CloudTrail 里CreateUser、AttachUserPolicy、PutBucketPolicy这三类 API 的调用来源。这三个信号同时出现异常时基本可以确定有人在搞事情。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取