ハンズオンブログ:「Build and Deploy a Secure Container Image with AWS and Snyk」をやってみた

Snykを用いたよりセキュアなコンテナビルドパイプランを実装するブログをやってみました。
2022.08.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回はタイトルの通り、ハンズオンブログ「Build and Deploy a Secure Container Image with AWS and Snyk」をやってみようと思います。

元ネタは以下のブログです。今回はブログを読んで、詰まったところや感想などを共有できればと思います。

構成図

今回は、次のような構成になります。

  1. AWS CodeCommitにプッシュされたマニフェストファイルに対して、Snyk Open Sourceスキャンを行います。
  2. Snyk Open Sourceのスキャン後に、Dockerイメージをビルドして、ECRにプッシュします。
  3. ビルドされたDockerイメージに対してもECR拡張イメージスキャンを行います。

構成図

引用:Build and Deploy a Secure Container Image with AWS and Snyk - Figure 1 – Architecture diagram with the primary services covered in this post.

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の内容は、次の通りで設定しました。

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」へアタッチします。

codebuild-apn-java-goof-parameterstore-policy

{
    "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:478468688580:key/*",
                "arn:aws:ssm:ap-northeast-1:478468688580:parameter/*"
            ]
        }
    ]
}

codebuild-apn-java-goof-ecr-policy

{
    "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:478468688580:repository/apn-java-goof"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        }
    ]
}

※「478468688580」の部分は適宜リソースを作成する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_)でした!