はじめに
はじめまして!たかくにと申します!
この記事は「Snykを使って開発者セキュリティに関する記事を投稿しよう!」というテーマに沿って投稿しています!
普段もIaCやAWSを中心にブログを書いているので、ご覧いただけると嬉しいです。
突然ですが、みなさん!IaC使っていますかー? IaC便利ですよね!
Googleトレンドでも、年々IaCの人気が増加していることが伺えます。
では、IaCで使っているコードのセキュリティを気にしていますか?
気にしていなくても、これから気にすれば大丈夫です!
今回は、AWS CodeBuildでSnyk IaCを使用してみた感想、詰まった点をご紹介します!
まずはじめに、Snyk IaCは、IaCのコードに対してセキュリティスキャンを行うツールです。コードに潜在した脆弱性を是正することで、各コードの品質を整えることができます。
具体的な使い方は私のブログをはじめ、さまざまな方々が書いて頂いている(これから書いて頂ける)と思うので今回は省略します。
参考までに私のブログを載せておきます。
構成
今回は、TerraformをデプロイするCICDパイプラインがあるとして、CodeBuildプロジェクトにSnyk IaCを組み込む際に詰まった点や解決方法をご紹介できればと思います。
具体的な構成図は次のとおりです。赤枠の線を中心にご紹介できればと思います。
2022/7/19 追記
検証で使用したCloudFormationファイルをアップします。
クリックで表示
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: "Docker Login Profile"
Parameters:
- DockerHubUserName
- DockerHubUserPassword
- Label:
default: "Snyk Organization Profile"
Parameters:
- SnykOrganizationId
- SnykOrganizationApiKey
- Label:
default: "Logs Configuration"
Parameters:
- BuildLogsRationDay
- Label:
default: "terraform build configuration"
Parameters:
- TerraformVersion
- Label:
default: "CodePipeline notification configuration"
Parameters:
- CodePipelineNotificationEmail
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 exclude.yml"
Default: "main"
DockerHubUserName:
Type: String
Description: "Enter Docker Hub loging user name. (ex. terraform)"
NoEcho: true
DockerHubUserPassword:
Type: String
Description: "Enter Docker Hub loging user password. (ex. P@ssw0rd) If you enable MFA in Docker Hub, generate AccessToken and enter instead of user password."
NoEcho: true
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"
CodePipelineNotificationEmail:
Type: String
Description: "Enter the email of CodePipeline notification email when pipeline failed."
Default: "examples@example.com"
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 (S3 tfstate)
#################################
KeyS3Tfstate:
Type: AWS::KMS::Key
Properties:
Description: "Terraform pipeline tfstate 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-tfstate-key"
KeyAliasS3Tfstate:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${PrjPrefix}-tf-pipeline-tfstate-key"
TargetKeyId: !Ref KeyS3Tfstate
#################################
# 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
#################################
# KMS (DynamoDB)
#################################
KeyDynamoDB:
Type: AWS::KMS::Key
Properties:
Description: "Terraform pipeline DynamoDB 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-dynamoDB-key"
KeyAliasDynamoDB:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${PrjPrefix}-tf-pipeline-dynamoDB-key"
TargetKeyId: !Ref KeyDynamoDB
#################################
# 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 (tfstate)
#################################
BucketTfstate:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub "${PrjPrefix}-tf-pipeline-tfstate"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: "aws:kms"
KMSMasterKeyID: !GetAtt KeyS3Tfstate.Arn
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: "Enabled"
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-tfstate"
BucketPolicyTfstate:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref BucketTfstate
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: "AllowSSLRequestsOnly"
Effect: "Deny"
Principal: "*"
Action: "s3:*"
Resource:
- !GetAtt BucketTfstate.Arn
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketTfstate.Arn }
Condition:
Bool:
aws:SecureTransport: "false"
#################################
# DynamoDB table (state lock)
#################################
DDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub "${PrjPrefix}-tf-pipeline-state-lock-table"
KeySchema:
- AttributeName: "LockID"
KeyType: "HASH"
AttributeDefinitions:
- AttributeName: "LockID"
AttributeType: "S"
BillingMode: "PAY_PER_REQUEST"
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
SSESpecification:
KMSMasterKeyId: !GetAtt KeyDynamoDB.Arn
SSEEnabled: true
SSEType: "KMS"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-state-lock-table"
#################################
# 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
- !GetAtt KeyS3Tfstate.Arn
- !GetAtt KeyDynamoDB.Arn
- Sid: "S3Tfstate"
Effect: "Allow"
Action:
- "s3:PutObject*"
- "s3:DeleteObject*"
Resource:
- !Sub
- "${BucketArn}/*"
- { BucketArn: !GetAtt BucketTfstate.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 }
- Sid: "DynamoDB"
Effect: "Allow"
Action:
- "dynamodb:PutItem"
- "dynamodb:DeleteItem"
Resource: !GetAtt DDBTable.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"
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:
install:
runtime-versions:
golang: 1.14
commands:
- curl https://static.snyk.io/cli/latest/snyk-linux -o snyk
- "chmod +x ./snyk"
- "mv ./snyk /usr/local/bin/"
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: "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
ImagePullCredentialsType: "CODEBUILD"
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"
ReportGroupSnykIaC:
Type: AWS::CodeBuild::ReportGroup
Properties:
Name: !Sub "${ProjectSnykIaC}-reports"
Type: "TEST"
DeleteReports: true
ExportConfig:
ExportConfigType: "NO_EXPORT"
Tags:
- Key: "Name"
Value: !Sub "${ProjectSnykIaC}-reports"
#################################
# CloudWatch Logs (CodeBuild terraform apply)
#################################
CWLTfapply:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-tfapply-project-role"
RetentionInDays: !Ref BuildLogsRationDay
KmsKeyId: !GetAtt KeyCWL.Arn
Tags:
- Key: "Name"
Value: !Sub "/aws/codebuild/${PrjPrefix}-tf-build-tfapply-project-role"
#################################
# CodeBuild (terraform apply)
#################################
RoleTfapply:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PrjPrefix}-tf-build-tfapply-project-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "codebuild.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AdministratorAccess"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-tfapply-project-role"
ProjectTfapply:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${PrjPrefix}-tf-build-tfapply-project"
Description: "Execute terraform apply 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 apply tfplan.binary -input=false -no-color -auto-approve"
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 CWLTfapply
EncryptionKey: !GetAtt KeyS3Arthifact.Arn
ResourceAccessRole: !GetAtt RoleTfapply.Arn
ServiceRole: !GetAtt RoleTfapply.Arn
TimeoutInMinutes: 60
Visibility: "PRIVATE"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-build-tfapply-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
- !GetAtt ProjectTfapply.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
- Name: "terraform_apply_Stage"
Actions:
- Name: "terraform_apply_command"
Namespace: TFAPPLY
ActionTypeId:
Category: "Build"
Owner: "AWS"
Provider: "CodeBuild"
Version: "1"
Configuration:
ProjectName: !Ref ProjectTfapply
InputArtifacts:
- Name: "Artifact_Build_terraform_plan"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 1
#################################
# 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
#################################
# SNS Topic (Snyk IaC Build Phase Failed)
#################################
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
DisplayName: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
SNSSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: "email"
TopicArn: !Ref SNSTopic
Endpoint: !Ref CodePipelineNotificationEmail
#################################
# CodeStar Notification case
#################################
SNSTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "AWSCodeStarNotifications_publish"
Effect: "Allow"
Principal:
Service: "codestar-notifications.amazonaws.com"
Action:
- "SNS:Publish"
Resource: !Ref SNSTopic
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Topics:
- !Ref SNSTopic
#################################
# CodeStar Notification case
#################################
CodePipelineFailedNotification:
Type: AWS::CodeStarNotifications::NotificationRule
Properties:
DetailType: "FULL"
EventTypeIds:
- "codebuild-project-build-phase-failure"
Name: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected"
Resource: !GetAtt ProjectSnykIaC.Arn
Status: "ENABLED"
Targets:
- TargetType: "SNS"
TargetAddress: !Ref SNSTopic
#################################
# CodeStar Notification case
#################################
CodePipelineManualNotification:
Type: AWS::CodeStarNotifications::NotificationRule
Properties:
DetailType: "FULL"
EventTypeIds:
- "codepipeline-pipeline-manual-approval-needed"
Name: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-manual-approve-needed"
Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}"
Status: "ENABLED"
Targets:
- TargetType: "SNS"
TargetAddress: !Ref SNSTopic
#################################
# Eventbridge Notification case
#################################
# SNSTopicPolicy:
# Type: AWS::SNS::TopicPolicy
# Properties:
# PolicyDocument:
# Version: "2012-10-17"
# Statement:
# - Sid: "AWSEventBridgeNotifications_publish"
# Effect: "Allow"
# Principal:
# Service: "events.amazonaws.com"
# Action:
# - "sns:Publish"
# Resource: !Ref SNSTopic
# Condition:
# StringEquals:
# aws:SourceAccount: !Ref AWS::AccountId
# Topics:
# - !Ref SNSTopic
# EventsTfPipeline:
# Type: AWS::Events::Rule
# Properties:
# Name: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-event"
# EventPattern:
# source:
# - "aws.codebuild"
# resources:
# - !GetAtt ProjectSnykIaC.Arn
# detail-type:
# - "Snyk IaC Build Detected"
# detail:
# build-status:
# - "FAILED"
# project-name:
# - !Ref ProjectSnykIaC
# State: "ENABLED"
# Targets:
# - Arn: !Ref SNSTopic
# Id: "SnykIaCDetectedTopic"
Outputs:
TfstateBucketName:
Value: !Ref BucketTfstate
Export:
Name: !Sub "${AWS::StackName}-TfstateBucketName"
DDBTableName:
Value: !Ref DDBTable
Export:
Name: !Sub "${AWS::StackName}-DDBTableName"
やってみた
Dockerイメージの取得先
Snykでは公式からDockerイメージが配布されています。
/app
ディレクトリに対して解析を行いイメージタグで、解析する言語等を分岐しているイメージになります。
私も当初、snyk/snyk:linux
イメージからCodeBuildの起動を試みましたが以下のエラーに遭遇しました。
[Container] 2022/07/17 14:02:00 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
調査の結果、DockerイメージにCA証明書が含まれていないためエラーが起きているようでした。
以下のブログの通り、aws/codebuild/amazonlinux2-x86_64-standard:3.0
イメージを使用してSnykインストール、Snyk CLIを実行しました。
buildspec.yaml
のサンプル
version: 0.2
env:
phases:
install:
runtime-versions:
golang: 1.14
commands:
- curl https://static.snyk.io/cli/latest/snyk-linux -o snyk
- "chmod +x ./snyk"
- "mv ./snyk /usr/local/bin/"
pre_build:
commands:
- "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"
Web UIへの出力
今回コードのソースは、AWS CodeCommitを使用しています。
そのため、Snyk IaCの結果をWeb UIで確認するには「--report
」コマンドを使用する必要があります。
Snyk CLIをCIで利用するためには、Service accountsが必要で、Service accountsは、AWSでいうアクセスキー
のようなAPIキーです。
外部に漏れないよう、Secrets Manager等の利用がオススメです。
Service accountsで発行したAPIキーは、「環境変数SNYK_TOKEN
に設定すること」で利用可能になります。
CloudFormation
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:
phases:
install:
runtime-versions:
golang: 1.14
commands:
- curl https://static.snyk.io/cli/latest/snyk-linux -o snyk
- "chmod +x ./snyk"
- "mv ./snyk /usr/local/bin/"
pre_build:
commands:
- "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"
Artifacts:
Type: "CODEPIPELINE"
Cache:
Type: "LOCAL"
Modes:
- "LOCAL_DOCKER_LAYER_CACHE"
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
ImagePullCredentialsType: "CODEBUILD"
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"
SNS通知はマストで設定したい
Snyk IaCは解析結果によって終了ステータスが変わります。終了ステータスが0以外の場合、CodeBuildプロジェクトは失敗します。
そのため、何が悪かったのか(文法なのか、脆弱性なのか、その他の原因)を開発者に通知する必要があります。
$ snyk iac test -h
IaC test
Usage
snyk iac test [<OPTIONS>] [<PATH>]
Description
The snyk iac test command tests for any known security issue.
For a list of related commands see the snyk iac help; iac --help
For more information see Snyk CLI for Infrastructure as Code
https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code
Exit codes
Possible exit codes and their meaning:
0: success, no vulnerabilities found
1: action_needed, vulnerabilities found
2: failure, try to re-run command
3: failure, no supported projects detected
SNS通知は大きく分けて、「CodeStar経由で通知」、「EventBridge経由で通知」の2パターンあるため、好みに合わせて設定しましょう。
CloudFormation
#################################
# SNS Topic (Snyk IaC Build Phase Failed)
#################################
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
DisplayName: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
Tags:
- Key: "Name"
Value: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-topic"
SNSTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "AWSEventBridgeNotifications_publish"
Effect: "Allow"
Principal:
Service: "events.amazonaws.com"
Action:
- "sns:Publish"
Resource: !Ref SNSTopic
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Topics:
- !Ref SNSTopic
SNSSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: "email"
TopicArn: !Ref SNSTopic
Endpoint: !Ref CodePipelineNotificationEmail
#################################
# EventBridge (CodeCommit State Change)
#################################
EventsTfPipeline:
Type: AWS::Events::Rule
Properties:
Name: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected-event"
EventPattern:
source:
- "aws.codebuild"
resources:
- !GetAtt ProjectSnykIaC.Arn
detail-type:
- "Snyk IaC Build Detected"
detail:
build-status:
- "FAILED"
project-name:
- !Ref ProjectSnykIaC
State: "ENABLED"
Targets:
- Arn: !Ref SNSTopic
Id: "SnykIaCDetectedTopic"
##############
# CodeStart経由の場合
##############
# SNSTopicPolicy:
# Type: AWS::SNS::TopicPolicy
# Properties:
# PolicyDocument:
# Version: "2012-10-17"
# Statement:
# - Sid: "AWSCodeStarNotifications_publish"
# Effect: "Allow"
# Principal:
# Service: "codestar-notifications.amazonaws.com"
# Action:
# - "SNS:Publish"
# Resource: !Ref SNSTopic
# Condition:
# StringEquals:
# aws:SourceAccount: !Ref AWS::AccountId
# Topics:
# - !Ref SNSTopic
# CodePipelineFailedNotification:
# Type: AWS::CodeStarNotifications::NotificationRule
# Properties:
# DetailType: "FULL"
# EventTypeIds:
# - "codebuild-project-build-phase-failure"
# Name: !Sub "${PrjPrefix}-tf-pipeline-snyk-iac-detected"
# Resource: !GetAtt ProjectSnykIaC.Arn
# Status: "ENABLED"
# Targets:
# - TargetType: "SNS"
# TargetAddress: !Ref SNSTopic
2022/7/19 追記
Good Practiceによると、終了コードは、「|| true」で書き換えることができるみたいでした。
version: 0.2
env:
exported-variables:
- BUILD_URL
phases:
install:
runtime-versions:
golang: 1.14
commands:
- curl https://static.snyk.io/cli/latest/snyk-linux -o snyk
- "chmod +x ./snyk"
- "mv ./snyk /usr/local/bin/"
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`"
終了コードを書き換えた場合は、後続のパイプラインで手動承認のアクションを追加することをオススメします!
手動承認アクションのレビュー用URLは、CodeBuildのビルド結果に遷移するページを指定していますが、Snyk Web UIに遷移する仕組みも面白そうです。
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
- Name: "terraform_apply_Stage"
Actions:
- Name: "terraform_apply_command"
Namespace: TFAPPLY
ActionTypeId:
Category: "Build"
Owner: "AWS"
Provider: "CodeBuild"
Version: "1"
Configuration:
ProjectName: !Ref ProjectTfapply
InputArtifacts:
- Name: "Artifact_Build_terraform_plan"
RoleArn: !GetAtt RoleTfPipelne.Arn
RunOrder: 1
.snykファイルの自動作成
Snyk IaCでは、.snyk
ファイルで検知を回避(Ignore)したいルールを定義できます。
.snyk
ファイルにルールを記載しなくても、Snyk CLIの実行には問題ないです。そのため、CodeCommitレポジトリ作成時にデフォルトで作成するようにLambdaを仕込みます。
CloudFormation
#################################
# 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"
最後に
以上、「AWS CodeBuildでSnyk IaCを使用してみた」でした!
脆弱性が見つかった場合、CodeBuildでエラーを返してCodePipelineが止まるため、是正するか.snyk
で回避するかの対応が必要だと感じました。
個人的には「スキップ」という選択肢がないため、セキュアなデプロイメントパイプラインが組めるのではないかと思いました。
この記事がどなたかの参考になればとっても嬉しいです。以上、たかくにでした!