🌊

Activity Analyzerを利用してサービスアカウントごとの認証時間を一覧化する

2024/02/27に公開

はじめに

いわゆるやってみた系の記事になります。
2024年2月現在、本機能はpreview版ですので、将来的に改善されたり、機能が変わる可能性がありますので、ご注意ください。
https://cloud.google.com/policy-intelligence/docs/service-account-usage-tools?hl=ja#sa-authn

現在、GCP上のデータ基盤の運用を行っていますが、システムからGCP上のリソースを参照する際にサービスアカウント(以降、SAと略します)を共有しています。
Workload identityを利用してsecret key自体の流出を防ぐアプローチもありますが、以前からGCPを利用しているシステムにはsecret keyが内包されたSAを共有する、というのが通常フローでした。

認証のやり取りに関しては、以下の記事が分かりやすいと思います。
Workload Identity Federationを図で理解する

SA管理・運用上の問題点

  • 一つのアカウントに対して複数の鍵が紐づくケースもある
  • 引き継ぎ時の問題でもあるが、利用目的が分からないSAが散見される
  • サービス停止されたSAが有効なまま残っている
    というセキュリティ上、あまり好ましくない問題が生じていました。

Cloud loggingを利用してSAの利用状況を調査しても良いのですが、少し調査コストが掛かる印象でした。
そこで良さそうなサービスだなと思い検証したのが、Activity Analyzerになります。

Activity Analyzerを利用して実現したいこと・ゴール

  • 鍵が有効なSAで直近利用がないものは無効化・削除したい
  • 直近、利用されているSAの洗い出し
    • 上記結果を利用して利用目的のヒアリングに繋げたい

GUI上でも特定SAの利用状況を確認できるみたいですが、社内では複数のGCPプロジェクトがあり、その配下でいくつもSAが発行されていました。
そこでシェルでループさせて、SAごとの認証時間を一覧化したいと思います。

事前準備・注意点

アクティビティ分析閲覧者の付与

以下でも説明されている通り、調査したいGCPのプロジェクトに対してアクティビティ分析閲覧者の権限が必要です。
今回は検証目的だったこと・権限付与の手間を省くため、チームの管理者アカウントを利用し、Cloud shell上で調査を行ないます。
https://cloud.google.com/policy-intelligence/docs/activity-analyzer-service-account-authentication?hl=ja#required-permissions

APIの有効化

本機能を利用するために、Policy Analyzer APIの有効化が必要です。
仮に有効にしないままコマンド実行すると、以下のように有効化するか確認されるので、そこで変更してもOKです。

APIを有効化せずにコマンド実行し、有効化しない、とした際のレスポンス
管理者アカウント@cloudshell:~/sugato$ gcloud policy-intelligence query-activity --activity-type=serviceAccountKeyLastAuthentication \
        --project="プロジェクトA" \
        --limit=5
API [policyanalyzer.googleapis.com] not enabled on project [プロジェクトA]. Would you like to enable and retry (this will take a few minutes)? (y/N)?  N

ERROR: (gcloud.policy-intelligence.query-activity) PERMISSION_DENIED: Policy Analyzer API has not been used in project プロジェクトA before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/policyanalyzer.googleapis.com/overview?project=プロジェクトA then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
- '@type': type.googleapis.com/google.rpc.Help
  links:
  - description: Google developers console API activation
    url: https://console.developers.google.com/apis/api/policyanalyzer.googleapis.com/overview?project=プロジェクトA
- '@type': type.googleapis.com/google.rpc.ErrorInfo
  domain: googleapis.com
  metadata:
    consumer: projects/プロジェクトA
    service: policyanalyzer.googleapis.com
  reason: SERVICE_DISABLED

ACTIVITY_TYPEの使い分け

以下の記述がありますが、本記事ではserviceAccountLastAuthenticationを利用します。

ACTIVITY_TYPE: 一覧表示するアクティビティのタイプ。サービス アカウントの直近の使用状況を一覧表示するには、serviceAccountLastAuthentication を使用します。サービス アカウント キーの最新の使用時間を一覧表示するには、serviceAccountKeyLastAuthentication を使用します。

SAごとの最終認証時間の洗い出し手順

