1
Help us understand the problem. What are the problem?

posted at

updated at

AWS Code系サービスのCICDパイプラインに脆弱性検査を簡単かつ柔軟に組み込む(good practiceを追ってみる編)

Good Practiceを読んでの学び

  • コンテナと依存先オープンソースの脆弱性検査をしたい場合は、snyk monitorがファーストステップ。既存のフローに手を加えることなく、脆弱性を一括で確認できるビューを直ぐに利用できる。
  • コードの静的解析と、IaCの各クラウドプロバイダ推奨確認の場合は、monitorがなく、testだけなので、||trueを設定した上で、結果のJSONなどを外部送信する仕組みがファーストステップ。

  • ただのビューから一歩踏み込んで、デプロイ前のgatekeeperとしてSnykを使う場合は、snyk-filterによるFail条件の設定と統制が良さそう。

はじめに

前回上記記事でCodePipelineの途中にSnykによる検査を組み込んでみました。
とはいえベストプラクティスには従っていないような気がするな…と恐れながらやっていました。
そこで、Snykのドキュメントを後日改めて読んでいると、CICDパイプラインに組み込むときのSnyk的グッドプラクティスがあったので、それを追ってもう少し実践的な追加検証をしてみようという記事です。

さて、前回手作業でCICDパイプラインを構築しましたが、前回の構成からSNSでのメール送信を取り払った構成について、CloudFormationを用意しました。
同じ構成で試す人がいるかはわかりませんが、試す場合は以下のCloudFormationを実行するだけで、AWS側の環境は揃えることができます。
buildspec.yamlも載せておきますが、こちらはみなさんカスタマイズすることになるかなと思います。(私自身もbuildspec.yamlは記事後半でまた別のものも使います)

arch_easy.png

CloudFormation
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  ParamS3PostFix:
    Type: String
    Description: type here random string. it's used for s3 postfix.
    # random postfix for s3 bucket

Resources:
  snykTestGeneralBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub
        - snyk-test-genbucket-${postfix}
        - { postfix: !Ref ParamS3PostFix }

  snykTestCommit:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryDescription: snyk test
      RepositoryName: snyk-test-commit

  snykTestBuild:
    Type: AWS::CodeBuild::Project
    Properties: 
      Name: snyk-test-build
      Artifacts: 
        Type: CODEPIPELINE
      Environment: 
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      LogsConfig: 
        CloudWatchLogs:
          Status: DISABLED
        S3Logs:
          Location: 
            Ref: snykTestGeneralBucket
          Status: ENABLED
      ServiceRole: 
        Ref: snykTestBuildRole
      Source: 
        Type: CODEPIPELINE

  snykTestPipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties: 
      ArtifactStore: 
        Type: S3
        Location:
          Ref: snykTestGeneralBucket
      Name: snyk-test-pipeline
      RoleArn: 
        !GetAtt [snykTestPipelineRole, "Arn"]
      Stages: 
        - Name: Source 
          Actions: 
            - Name: SourceAction
              ActionTypeId: 
                Category: Source 
                Owner: AWS 
                Version: 1 
                Provider: CodeCommit
              OutputArtifacts: 
                - Name: SourceOutput 
              Configuration: 
                RepositoryName: !GetAtt [snykTestCommit, "Name"]
                BranchName: main
        - Name: Build
          Actions:
            - Name: BuildAction
              ActionTypeId: 
                Category: Build
                Owner: AWS 
                Version: 1 
                Provider: CodeBuild
              InputArtifacts:
                - Name: SourceOutput
              Configuration: 
                ProjectName:
                  Ref: snykTestBuild

  snykTestPipelineRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument: {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "codepipeline.amazonaws.com"
                                ]
                            },
                            "Action": [
                                "sts:AssumeRole"
                            ]
                        }
                    ]
      }
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
        - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
      RoleName: snyk-test-pipeline-role

  snykTestBuildRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument: {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "codebuild.amazonaws.com"
                                ]
                            },
                            "Action": [
                                "sts:AssumeRole"
                            ]
                        }
                    ]
      }
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
      RoleName: snyk-test-build-role

buildspec.yaml
version: 0.2

phases:
  install:
    commands:
      - npm install snyk@latest -g

  build:
    commands:
      - snyk auth <YOUR_API_TOKEN>
      - snyk monitor

参照

https://docs.snyk.io/getting-started/snyk-cicd-integration-good-practices
↑のSnykさんの公式のgood practiceを読みつつ進めたいと思います。

Snyk CLIを使うのが適切だったのか?

