LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

Amazon Inspectorから脆弱性情報を取得してGitHub Issuesにチケット発行するのを自動化する

まえがき

こんにちは、インフラグループの yjszk です。
インフラグループでは、Amazon Inspectorで検出された脆弱性への対応を定期的に行っています。
ただ、脆弱性情報を収集して適切な対応を行うプロセスは手作業です。作業が面倒であり、トイルとなっていました。
そこで、PythonとGitHub Actionsを使ってGitHub IssuesにAmazon Inspectorで検出した脆弱性情報を登録し、必要な対応内容がひと目でわかるようにしました。
この自動化により、より迅速な脆弱性対応が可能になりました。具体的には以下のようなIssueを自動作成しています。

Amazon Inspectorについて

概要は以下です。

EC2インスタンスにAmazon Inspector エージェントをインストールして、ネットワーク到達性や、プラットフォームの脆弱性を診断し、潜在的なセキュリティ上の問題を発見するためのものです。 Inspectorは脆弱性の棚卸しで利用するサービスとなります。

Amazon Inspectorはデフォルトでは無効化されているので、有効化する必要があります。
また、診断対象にはEC2インスタンスとECRのコンテナスキャン、Lambda関数があります。今回の記事の対象であるEC2インスタンスに関してはSSMエージェントが入っていれば自動的にスキャンされます。

Python + GitHub Actionsによるジョブ作成

  • スクリプトの概要は以下となります

  • GitHub Actionsのスケジュール実行で毎日動くようにします
  • 対象のリポジトリは対象のAWSアカウントに向けて、OIDC認証等によるアクセスができる前提とします

Python

  • メインの箇所
    • GoogleTranslatorはTOKENいらずの太っ腹翻訳ライブラリです
import boto3
from deep_translator import GoogleTranslator
from github import Github
import os


PREVIOUS_RESULT_FILE = './vulnerability-list/vulnerability_list'

def main():
    # 前日分を取得
    vulnerability_list = get_revious_result_list()
    # 今日の分を取得
    critical_finding = get_critical_finding()
    print(str(len(critical_finding['findings'])) + ' critical findings found')

    issued_vulnerability = []
    for finding in critical_finding['findings']:
        # 前日データは除外
        if finding['packageVulnerabilityDetails']['vulnerabilityId'] not in vulnerability_list:
            instance_name = get_instance_name(finding['resources'])
            result = create_vulnerability_issue(f'{instance_name} - {finding["title"]}', translate(finding['description']),
                                                finding['description'], finding['packageVulnerabilityDetails']['sourceUrl'],
                                                get_instance_id_name(finding['resources']), '\n'.join(get_remediation(finding)))
            print(result)
            issued_vulnerability.append(finding['packageVulnerabilityDetails']['vulnerabilityId'])
    print(create_issued_vulnerability(issued_vulnerability))
  • Issueに登録済みの脆弱性情報をGitHub Artifactにアップロードするための関数です
def create_issued_vulnerability(vulnerability_list):
    # 差分を記録する
    # 差分があったら追記する
    if vulnerability_list == []:
        return "追加する脆弱性はありません"
    issued_vulnerability = '\n'.join(vulnerability_list)
    issued_vulnerability += '\n'
    # リストを改行区切りでファイルに追記する
    with open(PREVIOUS_RESULT_FILE, mode='a') as f:
        f.write(issued_vulnerability)
    return f"{','.join(vulnerability_list)}を追加しました"
  • GitHub Issuesに登録するための関数です
def create_vulnerability_issue(title, trans_description, description, url, instace_name, remediation):
    # GitHubのリポジトリ名とトークンを環境変数から取得する
    repository_name = os.environ['GITHUB_REPOSITORY']
    token = os.environ['GH_TOKEN']
    g = Github(token)
    # イシューの本文を作成する
    body = f"""
    ### 説明
    {trans_description}
    ### 原文
    {description}
    ### 詳細
    {url}
    ### インスタンス名
    {instace_name}
    ### 対処方法
    ```bash
    {remediation}
    ```
    """
    # インデントを削除する
    body = body.replace("    ", "")
    repo = g.get_repo(repository_name)
    result = repo.create_issue(title=title, body=body)
    return result
  • これだけで翻訳できるのは素晴らしいと思います
