こんにちは。AWS CLIが好きな福島です。
はじめに
私は、業務でSecurity Hubの対応を行うことがあるのですが、 進めるにあたり、セキュリティスコアの推移を確認したいなと思っております。
もちろん、セキュリティスコアは、マネジメントコンソールから確認することができるのですが、 複数のアカウントが存在する場合、各アカウントごとのセキュリティスコアを確認するのが大変です。
また、AWS CLIではセキュリティスコアを取得するコマンドが用意されていません。
ただ、どうにかしてAWS CLIでセキュリティスコアを確認したく思考していたところ、 方法を見つけたため、今回はその方法をご紹介したいと思います。
ただし、今回ご紹介する方法で取得したセキュリティスコアとマネジメントコンソールで表示されるセキュリティスコアは状況によっては、異なる可能性がある点はご留意ください。 (理由は後程ご説明いたします。)
前提
- Organizationsの統合により、組織全体のSecurity Hubを一元管理していること
- Security Hubを委任しているアカウントでコマンドを実施すること (委任していない場合は、マネジメントアカウントで実施))
利用するコマンド
- aws organizations list-accounts
→組織内のアカウント一覧を出力するコマンド - aws securityhub list-security-control-definitions
→SecurityHubのコントロール一覧を出力するコマンド - aws securityhub get-findings
→SecurityHubのコントロールのリソースごとの検出結果を出力するコマンド
結論(スクリプトの中身)
以下のスクリプトを使うことで組織内のアカウントごとのセキュリティスコアを取得することが可能です。
securityhub-score-check.sh
#!/bin/bash ######################################## ### ### 変数定義 ### ######################################## ## スクリプトの第1引数からチェックするコントロールの値を取得 STANDARDS=${1} ## 現在の時刻を設定 例: 2023-05-17 NOW_TIME=$(date '+%Y-%m-%d') ## 30日前の日付を設定 例: 2023-04-17T11:16:40 CHECK_TIME=$(date -d '-30 day' '+%Y-%m-%dT%H:%M:%S') ######################################## ### ### メイン処理 ### ######################################## ## 1. 組織内のアカウントの一覧をline変数に代入 line変数の例: 111111111111 fk-account-01 aws organizations list-accounts --query 'Accounts[?Status==`ACTIVE`].[Id,Name]' --output text | sort | while read line do ## 2. line変数からアカウントIDを取得 例: 111111111111 account_id=$(echo $line | awk '{print $1}') ## 3. line変数からアカウント名を取得 例: fk-account-01 account_name=$(echo $line | awk '{print $2}') ## 4. SecurityHubで指定したstandardsのコントロールの総数を取得 total_controls=$(aws securityhub list-security-control-definitions \ --standards-arn arn:aws:securityhub:ap-northeast-1::standards/${STANDARDS} \ --query "SecurityControlDefinitions[].[SecurityControlId,Title]" \ --output text | wc -l ) ## 5. 以下の条件に合致したコントロール数を取得 ## 特定のアカウントID ## 指定したstandards ## ステータスがFAILEDのリソース ## CHECK_TIME(デフォルト30日前)から現在までの検出結果 failed_controls=$(aws securityhub get-findings \ --filters "{\ \"ComplianceStatus\":[{\"Value\":\"FAILED\",\"Comparison\":\"EQUALS\"}],\ \"GeneratorId\":[{\"Value\": \"${STANDARDS}\",\"Comparison\":\"PREFIX\"}],\ \"AwsAccountId\":[{\"Value\": \"${account_id}\",\"Comparison\":\"PREFIX\"}]\ }" \ --query "Findings[?UpdatedAt<='${CHECK_TIME}'].[GeneratorId,Title,Compliance.Status]" \ --output text | sort | uniq | wc -l ) ## 6. FAILEDステータス以外のコントロール数を取得 not_failed_controls=$(expr ${total_controls} - ${failed_controls}) ## 7. FAILEDステータス以外のコントロール数の割合を取得 percent=$(expr ${not_failed_controls}00 / ${total_controls}) ## 8. 結果を標準出力 echo "$NOW_TIME $account_name($account_id) $percent% (${not_failed_controls}/${total_controls}) ${STANDARDS}" done
実行結果例
第1引数にコントロール名を指定します。
# ./securityhub-score-check.sh aws-foundational-security-best-practices/v/1.0.0 2023-05-17 fk-test-account-01(111111111111) 90% (187/207) aws-foundational-security-best-practices/v/1.0.0 2023-05-17 fk-test-account-02(222222222222) 93% (194/207) aws-foundational-security-best-practices/v/1.0.0 2023-05-17 fk-test-account-03(333333333333) 90% (187/207) aws-foundational-security-best-practices/v/1.0.0 #
指定できる値は以下の通りです。 (2列目の値の「arn:aws:securityhub:ap-northeast-1::standards/」以降の値です。) ただし、見てわかる通り、CIS AWS Foundations Benchmark v1.2.0だけARNが異なるため、本スクリプトで確認できません...
# aws securityhub describe-standards --query "Standards[].[Name,StandardsArn]" --output text | tr "\t" "," | column -s, -t AWS Foundational Security Best Practices v1.0.0 arn:aws:securityhub:ap-northeast-1::standards/aws-foundational-security-best-practices/v/1.0.0 CIS AWS Foundations Benchmark v1.2.0 arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0 CIS AWS Foundations Benchmark v1.4.0 arn:aws:securityhub:ap-northeast-1::standards/cis-aws-foundations-benchmark/v/1.4.0 NIST Special Publication 800-53 Revision 5 arn:aws:securityhub:ap-northeast-1::standards/nist-800-53/v/5.0.0 PCI DSS v3.2.1 arn:aws:securityhub:ap-northeast-1::standards/pci-dss/v/3.2.1
主要なコマンドの実行結果
スクリプトの処理をよりご理解いただくために、スクリプト内で実行されている主要なコマンドの実行結果を以下に記載します。
「1. 組織内のアカウントの一覧をline変数に代入」で利用しているコマンド
この情報を利用し、アカウントごとにセキュリティスコアを取得しています。
# aws organizations list-accounts --query 'Accounts[?Status==`ACTIVE`].[Id,Name]' --output text | sort 111111111111 fk-test-account-01 222222222222 fk-test-account-02 333333333333 fk-test-account-03 #
「4. SecurityHubで指定したstandardsのコントロールの総数を取得」で利用しているコマンド
割合を出すためにコントロールの総数を以下のコマンドを利用し算出しています。 (スクリプト内では以下のコマンドの出力結果の行数をカウントして総数を取得しています。)
# aws securityhub list-security-control-definitions \ --standards-arn arn:aws:securityhub:ap-northeast-1::standards/aws-foundational-security-best-practices/v/1.0.0 \ --query "SecurityControlDefinitions[].[SecurityControlId,Title]" \ --output text ACM.1 Imported and ACM-issued certificates should be renewed after a specified time period APIGateway.1 API Gateway REST and WebSocket API execution logging should be enabled APIGateway.2 API Gateway REST API stages should be configured to use SSL certificates for backend authentication APIGateway.3 API Gateway REST API stages should have AWS X-Ray tracing enabled APIGateway.4 API Gateway should be associated with a WAF Web ACL APIGateway.5 API Gateway REST API cache data should be encrypted at rest :
「5. 以下の条件に合致したコントロール数を取得」で利用しているコマンド
このコマンドがスクリプトの中で一番重要になるのですが、このコマンドは「Security Hubのコントロールごと」の結果を確認できる訳ではなく、 「Security Hubのコントロールでチェックしているリソースごと」の結果を確認できるコマンドとなります。
そのため、以下のコマンドの実行結果をご確認いただくと、CloudTrail.2とCloudTrail.5が2行ずつ出力されておりますが、 最終列を確認すると、それぞれ異なるリソースに対するチェック結果ということが分かるかと存じます。
# CHECK_TIME=$(date -d '-30 day' '+%Y-%m-%dT%H:%M:%S') # STANDARDS="aws-foundational-security-best-practices/v/1.0.0" # account_id=$(aws sts get-caller-identity --query "Account" --output text) # aws securityhub get-findings \ --filters "{\ \"ComplianceStatus\":[{\"Value\":\"FAILED\",\"Comparison\":\"EQUALS\"}],\ \"GeneratorId\":[{\"Value\": \"${STANDARDS}\",\"Comparison\":\"PREFIX\"}],\ \"AwsAccountId\":[{\"Value\": \"${account_id}\",\"Comparison\":\"PREFIX\"}]\ }" \ --query "Findings[?UpdatedAt<='${CHECK_TIME}'].[GeneratorId,Title,Compliance.Status,Resources[0].Id]" --output text \ --output text | sort aws-foundational-security-best-practices/v/1.0.0/CloudTrail.2 CloudTrail.2 CloudTrail should have encryption at-rest enabled FAILED arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/fk-data-events aws-foundational-security-best-practices/v/1.0.0/CloudTrail.2 CloudTrail.2 CloudTrail should have encryption at-rest enabled FAILED arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/fk-management-events aws-foundational-security-best-practices/v/1.0.0/CloudTrail.5 CloudTrail.5 CloudTrail trails should be integrated with Amazon CloudWatch Logs FAILED arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/fk-data-events aws-foundational-security-best-practices/v/1.0.0/CloudTrail.5 CloudTrail.5 CloudTrail trails should be integrated with Amazon CloudWatch Logs FAILED arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/fk-management-events
スクリプトではあえて最終列を出力せずに、uniqコマンドで重複排除し、FAILEDが1つでもあるコントロールの数をカウントしております。
マネジメントコンソールで表示されるセキュリティスコアと異なるリスクについて
セキュリティスコアが高く出てしまうリスク
まず前提として、Security Hubは裏側でConfig Rulesが利用されており、 Config Rulesのチェックは、チェックするリソースの設定変更がトリガーになります。 (Security Hubのコントロールに紐づくConfig Rulesによって異なる可能性があります)
そして今回は、30日前から現在までのSecurity Hubの検出結果(FAILEDの数)を基にセキュリティスコアを計算しております。
つまり、Config Rulesによりチェックするリソースが30日以上チェックされなかった場合、Security Hubの検出結果に何も残りません。 そのため本来は、FAILEDのステータスにも関わらず、スクリプト上では正常なコントロールとして計算されてしまいます。 (例えば、セキュリティスコアが本来は、80%にも関わらず、83%など高い数値が出てしまいます。)
セキュリティスコアが低く出てしまうリスク
Security Hubの検出結果はConfig Rulesによりチェックされたデータが90日間蓄積されます。 上書きされないため、あるリソースが30日の間に非準拠から準拠になった場合、その2つのデータが検出結果に残ります。
上述でも記載した通り、今回は、30日前から現在までのSecurity Hubの検出結果(FAILEDの数)を基にセキュリティスコアを計算しております。
つまり、既に改善しているコントロールも30日経過するまでは、正常なコントロールとして計算されないということになります。 (例えば、セキュリティスコアが本来は、80%にも関わらず、77%など低い数値が出てしまいます。)
チューニング
スクリプトの頭で定義しているCHECK_TIMEを変更することでチューニングが可能です。
30日の値を増やせば、セキュリティスコアが低く出るリスクは上がりますが、高くでるリスクは抑えられます。 逆に30日の値を減らせば、改善したコントロールの結果はすぐに確認できますが、本来の値より、セキュリティスコアが上がるリスクが上がります。
CHECK_TIME=$(date -d '-30 day' '+%Y-%m-%dT%H:%M:%S')
終わりに
今回は、AWS CLIでセキュリティスコアを取得する方法をご紹介いたしました。
どなたかのお役に立てれば幸いです。