前回私はあまりちゃんと確認せずにCLIを利用してしまったのですが、公式さん曰くCICDへのSnykの導入には、次の選択肢があるようです。
特に個人的には、バイナリの提供とdocker imageの提供が嬉しく、環境に追加コンポーネントを加えたくないDeveloper First選択肢で嬉しく思います。

  1. Using Snyk native plugins
    CI/CDパイプラインに対応したプラグインがある場合それを導入する方法です。最も手軽な方法として紹介されています。
  2. Deploy Snyk CLI using the npm method
    npmを利用してSnykCLIをインストールする方法です。前回私が利用したのもこれです。CLIをそのまま利用できるのでpluginよりトラブルシュートなどが容易など強み。
  3. Deploy Snyk CLI binary version
    npmが利用できない環境向けに、直接バイナリをダウンロードすることもできるとのこと。新し目のプロジェクトではdockerが導入されていることも多いと思いますので、その場合は後述のdockerを利用すれば良いですが、特にレガシー寄りのプロジェクトに適用する場合にはこの選択肢がとても助かるように思います。
  4. Deploy a Snyk Container
    Snykを実行できるコンテナイメージがdocker hubからダウンロードできます。CLIの導入などで環境をいじる必要が無いのはまさにDeveloper Firstですね…

少なくともCLIの使用はBadPracticeではありませんでした。
今回の以降の検証は前回に引き続き2.の"Deploy Snyk CLI using the npm method "で進めたいと思います。
前回の構成との差分を確認したい。というのと、CodeBuildではnpmを利用できるので、3,4を積極的に採用する理由があまりないからです。

snyk test || trueは適切だったのか?

Good PracticeにSnykの導入ステージ(もしくはロードマップ?)が定義されています。

Stage 1: Expose vulnerabilities (Snyk monitor)
Stage 2: Use Snyk as a gatekeeper (snyk test)
Stage 3: Continuous monitoring (snyk test + Snyk monitor)

の3段階だそうです。私は前回Stage1をすっ飛ばして、Stage2から始めた結果、testでエラーコードが出て先に進まない…というバグを踏んで||trueで解決しました。ここだけ読むと前回の構成はあまり良くない構成だったかもしれません。各段階を追ってその確認をしようと思います。

ところがこの3段階を順を踏んで進めるにあたり、snyk iacに monitorに相当する機能が無いという事態に陥りました(見逃していたらすみません)

ということで、まずはSnykの機能を見直してみました。結果としてこんな感じになっていると思います。

Snykの各プロダクトとCLI

  • Snyk Open Source : プロジェクトが利用している依存先オープンソースライブラリの脆弱性を監視。 CLIのsnyk testやsnyk monitorはこのプロダクトに相当する(と思われる)。
  • Snyk Code: 静的コード解析を対象適用し、脆弱性を処理の中に埋め込んでいないか?クレデンシャルが埋め込まれていないか?などを検出する。snyk code testのみでmonitorはない
  • Snyk Container: dockerfileの中に書いたシェルコマンド等の脆弱性の検知、依存先ベースイメージの脆弱性検知などを行う。これはtestもmonitorもある
  • Snyk Infrastructure as a Code: IaCコードを各クラウドプロバイダーのベストプラクティスなどと比較し、設定上の不備を検知する。これはtestのみでmonitorはない

Stage1 Expose vulnerabilities

プロダクトとCLIの対応を調べてみると、当然といえば当然ですが、依存先のリソースの脆弱性検査を行う機能にしかmonitorは無いようです。なので、Stage1のExpose vulnerabilitiesは、以下のように導入するのがよいように思えます。

  • Open Source, Containerについてはmonitorがあるので、monitorを導入する。
  • Code,IaCについては、testしかないので、 || true を設定して、buildが失敗しないように設定する。検査結果を何らかの方法でどこかに送信。集約する。という構成になるようです。

前回記事のアプローチは間違っていなかった。よかった...
Open SourceやContainerの場合は、monitorから入るのが良さそうです。
さて、前回はIaCを利用したので、少々特殊系となってしまいました。
そのため、今回は王道のOpenSourceを利用して、monitorを実施し、Stage1 Expose vulnerabilitiesとはどういうことなのか?を追加確認します。

Snyk monitorを実行してみる。

Stage1 Expose vulnerabilitiesの検証としてCLIからのsnyk monitorを試します。
試す対象として、Pythonを選択し(筆者が慣れているから)、適当なrequirements.txtについて、その依存先の脆弱性チェックをしてみたいと思います。

Snyk Python のリファレンスを見て、

Prerequisites
Ensure you've installed the relevant package manager before you begin using the Snyk CLI tool.

とのことなので、CodeBuildにPythonをインストールしておきます(Pythonを入れたらPipも入るので条件を満たす。)
CodeBuildの場合、python2系が混在していてpipではなくpip3に名称が変わってしまっているので、エイリアスを更新するコードも含めていざpush!

スクリーンショット 2021-12-18 23.35.25.png

...あれ?失敗している...
ということでビルドログを確認しに行くと、

Please run pip install -r requirements.txt. If the issue persists try again with --skip-unresolved.

でエラーが出ていました。Snyk Pythonの記事を読み直すと、pipの場合は依存先を含めての依存先ツリーが必要なのでpip install -rをしないといけないみたいです。エラーコードがかなりわかりやすくて助かります。
ということで、buildspecを修正します。

修正したbuildspec.yaml
version: 0.2

phases:
  install:
    commands:
      - npm install snyk@latest -g

  build:
    commands:
      - yum install -y python3
      - echo 'alias pip=pip3' >> ~/.bashrc
      - source ~/.bashrc
      - pip install -r requirements.txt
      - snyk auth <YOUR_API_TOKEN>
      - snyk monitor

