前回の記事では、脆弱性スキャンの重要性、また、Docker Scout を使うことにより依存関係とそれに関連する脆弱性の概要がいかにわかりやすくなるかついて説明しました。
この記事では、Dockerのコンテンツを本番ブランチと統合してDocker Hubに公開する前に、Dockerイメージを構築し脆弱性をスキャンするGitHub Workflowを作成することで、実際のDocker Scoutを学びましょう。 このプロセスにより、リリースされたアプリケーションの品質が高くなります。
サンプル アプリケーションの作成
このチュートリアルでは、例として私の Hypnos アプリケーションコードを使用します。 これは、React.js と Yarn パッケージ マネージャを使用して構築された Web アプリケーションです。 まず、Dockerファイルを作成する必要があります。
FROM node:14-alpine
WORKDIR /app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
次に、node_modules が意図せず Docker イメージにコピーされないよう、 Docker に指示しましょう。 このフォルダはかなり大きくなる可能性があり、いずれにしてもイメージをビルドするのでコピーする必要はありません。 これを行うには、次の内容の .dockerignore ファイルを作成します:
node_modules
実際のGitHub Actionに移る前に、、イメージをローカルで構築して、すべてが期待どおりに動作するようにしましょう。
docker image build -t hypnos:1.0 .
最後に、新しく作成したイメージでコンテナを実行できます。
docker run -p 3000:3000 hypnos:1.0
GitHub アクションを使用したビルドとスキャンの自動化
Docker のイメージが完成したので、Docker Scout を使用して脆弱性スキャンを自動化に集中しましょう。 Dockerは最近、Docker Scoutに特化したGitHubアクションをリリースしました。 ただし、この記事を書いている時点では、このアクションは(Docker Scout CLI でサポートされた)出力ファイルの作成をサポートしていません。 このため、このデモでは GitHub Action を使用しません。 代わりに、Docker Scout CLI を使用します。
シークレットの作成
Docker Scout は、サブスクリプションベースのモデルで動作する独自の脆弱性データベースを使用します。 したがって、Docker Scout では、イメージをスキャンする前に ユーザーがDocker Hub に認証する必要があります。 これには、この情報を GitHub のシークレットに保存します。
- Docker Hub にアクセスし、アカウントでログインします。
- ユーザ名をクリックし、ドロップダウンから [Account Settings] を選択します。
- [Security] タブを選択し、[New Access Token] をクリックします。
- トークンの説明を入力し、そのアクセス権限を選択します。 その場合は、読み書きが必要になります。
- Generateボタンをクリックし、トークンを保存してください。トークンは二度と表示されません。
次に、GitHub シークレットを作成し、Docker Hub トークンを保存しましょう。次に、別の GitHub シークレットを作成し、ユーザ名を保存しましょう。 今回、私はそれらをDOCKERHUB_TOKENとDOCKERHUB_USERとしました。
GitHub ワークフローの作成
まず、トリガーを定義することで、ワークフローがトリガーされるタイミングを指定する必要があります。 この場合、メインブランチを対象としたプッシュまたはプル要求が発生したときにワークフローを起動させることにします。 その後、パイプラインでは、イベントの種類に応じてブロックを実行するための条件を使用します。
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
次に、イメージの名前を含む環境変数を作成します。
env:
DOCKER_IMAGE_NAME: hypnos:$(date +%s)
その後、Docker Scoutプラグインをインストールしてイメージをビルドします。
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Docker Scout
if: ${{ github.event_name == 'pull_request' }}
run: |
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh -o install-scout.sh
sh install-scout.sh
- name: Build the Docker image
run: docker build . --file Dockerfile --tag $DOCKER_IMAGE_NAME
次に、Docker Scout アクションを追加し、コマンド ‘cves’ をトリガーします。このコマンドは、以前に作成したイメージ(ベース イメージを除く)で特定された CVE を表示し、結果を JSON ファイルに出力します。
- name: Run Docker Scout
if: ${{ github.event_name == 'pull_request' }}
run: |
docker scout cves --ignore-base --format sarif --output hypnos.sarif.json
スキャン中に見つかった脆弱性の数に基づいて、出力変数を true または false に設定します。
- name: Check vulnerabilities
id: check_vulnerabilities
if: ${{ github.event_name == 'pull_request' }}
run: |
if [[ $(cat hypnos.sarif.json | jq '.runs[0].results | length') -gt 0 ]]; then
echo -e "\e[31mThere were vulnerabilities in your Docker image. Check the comments on your PR to know more.\e[0m"
echo "fail_workflow=true" >> "$GITHUB_OUTPUT"
else
echo "There were no vulnerabilities in your Docker image. Good job!"
echo "fail_workflow=false" >> "$GITHUB_OUTPUT"
fi
脆弱性の数がゼロを超える場合は、プルリクエストにコメントを作成します。 このコメントには、スキャンのルール、結果、および全体的な結果が含まれます。 その後、パイプラインを強制的に失敗させます。
- name: Create Comment
if: ${{ github.event_name == 'pull_request' && steps.check_vulnerabilities.outputs.fail_workflow == 'true' }}
uses: actions/github-script@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const body = `
**JSON File Content:**
\`\`\`
${fs.readFileSync('hypnos.sarif.json', 'utf8')}
\`\`\`
`;
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
- name: Fail Workflow
if: ${{ github.event_name == 'pull_request' && steps.check_vulnerabilities.outputs.fail_workflow == 'true' }}
run: exit 1
脆弱性が見つからない場合、ワークフローは正常に完了します。プルリクエストが承認されメインブランチにマージされたら、イメージに適切なタグを付けて Docker Hub にプッシュします。
- name: Tag the Docker image
if: ${{ github.event_name == 'push' }}
run: docker image tag $DOCKER_IMAGE_NAME ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKER_IMAGE_NAME
- name: Publish the Docker image
if: ${{ github.event_name == 'push' }}
run: docker image push ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKER_IMAGE_NAME
このアプローチを採用し、Docker Scout を CI ワークフローに統合することで、開発プロセスの早い段階で脆弱性を積極的に特定することができます。これにより、迅速な修復が可能になり、リリースしたアプリケーションが高いレベルのセキュリティと品質を維持することができるようになります。
参考資料
- Hypnosアプリ:https://github.com/GTRekter/Hypnos
- ドッカースカウトアクション: https://github.com/docker/scout-action