Snyk 提供の Docker イメージから AWS CodeBuild プロジェクトを動かすために必要なものを調べてみた
こんにちは!AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。
今回は、Snyk から提供されている Docker イメージを使用して、CodeBuild プロジェクトを起動するにはどうすればいいのかご紹介します。
はじめに
Snyk では Docker Hub で Docker イメージを提供しています。イメージタグで解析する言語を切り替える仕組みとなっています。
今回は Snyk IaC(Snyk CLI)で利用するため、snyk/snyk:linux
をベースイメージとして選択します。
よく似たイメージで、snyk/snyk:cli
がありますが「Deprecation Notice」いわく、非推奨イメージで将来的に削除される可能性あるため、今回はsnyk/snyk:linux
を利用します。
前提条件
CodeBuild(CI)でsnyk test
系のコマンドを実行するには「認証トークン」または「サービスアカウント」が必要になります。
今回は Snyk で推奨されている「サービスアカウント」を利用します。
このブログを書くにあたって
このブログを書くにあたった背景をご紹介します。
先日、Qiita にて「AWS CodeBuild で Snyk IaC を使用してみた」というブログを書かせていただきました。重ねてご覧いただけますと幸いです。(LGTM もいただけると嬉しいです。)
この記事の中で、CodeBuild で使用するイメージは、aws/codebuild/amazonlinux2-x86_64-standard:3.0
を使用することにしました。
理由は、snyk/snyk:linux
は CA 証明書が含まれていない Docker イメージであるため https 通信ができませんでした。
私は Docker Hub への認証情報を Secrets Manager に保存していたため、Secrets Manager エンドポイントへ通信できずINSTALL
フェーズで CodeBuild がこけてしまいました。
[Container] 2022/07/17 13:45:59 Waiting for agent ping
[Container] 2022/07/17 13:46:00 Waiting for DOWNLOAD_SOURCE
[Container] 2022/07/17 13:46:00 Phase is DOWNLOAD_SOURCE
[Container] 2022/07/17 13:46:00 CODEBUILD_SRC_DIR=/codebuild/output/src815573339/src
[Container] 2022/07/17 13:46:00 YAML location is /codebuild/readonly/buildspec.yml
[Container] 2022/07/17 13:46:00 Processing environment variables
[Container] 2022/07/17 13:46:02 Phase complete: DOWNLOAD_SOURCE State: FAILED
[Container] 2022/07/17 13:46:02 Phase context status code: Secrets Manager Error Message: RequestError: send request failed
caused by: Post "https://secretsmanager.ap-northeast-1.amazonaws.com/": x509: certificate signed by unknown authority
さて、ここまででsnyk/snyk:linux
から直接、CodeBuild で起動できないことがわかりました。
それでは、CodeBuild で起動するにはどのようにすればいいのかをご紹介します。
やってみた
構成図
今回の構成は次のとおりです。snyk/snyk:linux
に CA 証明書をインストールしたイメージを ECR にプッシュして利用します。
Terraform のパイプラインを想定していますが、今回は Terraform が主題では無いのでterraform apply
コマンド用の CodeBuild は作成しません。
CloudFormation テンプレート
今回使用した CloudFormation のテンプレートファイルです。
Snyk IaC パイプラインの作成にお役に立てれば幸いです。
ファイル全体(クリックで表示できます)
AWSTemplateFormatVersion: '2010-09-09'
Description: Terraform pipeline template.
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project Name Prefix"
Parameters:
- PrjPrefix
- Label:
default: "Repository Configuration"
Parameters:
- BranchName
- Label:
default: "Snyk Organization Profile"
Parameters:
- SnykOrganizationId
- SnykOrganizationApiKey
- Label:
default: "Logs Configuration"
Parameters:
- BuildLogsRationDay
- Label:
default: "terraform build configuration"
Parameters:
- TerraformVersion
Parameters:
PrjPrefix:
Type: String
Description: "Enter resource prefix. (ex. terraform-dev)"
Default: "terraform-dev"
BranchName:
Type: String
Description: "Enter the name of the branch where you want to store .snyk file."
Default: "main"
SnykOrganizationId:
Type: String
Description: "Enter Snyk Organization ID."
NoEcho: true
SnykOrganizationApiKey:
Type: String
Description: "Enter Snyk Organization API Key."
NoEcho: true
BuildLogsRationDay:
Type: Number
Description: "Enter build log retention period."
Default: 90
AllowedValues:
- 1
- 3
- 5
- 7
- 14
- 30
- 60
- 90
- 120
- 150
- 180
- 365
- 400
- 545
- 731
- 1827
- 3653
TerraformVersion:
Type: String
Description: "Enter the version of Terraform that use with CodeBuild."
Default: "1.2.0"
Resources:
#################################
# KMS (CloudWatch Logs)
#################################
KeyCWL:
Type: AWS::KMS::Key
Properties:
Description: "Terraform pipeline Build Logs Key"
Enabled: true
EnableKeyRotation: true
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "Enable IAM User Permissions"
Effect: "Allow"
Principal:
AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root"
Action: "kms:*"
Resource: "*"
- Sid: Allow use of the key from CWL
Effect: "Allow"
Principal:
Service: !Sub "logs.${AWS::Region}.amazonaws.com"
Action:
- "kms:Encrypt"
- "kms:Decrypt"
- "kms:ReEncrypt"
- "kms:GenerateDataKey"
- "kms:Describe"
Resource: "*"
Condition:
ArnLike:
kms:EncryptionContext:aws:logs:arn: !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*"
KeySpec: "SYMMETRIC_DEFAULT"
KeyUsage: "ENCRYPT_DECRYPT"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-logs-key"
KeyAliasCWL:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${PrjPrefix}-tf-pipeline-logs-key"
TargetKeyId: !Ref KeyCWL
#################################
# KMS (S3 arthifact)
#################################
KeyS3Arthifact:
Type: AWS::KMS::Key
Properties:
Description: "Terraform pipeline arthifact Key"
Enabled: true
EnableKeyRotation: true
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "Enable IAM User Permissions"
Effect: "Allow"
Principal:
AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root"
Action: "kms:*"
Resource: "*"
KeySpec: "SYMMETRIC_DEFAULT"
KeyUsage: "ENCRYPT_DECRYPT"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-artifact-key"
KeyAliasS3Arthifact:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${PrjPrefix}-tf-pipeline-artifact-key"
TargetKeyId: !Ref KeyS3Arthifact
#################################
# KMS (Secrets Manager)
#################################
KeySecretsManager:
Type: AWS::KMS::Key
Properties:
Description: "Terraform pipeline Secrets Manager Key"
Enabled: true
EnableKeyRotation: true
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "Enable IAM User Permissions"
Effect: "Allow"
Principal:
AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root"
Action: "kms:*"
Resource: "*"
KeySpec: "SYMMETRIC_DEFAULT"
KeyUsage: "ENCRYPT_DECRYPT"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-secretsmanager-key"
KeyAliasSecretsManager:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${PrjPrefix}-tf-pipeline-secretsmanager-key"
TargetKeyId: !Ref KeySecretsManager
#################################
# CodeCommit
#################################
CodeCommit:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: !Sub "${PrjPrefix}-tf-repo"
RepositoryDescription: !Sub "${PrjPrefix}-tf-repo"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-repo"
#################################
# Custom Resource
#################################
PutExcludeRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
- "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
RoleName: !Sub "${PrjPrefix}-tf-repo-${BranchName}-put-exclude-role"
PutExcludeLog:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${PrjPrefix}-tf-repo-${BranchName}-put-exclude"
KmsKeyId: !GetAtt KeyCWL.Arn
PutExcludeFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "${PrjPrefix}-tf-repo-${BranchName}-put-exclude"
Code:
ZipFile: |
import json
import boto3
import cfnresponse
def handler(event, context):
try:
repository = event['ResourceProperties']['RepositoryName']
branch = event['ResourceProperties']['BranchName']
content = event['ResourceProperties']['FileContent'].encode()
path = event['ResourceProperties']['FilePath']
if event['RequestType'] == 'Create':
codecommit = boto3.client('codecommit')
response = codecommit.put_file(
repositoryName=repository,
branchName=branch,
fileContent=content,
filePath=path,
commitMessage='Initial Commit',
name='Your Lambda Helper'
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
if event['RequestType'] == 'Update':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, {})
Handler: index.handler
MemorySize: 128
Role: !GetAtt PutExcludeRole.Arn
Runtime: "python3.9"
Timeout: 60
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-repo-${BranchName}-put-exclude"
DependsOn: PutExcludeLog
PutExclude:
Type: Custom::CodeCommitPutExclude
Properties:
ServiceToken: !GetAtt PutExcludeFunction.Arn
RepositoryName: !GetAtt CodeCommit.Name
BranchName: !Ref BranchName
FileContent: "ignore:"
FilePath: ".snyk"
#################################
# Secrets Manager
#################################
SnykOrganizationProfile:
Type: AWS::SecretsManager::Secret
Properties:
Description: "Snyk Web UI Organization profile for terraform pipelines"
Name: !Sub "${PrjPrefix}/snyk_org"
SecretString: !Sub '{"OrgId":"${SnykOrganizationId}","OrgApi":"${SnykOrganizationApiKey}"}'
KmsKeyId: !GetAtt KeySecretsManager.Arn
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}/snyk_org"
#################################
# S3 Bucket (Artifact)
#################################
BucketArtifacts:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub "${PrjPrefix}-tf-pipeline-artifacts"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: "aws:kms"
KMSMasterKeyID: !GetAtt KeyS3Arthifact.Arn
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-artifacts"
BucketPolicyArtifacts:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref BucketArtifacts
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: "AllowSSLRequestsOnly"
Effect: "Deny"
Principal: "*"
Action: "s3:*"
Resource:
- !GetAtt BucketArtifacts.Arn
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketArtifacts.Arn }
Condition:
Bool:
aws:SecureTransport: "false"
#################################
# CloudWatch Logs (CodeBuild terraform plan)
#################################
CWLTfplan:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-tfplan-project"
RetentionInDays: !Ref BuildLogsRationDay
KmsKeyId: !GetAtt KeyCWL.Arn
Tags:
- Key: "Name"
Value: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-tfplan-project"
#################################
# CodeBuild (terraform plan)
#################################
PolicyTfplan:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub "${PrjPrefix}-tf-build-tfplan-project-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "BuildLogs"
Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CWLTfplan}"
- !GetAtt CWLTfplan.Arn
- Sid: "KmsKey"
Effect: "Allow"
Action:
- "kms:Decrypt"
- "kms:Encrypt"
- "kms:ReEncrypt"
- "kms:GenerateDataKey"
Resource:
- !GetAtt KeyS3Arthifact.Arn
- Sid: "S3Artifact"
Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketAcl"
- "s3:GetBucketLocation"
Resource:
- !GetAtt BucketArtifacts.Arn
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketArtifacts.Arn }
RoleTfplan:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PrjPrefix}-tf-build-tfplan-project-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "codebuild.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- !Ref PolicyTfplan
- "arn:aws:iam::aws:policy/ReadOnlyAccess"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-tfplan-project-role"
ProjectTfplan:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${PrjPrefix}-tf-build-tfplan-project"
Description: "Execute terraform plan command."
Source:
Type: "CODEPIPELINE"
BuildSpec: |
version: 0.2
env:
exported-variables:
- BUILD_URL
phases:
pre_build:
commands:
- "terraform init -input=false -no-color"
build:
commands:
- "terraform plan -input=false -no-color -out=tfplan.binary"
post_build:
commands:
- "terraform show -no-color -json tfplan.binary > tfplan.json"
- "export BUILD_URL=`echo $CODEBUILD_BUILD_URL`"
artifacts:
files:
- "tfplan.binary"
- "tfplan.json"
Artifacts:
Type: "CODEPIPELINE"
Cache:
Type: "LOCAL"
Modes:
- "LOCAL_DOCKER_LAYER_CACHE"
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: !Sub "public.ecr.aws/hashicorp/terraform:${TerraformVersion}"
ImagePullCredentialsType: "CODEBUILD"
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
Status: "ENABLED"
GroupName: !Ref CWLTfplan
EncryptionKey: !GetAtt KeyS3Arthifact.Arn
ResourceAccessRole: !GetAtt RoleTfplan.Arn
ServiceRole: !GetAtt RoleTfplan.Arn
TimeoutInMinutes: 60
Visibility: "PRIVATE"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-tfplan-project"
#################################
# CloudWatch Logs (CodeBuild Snyk IaC)
#################################
CWLSnykIaC:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-snyk-iac-project"
RetentionInDays: !Ref BuildLogsRationDay
KmsKeyId: !GetAtt KeyCWL.Arn
Tags:
- Key: "Name"
Value: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-snyk-iac-project"
#################################
# CodeBuild (Snyk IaC)
#################################
PolicySnykIaC:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub "${PrjPrefix}-tf-build-snyk-iac-project-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "BuildLogs"
Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CWLSnykIaC}"
- !GetAtt CWLSnykIaC.Arn
- Sid: "BuildReports"
Effect: "Allow"
Action:
- "codebuild:CreateReportGroup"
- "codebuild:CreateReport"
- "codebuild:UpdateReport"
- "codebuild:BatchPutTestCases"
- "codebuild:BatchPutCodeCoverages"
Resource:
- !Sub "arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${PrjPrefix}-tf-build-snyk-iac-project-reports"
- Sid: "GitPull"
Effect: "Allow"
Action: "codecommit:GitPull"
Resource: !GetAtt CodeCommit.Arn
- Sid: "GetSecretValue"
Effect: "Allow"
Action: "secretsmanager:GetSecretValue"
Resource:
- !Ref SnykOrganizationProfile
- Sid: "KmsKey"
Effect: "Allow"
Action:
- "kms:Decrypt"
- "kms:DescribeKey"
- "kms:Encrypt"
- "kms:ReEncrypt"
- "kms:GenerateDataKey"
Resource:
- !GetAtt KeyS3Arthifact.Arn
- !GetAtt KeySecretsManager.Arn
- Sid: "S3Artifact"
Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketAcl"
- "s3:GetBucketLocation"
Resource:
- !GetAtt BucketArtifacts.Arn
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketArtifacts.Arn }
RoleSnykIaC:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PrjPrefix}-tf-build-snyk-iac-project-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "codebuild.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- !Ref PolicySnykIaC
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-snyk-iac-project-role"
SnykIaCImageRepo:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub "${PrjPrefix}-snyk-iac"
RepositoryPolicyText:
Version: "2012-10-17"
Statement:
- Sid: "CodeBuildProject"
Effect: "Allow"
Principal:
Service: "codebuild.amazonaws.com"
Action:
- "ecr:GetDownloadUrlForLayer"
- "ecr:BatchGetImage"
- "ecr:BatchCheckLayerAvailability"
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
aws:SourceArn: !Sub "arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/${PrjPrefix}-tf-build-snyk-iac-project"
ImageScanningConfiguration:
ScanOnPush: true
ImageTagMutability: "MUTABLE"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-snyk-iac"
ProjectSnykIaC:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${PrjPrefix}-tf-build-snyk-iac-project"
Description: "Analyze the code for vulnerabilities using Snyk IaC."
Source:
Type: "CODEPIPELINE"
BuildSpec: |
version: 0.2
env:
exported-variables:
- BUILD_URL
phases:
pre_build:
commands:
- "printenv"
- "mkdir -p ${SRC_DIR}"
- "cp -p tfplan.json ${SRC_DIR}/"
- "cd ${SRC_DIR}"
- "echo Executing Snyk IaC"
build:
commands:
- "snyk iac test tfplan.json --report || true"
post_build:
commands:
- "export BUILD_URL=`echo $CODEBUILD_BUILD_URL`"
Artifacts:
Type: "CODEPIPELINE"
Cache:
Type: "LOCAL"
Modes:
- "LOCAL_DOCKER_LAYER_CACHE"
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: !GetAtt SnykIaCImageRepo.RepositoryUri
# ImagePullCredentialsType: "CODEBUILD"
ImagePullCredentialsType: "SERVICE_ROLE"
EnvironmentVariables:
- Name: "SNYK_TOKEN"
Type: "SECRETS_MANAGER"
Value: !Sub "${SnykOrganizationProfile}:OrgApi"
- Name: "SNYK_CFG_ORG"
Type: "SECRETS_MANAGER"
Value: !Sub "${SnykOrganizationProfile}:OrgId"
- Name: "SRC_DIR"
Type: "PLAINTEXT"
Value: !Sub "${PrjPrefix}-tf-build-snyk-iac-project"
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
Status: "ENABLED"
GroupName: !Ref CWLSnykIaC
EncryptionKey: !GetAtt KeyS3Arthifact.Arn
ResourceAccessRole: !GetAtt RoleSnykIaC.Arn
ServiceRole: !GetAtt RoleSnykIaC.Arn
TimeoutInMinutes: 60
Visibility: "PRIVATE"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-tfsec-project"
#################################
# CodePipeline
#################################
PolicyTfPipeline:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub "${PrjPrefix}-tf-pipeline-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "S3Artifact"
Effect: "Allow"
Action:
- "s3:GetObject*"
- "s3:GetBucket*"
- "s3:List*"
- "s3:DeleteObject*"
- "s3:PutObject"
- "s3:Abort*"
Resource:
- !GetAtt BucketArtifacts.Arn
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketArtifacts.Arn }
- Sid: "KmsKey"
Effect: "Allow"
Action:
- "kms:Decrypt"
- "kms:DescribeKey"
- "kms:Encrypt"
- "kms:ReEncrypt*"
- "kms:GenerateDataKey*"
Resource:
- !GetAtt KeyS3Arthifact.Arn
- Sid: "CodeCommitRepo"
Effect: "Allow"
Action:
- "codecommit:GetBranch"
- "codecommit:GetCommit"
- "codecommit:UploadArchive"
- "codecommit:GetUploadArchiveStatus"
- "codecommit:CancelUploadArchive"
Resource:
- !GetAtt CodeCommit.Arn
- Sid: "CodeBuildProjects"
Effect: "Allow"
Action:
- "codebuild:BatchGetBuilds"
- "codebuild:StartBuild"
- "codebuild:StopBuild"
Resource:
- !GetAtt ProjectSnykIaC.Arn
- !GetAtt ProjectTfplan.Arn
RoleTfPipelne:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PrjPrefix}-tf-pipeline-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "codepipeline.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- !Ref PolicyTfPipeline
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-role"
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub "${PrjPrefix}-tf-pipeline"
ArtifactStore:
EncryptionKey:
Id: !GetAtt KeyS3Arthifact.Arn
Type: "KMS"
Location: !Ref BucketArtifacts
Type: "S3"
RoleArn: !GetAtt RoleTfPipelne.Arn
Stages:
- Name: "Source"
Actions:
- Name: "CodeCommit_Source"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Provider: "CodeCommit"
Version: "1"
Configuration:
RepositoryName: !GetAtt CodeCommit.Name
BranchName: !Ref BranchName
PollForSourceChanges: false
OutputArtifacts:
- Name: "Artifact_Source_CodeCommit"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 1
- Name: "terraform_plan_Stage"
Actions:
- Name: "terraform_plan_command"
Namespace: TFPLAN
ActionTypeId:
Category: "Build"
Owner: "AWS"
Provider: "CodeBuild"
Version: "1"
Configuration:
ProjectName: !Ref ProjectTfplan
InputArtifacts:
- Name: "Artifact_Source_CodeCommit"
OutputArtifacts:
- Name: "Artifact_Build_terraform_plan"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 1
- Name: "terraform_plan_command_Manual_Review"
ActionTypeId:
Category: "Approval"
Owner: "AWS"
Provider: "Manual"
Version: "1"
Configuration:
CustomData: "terraform plan review"
ExternalEntityLink: "#{TFPLAN.BUILD_URL}"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 2
- Name: "snyk_iac_Stage"
Actions:
- Name: "Terraform_Security_Analysis"
Namespace: SNYKIAC
ActionTypeId:
Category: "Build"
Owner: "AWS"
Provider: "CodeBuild"
Version: "1"
Configuration:
ProjectName: !Ref ProjectSnykIaC
InputArtifacts:
- Name: "Artifact_Build_terraform_plan"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 1
- Name: "Terraform_Security_Analysis_Manual_Review"
ActionTypeId:
Category: "Approval"
Owner: "AWS"
Provider: "Manual"
Version: "1"
Configuration:
CustomData: "snyk iac review"
ExternalEntityLink: "#{SNYKIAC.BUILD_URL}"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 2
#################################
# EventBridge (CodeCommit State Change)
#################################
PolicyEventBridgeCodeCommit:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub "${PrjPrefix}-tf-pipeline-event-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "CodePipelineExec"
Effect: "Allow"
Action: "codepipeline:StartPipelineExecution"
Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}"
RoleEventBridgeCodeCommit:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PrjPrefix}-tf-pipeline-event-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "events.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- !Ref PolicyEventBridgeCodeCommit
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-event-role"
EventsTfPipelineCodeCommit:
Type: AWS::Events::Rule
Properties:
Name: !Sub "${PrjPrefix}-tf-pipeline-event"
EventPattern:
source:
- "aws.codecommit"
resources:
- !GetAtt CodeCommit.Arn
detail-type:
- "CodeCommit Repository State Change"
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- "branch"
referenceName:
- !Ref BranchName
State: "ENABLED"
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}"
Id: "TerraformPipeline"
RoleArn: !GetAtt RoleEventBridgeCodeCommit.Arn
</details>
パラメータは以下を指定しました。
設定値 | 値 | 備考 |
---|---|---|
BranchName | main | |
BuildLogsRationDay | 90 | |
SnykOrganizationApiKey | Service Account の API キー | |
SnykOrganizationId | Snyk Organization の ID | |
PrjPrefix | takakuni | 任意の値 |
TerraformVersion | 1.2.0 | 任意の値 |
Terraform コード
今回は次の Terraform のコードをsnyk iac test
コマンドで解析します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.23.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
data "aws_region" "current" {}
data "aws_caller_identity" "self" {}
resource "aws_ebs_volume" "example" {
availability_zone = "${data.aws_region.current.name}a"
size = 40
tags = {
Name = "HelloWorld"
}
}
EBS が暗号化されていない脆弱性が検出されるコードになります。
ca-certificates のインストール
Dockerfile のRUN
を使用してエラーの原因である、CA 証明書のインストールを行います。
FROM snyk/snyk:linux
RUN apt update && apt install -y ca-certificates
イメージのビルド、ECR へプッシュします。
docker build -t 123456789123.dkr.ecr.ap-northeast-1.amazonaws.com/takakuni-snyk-iac:latest .
docker image push 123456789123.dkr.ecr.ap-northeast-1.amazonaws.com/takakuni-snyk-iac:latest
環境変数
サービスアカウントを使用して Snyk CLI を利用するには、CodeBuild の環境変数SNYK_TOKEN
に、API キーを設定する必要があります。
今回は、Secrets Manager 経由で CodeBuild から参照できるように設定しています。
また、組織 ID も環境変数SNYK_CFG_ORG
で設定可能なため、Secrets Manager 経由で設定しています。
ProjectSnykIaC:
Type: AWS::CodeBuild::Project
Properties:
# 抜粋
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: !GetAtt SnykIaCImageRepo.RepositoryUri
# ImagePullCredentialsType: "CODEBUILD"
ImagePullCredentialsType: "SERVICE_ROLE"
EnvironmentVariables:
- Name: "SNYK_TOKEN"
Type: "SECRETS_MANAGER"
Value: !Sub "${SnykOrganizationProfile}:OrgApi"
- Name: "SNYK_CFG_ORG"
Type: "SECRETS_MANAGER"
Value: !Sub "${SnykOrganizationProfile}:OrgId"
実行ディレクトリ
snyk iac test
コマンドを--report
オプション付きで実行すると、Snyk Web UI に直接レポートを出力します。
「--report
」オプションでは、実行しているディレクトリ名をプロジェクト名としてレポートを出力します。
デフォルトでは、CodeBuild はsrc
配下でビルドを開始するため、レポート出力を行うディレクトリを分けないと、CodeBuild で実行したプロジェクト名は全てsrc
になってしまいます。
複数の CodeBuild から同じ Snyk の組織に対して、レポートが出力されても問題ないように、新しいディレクトリを作成してsnyk iac test
コマンドを実行します。
ProjectSnykIaC:
Type: AWS::CodeBuild::Project
Properties:
# 抜粋
Source:
Type: "CODEPIPELINE"
BuildSpec: |
version: 0.2
env:
exported-variables:
- BUILD_URL
phases:
pre_build:
commands:
- "printenv"
- "mkdir -p ${SRC_DIR}"
- "cp -p tfplan.json ${SRC_DIR}/"
- "cd ${SRC_DIR}"
- "echo Executing Snyk IaC"
build:
commands:
- "snyk iac test tfplan.json --report || true"
post_build:
commands:
- "export BUILD_URL=`echo $CODEBUILD_BUILD_URL`"
# 抜粋
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: !GetAtt SnykIaCImageRepo.RepositoryUri
# ImagePullCredentialsType: "CODEBUILD"
ImagePullCredentialsType: "SERVICE_ROLE"
EnvironmentVariables:
- Name: "SRC_DIR"
Type: "PLAINTEXT"
Value: !Sub "${PrjPrefix}-tf-build-snyk-iac-project"
出力結果
CodeBuild ログ、Snyk Web UI ともに結果が出力されていることが確認できました。
CodeBuild ログ
Snyk Web UI
(番外編)COPY コマンドを使用して Docker イメージを作成する
今回、snyk/snyk:linux
をベースイメージとしてRUN
を使用して CA 証明書をインストールする方法をご紹介しましたが、COPY
コマンドを使用して別のベースイメージで作成する方法もあります。
FROM ubuntu
COPY /usr/local/bin/snyk /usr/local/bin/snyk
まとめ
以上、「Snyk 提供の Docker イメージから AWS CodeBuild プロジェクトを動かすために必要なものを調べてみた」でした。
Amazon ECR Public Galleryにはまだ登録されていないですが、今後に期待ですね!
以上、AWS 事業本部コンサルティング部のたかくに(@takakuni_)でした!