
ハンズオンブログ:「Build and Deploy a Secure Container Image with AWS and Snyk」をやってみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。
今回はタイトルの通り、ハンズオンブログ「Build and Deploy a Secure Container Image with AWS and Snyk」をやってみようと思います。
元ネタは以下のブログです。今回はブログを読んで、詰まったところや感想などを共有できればと思います。
構成図
今回は、次のような構成になります。
- AWS CodeCommitにプッシュされたマニフェストファイルに対して、Snyk Open Sourceスキャンを行います。
 - Snyk Open Sourceのスキャン後に、Dockerイメージをビルドして、ECRにプッシュします。
 - ビルドされたDockerイメージに対してもECR拡張イメージスキャンを行います。
 

Snyk Open Sourceとは
Snyk Open Sourceとは、アプリケーション内で利用されるオープンソースライブラリに対して脆弱性がないかをスキャンする製品です。
Javaだとpom.xml、JavaScriptだとpackage.jsonなどのファイルに対してスキャンを行い、直接依存のみならず間接的な依存関係で発生している脆弱性も検知する製品となっています。
サポートされている言語等の情報は次の公式ドキュメントを参照してください。
前提条件
はじめに、今回のハンズオンでは次のアカウントが必要です。
- AWS アカウント
 - Snyk アカウント(FreeプランでOK)
 - Docker Hub アカウント(認証情報を使用するため)
 
また、次のAWSサービスをサポートしているリージョンでリソースを作成する必要があります。
今回は、東京リージョンでハンズオンを行いました。
- AWS CodeCommit
 - AWS CodeBuild
 - AWS CodePipeline
 - Amazon Inspector
 - AWS KMS
 - AWS Systems Manager(Parameter Store)
 - Amazon ECR
 
AWS CodeCommitの作成
CodeCommitリポジトリを作成し、Dockerfileを保存する領域を作成します。
リポジトリの作成後、github.com/snyk-labs/java-goofにあるコードのミラーリングを行います。
CodeCommitへの接続は、「AWSCodeCommitPowerUser」を持ったIAMユーザーが必要です。今回CodeCommitの名前は、「apn-java-goof」で作成しました。
続いて、コードのミラーリングを行います。
git clone --mirror https://github.com/snyk-labs/java-goof apn-java-goof
cd apn-java-goof
git push https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/apn-java-goof
git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/apn-java-goof
ECRの作成
ECRレポジトリの作成を行います。
今回、latestタグでECRへDockerイメージをプッシュするため「タグのイミュータビリティ」は無効にしたまま作成します。名前は「apn-java-goof」で作成しました。

KMSの作成
Systems ManagerのParameter Storeを暗号化するため、KMSキーを作成します。
キーポリシー等は検証用のためデフォルトの設定で作成し、エイリアスは「apn-java-goof」で作成しました。

Parameter Storeの作成
Docker Hubの認証情報を保管するためParameter Storeを利用します。
Docker Hubの認証情報は、DockerイメージをビルドするCodeBuildプロジェクトが使用します。詳しくは次のブログを参照してください。
今回、私は次のパラメータを作成しました。
- /apn-java-goof/dockerhub/takakuni-token(takakuniは任意の値)
 - /apn-java-goof/dockerhub/takakuni-username(takakuniは任意の値)
 
なお、Docker Hubの認証方法は、機微な情報のため「Secure String」の形式で保存しましょう。(暗号化キーには先ほど作成したKMSキーを指定します。)
CodePipelineの作成
CodePipeline作成画面からCodePipeline、CodeBuild、IAMロールの複数リソースを作成します。

次の設定値を指定しました。
- パイプライン名:apn-java-goof
 - アーティファクトストア:カスタムロケーション(デフォルトでもOK)
 - 暗号化キー:デフォルトの AWS マネージド型キー
 
Sourceステージの追加
Sourceステージでは、作成したCodeCommitレポジトリ(apn-java-goof)を選択します。

Buildステージの作成
DockerイメージをビルドするCodeBuildプロジェクトを作成します。
元ネタと同じ様に、Ubuntu OSのDockerイメージ「aws/codebuild/standard:5.0」を利用します。
Dockerイメージをビルドするためには、「特権付与」を有効化する必要があるため注意です。

環境変数
CodeBuildへ環境変数を設定します。環境変数は、「Docker Hubへの認証情報」、「ECRの情報」等の受け渡しで使用します。
| 名前 | 値 | タイプ | 
|---|---|---|
| AWS_DEFAULT_REGION | ap-northeast-1 | プレーンテキスト | 
| AWS_ACCOUNT_ID | ECRレポジトリを作成したアカウントID | プレーンテキスト | 
| IMAGE_TAG | apn-java-goof | プレーンテキスト | 
| IMAGE_REPO_NAME | apn-java-goof | プレーンテキスト | 
| DOCKERHUB_USERNAME | /apn-java-goof/dockerhub/takakuni-username | パラメータ | 
| DOCKERHUB_PASSWORD | /apn-java-goof/dockerhub/takakuni-token | パラメータ | 
buildspec
buildspec.yamlの内容は、次の通りで設定しました。
version: 0.2
phases:
  pre_build:
    commands:
      - echo $DOCKERHUB_PASSWORD | docker login --username $DOCKERHUB_USERNAME --password-stdin
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo "Hello World"
      - cd todolist-goof
      - mvn -B verify --file pom.xml
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
デプロイステージはスキップします。
後ほど、アクションの追加を行いますがいったん確認して問題なければ作成します。

