Security Hubの検出結果をノート(note)をつけてまとめて抑制してみた
こんにちは。たかやまです。
Security Hub 100%にしていますか?
私は定期的にSecurity Hubの結果を確認して棚卸しをしていますが、新たにリソースをデプロイした場合にはどうしてもSecurity Hubの失敗コントロールが出てしまいます。
対処するべき項目はリソース設定を見直して改善するべきですが、結果の内容次第では抑制しても問題ない項目もあります。
私が抑制する場合にはよくこちらの ノート(Note)機能を使ってコメントを残して抑制しています。
ただこちらのノート機能、失敗コントロールの検出結果ごとに検出結果のID(Id)
と製品ARN(ProductArn)
と個別のコメントをAPI(BatchUpdateFindings)から登録する必要があります。
1,2件であれば個別確認して登録でも問題ないですが、件数が多いと手間がかかります。
そこで、今回はSecurity Hubの失敗コントロールをまとめてノートを記載して抑制するスクリプトを用意したのでご紹介します。
スクリプト
import argparse import csv import re import boto3 # STSクライアントの作成 sts = boto3.client("sts") # 現在のアカウントIDを取得 account_id = sts.get_caller_identity()["Account"] # Security Hubクライアントの作成 securityhub = boto3.client("securityhub") def extract_control_id(finding_id): # Finding IDからControlIdを抽出するための正規表現パターン pattern = r"security-control/([^/]+)/finding" match = re.search(pattern, finding_id) if match: return match.group(1) return "" def export_findings_to_csv(csv_file_path, workflow_status): # 指定されたWorkflowStatusの調査結果を取得 findings = [] next_token = "" while True: if next_token: response = securityhub.get_findings( Filters={ "ProductName": [{"Value": "Security Hub", "Comparison": "EQUALS"}], "ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}], "WorkflowStatus": [ {"Value": workflow_status, "Comparison": "EQUALS"} ], "RecordState": [{"Value": "ACTIVE", "Comparison": "EQUALS"}], }, NextToken=next_token, ) else: response = securityhub.get_findings( Filters={ "ProductName": [{"Value": "Security Hub", "Comparison": "EQUALS"}], "ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}], "WorkflowStatus": [ {"Value": workflow_status, "Comparison": "EQUALS"} ], "RecordState": [{"Value": "ACTIVE", "Comparison": "EQUALS"}], } ) findings.extend(response["Findings"]) if "NextToken" in response: next_token = response["NextToken"] else: break # 調査結果をControlIdとIDで昇順にソート findings.sort(key=lambda x: (extract_control_id(x["Id"]), x["Id"])) # CSVファイルを書き込みモードで開く with open(csv_file_path, "w", newline="", encoding='utf-8') as csv_file: # CSVライターを作成 fieldnames = [ "ControlId", "Id", "ProductArn", "ResourceId", ] if workflow_status == "NEW": fieldnames.append("UpdateText") elif workflow_status == "SUPPRESSED": fieldnames.extend(["Text", "UpdateText"]) csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames) csv_writer.writeheader() # 各Findingの情報を抽出してCSVファイルに書き込む for finding in findings: product_arn = finding["ProductArn"] resource_id = ( finding["Resources"][0]["Id"] if finding.get("Resources") else "" ) control_id = extract_control_id(finding["Id"]) row_data = { "ControlId": control_id, "Id": finding["Id"], "ProductArn": product_arn, "ResourceId": resource_id, } if workflow_status == "NEW": row_data["UpdateText"] = "" # UpdateTextは空白で初期化 elif workflow_status == "SUPPRESSED": suppression_reason = finding.get("Note", {}).get("Text", "") row_data["Text"] = suppression_reason row_data["UpdateText"] = "" # UpdateTextは空白で初期化 csv_writer.writerow(row_data) print(f"Findingsの情報が {csv_file_path} に出力されました。") def suppress_findings_from_csv(csv_file_path, updated_by): # 試行するエンコーディングのリスト encodings = ['utf-8', 'cp932', 'shift_jis'] for encoding in encodings: try: with open(csv_file_path, "r", encoding=encoding) as csv_file: csv_reader = csv.DictReader(csv_file) for row in csv_reader: finding_id = row["Id"] product_arn = row["ProductArn"] update_text = row.get("UpdateText", "") if update_text == "解除": # UpdateTextが"解除"の場合、リソースの抑制を解除する securityhub.batch_update_findings( FindingIdentifiers=[{"Id": finding_id, "ProductArn": product_arn}], Workflow={"Status": "NEW"}, # Noteの削除はできないため、更新理由を記載 Note={ "Text": "SUPPRESSED -> NEW へ更新", "UpdatedBy": updated_by, }, ) print(f"リソース {finding_id} の抑制を解除しました。") elif update_text: # UpdateTextが設定されている場合、リソースを抑制する securityhub.batch_update_findings( FindingIdentifiers=[{"Id": finding_id, "ProductArn": product_arn}], Workflow={"Status": "SUPPRESSED"}, Note={"Text": update_text, "UpdatedBy": updated_by}, ) print(f"リソース {finding_id} を抑制しました。理由: {update_text}") else: # UpdateTextが空の場合は何もしない continue # 正常に読み込めた場合はループを抜ける break except UnicodeDecodeError: if encoding == encodings[-1]: # 全てのエンコーディングを試して失敗した場合 raise Exception("CSVファイルの文字エンコーディングを特定できませんでした。") continue def main(): parser = argparse.ArgumentParser( description="Security Hub Findings CSVエクスポート/抑制スクリプト\n\n" "UpdateTextカラムを使用して、リソースの抑制/抑制解除を行えます。\n" "- UpdateTextが空の場合: 何もしない\n" "- UpdateTextに「解除」と入力: リソースの抑制を解除する\n" "- UpdateTextにその他の値を入力: その値をNote.Textとしてリソースを抑制する", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "--export", metavar="CSV_FILE", help="失敗コントロールのFindingsをCSVファイルにエクスポートします", ) parser.add_argument( "--suppress", nargs=2, metavar=("CSV_FILE", "UPDATED_BY"), help="指定したCSVファイルからUpdateTextを読み込み、該当のリソースを抑制します", ) parser.add_argument( "--export-suppressed", metavar="CSV_FILE", help="抑制済みのFindingsをCSVファイルにエクスポートします", ) args = parser.parse_args() if args.export: export_findings_to_csv(args.export, "NEW") elif args.suppress: suppress_findings_from_csv(args.suppress[0], args.suppress[1]) elif args.export_suppressed: export_findings_to_csv(args.export_suppressed, "SUPPRESSED") else: parser.print_help() if __name__ == "__main__": main()
ざっくりとした使い方はこちらです。
$ python3 securityhub_findings_csv_manager.py --help usage: securityhub_findings_csv_manager.py [-h] [--export CSV_FILE] [--suppress CSV_FILE UPDATED_BY] [--export-suppressed CSV_FILE] Security Hub Findings CSVエクスポート/抑制スクリプト UpdateTextカラムを使用して、リソースの抑制/抑制解除を行えます。 - UpdateTextが空の場合: 何もしない - UpdateTextに「解除」と入力: リソースの抑制を解除する - UpdateTextにその他の値を入力: その値をNote.Textとしてリソースを抑制する options: -h, --help show this help message and exit --export CSV_FILE 失敗コントロールのFindingsをCSVファイルにエクスポートします --suppress CSV_FILE UPDATED_BY 指定したCSVファイルからUpdateTextを読み込み、該当のリソースを抑制します --export-suppressed CSV_FILE 抑制済みのFindingsをCSVファイルにエクスポートします
やってみる
--export : 失敗コントロールのFindingsをCSVファイルにエクスポート
以下のコマンドで失敗コントロールのFindingsをCSVファイルにエクスポートします。
python3 securityhub_findings_csv_manager.py --export failed-findings.csv
実行例
$ python3 securityhub_findings_csv_manager.py --export failed-findings.csv Findingsの情報が failed-findings.csv に出力されました。
指定した名前のCSVファイルが同じディレクトリに作成されます。
CSVファイルには以下のような形で失敗コントロールのFindingsが記録されています。
Id,ProductArn,ResourceId,UpdateText arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.1/finding/8211ced4-a69a-4ac8-9629-7f853af6cd0e,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:dev/hogehoge, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.4/finding/39a63a81-78d4-441f-b9ed-b7882d003d09,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:events!connection/fugafuga/a740b061-ff5e-4323-a671-97e7e17ccdf8-PTCfaw,
こちらのCSVファイルは --suppress
オプションで使用します。
--export-suppressed : 抑制済みのFindingsをCSVファイルにエクポート
以下のコマンドで抑制済みのFindingsをCSVファイルにエクスポートします。
python3 securityhub_findings_csv_manager.py --export-suppressed suppressed-findings.csv
実行例
$ python3 securityhub_findings_csv_manager.py --export-suppressed suppressed-findings.csv Findingsの情報が suppressed-findings.csv に出力されました。
指定した名前のCSVファイルが同じディレクトリに作成されます。
CSVファイルには以下のような形で抑制済みのFindingsが記録されています。
Id,ProductArn,ResourceId,Text,UpdateText arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.1/finding/64b6b59e-06dd-4c78-841a-ca7dd68c8fb7,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/tm7up9scmd/stages/dev,ロギングは必須ではないため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.4/finding/ac4eff05-2ed1-491f-b54f-11020a5459b0,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/znf7gv0ryb/stages/prod,CloudFrontでWAF設定をしているため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.4/finding/d9b83249-a8f2-449a-a75c-cd0fc9a3a0ff,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/tm7up9scmd/stages/dev,WAFの利用が必要なリソースではないため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.1/finding/493cb557-c041-4834-a101-33e0ae5507a1,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.2/finding/3453e357-ffe8-4125-850b-bf175025aa72,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.6/finding/4bc89acf-aa48-43ce-bf43-914e66bf3b5c,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制,
こちらのCSVファイルも --suppress
オプションで使用します。
--suppress : 指定したCSVファイルからTextを読み込み、該当のリソースを抑制
こちらのオプションでは、CSVの UpdateText
カラムに記載された内容をノートに記載してリソースを抑制します。
最後の UpdateText
には抑制理由を記載して保存します。
Id,ProductArn,ResourceId,UpdateText arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.1/finding/8211ced4-a69a-4ac8-9629-7f853af6cd0e,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:dev/hogehoge,ローテーション対応は不要のため抑制 arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.4/finding/39a63a81-78d4-441f-b9ed-b7882d003d09,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:events!connection/fugafuga/a740b061-ff5e-4323-a671-97e7e17ccdf8-PTCfaw,ローテーション対応は不要のため抑制
以下のコマンドで指定したCSVファイルからUpdateTextを読み込み、該当のリソースを抑制します。
第二引数には更新者名を指定してください。
python3 securityhub_findings_csv_manager.py --suppress failed-findings.csv cm-takayama.kotaro
実行例
$ python3 securityhub_findings_csv_manager.py --suppress failed-findings.csv cm-takayama.kotaro リソース arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.1/finding/8211ced4-a69a-4ac8-9629-7f853af6cd0e を抑制しました。理由: ローテーション対応は不要のため抑制 リソース arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/SecretsManager.4/finding/39a63a81-78d4-441f-b9ed-b7882d003d09 を抑制しました。理由: ローテーション対応は不要のため抑制-850b-bf175025aa72
--export-suppressed
オプションでエクスポートしたCSVファイルをベースにコメントを編集したり抑制を解除することも可能です。
以下では以下の設定をしています。
- 3行目 : リソースの抑制を解除
- 5行目 : コメント更新
Id,ProductArn,ResourceId,Text,UpdateText arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.1/finding/64b6b59e-06dd-4c78-841a-ca7dd68c8fb7,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/tm7up9scmd/stages/dev,ロギングは必須ではないため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.4/finding/ac4eff05-2ed1-491f-b54f-11020a5459b0,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/znf7gv0ryb/stages/prod,CloudFrontでWAF設定をしているため抑制,解除 arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.4/finding/d9b83249-a8f2-449a-a75c-cd0fc9a3a0ff,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:apigateway:ap-northeast-1::/restapis/tm7up9scmd/stages/dev,WAFの利用が必要なリソースではないため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.1/finding/493cb557-c041-4834-a101-33e0ae5507a1,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制,コメント更新 arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.2/finding/3453e357-ffe8-4125-850b-bf175025aa72,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制, arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.6/finding/4bc89acf-aa48-43ce-bf43-914e66bf3b5c,arn:aws:securityhub:ap-northeast-1::product/aws/securityhub,arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/aha-DynamoDBTable-197F6M18IOT28,AHAリソースのため抑制,
--export-suppressed
で出力したファイルを対象に以下のコマンドを実行します。
python3 securityhub_findings_csv_manager.py --suppress suppressed-findings.csv cm-takayama.kotaro
UpdateText
に 解除
が設定されているリソースは抑制を解除し、それ以外のリソースは Text
に記載された内容でコメントの更新を行います。
$ python3 securityhub_findings_csv_manager.py --suppress suppressed-findings.csv cm-takayama.kotaro リソース arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/APIGateway.4/finding/ac4eff05-2ed1-491f-b54f-11020a5459b0 の抑制を解除しました。 リソース arn:aws:securityhub:ap-northeast-1:xxxxxxxxxxxx:security-control/DynamoDB.1/finding/493cb557-c041-4834-a101-33e0ae5507a1 を抑制しました。理由: コメント更新
最後に
Security Hubの失敗コントロールを抑制するスクリプトをご紹介しました。
100%の維持はなかなか骨が折れますが、スクリプトなどを使って作業を効率化していきましょう!
抑制して良い項目か判断が難しい、または対処方法がわからないという場合にはこちらのブログ/ドキュメントで弊社としての推奨対応を公開しているので参考にしてみてください。
- AWS Security Hub 基礎セキュリティのベストプラクティスコントロール修復手順 の記事一覧 | DevelopersIO
- 「AWS 基礎セキュリティのベストプラクティス v1.0.0」ガイド - Classmethod Cloud Guidebook
- クラスメソッドメンバーズユーザの方のみご覧いただけます。
- 詳細はこちらをご覧ください。
以上、たかやま(@nyan_kotaroo)でした。