CSVファイルに結果を出力

shell内部に記述しているサービスアカウントのリソース名というのは、以下のパターンで渡ります。
コード側で処理しても良かったですが、スプレッドシート上で整形してもOKと思い割愛しました。

//iam.googleapis.com/projects/GCPのプロジェクトID/serviceAccounts/SAのアドレス

検証用サンプルコード
#!/bin/bash

# 出力ファイル名を定義
OUTPUT_FILE="service_account_activity.csv"

# ヘッダーを出力ファイルに追加
echo "プロジェクト名,プロジェクトID,サービスアカウントのリソース名,最終認証時間" > "$OUTPUT_FILE"

# 全てのプロジェクトIDを取得してループする場合
# PROJECT_IDS=$(gcloud projects list --format="value(projectId)")
# 調査対象を限定する場合、以下のようにプロジェクトIDをスペース区切りで渡してください
PROJECT_IDS=("プロジェクトA" "プロジェクトB")

for PROJECT_ID in "${PROJECT_IDS[@]}"; do
    # プロジェクト名を取得
    PROJECT_NAME=$(gcloud projects describe $PROJECT_ID --format="value(name)")
    # プロジェクトのサービスアカウントを取得してループ
    # policy-intelligence query-activityコマンドを実行してJSONで出力し、jqで処理
    gcloud policy-intelligence query-activity --activity-type=serviceAccountLastAuthentication \
        --project=$PROJECT_ID \
        --format="json" | jq -r --arg PROJECT_NAME "$PROJECT_NAME" --arg PROJECT_ID "$PROJECT_ID" '.[] | [$PROJECT_NAME, $PROJECT_ID, (.activity.serviceAccount.fullResourceName), .activity.lastAuthenticatedTime] | @csv' >> "$OUTPUT_FILE"
done

echo "実行終了。出力ファイル名: $OUTPUT_FILE"

スプレッドシート上での整形

上述コードを利用すると、カンマ区切りで値が出力できていると思います
そちらをスプレッドシートに貼り付け、データ → テキストを列に分割でカンマを選択すると、綺麗な表形式のデータに整形されます。

その後、サービスアカウントのリソース名からSAを出力した整形結果のイメージが下記になります。
SAの認証時間

APIの挙動に関する注意点・不明点

最終認証時間は分秒まで返ってこない

認証時間が08:00:00Zとなっていますが、これは仕様みたいです。
(ドキュメント上はT07:00:00Zとなってますが、タイムゾーンを加味しても上記で返ってきた理由は不明)

lastAuthenticatedTime: 最新の認証イベントが発生した日付を表すタイムスタンプ。認証イベントの正確な時刻に関係なく、このタイムスタンプの時刻は常に T07:00:00Z です。

最終認証時間が結果整合の可能性がある

私が検証した日時は2024年2月17日だったのですが、認証時間は2月14日で返ってきました。
この中には、日時のバッチで利用されているSAもあったため、明らかに違和感がありました。

また1つのSAだけでなく複数のSAで、一番日付が新しいものが2月14日になっていました。
さらに、直近利用していない複数のアカウントでも古い日付で揃っており、Googleの方に内部仕様を質問中です。

observationPeriodの更新タイミングがよく分からない

サンプルコードには含めていませんが、事前の挙動確認時に感じた内容です。
公式には以下の記述があり確かめてみたところ、lastAuthenticatedTimeは1ヶ月以上前でも、observationPeriodは2月14日、というケースもありました。

結果に、直近の認証イベントが含まれない場合があります。分析中に使用された正確な期間を確認するには、observationPeriod を確認してください。結果には、この範囲外で発生した認証イベントは含まれません。

lastAuthenticatedTimemの挙動が分からなければ、代わりにこちらを使ったら行けるかもと思ったのですが..
こちらもちょっと内部仕様が分からず、Googleの方に質問中です。

おわりに

とはいえざっくりではあるものの、認証時間が直近かどうかで、継続利用されている・いない確認には利用できそうと感じました。
まだpreview機能ということですが、これから改善もされていくと思いますので、もし興味がある方は使ってみてください。
SAの棚卸しを行いたい方にとって、何かしら参考になれば幸いです!

Discussion