IAM Roleの修正
CodeBuildプロジェクトに付与されたIAMロールは、KMS, Parameter Store, ECRへの権限がありません。
そのため、ECR、Parameter Storeとその値を暗号化しているKMSキーを触れる様に権限を付与します。
今回は、次のIAM管理ポリシーを作成して、「codebuild-apn-java-goof-service-role」へアタッチします。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey",
                "ssm:GetParameters",
                "ssm:GetParameter"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:1234567879012:key/*",
                "arn:aws:ssm:ap-northeast-1:1234567879012:parameter/*"
            ]
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ecr:CompleteLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": "arn:aws:ecr:ap-northeast-1:1234567879012:repository/apn-java-goof"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        }
    ]
}
※「1234567879012」の部分は適宜リソースを作成するAWSアカウントIDに置き換えてください。
IAM管理ポリシーができたら、IAMロールへアタッチします。

Snyk Scanアクションの追加
CodePipelineとSnykはシームレスに統合されており、LambdaやCodeBuild等で作り込みをしなくてもSnyk Open SourceをCodePipeline上で呼び出すことができます。

今回は次のように設定し、CodeCommitにプッシュされたアーティファクトに対してSnyk Open Sourceスキャンを行います。

上記の「Snykに接続」をクリックするとSnykとの連携画面に遷移します。

各種設定値について解説します。
「Snyk Organization」はSnykと連携する組織を定義します。AWSのOrganizationsとは別物です。
「Vulnerability handling」は、Snyk Open Sourceで脆弱性が検知された場合のCodePipelineの動作を定義します。チェックボックスがオンの場合、脆弱性を検知するとCodePipelineは失敗する仕様になります。今回は検証のため、チェックボックスはオフにしました。
「Block deployment for vulnerabilities with a minimum severity of」は、Vulnerability handling設定でブロックする脆弱性のしきい値を設定します。Low, Medium, High, Criticalの4種類あり、今回はVulnerability handlingを有効化しないので気にしなくても大丈夫です。
「Monitoring behavior on build」は、アクション実行時にSnyk Web UIへスナップショット(脆弱性が検知された状態記録)を送るかを設定します。今回は送信しない「Never monitor」を設定しました。
その他設定値に関しての詳細は、以下のリファレンスを参照してください。
設定が完了したら、「Continue」、「確認」で接続を完了します。
接続が完了するとアクション編集画面に「プロバイダに正常にアクションを設定しました。」と表示されます。

変更のリリース
以上がハンズオンブログで紹介されていた設定部分になります。
ここからは、Pipelineの確認に入ります。Snykアクションを追加したので「変更をリリースする」をクリックしてPipelineを再度実行します。CodeBuildが失敗していることがわかります。

本題
ここからが今日の本題です。
どうしてCodeBuildが失敗したのか確認してみます。
BuildKitの有効化
ログを確認すると、Dockerイメージのビルドが失敗していました。
Step 9/15 : RUN --mount=target=$HOME/.m2,type=cache mvn install
the --mount option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled
どうやらBuildKitが有効化されていないことが原因の様です。ログに出力された、リファレンスを確認すると環境変数を使用して、BuildKitが有効化できることがわかりました。
そのため、CodeBuildプロジェクトに次の環境変数を追加します。
| 名前 | 値 | タイプ | 
|---|---|---|
| DOCKER_BUILDKIT | 1 | プレーンテキスト | 

「再試行」をクリックすると無事成功しました。

中身を見てみる
今回、脆弱性を検出する仕組みは、Snyk Open SourceアクションとECR拡張スキャンの2つあります。
Snyk Open Source
まずは、Snyk Open Sourceから確認してみます。CodePipeline実行画面の、Snyk Actionに表示されている「詳細」から実行結果が確認できます。
実行結果は、最大14日間閲覧可能でパイプラインが実行されるたびに有効期限は更新されます。

ECR
ECRにプッシュされたDockerイメージに対してもInspectorを使用した拡張脆弱性スキャンが行われていることがわかります。

(番外編)Snyk Container
SnykはECRと統合して、ECRに保存されたDockerイメージのスキャンを行うことができます。
次の画像は、今回プッシュしたDockerイメージに対してのスキャン結果となります。Inspector拡張スキャンと検出結果に違いがあるのも大変興味深いです。

まとめ
以上、ハンズオンブログ「Build and Deploy a Secure Container Image with AWS and Snyk」をやってみたでした。
個人的に、Snyk Open SourceをCodePipelineに組込みなしで実装できる点がかなり魅力的だなと思いました。
その他、Snyk製品をCodeBuild等で動かしたい場合は次のブログが参考になるかもしれないです。
以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!






