最新リビジョンのタスク定義で使用しているイメージを対象にInspectorスキャンを自動化してみた
はじめに
株式会社ココペリのSREグループの尾嵜 成真です。
SREとして日々ココペリサービスの信頼性の向上に命を捧げております!(`・ω・´)ゞ
弊社ではAmazon Inspectorを利用して、コンテナ脆弱性スキャンを実施しています。
Inspectorを使用すればECRで管理しているイメージに対して、脆弱性スキャンを実行することができますが、運用していく中で色々と課題がありました。
脆弱性スキャンの対象を実際にサービスで使用しているイメージのみにしたい。
脆弱性の検知が頻繁に発生してその度に対応するのはかなり労力がかかってしまう。
対応すべきイメージのスキャンの結果を一覧化したい。
スキャンを実行したら、その対応内容をJiraでチケット化したい。
上記のように脆弱性対応をより負担なく、効率的に対応していきたいというところが大きな課題でした。
ということで、今回の記事では弊社で実装した脆弱性スキャンを自動化する対応方法を紹介したいと思います。
課題解決方法
上記の課題に対して、以下のようなアプローチで解決するように取り組みました。
脆弱性スキャンの対象を実際にサービスで使用しているイメージのみにしたい。
→ 脆弱性スキャンの対象を実際に使用している最新リビジョンのタスク定義の中で利用しているコンテナイメージを対象にします。
対応の必要のないイメージを除外することで無駄な労力を省くことができます。
脆弱性の検知が頻繁に発生してその度に対応するのはかなり労力がかかってしまう。
→ 脆弱性スキャンを定期的に実行して、対応する脆弱性レベルをMEDIUM以上に限定します。脆弱性対応を必要最低限にして、一括で効率的に対応するようにします。
緊急性の高い脆弱性やセキュリティリスクを限りなく最小限に抑えなければいけないなどの制約がある場合は、必要に応じて頻度や検知方法を変える必要はありそうです。
対応すべきイメージのスキャンの結果を一覧化したい。
→ InspectorやECRでスキャン結果を確認することができますが、上記のように対応が必要なイメージのスキャン結果のみを一覧化することはできません。対応が必要なイメージのスキャン結果をS3に保存することで、結果を一覧化(リスト化)することができます。
スキャンを実行したら、その対応内容をJiraでチケット化したい。
→ 弊社ではタスクをJiraで管理しています。脆弱性の対応が必要なイメージ数は結構な数になることが多く、SREチームだけでは管理しきれないので他チームにも対応していただく必要があります。
全チームが必ず対応していただくために、各チームに応じた対応内容を脆弱性のスキャンと同時に自動的にチケット化することにします。
構成図
構成図はこんな感じです。
Lambdaをメインに使用しています。
各実行ステップに応じてLambdaを分離することで、可読性とメンテナンス性の向上を確保しています。
構成内容
EventBridgeで定期的に脆弱性スキャンフローを実行します。
1つ目のLambda「list-task-images.py」で実際に使用しているタスク定義を対象にして、そのタスク定義の最新リビジョンで利用しているコンテナイメージをリスト化します。 そのリストした情報をSQSに渡します。
2つ目のLambda「inspector-scan.py」でSQSから受け取ったイメージのリストを対象に、Inspectorで脆弱性スキャンを実行します。 イメージのリスト情報ををSQSに渡します。
3つ目のLambda「scan-check.py」でSQSから受け取ったイメージのリストを対象に、脆弱性スキャンの結果をチェックして、S3に結果情報を保存します。 スキャンのチェックが完了したことをSQSに渡します。
4つ目のLambda「create-tickets.py」でS3に保存された結果情報と各Lambdaの実行ログ情報を元にJIraのチケットに対応内容を起票します。
CDKのコードツリー
automation-ecr-inspector-scan
├── cdk
│ ├── bin
│ │ └── cdk.ts
│ └── lib
│ └── cdk-stack.ts
└── lambda
├── create-tickets.py
├── inspector-scan.py
├── list-task-images.py
└── scan-check.py
各コード(GitHub Gist)
cdk-stack.ts リソースの解説
bucket
Inspectorのスキャン結果を保存する用のバケットです。
SQS
それぞれのLambdaから受け取った情報を次のLamndaに渡す役割を担っています。
2,3,4個目のLambdaはSQSがメッセージを受け取ったことをトリガーに実行されます。
「toScanCheckLambdaSqs」はInspectorスキャンの実行結果を待ってから次のLambdaに渡すために、3分間待機します。
vpc
「createTicketsLambda」でJiraのAPIを取得するために外部通信を行うことができるサブネットへ配置する必要があります。ここでは既存のVPCとサブネットを指定しています。
SGはアウトバウンドのみ許可しています。
createLambdaFunction
4つのLambdaを作成するためのテンプレートを作成します。各Lambdaはパラメータを記載する形で作成します。
listTaskImagesLambda list-task-images.py
まずはPaginatorを使用してタスク定義全体の一覧を取得します。
スキャンしたい対象の名前を「include_pattern」で指定して、その名前を含むタスク定義の一覧を取得します。場合によってはexcludeで除外指定しても良いですね。
タスク定義からコンテナイメージを取得します。イメージが重複する場合は取り除きます。
取得したイメージ名をreturnしてSQSに渡します。
inspectorScanLambda inspector-scan.py
SQSから受け取ったイメージのリストに対して、Inspectorスキャンを実行します。
一気に実行するとInspector側でレートリミットが発生しまうため、各スキャンを5秒間隔で実行します。
イメージのリストをそのままSQSへ渡します。
scanCheckLambda scan-check.py
SQSから受け取ったイメージのリストからスキャンを実行したECRとイメージ名を特定して、そのスキャン結果を取得します。
脆弱性の重大度がMEDIUM以上のものがある場合は、スキャン結果をS3に保存します。
重大度がMEDIUM未満の場合は、脆弱性対応から除外するためS3には保存しません。
createTicketsLambda create-tickets.py
脆弱性対応用のJiraチケットを作成します。JIraのチケットを起票するためにJiraのAPIトークンを外部通信で取得する必要があるので、NGW宛のルートがあるサブネットにLambdaを配置しています。
チケットにはスキャン結果が保存されたS3のURLや、Lambdaの実行ログなどの情報を記載しています。
機密情報なのでチケットの内容を一部省略していますが、他チームが対応できるように対応手順などをなるべく簡潔にわかりやすい記載をしました。
実際にやってみた
1.EventBridgeで定期的に脆弱性スキャンフローを実行します。 テストなので近い時間を適当に入力して実行させてみます。
2.1つ目のLambda「list-task-images.py」で実際に使用しているタスク定義を対象にして、そのタスク定義の最新リビジョンで利用しているコンテナイメージをリスト化します。
Lambdaのログからリストされたタスク定義の一覧を取得できていることを確認できました。
3.2つ目のLambda「inspector-scan.py」でSQSから受け取ったイメージのリストを対象に、Inspectorで脆弱性スキャンを実行します。 Lambdaのログから対象のECRとそのイメージに対してInspectorスキャンが実行されていることを確認しました。 ECR上でもスキャン結果を確認することができます。
4.3つ目のLambda「scan-check.py」でSQSから受け取ったイメージのリストを対象に、脆弱性スキャンの結果をチェックして、S3に結果情報を保存します。 Lambdaのログからスキャン結果がS3に保存されたことを確認しました。また、脆弱性が検出されていないものやスキャンが失敗していることをログから確認できました。 S3上でもスキャンした日付のフォルダ内に各リポジトリフォルダとイメージごとのスキャン結果が保存されていることを確認しました。
5.4つ目のLambda「create-tickets.py」でS3に保存された結果情報と各Lambdaの実行ログ情報を元にJIraのチケットに対応内容を起票します。 Lambdaのログからチケットが起票されていることを確認しました。ログに出ているURLにアクセスしてみます。
6.JIraチケットが記載されていることを確認できました。
今後の課題
前述しましたが、緊急性の高い脆弱性やセキュリティリスクを限りなく最小限に抑えなければいけないなどの制約がある場合は、必要に応じて頻度や検知方法を変える必要はあります。
今回は脆弱性のスキャンとチケット化までを自動化しましたが、脆弱性対応も自動化できればベストですね。弊社では脆弱性の対応の自動化を含めて現在取り組んでいます。
脆弱性対応は基本的にはイメージのアップデートで対応することができますが、アップデートによるサービス影響は慎重に検証して対応する必要があります。
自動化するとともにそこの信頼性の担保も併せて考えないといけないですね。
まとめ
我々SREチームは常にセキュリティの向上を目指していますが、その一方でトレードオフ関係である労働コストを如何に下げるかということも重要視しています。
労働コストを下げるために色々な作業を自動化することは多いですが、自動化することだけにフォーカスするのではなく、まずはその労働自体に無駄がないか、その作業は本当にサービスの信頼性向上につながるかということを問うように常に心がけています。
脆弱性対応についてもやろうと思えばいくらでもやれることはありますが、その中で労働コストと信頼性の向上のバランスを考えながらどうするべきかということを決めていきたいですね!
この記事でみなさんのサービスの信頼性が少しでも高まることができれば幸いです。
ではまた!