さて、修正したbuildspecで再pushを行い、パイプライン成功したので、snykの方を見に行きます。
スクリーンショット 2021-12-18 23.39.35.png

うお...適当に作ったrequirements.txtでしたが、思いもかけずcriticalな脆弱性があるようです。
今回軽い検証のつもりでやりましたが、普通にめちゃくちゃ助かりました。幸いinternet-facingにはtensorflowは利用していませんでしたが、ドキッとしますね。

monitorは、その時の依存先ライブラリ情報のスナップショットを作成し、それと各依存先の脆弱性を照合して追跡し続ける機能です。また、snykのgood parcticeの通り、monitor自体は基本的に失敗がないため、既存のフローに組み込みやすいです。
snyk monitorのorganizationsオプション等さえ設定するだけで、チームの複数プロジェクトの脆弱性をまとめて見れるビューが、既存のビルド等にほとんど影響を及ぼさず導入できる。
というのはかなり便利に思います。

Stage 2: Use Snyk as a gatekeeperの導入

Stage2では、上記のmonitorと同じ検査を実施しますが、脆弱性が見つかった場合にCICDをFailさせるような、まさにゲートキーパーとして使用する段階です。

正直なところここは多少茨の道だと思います。というのもSnykさんの方に書かれている通り、"all projects are vulnerable"ですが、Snyk testのデフォルト設定は脆弱性が一つでも見つかったらExit code!=0を出力する設定になっていて、CICDが停止してしまいます。そのため、うまくtestのFail基準を検討し、また脆弱性対応も行っていく必要があるからです。

もちろんSnykにはビルドの失敗基準を調整することができるオプションがあり、今参考にしているgood practiceにも記載されています。

この記載の中で、特に個人的に気になった、snyk-filterによる条件設定を試してみたいと思います。
snyk-filterはfilter条件を.ymlの形で記載できるため、あるセキュリティ基準を反映したfilterを作り、それを配布することで広くガバナンスを統一的に効かせることができるという嬉しさがあります。

snyk-filterを利用したsnyk test fail基準の設定。

  • さっき見つかったtensorflowの脆弱性が引っかからないようなfilterを作成
  • そのフィルタ条件でsnyk test を試してみます。上手く設定できればsnyk testでCICDがFailになりません。 フィルタ条件は様々設定できるようですが、簡単に、CVSSスコアの基準を設定してフィルタリングしてみます。

まず、snyk-filterのインストールと、snyk-filterを利用したsnyk testのfail判定をbuildspec.yamlに記述します。snyk-filterのインストールはsnyk-filterのGithubリポジトリを参考に記述します。
また、同リポジトリにCVSSの値でフィルタリングするフィルタ設定ファイルのサンプルがあるため、それを少しだけ変更し、同じCodeCommitに配置します。以下に完成形のyaml等を載せます。

buildspec.yaml
version: 0.2

phases:
  install:
    commands:
      - npm install snyk@latest -g
      - git clone https://github.com/snyk-tech-services/snyk-filter.git temp
      - cd temp
      - yum install -y jq
      - export NODE_JQ_SKIP_INSTALL_BINARY=true
      - export JQ_PATH=$(which jq)
      - npm install -g

  build:
    commands:
      - cd ../
      - yum install -y python3
      - echo 'alias pip=pip3' >> ~/.bashrc
      - source ~/.bashrc
      - pip install -r requirements.txt
      - snyk auth <YOUR_API_TOKEN>
      - snyk test --json |  snyk-filter -f cvss-9.5-or-above.yml
cvss-9.5-or-above.yml
version: 2
customFilters:
  filter: ".vulnerabilities |= map(if .cvssScore >= 9.5 then . else empty end)"
  pass: "[.vulnerabilities[] | select(.cvssScore >= 9.5)] | length"
  msg: "Vulnerabilities with CVSS Score of 9.5+ found"

これでtestを実施すると、脆弱性は依然として検知されるものの、testはFailせずにCICDが動きます。
スクリーンショット 2021-12-19 1.36.42.png
今回はごく簡単なフィルタとしましたが、プロダクトのセキュリティ要件に合わせ、これを調整していくことが必要です。
ただ、統一的なセキュリティ基準をymlファイルの記載のみで広く適用できるというのはセキュリティ対策の中ではかなり楽な方だとは思います。

Stage 3: Continuous monitoring (snyk test + Snyk monitor)の導入

Stage3は、testに加えてmonitorを実施することで、脆弱性の継続的トラッキングを行い、ゼロデイ攻撃にしても耐性をある程度持たせる段階となります。
実際のところ、Stage2からStage3への移行は簡単かと思います。stage2のFailの問題が解決したら、あとは単にtestとmonitorの両方をCLIで記載するだけだからです。
Stage1からStage2への移行が鍵だと思います。

おわりに

ドキュメントはちゃんと読もう!
(SnykはGood Practiceが Getting Startedの中にあるので、Getting Startedを確認すると良いです)

IaCやCodeについても、Snykのダッシュボードに表示できるような機能があると嬉しいです!Snykさんお願いします!(自分が見落としてるだけかもだけど)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?