まえがき
こんにちは、インフラグループの 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
全部入りのリポジトリ
あとがき
作ってて思ったことを箇条書きで書いてみます。
- サーバーにログインするのがめんどくさいので、例えばIssueにチェックボックスを用意して、チェックしたらSSMでコマンドを実行したい
- 結果をIssueのコメントに貼り付けたい
- 成功したらIssueを自動Closeしたい
- 失敗した結果だけSlack通知したい
- こういう系はLambdaでやっていましたが、GitHub ActionsだとOSコマンドが使えるので柔軟に対応できてとても良い
- LambdaはTerraformでの扱いがチョットタイヘン
- こういうのを考えなくていいマネージドサービスはやっぱり神
今後は上記に書いたように、やはりサーバーにログインしないで、アップデートを実行する仕組みを作ってみようと思います。ありがとうございました。