CI/CDパイプラインは攻撃対象となりやすいため、適切なセキュリティ対策が必要です。本記事では、IAM記事で触れなかったパイプライン固有のセキュリティ実装を解説します。
パイプラインセキュリティ概要
flowchart TB
subgraph Security["パイプラインセキュリティ"]
IAM["最小権限IAM"]
Encryption["アーティファクト暗号化"]
CrossAccount["クロスアカウント分離"]
Audit["監査ログ"]
Approval["承認フロー"]
end
subgraph Pipeline["CI/CDパイプライン"]
Source["ソース"]
Build["Build"]
Test["Testing"]
Deploy["Deployment"]
end
Security --> Pipeline
style Security fill:#ef4444,color:#fff
style Pipeline fill:#3b82f6,color:#fff
最小権限IAM
CodePipelineサービスロール
CodePipelineRole:
Type: AWS::IAM::Role
Properties:
RoleName: codepipeline-service-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodePipelinePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# ソースアクセス(CodeCommit)
- Effect: Allow
Action:
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:UploadArchive
- codecommit:GetUploadArchiveStatus
- codecommit:CancelUploadArchive
Resource: !GetAtt CodeCommitRepo.Arn
# CodeBuild起動
- Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
Resource: !GetAtt BuildProject.Arn
# CodeDeploy起動
- Effect: Allow
Action:
- codedeploy:CreateDeployment
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:GetApplicationRevision
- codedeploy:RegisterApplicationRevision
Resource:
- !Sub arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${CodeDeployApp}/*
- !Sub arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentconfig:*
# S3アーティファクト
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
Resource: !Sub ${ArtifactBucket.Arn}/*
# KMS
- Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt PipelineKey.Arn
CodeBuildサービスロール
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: codebuild-service-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# CloudWatch Logs
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${BuildProject}
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${BuildProject}:*
# S3アーティファクト
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: !Sub ${ArtifactBucket.Arn}/*
# ECRプッシュ
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: "*"
- Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource: !GetAtt ECRRepository.Arn
# Secrets Manager(特定シークレットのみ)
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Ref BuildSecrets
Condition:
StringEquals:
secretsmanager:VersionStage: AWSCURRENT
# KMS
- Effect: Allow
Action:
- kms:Decrypt
Resource: !GetAtt PipelineKey.Arn
権限境界の適用
# 権限境界ポリシー
PipelinePermissionsBoundary:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: pipeline-permissions-boundary
PolicyDocument:
Version: '2012-10-17'
Statement:
# 許可される最大権限を定義
- Effect: Allow
Action:
- s3:*
- ecr:*
- ecs:*
- lambda:*
- cloudformation:*
- codebuild:*
- codedeploy:*
Resource: "*"
# 禁止アクション
- Effect: Deny
Action:
- iam:CreateUser
- iam:CreateAccessKey
- iam:AttachUserPolicy
- organizations:*
- account:*
Resource: "*"
# 本番環境への直接アクセス禁止
- Effect: Deny
Action:
- rds:DeleteDBInstance
- rds:DeleteDBCluster
- dynamodb:DeleteTable
- s3:DeleteBucket
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Environment: production
# ロールに権限境界を適用
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
PermissionsBoundary: !Ref PipelinePermissionsBoundary
アーティファクト暗号化
KMS設定
PipelineKey:
Type: AWS::KMS::Key
Properties:
Description: Key for CI/CD pipeline artifacts
EnableKeyRotation: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: AllowRootAccess
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action: kms:*
Resource: "*"
- Sid: AllowCodePipelineUse
Effect: Allow
Principal:
AWS:
- !GetAtt CodePipelineRole.Arn
- !GetAtt CodeBuildRole.Arn
Action:
- kms:Decrypt
- kms:GenerateDataKey
- kms:DescribeKey
Resource: "*"
# クロスアカウントアクセス
- Sid: AllowCrossAccountUse
Effect: Allow
Principal:
AWS:
- arn:aws:iam::STAGING_ACCOUNT:role/codepipeline-cross-account
- arn:aws:iam::PROD_ACCOUNT:role/codepipeline-cross-account
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: "*"
PipelineKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/pipeline-key
TargetKeyId: !Ref PipelineKey
# 暗号化S3バケット
ArtifactBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-artifacts-${AWS::AccountId}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !GetAtt PipelineKey.Arn
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
パイプラインでの暗号化設定
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
EncryptionKey:
Id: !GetAtt PipelineKey.Arn
Type: KMS
クロスアカウントデプロイ
アーキテクチャ
flowchart TB
subgraph DevAccount["開発アカウント"]
Pipeline["CodePipeline"]
Build["CodeBuild"]
Artifacts["S3 Artifacts"]
KMS["KMS Key"]
end
subgraph StagingAccount["ステージングアカウント"]
StagingRole["Cross-Account Role"]
StagingECS["ECS Service"]
end
subgraph ProdAccount["本番アカウント"]
ProdRole["Cross-Account Role"]
ProdECS["ECS Service"]
end
Pipeline --> Build
Build --> Artifacts
Artifacts --> KMS
Pipeline --> |"AssumeRole"| StagingRole
Pipeline --> |"AssumeRole"| ProdRole
StagingRole --> StagingECS
ProdRole --> ProdECS
style DevAccount fill:#3b82f6,color:#fff
style StagingAccount fill:#f59e0b,color:#000
style ProdAccount fill:#22c55e,color:#fff
クロスアカウントロール(ターゲットアカウント)
# ステージング/本番アカウントに作成
CrossAccountDeployRole:
Type: AWS::IAM::Role
Properties:
RoleName: codepipeline-cross-account-deploy
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: arn:aws:iam::DEV_ACCOUNT_ID:root
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref ExternalId
ArnLike:
aws:PrincipalArn: arn:aws:iam::DEV_ACCOUNT_ID:role/codepipeline-*
Policies:
- PolicyName: DeployPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# ECSデプロイ
- Effect: Allow
Action:
- ecs:UpdateService
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:RegisterTaskDefinition
Resource: "*"
# CloudFormationデプロイ
- Effect: Allow
Action:
- cloudformation:CreateStack
- cloudformation:UpdateStack
- cloudformation:DescribeStacks
- cloudformation:DeleteStack
Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/*
# S3アーティファクト読み取り
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: arn:aws:s3:::dev-account-artifacts-bucket/*
# KMS復号化
- Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: arn:aws:kms:ap-northeast-1:DEV_ACCOUNT_ID:key/pipeline-key-id
# S3バケットポリシー(開発アカウント)
ArtifactBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ArtifactBucket
PolicyDocument:
Statement:
- Effect: Allow
Principal:
AWS:
- arn:aws:iam::STAGING_ACCOUNT:role/codepipeline-cross-account-deploy
- arn:aws:iam::PROD_ACCOUNT:role/codepipeline-cross-account-deploy
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: !Sub ${ArtifactBucket.Arn}/*
クロスアカウントパイプライン
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: "1"
Configuration:
RepositoryName: !Ref RepoName
BranchName: main
OutputArtifacts:
- Name: SourceOutput
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !Ref BuildProject
InputArtifacts:
- Name: SourceOutput
OutputArtifacts:
- Name: BuildOutput
- Name: DeployStaging
Actions:
- Name: DeployToStaging
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: "1"
Configuration:
ClusterName: staging-cluster
ServiceName: my-service
InputArtifacts:
- Name: BuildOutput
RoleArn: arn:aws:iam::STAGING_ACCOUNT:role/codepipeline-cross-account-deploy
- Name: Approval
Actions:
- Name: ManualApproval
ActionTypeId:
Category: Approval
Owner: AWS
Provider: Manual
Version: "1"
Configuration:
NotificationArn: !Ref ApprovalTopic
CustomData: "本番環境へのデプロイを承認してください"
- Name: DeployProduction
Actions:
- Name: DeployToProduction
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: "1"
Configuration:
ClusterName: production-cluster
ServiceName: my-service
InputArtifacts:
- Name: BuildOutput
RoleArn: arn:aws:iam::PROD_ACCOUNT:role/codepipeline-cross-account-deploy
監査ログ
CloudTrail設定
PipelineTrail:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: pipeline-audit-trail
S3BucketName: !Ref AuditLogBucket
IsMultiRegionTrail: true
EnableLogFileValidation: true
KMSKeyId: !Ref AuditKMSKey
EventSelectors:
- ReadWriteType: All
IncludeManagementEvents: true
DataResources:
- Type: AWS::S3::Object
Values:
- !Sub ${ArtifactBucket.Arn}/
InsightSelectors:
- InsightType: ApiCallRateInsight
- InsightType: ApiErrorRateInsight
監査アラート
# 不正なパイプライン変更検出
UnauthorizedPipelineChange:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codepipeline
detail-type:
- AWS API Call via CloudTrail
detail:
eventSource:
- codepipeline.amazonaws.com
eventName:
- UpdatePipeline
- DeletePipeline
- PutJobSuccessResult
- PutJobFailureResult
userIdentity:
type:
- anything-but: AssumedRole
Targets:
- Id: AlertSNS
Arn: !Ref SecurityAlertTopic
- Id: AlertLambda
Arn: !GetAtt SecurityAlertFunction.Arn
# ビルドシークレットアクセス監視
SecretsAccessMonitor:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: /aws/cloudtrail/pipeline-audit
FilterPattern: '{ $.eventSource = "secretsmanager.amazonaws.com" && $.eventName = "GetSecretValue" }'
MetricTransformations:
- MetricName: SecretsAccessCount
MetricNamespace: Pipeline/Security
MetricValue: "1"
Dimensions:
- Key: SecretId
Value: $.requestParameters.secretId
監査ダッシュボード
AuditDashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: pipeline-security-audit
DashboardBody: !Sub |
{
"widgets": [
{
"type": "metric",
"properties": {
"title": "Pipeline Executions",
"metrics": [
["AWS/CodePipeline", "ExecutionSucceeded", "PipelineName", "${Pipeline}"],
["AWS/CodePipeline", "ExecutionFailed", "PipelineName", "${Pipeline}"]
],
"period": 3600
}
},
{
"type": "log",
"properties": {
"title": "Pipeline API Calls",
"query": "SOURCE '/aws/cloudtrail/pipeline-audit' | filter eventSource = 'codepipeline.amazonaws.com' | stats count(*) by eventName | sort count desc"
}
},
{
"type": "metric",
"properties": {
"title": "Secrets Access",
"metrics": [
["Pipeline/Security", "SecretsAccessCount"]
]
}
}
]
}
承認フロー
マルチステップ承認
ApprovalWorkflow:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
StartAt: RequestApproval
States:
RequestApproval:
Type: Task
Resource: arn:aws:states:::lambda:invoke.waitForTaskToken
Parameters:
FunctionName: !Ref RequestApprovalFunction
Payload:
taskToken.$: $$.Task.Token
pipelineExecutionId.$: $.pipelineExecutionId
approvers:
- security-team@example.com
- platform-team@example.com
TimeoutSeconds: 86400
Next: CheckApprovalCount
CheckApprovalCount:
Type: Choice
Choices:
- Variable: $.approvalCount
NumericGreaterThanEquals: 2
Next: Approved
Default: RequestApproval
Approved:
Type: Task
Resource: arn:aws:states:::codepipeline:putApprovalResult
Parameters:
pipelineName.$: $.pipelineName
stageName.$: $.stageName
actionName.$: $.actionName
token.$: $.token
result:
status: Approved
summary: Approved by required approvers
End: true
ベストプラクティス
flowchart TB
subgraph BestPractices["Best Practices"]
LeastPrivilege["最小権限の原則"]
Encryption["全てを暗号化"]
Separation["環境の分離"]
Audit["包括的な監査"]
end
style BestPractices fill:#22c55e,color:#fff
| カテゴリ | 項目 |
|---|---|
| IAM | 最小権限 + 権限境界 |
| 暗号化 | KMSで全アーティファクト暗号化 |
| 分離 | クロスアカウントで環境分離 |
| 監査 | CloudTrailで全操作記録 |
まとめ
| 領域 | 対策 |
|---|---|
| アクセス制御 | 最小権限IAMロール |
| データ保護 | KMS暗号化 |
| 環境分離 | クロスアカウントデプロイ |
| 可視性 | 監査ログと承認フロー |
パイプラインセキュリティを適切に実装することで、安全なCI/CD環境を実現できます。