こんにちは、PSIRTのWaTTsonです。私は平成の初め頃に生まれて平成を育ってきたので、12月23日といえば天皇誕生日で休み、という意識でしたが、もう令和になって天皇誕生日が2月に移ってから3年目になるんですね。ということで、freee Developers Advent Calendar 2022 の23日目の記事です。
今年のAdvent Calendarでは25日中5日間をPSIRTのメンバーが担当しているので、これまでにもいくつかセキュリティの話を見かけたと思います。プロダクトのセキュリティを高める方法にはいろいろなアプローチがありますが、今日はAWSマネージドサービスの「Security Hub」のお話です。
Security Hubとセキュリティのベストプラクティス
AWSには、セキュリティ上の問題を検出するマネージドサービスが、例えば脅威検出を行う「GuardDuty」、脆弱性管理を行う「Inspector」など、目的に応じていろいろと用意されています。Security Hubもそうしたマネージドサービスの1つですが、これが担当しているのは主に以下のような領域です:
- セキュリティのベストプラクティスからの逸脱を検出する
- AWSのサービス、もしくはその他の対象サービスの、セキュリティ関係のアラート結果を集約する
- アラートの内容に応じて自動修復を行う
今回扱うのはこの中で「セキュリティのベストプラクティスからの逸脱を検出する」という部分についてなので、これについて少し詳しく説明をします。
Security Hubでは、例えば「S3 bucketのパブリックアクセスを無効にする」とか「EBSをデフォルトで暗号化する設定を有効にする」といったセキュリティ上のチェック項目を、「コントロール」という単位で管理しています。そして、これらのコントロールがいくつかまとめられた「セキュリティ基準」が用意されており、有効化したセキュリティ基準に含まれているコントロールが自動で全てチェックされる仕組みになっています。2022年12月23日現在では、以下の3種類(バージョン違いも含めると4種類)が使用可能になっているようです:
- AWS 基礎セキュリティのベストプラクティス(v1.0.0)
- CIS AWS Foundations Benchmark(v1.2.0 および v1.4.0)
- PCI DSS(v3.2.1)
例えば「AWS 基礎セキュリティのベストプラクティス v1.0.0」を有効化すると、これに含まれる様々なコントロールについてチェックが行われ、各コントロールの結果と全体のセキュリティスコアが表示されます。
各コントロールの詳細な説明と修正の方法については、Security Hubのユーザーガイド(https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html )の中に書かれているので、これに従って各検出結果の対応をしていく、という形です。
なお、上述の通りSecurity Hubでは検出結果に対して自動修復する機能も備わっていますが、freeeではAWSのリソースをterraformで管理しており、AWS上での変更だけでなくterraformにも変更を加える必要があるため、今回はこの機能は使っていません。
お試しでSecurity Hubを導入してみた
freeeではSecurity Hubを運用した実績がこれまでなかったので、いろいろ情報を集めつつ、実際にfreeeが管理しているAWSアカウントの1つで動かしてみることにしました。今年の6月末に新規作成したAWSアカウント*1があり、まだあまりリソースが作られていないので検出結果の対応がしやすそう&コストもそこまでかからなそうということで、このアカウントについて8月初めにSecurity Hubの「AWS 基礎セキュリティのベストプラクティス v1.0.0」を有効化しました。
有効化してからしばらく運用していく中で、いくつかの問題が明らかになってきました。
まず1つめは、検出結果の「ワークフローのステータス」を変更する際にメモを残せない、という問題です。Security Hubの検出結果には、その結果に対する調査・対処の進行状況を示す「ワークフローのステータス」という値が用意されています。これは、最初に検出された際には「NEW」として挙げられますが、「NOTIFIED(通知済み)」「SUPPRESSED(抑制済み)」「RESOLVED(解決済み)」とステータスを変更できるようになっています。
この「ワークフローのステータス」の変更は、ただプルダウンリストで選択できるだけで、そこに至った経緯などを記録しておくことはできません。
freeeではリソースをterraformで管理しているので、解決するためにリソースを変更したものについてはPRの形で記録が残ります。なので、特に問題となるのは「抑制済み」に設定したものについてです。これについては、今のところ数がそれほど多くないので、Google スプレッドシートに検出結果を書き写して、抑制済みにした経緯・理由を記録する形で運用しています。これはおそらく今後数が増えてくると管理できなくなってくると思われるので、いずれは何かしら管理しやすい仕組みを構築する必要がありそうです。
他の問題として、「マネジメントコンソールの検出結果の一覧機能が使いづらい」というものがあります。SecurityHubを導入したアカウント上では複数のプロジェクトを動かしていて、それぞれ別々のメンバーが運用に携わっていますが、検出結果への対応のうち各プロジェクトのリソースに関するものはそれぞれの開発チームのメンバーに対処してもらうのが望ましいです。ですが、Security Hubの検出結果を表示するマネジメントコンソールの機能では、うまくプロジェクトごとにフィルターするのが難しいということが分かりました。
SecurityHubの検出結果は、データ形式としては「AWS Security Finding 形式 (ASFF)」という形のJSONで管理されています。これは、ユーザーガイド(https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-findings-format-syntax.html )に詳細な仕様が記載されています。
この検出結果は、Security Hubのマネジメントコンソール上では「検出結果」のページで一覧できるようになっています。
ここでは、さまざまな値でフィルターをすることができますが、これはあくまで検出結果として出力されているものについてしか対応していません。プロジェクトごとにフィルターするのには、リソースにプロジェクトごとのタグをつけて、リソースタグでのフィルターを使うのが良さそうですが、「AWS 基礎セキュリティのベストプラクティス v1.0.0」の検出結果にはリソースタグが含まれていないため、リソースタグでのフィルターを入れても検出結果が引っかかりません。
リソースタグを自分で付ける
ということで、なければ自分で付けちゃえ、ということを考えます。
Security Hubの検出結果(ASFF)には"Resources"という項目がありますが、これには"Id"の項目が入っていて、リソースのARNが記録されています。AWSのリソースのほとんどは、AWS Resource Groupsのタグ付け機能にある「Resource Groups Tagging API」を使ってリソースタグを一括で取得することができます。これを使って適当なスクリプトを書けば、自分でリソースタグを付けることができそうです。
とはいえ、引いてきたリソースタグをASFFに付け加えたとして、それをそのままSecurity Hubに自分で入れることはできません。Security HubのAPIにはBatchUpdateFindings(https://docs.aws.amazon.com/securityhub/1.0/APIReference/API_BatchUpdateFindings.html )というものがありますが、ここで指定できる内容にResourcesは含まれていません。
1つの選択肢としては、BatchUpdateFindingsを使って、ユーザー定義フィールド(UserDefinedFields)にリソースタグの情報を入れるというやり方が考えられます。これは有力な選択肢の1つと思いますが、検出結果には複数のリソースが含まれる場合があるので、その場合のデータの構造を考える必要があります。これはこれでうまくやればできそうな気がしますが、今回は別の方法をとりました。
別の選択肢として、Security Hubのコンソールに頼らず、別の場所で可視化できるようにする、という方法が考えられます。freeeでは、セキュリティ関係の情報をSIEM on Amazon OpenSearch Service(https://github.com/aws-samples/siem-on-amazon-opensearch-service )を使ってOpenSearch Dashboard上に集約する運用をしているので、Security Hubの検出結果もこれを使って集約することを試みました。
システム全体の構成は上図のようになりました。dailyで定時実行されるLambdaでSecurity Hubの検出結果を取得し、その結果からリソースIDを抽出して、Resource Groups Tagging APIでリソースタグを取得します。取得した情報をいい感じに埋め込んだJSONをS3 bucketに保存します。S3 bucketへの保存をトリガーに、SIEM on OpenSearchにデータを読み込むLambdaが動く、という形です。
将来的に管理対象アカウントを増やしていけるようにするため、Lambdaは対象のアカウント上に置くのでは無く、セキュリティ情報を集約しているアカウントに配置する構成を取りました*2。
上の図では、「resource tagsを取得」の箇所でResource Groupsの他にIAMにもリクエストを投げる構成を書いています。これは、Resource Tagging APIがIAMリソースに対応していないためです。API Reference(https://docs.aws.amazon.com/ja_jp/resourcegroupstagging/latest/APIReference/supported-services.html )に列挙されているとおり、Resourse Tagging APIはほとんどのAWSリソースを扱えるようになっていますが、IAMはこのリストの中に挙げられていません。Security Hubの検出結果(ASFF)では「AwsIamAccessKey」「AwsIamGroup」「AwsIamPolicy」「AwsIamRole」「AwsIamUser」と5種類のIAMリソースが含まれているので、これらのうちタグ付けできる「AwsIamPolicy」「AwsIamRole」「AwsIamUser」については、IAM APIの「ListPolicyTags」「ListRoleTags」「ListUserTags」を使って個別にタグを取得します。
Lambdaの実装
長くなるので具体的な実装は書きませんが、Lambda関数には以下のような処理を実装しました:
- 管理対象アカウントのIAM Roleに対してassumeRoleする
- Lambdaが配置されているアカウントと管理対象のアカウントが異なるため、各種APIはクロスアカウントアクセスが必須
- Lambdaの実行RoleにはassumeRoleの権限を付与しておく
- 管理対象アカウントのIAM RoleにはSecurityHub、ResourceGroups、IAMのread権限を与えておく
- SecurityHub findingsの取得
- timestampを追加
- 取得したのがいつ時点のsnapshotなのかを記録しておく
- findingsからresource typeとresource arnを抽出
- resource arnをもとにtagsを取得
- Resource Tagging APIが対応しているものはget_resources()にResourceARNListを渡して一括取得
- IAMリソースについては、resource typeごとに条件分岐して「ListPolicyTags」「ListRoleTags」「ListUserTags」で取得
- findingsに取得したresource tagsの情報を付加
- findingsの形式を整える
- OpenSearchに読み込む際、Resourcesの配列をそのまま渡すとうまくパースしてくれないので、"Resource": {"0": {1つめのリソースの情報}, "1": {2つめのリソースの情報}, ...}のような形のdictに変形しておく
- SIEMに読み込むes-loaderの設定でjson_delimiter = findingsと設定されているので、{"findings": [各findingのデータ]}の形のJSONにする
- S3 bucketに保存
- es-loaderのトリガーになっているS3 bucketに、gzip圧縮して保存
es_loaderの設定
SIEM on Amazon OpenSearch Serviceの設定には、デフォルトでSecurity Hubの検出結果を読み込むための設定が記述されています(https://github.com/aws-samples/siem-on-amazon-opensearch-service/blob/main/source/lambda/es_loader/aws.ini#L635-L690 )。
基本的にはこの記述をそのまま使いますが、この設定はCloudWatch Event経由で読み込むことが想定されており、timestamp_key_listに「cwe_timestamp」が指定されています。今回はEventBridgeを使わずにLambda→S3 bucketという経路で読み込んでいるので、cwe_timestampのフィールドが存在しません。このため、es_loaderの設定からcwe_timestampを外します。また、Lambdaの処理でtimestampフィールドを追加しておいたので、それをtimestamp_keyに指定します。
デフォルトのaws.iniでは
[securityhub] # 中略 timestamp_key_list = cwe_timestamp UpdatedAt # 後略
とあるので、user.iniに以下のように書いて上書きします:
[securityhub] timestamp_key_list = timestamp
※追記 2022.12.23 19:00
@timestamp
の設定は、aws.ini/user.iniのtimestamp_key_list
で指定する部分とは別に、スクリプトで変更している部分(https://github.com/aws-samples/siem-on-amazon-opensearch-service/blob/v2.8.0/source/lambda/es_loader/siem/sf_securityhub.py#L135)もあるようなので、こちらも修正が必要でした。これは、このsf_securityhub.pyを複製して当該部分をコメントアウトしたものを、ユーザー定義スクリプトとして追加しておくことで対処できます。
Terraformでリソースにデフォルトタグを付ける
管理対象アカウント内のリソースは、基本的に全てterraformで管理されています。terraformでは、default_tagsを設定して自動的に全てのタグ付け可能なリソースにデフォルトタグを付ける書き方がある(https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider )ので、これを各プロジェクトのterraformファイルに書いていきます。
provider "aws" { default_tags { tags = { SecurityHub = "swift-shall-we" } } }
このdefault_tagsの設定は、個別のリソース定義に書いているtagsと重複するとエラーとなります。「Project」というタグは個別の箇所でしばしば書かれがちなので、ここではそれと競合しないように「SecurityHub」というkeyでタグを設定しました。
これで、OpenSearchでSecurityHubの検出結果を表示して、タグによるフィルターができるようになりました。
今後対応していきたい課題点
Security Hubの検出結果をSIEM on Amazon OpenSearch Serviceでプロジェクトごとにフィルターできるようになったことで、フィルターしたOpenSearchのリンクを各プロジェクトのチームに渡して、それぞれ対処してもらう運用ができるようになりました。
ですが、この仕組みでは対応すべき検出結果がフィルターできるようになっただけで、実際に対処する際には少し手間がかかります。現状では、出力された検出結果を1つ1つJiraのissueにして、担当者をassignして対処する形で進めていますが、これはAdvent Calendar 8日目の記事でeijiさんが書いていたようなslack botを使った半自動化処理と似たような形で省力化できそうです。このslack botはAWS ECSで動かしているので、SecurityHubのAPIを使ってワークフローのステータスを変更する仕組みも入れられそうです。
Breaking News: 2023年第1四半期にSecurityHubに大きめのアップデートが入るらしい
ここまで書いてきたところで、このようなニュースが入ってきました:
コントロールビューの統合と検出結果の統合が実装される関係で、ASFFの形式が少し変わるようです。
今回の記事で扱っている内容と直接コンフリクトする部分はそこまで多くなさそうですが、ASFFのフィールドの意味が変わったりするのでOpenSearchで見るときのフィルターを修正したりする必要がありそうです。
おわりに
SecurityHubは最近AWSが力を入れている部分の1つなようですし、なかなか便利なサービスなので、うまく運用のシステムに組み込んでいきたいところです。
SIEM on Amazon OpenSearch Serviceとの連携はAWS Security Blogでも紹介されている有用な方法なので、この記事での説明が何かの参考になれば幸いです。
明日のfreee Developers Advent Calendar 2022はPSIRTのtdtdsさんです。お楽しみに!
*1:このアカウントでは新卒研修で作った社内コミュニケーション施策用のアプリケーション「Swift "Shall We!"」が動いていたりもします。そちらについてはnoteのあえ共freee「新卒研修で作ったWebサービスで社内のバックオフィスオペレーションを効率化した話」やfreee Tech Night 「freeersにマジ価値を届けた新卒開発研修」で扱っています。
*2:これは、実際のところはSecurity Hubの管理者アカウント/メンバーアカウントの仕組みを使った方がスマートに管理できると思います。