AWS CI/CD 实战系列 07:权限安全深度解析 —— IAM 信任关系、最小权限、KMS 加密与 VPC 内构建

系列导读: 前六篇我们搭建了两种 CI/CD 模式、踩了各种坑、跑通了 mfmsapp 的版本演进。但有一个关键话题一直没深入——权限安全。CI/CD 流水线涉及多个服务角色交叉调用,一个配置不当就可能成为安全漏洞。本篇从 IAM 信任关系开始,逐一拆解 CI/CD 全链路的安全要点。


CI/CD 权限架构全景图

一条完整的 CodePipeline 流水线至少涉及 4 个 IAM 角色

graph LR A[CodePipeline 服务角色] -->|触发| B[CodeBuild 服务角色] B -->|产出 Artifact| C[S3 加密桶] A -->|部署指令| D[CodeDeploy 服务角色] D -->|操作 EC2| E[EC2 实例 角色] style A fill:#FF9900,stroke:#232F3E,color:#fff style B fill:#3F8624,stroke:#232F3E,color:#fff style D fill:#1A73E8,stroke:#232F3E,color:#fff style E fill:#D13212,stroke:#232F3E,color:#fff

角色

职责

典型信任策略

CodePipeline 服务角色

协调各阶段流转

codepipeline.amazonaws.com

CodeBuild 服务角色

执行构建脚本

codebuild.amazonaws.com

CodeDeploy 服务角色

管理部署生命周期

codedeploy.amazonaws.com

EC2 实例角色

拉取代码/Artifact、运行应用

ec2.amazonaws.com


坑 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 有两个独立的权限维度,新手经常搞混:

维度

文件

控制什么

权限策略 (Policy)

Inline Policy / Managed Policy

"这个角色 能做什么"

信任策略 (Trust Policy)

AssumeRole Policy Document

"谁 能扮演 这个角色"

常见错误组合:

{
  "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 等)。需要同时解决两个问题:

  1. 拉取依赖:需要 NAT Gateway 或 VPC Endpoint

  2. 推送 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

#

检查项

验证命令

期望结果

1

无角色使用 AdministratorAccess

aws iam list-attached-role-policies --role-name xxx

不包含 arn:aws:iam::aws:policy/AdministratorAccess

2

信任策略仅限对应服务

aws iam get-role --role-name xxx

Principal.Service 只有一个

3

Artifact 桶已启用 KMS 加密

aws s3api get-bucket-encryption --bucket xxx

SSEAlgorithm = aws:kms

4

buildspec 无硬编码密码

grep -E "(password|secret|key)" buildspec.yml

只出现 parameter-store/secrets-manager 引用

5

CodeBuild 日志加密

aws logs describe-log-groups --log-group-name-prefix /aws/codebuild/mfmsapp

kmsKeyId 不为空

6

S3 桶禁止公开访问

aws s3api get-public-access-block --bucket xxx

四项全为 true

7

PassRole 限制 Condition

检查 IAM Policy 中的 PassRole

有 PassedToService Condition


完整安全策略模板

我已经将上述所有策略整理成一个可直接使用的 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 安全不是"跑通就好",而是"跑通且不出事"。本篇核心要点:

  1. 信任策略:每个角色只信任对应服务,不多不少

  2. 最小权限:按 Resource + Action 精确控制,拒绝 AdministratorAccess

  3. Artifact 加密:KMS 加密是标配,不是可选项

  4. 敏感信息:用 Parameter Store / Secrets Manager,不要硬编码

  5. VPC 隔离:按需配置,注意 Endpoint


相关文档


下一篇: 系列 08:监控与故障排查 —— CloudWatch 日志、SNS 告警、Rollback 配置、手动重试,让你的流水线可观测、可追溯、可恢复。