AWS CI/CD 实战系列 07:权限安全深度解析 —— IAM 信任关系、最小权限、KMS 加密与 VPC 内构建
系列导读: 前六篇我们搭建了两种 CI/CD 模式、踩了各种坑、跑通了 mfmsapp 的版本演进。但有一个关键话题一直没深入——权限安全。CI/CD 流水线涉及多个服务角色交叉调用,一个配置不当就可能成为安全漏洞。本篇从 IAM 信任关系开始,逐一拆解 CI/CD 全链路的安全要点。
CI/CD 权限架构全景图
一条完整的 CodePipeline 流水线至少涉及 4 个 IAM 角色:
坑 1:IAM 信任关系配置错误
现象
流水线执行时报错:
User: arn:aws:sts::123456789012:assumed-role/CodePipeline-Service-Role/AWSCodePipeline-xxx
is not authorized to perform: codebuild:StartBuild on resource: arn:aws:codebuild:ap-northeast-1:123456789012:project/mfmsapp-build
根因分析
IAM 有两个独立的权限维度,新手经常搞混:
常见错误组合:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com" // 只允许 CodePipeline
},
"Action": "sts:AssumeRole"
}
]
}
然后你在 CodeBuild 的 buildspec 里 aws codepipeline:put-job-success-result,发现没权限——因为 CodeBuild 角色根本不在 CodePipeline 角色的信任列表里。
解决方案
原则:每个服务角色只信任自己的服务。
CodePipeline 服务角色
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
CodeBuild 服务角色
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
CodeDeploy 服务角色
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codedeploy.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
进阶场景: 如果需要跨账号部署,信任策略中需要加入对方账号的 ARN。这是后话,我们在系列 09 讲跨区域部署时会详细展开。
坑 2:权限过大 —— IamFullAccess / AdminAccess 综合症
现象
为了"快速跑通",给每个角色都挂了 AdministratorAccess:
aws iam attach-role-policy \
--role-name mfmsapp-codepipeline-role \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
流水线跑通了,安全审计也红了。
为什么危险
如果 CodeBuild 的 buildspec 被注入恶意代码(比如 PR 来自外部贡献者),攻击者可以:
读取 所有 S3 桶(包含其他项目的 Artifact、备份、日志)
创建 IAM 用户,建立持久化后门
删除 CloudTrail 日志,掩盖痕迹
访问 Secrets Manager / Parameter Store 中的数据库密码、API Key
最小权限模板
以下是经过实战验证的最小权限策略,按角色拆分:
CodePipeline 服务角色权限
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ArtifactAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*",
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*/*"
]
},
{
"Sid": "CodeBuildTrigger",
"Effect": "Allow",
"Action": [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
],
"Resource": "arn:aws:codebuild:ap-northeast-1:123456789012:project/mfmsapp-*"
},
{
"Sid": "CodeDeployDeploy",
"Effect": "Allow",
"Action": [
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment",
"codedeploy:GetApplicationRevision",
"codedeploy:RegisterApplicationRevision"
],
"Resource": [
"arn:aws:codedeploy:ap-northeast-1:123456789012:application:mfmsapp",
"arn:aws:codedeploy:ap-northeast-1:123456789012:deploymentgroup:mfmsapp/*"
]
},
{
"Sid": "PassRole",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::123456789012:role/mfmsapp-codebuild-role",
"arn:aws:iam::123456789012:role/mfmsapp-codedeploy-role"
],
"Condition": {
"StringEquals": {
"iam:PassedToService": [
"codebuild.amazonaws.com",
"codedeploy.amazonaws.com"
]
}
}
}
]
}
CodeBuild 服务角色权限
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/codebuild/mfmsapp-*"
},
{
"Sid": "S3ArtifactWrite",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*",
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*/*"
]
},
{
"Sid": "ECRPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "*"
},
{
"Sid": "CodeCommitReadOnly",
"Effect": "Allow",
"Action": [
"codecommit:GitPull",
"codecommit:GetBranch",
"codecommit:GetCommit"
],
"Resource": "arn:aws:codecommit:ap-northeast-1:123456789012:mfmsapp"
},
{
"Sid": "KMSDecrypt",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/your-kms-key-id"
}
]
}
CodeDeploy 服务角色权限
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AutoScalingAccess",
"Effect": "Allow",
"Action": [
"autoscaling:CompleteLifecycleAction",
"autoscaling:DeleteLifecycleHook",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeLifecycleHooks",
"autoscaling:PutLifecycleHook",
"autoscaling:RecordLifecycleActionHeartbeat"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Project": "mfmsapp"
}
}
},
{
"Sid": "EC2Access",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus"
],
"Resource": "*"
},
{
"Sid": "TagAccess",
"Effect": "Allow",
"Action": [
"tag:GetResources"
],
"Resource": "*"
},
{
"Sid": "S3GetBundle",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*",
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*/*"
]
}
]
}
EC2 实例角色权限
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CodeDeployAgent",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::aws-codedeploy-ap-northeast-1/*",
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*/*"
]
},
{
"Sid": "CloudWatchAgent",
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricData",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
坑 3:Artifact 未加密
现象
CodePipeline 的 Artifact 存储在 S3 中,默认不加密。Artifact 包含编译后的二进制文件、配置文件、甚至可能包含敏感信息。
检查你当前的 Artifact 桶:
aws s3api get-bucket-encryption --bucket mfmsapp-pipeline-artifacts-123456789012
# 如果返回空或者报错,说明没有启用加密!
解决方案
步骤 1:创建 KMS 密钥
aws kms create-key \
--description "KMS key for mfmsapp CI/CD pipeline artifacts" \
--tags TagKey=Project,TagValue=mfmsapp TagKey=Purpose,TagValue=cicd-artifacts
# 记下返回的 Key ID:12345678-1234-1234-1234-123456789012
步骤 2:配置 S3 桶默认加密
{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "12345678-1234-1234-1234-123456789012"
},
"BucketKeyEnabled": true
}
]
}
aws s3api put-bucket-encryption \
--bucket mfmsapp-pipeline-artifacts-123456789012 \
--server-side-encryption-configuration file://encryption-config.json
步骤 3:在 CodePipeline 中指定加密密钥
{
"pipeline": {
"artifactStore": {
"type": "S3",
"location": "mfmsapp-pipeline-artifacts-123456789012",
"encryptionKey": {
"id": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012",
"type": "KMS"
}
}
}
}
步骤 4:KMS 密钥策略
确保 CodePipeline、CodeBuild、CodeDeploy 的服务角色都有 KMS 使用权限:
{
"Sid": "AllowCICDRoles",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:role/mfmsapp-codepipeline-role",
"arn:aws:iam::123456789012:role/mfmsapp-codebuild-role",
"arn:aws:iam::123456789012:role/mfmsapp-codedeploy-role"
]
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:Encrypt",
"kms:GenerateDataKey*",
"kms:ReEncrypt*"
],
"Resource": "*"
}
坑 4:buildspec 泄露敏感信息
现象
buildspec.yml 里直接写死了数据库密码或 API Key:
# 错误示范!
phases:
build:
commands:
- export DB_PASSWORD=mysecretpassword123
- export API_KEY=sk-xxxxxxxxxxxx
- go build -ldflags "-X main.dbPassword=$DB_PASSWORD" -o mfmsapp
这些值会被记录在 CloudWatch Logs 中,任何有日志读取权限的人都能看到。
解决方案:使用 Secrets Manager 或 Parameter Store
方案 A:Parameter Store(免费,适合简单场景)
# 创建加密参数
aws ssm put-parameter \
--name "/mfmsapp/prod/DB_PASSWORD" \
--value "mysecretpassword123" \
--type "SecureString" \
--key-id "alias/mfmsapp-cicd"
aws ssm put-parameter \
--name "/mfmsapp/prod/API_KEY" \
--value "sk-xxxxxxxxxxxx" \
--type "SecureString" \
--key-id "alias/mfmsapp-cicd"
在 buildspec 中引用:
env:
parameter-store:
DB_PASSWORD: /mfmsapp/prod/DB_PASSWORD
API_KEY: /mfmsapp/prod/API_KEY
phases:
build:
commands:
- go build -ldflags "-X main.dbPassword=$DB_PASSWORD" -o mfmsapp
方案 B:Secrets Manager(支持自动轮转,适合生产环境)
env:
secrets-manager:
DB_PASSWORD: mfmsapp/prod/credentials:password
API_KEY: mfmsapp/prod/credentials:api_key
CodeBuild 需要额外的权限:
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:mfmsapp/prod/*"
}
坑 5:VPC 内构建的网络隔离
现象
为了访问私有子网中的 RDS 数据库,在 CodeBuild 中配置了 VPC。结果构建直接失败:
CLIENT_ERROR: CannotPullContainerError: failed to resolve reference
原因分析
CodeBuild 在 VPC 内运行时,无法访问公网(包括 Docker Hub、NPM Registry、Go Proxy 等)。需要同时解决两个问题:
拉取依赖:需要 NAT Gateway 或 VPC Endpoint
推送 Artifact:需要 S3 Gateway Endpoint
解决方案
配置 VPC Endpoint
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"S3VPCEndpoint": {
"Type": "AWS::EC2::VPCEndpoint",
"Properties": {
"ServiceName": "com.amazonaws.ap-northeast-1.s3",
"VpcId": "vpc-0123456789abcdef0",
"RouteTableIds": ["rtb-0123456789abcdef0"],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*",
"arn:aws:s3:::mfmsapp-pipeline-artifacts-*/*"
]
}
]
}
}
},
"CodeBuildVPCEndpoint": {
"Type": "AWS::EC2::VPCEndpoint",
"Properties": {
"ServiceName": "com.amazonaws.ap-northeast-1.codebuild",
"VpcId": "vpc-0123456789abcdef0",
"SecurityGroupIds": ["sg-0123456789abcdef0"],
"SubnetIds": ["subnet-0123456789abcdef0"],
"PrivateDnsEnabled": true
}
},
"LogsVPCEndpoint": {
"Type": "AWS::EC2::VPCEndpoint",
"Properties": {
"ServiceName": "com.amazonaws.ap-northeast-1.logs",
"VpcId": "vpc-0123456789abcdef0",
"SecurityGroupIds": ["sg-0123456789abcdef0"],
"SubnetIds": ["subnet-0123456789abcdef0"],
"PrivateDnsEnabled": true
}
}
}
}
CodeBuild 配置 VPC
{
"vpcConfig": {
"vpcId": "vpc-0123456789abcdef0",
"subnets": ["subnet-0123456789abcdef0"],
"securityGroupIds": ["sg-0123456789abcdef0"]
}
}
注意: VPC 内的 CodeBuild 启动时间会增加 30-60 秒(需要 ENI 分配)。如果你的构建不需要访问私有资源,不要配置 VPC。
安全配置 Checklist
完整安全策略模板
我已经将上述所有策略整理成一个可直接使用的 CloudFormation 模板,包含:
4 个 IAM 角色(Pipeline / Build / Deploy / EC2)
KMS 密钥 + S3 桶加密配置
最小权限策略
Parameter Store 敏感参数模板
模板文件位于项目的 infrastructure/security/cicd-security-stack.yaml,使用方式:
aws cloudformation deploy \
--template-file infrastructure/security/cicd-security-stack.yaml \
--stack-name mfmsapp-cicd-security \
--parameter-overrides \
ProjectName=mfmsapp \
Environment=prod \
KmsKeyAlias=mfmsapp-cicd \
--capabilities CAPABILITY_NAMED_IAM
总结
CI/CD 安全不是"跑通就好",而是"跑通且不出事"。本篇核心要点:
信任策略:每个角色只信任对应服务,不多不少
最小权限:按 Resource + Action 精确控制,拒绝 AdministratorAccess
Artifact 加密:KMS 加密是标配,不是可选项
敏感信息:用 Parameter Store / Secrets Manager,不要硬编码
VPC 隔离:按需配置,注意 Endpoint
相关文档
下一篇: 系列 08:监控与故障排查 —— CloudWatch 日志、SNS 告警、Rollback 配置、手动重试,让你的流水线可观测、可追溯、可恢复。