こんにちは、G-gen 又吉です。今回は、Security Command Center から検出される脅威を Slack に通知したり、特定の検出機能においては自動で修復する仕組みを実装してみたいと思います。
概要
Security Command Centerとは
Security Command Center とは、Google Cloud 環境の構成ミス、脆弱性、脅威を特定して、セキュリティ強化・リスク軽減をするためのサービスです。
Security Command Center については、以下の記事で詳しく解説されているため事前にご覧いただけると幸いです。
また筆者の環境は、Security Command Center の スタンダード ティア が組織と組織配下のプロジェクトですべて有効化された状態ですので、まだ有効化されていない方は事前に以下を参考にSecurity Command Center を有効化していただけますと幸いです。
https://cloud.google.com/security-command-center/docs/set-up?hl=ja
作成するもの
Security Command Center から検出機能の中で、重要度が「重大」または「高」のものを対象に Slack に推奨事項(改善策)とともに検出結果を通知します。
上記は、こちらのチュートリアルを参考にしました。
また、検出機能の中でも特定の検出機能が検出された時、自動で修復する仕組みも実装します。
今回ピックアップした検出機能は、「NON_ORG_IAM_MEMBER」です。こちらは、組織内で "@gmail.com" メールアドレスのアカウントに IAM 権限が紐づけられている場合に、リアルタイムに検出されます。
「NON_ORG_IAM_MEMBER」が検出された際は、対象の "@gmail.com" のユーザーに紐づけられたロールを自動で削除して、削除結果を Slack にて報告するスクリプトを記述し、Cloud Functions で実装していきたいと思います。
作成の手順
手順としては、大きく以下の3つとなります。
- Pub/Sub トピックを作成
- サービスアカウントの作成
- Cloud Functions を構成
Pub/Sub トピックを作成
Cloud Shell を起動して、Cloud Pub/Sub トピックを設定していきます。
上記の赤枠をクリックすると Cloud Shell が起動しますので、Cloud Shell ターミナルから以下を実行します。
# 環境変数に Google Cloud プロジェクト を指定 export PROJECT_ID=PROJECT_ID # 環境変数に Google Cloud 組織を指定 export ORG_ID=ORG_ID # gcloud コマンドにプロジェクト ID を設定 gcloud config set project PROJECT_ID # 通知をパブリッシュする Pub/Sub トピックを作成 gcloud pubsub topics create scc-critical-and-high-severity-findings-topic # 環境変数にトピックを指定 export TOPIC=projects/$PROJECT_ID/topics/scc-critical-and-high-severity-findings-topic # メッセージがトピックにパブリッシュされたときに、 Cloud Functions に通知するサブスクリプションを作成 gcloud pubsub subscriptions create scc-critical-and-high-severity-findings-sub --topic scc-critical-and-high-severity-findings-topic # トピックに通知をパブリッシュするように、Security Command Center を構成 (フィルタ機能を用いて、重要度が「重大」と「高」のアクティブな検出結果に関する通知をパブリッシュ) gcloud scc notifications create scc-critical-and-high-severity-findings-notify --pubsub-topic $TOPIC --organization $ORG_ID --filter "(severity=\"HIGH\" OR severity=\"CRITICAL\") AND state=\"ACTIVE\""
サービスアカウントの作成
Cloud Functions に紐づるサービスアカウントを作成していきます。
IAM と管理
>サービスアカウント
へ移動- サービスアカウントを作成 をクリック
- 「Cloud Functions 起動元」と「Pub/Sub サブスクライバー」、「Project IAM 管理者」のロールをアタッチ
- 完了 をクリック
Cloud Functions の作成
Cloud Functions を作成していきます。
Cloud Functions
へ移動- 関数の作成 をクリック
- トリガー に「Cloud Pub/Sub」を、トピックに先程作成した「${TOPIC}」を選択
- サービスアカウント に先程作成した「${サービスアカウント}」を入力
- 環境変数に Slack Webhook のURLを入力 Slack Webhook URL の発行手順は こちら を参考にしました。
- 次へ をクリック
- ランタイム に Python 3.9 を選択し、エントリポイントを「send_slack_chat_notification」と入力
- main.py とrerequirements.txt のファイルをそれぞれ以下のコードに書き換え、[デプロイ]をクリック
main.py の内容
import base64 import json import os import slackweb from google.cloud import resourcemanager_v3 from google.iam.v1 import iam_policy_pb2 # type: ignore # 環境変数から SLACK_WEB_HOOK_URL を取得 SLACK_WEB_HOOK_URL = os.environ.get("SLACK_WEB_HOOK_URL") # slack クライアントの初期化 slack = slackweb.Slack(url=SLACK_WEB_HOOK_URL) # ProjectsClient クライアントの初期化 client = resourcemanager_v3.ProjectsClient() def send_slack_chat_notification(event, context): # SCC から受け取ったデータを json 形式で展開 pubsub_message = base64.b64decode(event['data']).decode('utf-8') message_json = json.loads(pubsub_message) finding = message_json['finding'] # カテゴリー(検出機能)とレコメンド(改善策)を取得 category = finding['category'] recommendation = finding['sourceProperties']['Recommendation'] # 検出機能が「NON_ORG_IAM_MEMBER」の時、"@gmail.com" の対象ユーザーに紐づけられているロールを削除する if category == "NON_ORG_IAM_MEMBER": # プロジェクトIDとメンバーのメールアドレス、紐づけられたロールの情報を取得 project_id = finding['sourceProperties']['ResourcePath'][0].split('/')[1] member = finding['sourceProperties']['OffendingIamRolesList'][0]['member'] roles = [] for i in range(len(finding['sourceProperties']['OffendingIamRolesList'][0]['roles'])): roles.append(finding['sourceProperties']['OffendingIamRolesList'][0]['roles'][i]) # 対象メンバー (@gmail.com) が紐付くロールから、対象メンバーを削除する関数を実行 modify_policy_remove_member(project_id, roles, member) # 削除結果を Slack で通知 slack.notify(text=f"【セキュリテクリスク検出】\n{category} \n 【報告】\n project_id:{project_id} に@gmailを含むユーザーが追加されたことを検知したため、対象ユーザに付与されたロールを自動的に削除しました \n 【対象ユーザ】\n {member} \n 【削除したロール】\n {roles} ") return else: # 検出機能が「NON_ORG_IAM_MEMBER」以外の時、検出機能とレコメンド(改善策)の情報を Slack で通知 slack.notify(text=f"【セキュリテクリスク検出】\n {category} \n 【推奨事項】\n{recommendation}") return def modify_policy_remove_member(project_id, roles, member): # プロジェクト内の全てのアクティブなロールを取得 iam_policy = get_iam_policy(project_id) # roles リストにある role の数だけ処理を回す for i in range(len(roles)): # iam_policy の中のロールと、引数で受け取ったロールが同じ場合、対象の bindings を取得 bindings = next(b for b in iam_policy.bindings if b.role == roles[i]) # bindings の中に、引数で受け取ったメンバー("@gmail.com")が含まれる場合、取り除く if bool(bindings.members) and member in bindings.members: bindings.members.remove(member) # 上記で修正したポリシーを、プロジェクトの新ポリシーとして上書きする set_iam_policy(project_id, iam_policy) return def get_iam_policy(project_id): # 対象プロジェクト内の IAM Policy の取得 request = iam_policy_pb2.GetIamPolicyRequest( resource=f"projects/{project_id}" ) iam_policy = client.get_iam_policy(request=request) return iam_policy def set_iam_policy(project_id, iam_policy): # 対象プロジェクト内の IAM Policy を上書き request = iam_policy_pb2.SetIamPolicyRequest( resource=f"projects/{project_id}", policy=iam_policy, ) response = client.set_iam_policy(request=request) return
requirements.txt の内容
slackweb==1.0.5 requests==2.28.1 google-cloud-resource-manager==1.6.1 grpc-google-iam-v1==0.12.4
ポリシーの更新手順
"@gmail.com" の対象ユーザーに紐づけられているロールを削除する仕組みは、以下の流れで行われています。
ポリシーの更新手順
- 既存のポリシーの取得( get_iam_policy )
- ポリシーの変更( modify_policy_remove_member )
- ポリシー全体の書き込み( set_iam_policy )
2 ポリシーの変更時に、"@gmail.com" の対象ユーザーに紐づけられているロールを削除しています。
get_iam_policy
get_iam_policy で取得できるデータは以下のような形式です。
version: 1 bindings { role: "roles/bigquery.admin" members: "user:hogehoge@gmail.com" members: "user:matayuuu@g-gen.co.jp" } bindings { role: "roles/owner" members: "user:matayuuu@g-gen.co.jp" } ・ ・ ・ etag: "\007\005\351\264\211C\021\344"
対象のプロジェクト内でアクティブなロールに紐づくメンバーが bindings として取得できます。
また、 etag はポリシーが更新されるたびに変更されるため、ポリシーの書き込み時の競合防止に使われています(参考)。
set_iam_policy
get_iam_policy で取得できるデータの一部を変更して、set_iam_policy でポリシーを更新できます。
仮に、hogehoge@gmail.com のユーザーから "roles/bigquery.admin" のロールを削除したとすると、以下のようになります。
version: 1 bindings { role: "roles/bigquery.admin" members: "user:matayuuu@g-gen.co.jp" } bindings { role: "roles/owner" members: "user:matayuuu@g-gen.co.jp" } ・ ・ ・ etag: "\007\005\351\264\211C\021\344"
上記の状態で、set_iam_policy をすることで、etag の中身が get_iam_policy で取得したときと同じであれば新しいポリシーとして上書きが成功します。
逆に、etag の中身が get_iam_policy で取得したときと変わっていれば、自分が get_iam_policy した後に他の誰かがポリシーを変更して上書きしているため更新が失敗となります(競合の発生)。
その場合は、再度 get_iam_policy してから、その内容に変更を加えて set_iam_policy する流れになります。
動作確認
"@gmail.com" に単一のロールを紐づけた時
IAM と管理
>IAM
から、"@gmail.com" ユーザーに 任意のロールを紐づけし、自動で紐づけたロールが削除されるか確認します。
保存 をクリックしたタイミングで、Slack に以下の通知が届きました。
"@gmail.com" ユーザーのロールが上手く削除できた際、削除したことを報告する内容が Slack に届きます。
念の為、IAM と管理
>IAM
でも ”@gmail.com" のユーザーにロールが紐付いていないか確認しましたが、問題なく削除されておりました。
"@gmail.com" に複数のロールを紐づけた時
次に、一人のユーザーに対し、一度に複数のロールを紐づけた際の挙動も確認していきたいと思います。 先程と同様、 "@gmail.com" ユーザーに任意のロールを複数紐づけます。
保存 をクリックしたタイミングで、Slack に以下の通知が届きました。
"@gmail.com" ユーザーからロールが削除できています。
検出機能が「NON_ORG_IAM_MEMBER」以外の時
最後に、検出機能が「NON_ORG_IAM_MEMBER」以外の時の挙動も確認します。
こちらは、SCC
>検出
から適当な検出機能を選択し、非アクティブ にした後、再度 有効 にすることで Slack へ通知されるか確認したいと思います。
「OPEN_FIREWALL」を、一度 非アクティブ にした後、再度 有効 にしたタイミングで、Slack に以下の通知が届きました。
「NON_ORG_IAM_MEMBER」以外の時は、セキュリティリスクを検出したことを報告し、推奨事項 (改善案) を通知する文が Slack に届きます。
今回作成した自動修復の仕組みは「NON_ORG_IAM_MEMBER」の検出機能のみでしたが、こちらを応用すると組織としてクリティカルな脅威に対して、自動修復する是正的な統制も実現できそうですね。
又吉 佑樹(記事一覧)
クラウドソリューション部
はいさい、沖縄出身のクラウドエンジニア!
セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Champion Innovator (AI/ML)。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリスト。好きな分野は生成 AI。
Follow @matayuuuu