Security Command Center の検出結果を Slack に連携する
はじめに
Security Command Center(SCC) による脆弱性や構成ミス、脅威検知などの検出結果を Slack チャンネルに通知する方法について記事にしていきます。
概要
SCC から Slack へのメッセージ通知は以下のように構成します。
SCC から Slack へのメッセージ通知 アーキテクチャ
ポイントは以下です。
- SCC で検知した Google Cloud リソースの脆弱性や構成ミス、脅威などは 継続的エクスポート という機能で検知メッセージをリアルタイムに Cloud Pub/Sub にパブリッシュします。
- Cloud Pub/Sub にパブリッシュされたメッセージは Eventarc に転送され、Eventarc は CloudEvents形式で Cloud Run functions に配信されます。
- Cloud Run functions では HTTP による CloudEvents の受信をトリガーとしてメッセージを処理し、Slack API を介して Slack アプリにメッセージを POST します。
- Slack アプリにアクセスするための OAuth トークンは Secret Manager に格納し、Cloud Run functions から参照します。
- Slack アプリは受信したメッセージを元に Slack チャンネルにメッセージを書き込みします。
構築手順の詳細は以下ドキュメントを参考にしています。
なお、本検証では Cloud Run funcitons の 第二世代を利用した構成 としています。第二世代は Cloud Run に統合され、関数を Cloud Run サービスとしてデプロイできます。Cloud Run サービスの仕様を踏襲しており、Cloud Run サービスで利用可能な機能をサポートしています。一方、第一世代は Cloud Run に統合されていない関数実行サービスです。
第一世代は Cloud Pub/Sub などのトリガー機能を直接サポートするため Eventarc の構成が不要で設定はしやすいのですが、現在 Google Cloud では Cloud Run の豊富な機能が享受できる 第二世代を推奨 しています。今後も第一世代はサポート継続されるとドキュメントには明記されていますが、将来的な第二世代への移行の可能性なども考慮し本記事では第二世代の構成方法で試していきます。
Cloud Run functions の世代についての詳細は以下ドキュメントをご参照ください。
やってみた
本検証では Google Cloud の構成は Cloud Shell から gcloud コマンドを利用して構築していきます。オーナー権限を持つユーザで検証しますが、事前定義ロールを付与する場合は以下をご参照ください。
本検証で必要な事前定義ロール
- Service Usage 管理者(roles/serviceusage.serviceUsageAdmin)
- Pub/Sub 管理者(roles/pubsub.admin)
- セキュリティ センター管理者編集者(roles/securitycenter.adminEditor)
- Secret Manager 管理者(roles/secretmanager.admin)
- サービス アカウント管理者(roles/iam.serviceAccountAdmin)
- Project IAM 管理者(roles/resourcemanager.projectIamAdmin)
- Cloud Run 管理者(roles/run.admin)
- Artifact Registry 管理者(roles/artifactregistry.admin)
- ストレージ管理者(roles/storage.admin)
- サービス アカウント ユーザー(roles/iam.serviceAccountUser)
- Cloud Build 編集者(roles/cloudbuild.builds.editor)
- Eventarc 管理者(roles/eventarc.admin)
- ログ表示アクセス者(roles/logging.viewAccessor)
- Project IAM 管理者(roles/resourcemanager.projectIamAdmin)
SCC は組織/フォルダ/プロジェクトレベルで構成可能ですが、今回はプロジェクトレベルで有効化して通知を構成します。
API 有効化
Google Cloud で対象のプロジェクトを選択し、Cloud Shell から実行していきます。以下コマンドを実行し、Google Cloud 上でリソースを構成するうえで必要な API を有効化します。
gcloud services enable artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
eventarc.googleapis.com \
run.googleapis.com \
pubsub.googleapis.com \
securitycenter.googleapis.com \
secretmanager.googleapis.com
Pub/Sub トピックとサブスクリプションの作成
Pub/Sub トピックとサブスクリプションの作成
SCC がメッセージをパブリッシュする宛先となる Cloud Pub/Sub トピックと、Eventarc にメッセージを連携するための Cloud Pub/Sub サブスクリプションを作成していきます。
まずは環境変数 PROJECT_ID
にプロジェクト ID を指定します。
export PROJECT_ID=$(gcloud config get-value project)
Pub/Sub トピックを作成します。
gcloud pubsub topics create scc-topic
環境変数 TOPIC
に、作成した Pub/Sub トピックを指定しておきます。
export TOPIC=projects/$PROJECT_ID/topics/scc-topic
SCC からの通知メッセージが Pub/Sub トピックにパブリッシュされたことをトリガーとし、Eventarc が Cloud Run functions にイベント配信を行います。Eventarc にメッセージを連携するための Pub/Sub サブスクリプションを作成していきます。
gcloud pubsub subscriptions create scc-sub \
--topic scc-topic
SCC の継続的エクスポートを作成
SCC の継続的エクスポートを作成
SCC から Pub/Sub トピックに通知をパブリッシュするよう SCC の 継続的エクスポート を構成します。
Cloud Shell から以下コマンドを実行します。本検証では --filter
を利用して、重大度が 「重大」 「高」 「中」 でかつアクティブな検出結果のみ通知するフィルタを設定しました。
gcloud scc notifications create scc-notification \
--pubsub-topic=$TOPIC \
--project=$PROJECT_ID \
--filter "(severity=\"CRITICAL\" OR severity=\"HIGH\" OR severity=\"MEDIUM\") AND state=\"ACTIVE\""
フィルタの詳細は以下ドキュメントをご参照ください。
Slack アプリの作成とワークスペースへのインストール
Slack アプリの作成とインストール
Google Cloud から通知されたメッセージを Slack チャンネルに投稿するための Slack アプリを作成していきます。 https://api.slack.com/apps にアクセスします。
[Create New App] をクリックします。
[From a manifest] をクリックします。
[select a workspace] から Slack アプリをインストールするワークスペースを選択し [Next] をクリックします。
必要に応じて、作成するアプリのマニフェストを修正し、[Next] をクリックします。今回は display_information
の name
のみ更新しています。
[Create] をクリックします。
Slack アプリが作成されたら、左側の項目から [OAuth & Permissions] を選択し、[Scopes] -> [Bot Token Scopes] の [Add an OAuth Scope] をクリックします。
[chat:write] と [chat:write.public] を選択します。
左側の項目から [Install App] を選択し、[Install to xxxx] (xxxx はワークスペース名) をクリックします。その後、確認のダイアログで [許可] をクリックします。
Slack API の [Install App] -> [OAuth Tokens] の [Bot User OAuth Token] をコピーします。これは Cloud Run functions から Slack アプリにアクセスするために利用する OAuthトークンとなります。
ワークスペースにインストールした Slack アプリはワークスペース上の [App] -> [+アプリを追加する] から追加できます。
Secret Manager に OAuth トークンを保存
Secret Manager に OAuth トークンを保存
前述の手順でコピーした OAuth トークンは Cloud Run functions から Slack アプリにアクセスするために利用します。トークンを Cloud Run functions にハードコードすることは漏洩のリスクが大きいため推奨しません。そのため、シークレット管理のためのマネージドサービスである Secret Manager にトークンを保存します。Secret Manager に保存したシークレットは Cloud Run functions でマウントして参照することが可能です。
以下コマンドを実行し、Secret Manager にシークレットを作成します。
gcloud secrets create slack_bot_user_oauth_token \
--replication-policy="automatic"
先ほどコピーした Slack 用の OAuth トークンを以下コマンドに入力して実行します。
echo -n "<Slack Bot User OAuth Token>" | \
gcloud secrets versions add slack_bot_user_oauth_token --data-file=-
Cloud Run functions 用サービスアカウントの作成
Cloud Run functions が Secret Manager にアクセスするために、Secret Manager へのアクセス権限を付与したサービスアカウントを作成し Cloud Run functions をデプロイする際にアタッチする必要があります。
ここでは事前にサービスアカウントの作成と権限付与を行います。以下コマンドを実行します。
gcloud iam service-accounts create slack-notification
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:slack-notification@$PROJECT_ID.iam.gserviceaccount.com \
--role=roles/secretmanager.secretAccessor \
--condition=None
Cloud Run functions のデプロイ
Cloud Run functions のデプロイ
SCC による検出結果が Cloud Pub/Sub にパブリッシュされたことをきっかけとし、Slack アプリに通知を行う Cloud Run functions をデプロイしていきます。
まずは Cloud Shell のユーザ環境に、Cloud Run functions にデプロイする Python のソースコードを準備していきます。最初にディレクトリを作成します。
mkdir slack-notification
cd slack-notification
slack-notification
ディレクトリ配下に requirements.txt
を作成します。
requests
functions_framework
google-cloud-secret-manager
slack-notification
ディレクトリ配下に main.py
を作成します。
import base64
import json
import requests
import functions_framework
from google.cloud import secretmanager
import os
@functions_framework.cloud_event
def send_slack_chat_notification(cloud_event):
project_id = os.environ.get("PROJECT_ID") # Cloud Run functions デプロイ時に環境変数を定義
secret_id = "slack_bot_user_oauth_token" # シークレット ID
version_id = "latest" # 最新バージョンを使用
# Secret Managerからトークンを取得
client = secretmanager.SecretManagerServiceClient()
name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
response = client.access_secret_version(request={"name": name})
token = response.payload.data.decode("UTF-8")
# CloudEvent 形式のデータから必要な情報を取得
event_data = cloud_event.data
pubsub_message = base64.b64decode(event_data['message']['data']).decode('utf-8')
message_json = json.loads(pubsub_message)
finding = message_json['finding']
resource = message_json['resource']
text = (
f"category: {finding.get('category', '')}\n"
f"projectDisplayName: {resource.get('displayName', '')}\n"
f"findingClass: {finding.get('findingClass', '')}\n"
f"severity: {finding.get('severity', '')}"
)
# Slack API への POST
requests.post("https://slack.com/api/chat.postMessage", data={
"token": token,
"channel": "#scc-notification", # Slack チャンネル名
"text": text
})
main.py のポイント
- このあと Cloud Run functions デプロイ時に環境変数
PROJECT_ID
を設定します。プロジェクト ID は Cloud Run の環境変数から読み込みます。 - このあと Cloud Run functions デプロイ時に Secret Manager のシークレットをマウントします。マウントしたシークレットをクライアントライブラリで呼び出します。
- Slack API に渡す
data
の"channel":
は Slack チャンネル名を指定します。ここでは"#scc-notification"
としました。 - Eventarc から CloudEvent 形式で渡されたデータから必要な情報を抽出してテキスト化していますので、必要に応じてカスタマイズしてください。ここでは、
category
/projectDisplayName
/findingClass
/severity
の項目を取得しています。SCC から Cloud Pub/Sub に通知されるメッセージのフォーマットはこちらをご参照ください。
gcloud run deploy
コマンドで Cloud Run functions に関数をデプロイします。gcloud functions deploy --gen2
でのデプロイも可能ですが、Cloud Run に統合された Cloud Console での管理において不整合が発生する部分がありましたので gcloud run deploy
を利用しています。
先ほどの説明でも記載しましたが、環境変数として PROJECT_ID
とシークレットのマウント設定をしています。また、シークレットにアクセスするための権限を付与したサービスアカウントを指定しています。
gcloud run deploy slack-notification \
--source . \
--function send_slack_chat_notification \
--base-image python312 \
--region asia-northeast1 \
--no-allow-unauthenticated \
--set-env-vars PROJECT_ID=$PROJECT_ID \
--set-secrets 'mnt/secrets=slack_bot_user_oauth_token:latest' \
--service-account=slack-notification@$PROJECT_ID.iam.gserviceaccount.com
Eventarc 用サービスアカウントの作成
Eventarc を設定する前に、まずは Eventarc に紐づけるサービスアカウントの作成します。
gcloud iam service-accounts create scc-pubsub-trigger
イベントプロバイダ(この場合 Cloud Pub/Sub)からイベントを受信できるよう Eventarc イベント レシーバー(roles/eventarc.eventReceiver) の IAM ロールを付与します。
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:scc-pubsub-trigger@$PROJECT_ID.iam.gserviceaccount.com" \
--role=roles/eventarc.eventReceiver \
--condition=None
また、Cloud Run functions を起動できるよう Cloud Run 起動元(roles/run.invoker) の IAM ロールを付与します。
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:scc-pubsub-trigger@$PROJECT_ID.iam.gserviceaccount.com" \
--role=roles/run.invoker \
--condition=None
Eventarc トリガーの作成
Eventarc トリガーの作成
SCC からのメッセージが Cloud Pub/Sub にパブリッシュされたことをトリガーとし、Cloud Run functions にメッセージを配信する Eventarc トリガーを作成します。--destination-run-service
で作成した Cloud Run functions を選択、--event-filters
は Cloud Pub/Sub のイベントを指定、--transport-topic
で Pub/Sub トピックを指定します。先ほど作成したサービスアカウントも紐づけます。
gcloud eventarc triggers create scc-pubsub-trigger \
--location=asia-northeast1 \
--destination-run-service=slack-notification \
--destination-run-region=asia-northeast1 \
--event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
--service-account="scc-pubsub-trigger@$PROJECT_ID.iam.gserviceaccount.com" \
--transport-topic=projects/$PROJECT_ID/topics/scc-topic
テスト
実際に SCC の検出結果が Slack チャンネルに連携されるか試してみます。
Cloud Console の [セキュリティ] -> [検出結果] から、クエリを以下のようにします。
NOT mute="MUTED"
表示された検出結果を1つ選択してチェックボックスをクリックします。 [アクティブ状態を変更] -> [Inactive] を選択します。
Inactive にした検出結果のチェックボックスを再度クリックし、[アクティブ状態を変更] -> [Active] を選択します。
以下のように、リアルタイムで Slack チャンネルに検出結果が連携されました。
おわりに
Cloud Run functions が Cloud Run に統合されたこともあり、複数の公式ドキュメントを参照しながら構築しました。様々なリソースを組み合わせて構築することから、リソースをデプロイするユーザやサービスアカウントの権限実装で特に苦労しました。
ここに残した情報が皆様の役に立てれば幸いです。