def translate(text):
    return GoogleTranslator(source='auto', target='ja').translate(text)
  • GitHub Artifactからダウンロードしてきた登録済みの脆弱性を読み込む関数です
def get_revious_result_list():
    # 前回の結果をリストで取得する
    vulnerability_list = []
    with open(PREVIOUS_RESULT_FILE) as f:
        for line in f:
            vulnerability_list.append(line.strip())
    return vulnerability_list
  • 細かい系で意味は関数名そのままです
def get_instance_id_name(data):
    txt = ''
    for i in data:
        txt += i['tags']['Name'] + '(' + i['id'] + ')'
    return txt

def get_instance_name(data):
    txt = ''
    for i in data:
        txt += i['tags']['Name']
    return txt


def get_remediation(data):
    remediation = [i['remediation']
                   for i in data['packageVulnerabilityDetails']['vulnerablePackages']]
    return remediation
  • ActiveかつCriticalの脆弱性を取得します
    • ここを変更することで起票対象を変えられます
def get_critical_finding():
    inspector2 = boto3.client("inspector2")
    # ActiveかつCriticalの脆弱性を取得する
    critical_finding = inspector2.list_findings(filterCriteria={
        'findingStatus': [
            {
                'comparison': 'EQUALS',
                'value': 'ACTIVE'
            },
        ],
        'severity': [
            {
                'comparison': 'EQUALS',
                'value': 'CRITICAL'
            }
        ]
    }
    )
    return critical_finding
  • スクリプトとして実行された場合にのみ動くようになる記述です
if __name__ == '__main__':
    main()

GitHub Actions

name: check-vulnerability

on:
  schedule:
   - cron: '00 01 * * *' # JST 10:00
  workflow_dispatch:


jobs:
  check-vulnerability:
    name: check-vulnerability
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      issues: write
      actions: write
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Python 3.8
        uses: actions/setup-python@v4
        with:
          python-version: 3.8
          cache: 'pip' # caching pip dependencies

      - name: pip install
        run: pip install --upgrade pip && pip install -r requirements.txt

      - name: Download Previous Vulnerability List
        uses: dawidd6/action-download-artifact@v2
        continue-on-error: true
        with:
          branch: main
          workflow: check-vulnerability.yaml
          path: ./

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::XXXXXXXXXX:role/XXXXX-XXXXX-XXXXX
          aws-region: ap-northeast-1

      - name: Find Vulnerability
        run: |
          ls ./vulnerability-list/vulnerability_list || \ # もしダウンロードが失敗した場合は空ファイルを作成する
          mkdir -p ./vulnerability-list && touch ./vulnerability-list/vulnerability_list && \
          python inspector_finding.py && \
          cp ./vulnerability-list/vulnerability_list ./vulnerability_list # actions/upload-artifact@v3は/にあるファイルを見るためそのためにコピー

      - name: Update Current Vulnerability List
        uses: actions/upload-artifact@v3
        with:
          name: vulnerability-list
          path: ./vulnerability_list

全部入りのリポジトリ

github.com

あとがき

作ってて思ったことを箇条書きで書いてみます。

  • サーバーにログインするのがめんどくさいので、例えばIssueにチェックボックスを用意して、チェックしたらSSMでコマンドを実行したい
    • 結果をIssueのコメントに貼り付けたい
    • 成功したらIssueを自動Closeしたい
    • 失敗した結果だけSlack通知したい
  • こういう系はLambdaでやっていましたが、GitHub ActionsだとOSコマンドが使えるので柔軟に対応できてとても良い
    • LambdaはTerraformでの扱いがチョットタイヘン
  • こういうのを考えなくていいマネージドサービスはやっぱり神

今後は上記に書いたように、やはりサーバーにログインしないで、アップデートを実行する仕組みを作ってみようと思います。ありがとうございました。