パイプラインで実際にチェックできること(セキュリティスキャンだけじゃない)
デプロイパイプラインにチェックを追加しようと考えたとき、多くのチームが最初に思い浮かべるのはアプリケーションコードのセキュリティスキャンです。SASTツールを実行し、脆弱性をいくつか見つけて、それで終わり。しかし、実際にパイプラインを流れるものを見渡せば、チェックすべき項目はもっとたくさんあります。その中には、コードスキャンだけでは決して捕捉できない問題からあなたを救ってくれるものもあります。
モダンなソフトウェアデリバリーは、ソースコードだけを運ぶわけではありません。依存関係リスト、コンテナイメージ、インフラストラクチャ定義、設定ファイル、認証情報——これらすべてがパイプラインを流れます。これらのアーティファクトにはそれぞれ独自のリスクが伴います。アプリケーションコードだけをスキャンするパイプラインは、攻撃対象領域の大部分を未チェックのままにしていることになります。
以下に、パイプラインで実行できるチェックの種類、それらが捕捉するもの、そしてどのような場面で有効かを示します。
依存関係スキャン
今日、スクラッチから書かれたアプリケーションはほとんどありません。あなたのプロジェクトはnpm、PyPI、Goモジュール、NuGet、Mavenからライブラリを引き寄せます。それらの依存関係はすべて他人が書いたコードであり、そのコードには公に知られた脆弱性が存在する可能性があります。
依存関係スキャンは、依存関係のリストを脆弱性データベースと照合します。使用しているバージョンのライブラリに既知のセキュリティ問題があるかどうかを教えてくれます。これはゼロデイを探すものではありません。すでに文書化され、多くの場合パッチが利用可能な脆弱性を捕捉することが目的です。
このスキャンは、依存関係ファイル(package.json、go.mod、requirements.txt、または使用しているエコシステムのファイル)が変更されるたびに実行してください。最低でも、ビルドフェーズ中に一度は実行します。プルリクエストだけで実行すると、依存関係の更新をメインブランチに直接マージしたことによって脆弱性が混入した場合に見逃す可能性があります。
以下は、npm audit を実行し、高重要度の脆弱性が見つかった場合にパイプラインを失敗させるGitHub Actionsジョブの最小限のYAMLスニペットです。
name: dependency-scan
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm audit --audit-level=high
コンテナイメージスキャン
アプリケーションをコンテナイメージとしてパッケージ化する場合、そのイメージにはあなたのコード以上のものが含まれます。ベースイメージ、オペレーティングシステム層、システムライブラリ、あなたが書いていないバイナリが含まれています。ベースイメージの脆弱性は、あなた自身のコードの脆弱性と同じくらいアプリケーションに影響を与えます。
コンテナイメージスキャンは、イメージの全内容(OSパッケージやインストールされたライブラリを含む)を検査します。ベースイメージ内の脆弱なバージョンのOpenSSLや、アプリケーションが直接呼び出すことはないがランタイムには存在するシステムユーティリティの既知のエクスプロイトなどを捕捉します。
このスキャンを実行する適切なタイミングは、イメージがビルドされた直後、レジストリにプッシュされたりどこかにデプロイされたりする前です。イメージが本番環境にある場合でも、可視性のためにスキャンすることは有用ですが、その時点ではすでに被害が発生している可能性があります。
Infrastructure as Code(IaC)スキャン
Terraform、CloudFormation、Pulumiでインフラストラクチャをコードとして記述する場合、テキストファイルでクラウドリソースを定義しています。これらのファイルには、セキュリティホールを生み出す設定ミスが含まれる可能性があります。つまり、パブリックアクセス可能なストレージバケット、インターネットに露出したデータベース、暗号化の無効化、過度に寛容なIAMロールなどです。
IaCスキャンは、インフラストラクチャ定義を適用する前に、セキュリティのベストプラクティスと照合してチェックします。これは、実行中の環境をスキャンするクラウドセキュリティ態勢管理ツールの代わりにはなりません。問題がデプロイされたリソースになる前に捕捉する予防的なチェックです。
このスキャンは、誰かがインフラストラクチャコードを変更するプルリクエストを開いたときに実行します。その時点であれば、すでに実行中の設定ミスのあるリソースをクリーンアップする必要なく、設定を修正できます。
シークレットスキャン
開発者が誤ってシークレットをリポジトリにコミットしてしまうことは、チームが認めたがるよりも頻繁に発生します。APIキー、データベースパスワード、SSH秘密鍵、サービスアカウントトークンがコード、設定ファイル、またはコミットメッセージに紛れ込んでしまいます。一度シークレットがリポジトリの履歴に入り込むと、git履歴に存在するため削除は困難です。
シークレットスキャンは、認証情報のように見えるパターン(AWSアクセスキー、GitHubトークン、Slackウェブフック、汎用パスワードの形式に一致する文字列)を検出します。コミットがメインブランチに到達する前にフラグを立てます。
このスキャンは、すべてのコミット、または少なくともすべてのプルリクエストで実行するのが最も効果的です。漏洩したシークレットを早期に捕捉すればするほど、露出は少なくなります。コードがマージされた後にのみスキャンした場合、シークレットはすでにリポジトリに存在しており、アクセス権を持つ誰でも取得できた可能性があります。
ライセンススキャン
使用するすべての依存関係にはライセンスが付随しています。一部のライセンスは、帰属表示を含めることを要求します。その他は商用利用を制限したり、使用する場合は自身のコードをオープンソースにすることを要求したりします。ライセンススキャンは、すべての依存関係のライセンスを、チームまたは組織が定義したポリシーと照合してチェックします。
これはセキュリティチェックではありませんが、無視するとリリースをブロックする可能性のある法的およびコンプライアンス上のチェックです。多くのチームは、依存関係スキャンと同じソースデータ(依存関係のリスト)を使用するため、ライセンススキャンを依存関係スキャンと並行して実行しています。
ポリシーアズコード
ポリシーアズコードは単一のタイプのスキャンではありません。ルールをコード化し、パイプラインでプログラム的に強制する方法です。事前に構築されたスキャナに依存する代わりに、独自のチェックを定義します。「すべてのデータベース変更はDBAによるレビューが必要」「すべてのコンテナイメージは承認されたレジストリから取得する必要がある」「本番環境へのすべてのデプロイメントは負荷テストに合格している必要がある」などです。
これらのルールはコードとして記述され、パイプラインで自動的に実行されます。ポリシーアズコードは、ベンダーが機能を追加するのを待つことなく、自分のコンテキストに関連するルールを強制する柔軟性を提供します。
何をいつ実行するかの選択
すべてのチェックをすべてのステージで実行する必要はありません。中にはすべてのコミットで実行するのに十分軽量なものもあります。その他はより低速であり、プルリクエスト時やデプロイメント前に実行する方が理にかなっています。重要なのは、すべてをどこでも実行することではなく、各チェックを最も価値が高く、摩擦が少ないポイントに配置することです。
以下は実用的な出発点です。
- すべてのコミット: シークレットスキャン、依存関係スキャン(依存関係ファイルが変更された場合)
- プルリクエスト: IaCスキャン、ライセンススキャン、コンテナイメージスキャン(イメージが再ビルドされた場合)
- デプロイメント前: コンテナイメージスキャン(まだ実行されていない場合)、ポリシーアズコードチェック
パイプラインのためのクイックチェックリスト
- 依存関係スキャンは、依存関係ファイルが変更されたときに実行される
- コンテナイメージは、レジストリに到達する前にスキャンされる
- インフラストラクチャコードは、適用される前にスキャンされる
- シークレットは、メインブランチに到達する前に検出される
- ライセンスコンプライアンスは、すべての依存関係についてチェックされる
- カスタムポリシーは、手動ゲートではなくコードとして強制される
具体的なまとめ
アプリケーションコードのみをスキャンするパイプラインは、多層的なデリバリープロセスの1つの層しかチェックしていません。依存関係、コンテナ、インフラストラクチャ定義、認証情報——これらすべてがリスクをはらんでいます。各アーティファクトタイプに対して、パイプラインの適切なポイントでチェックを追加してください。目標はすべての変更をブロックすることではなく、修正がまだ安価なうちに問題を早期に捕捉することです。