実行できるユーザーvs実行者: OCI IAMでのユーザー資格証明の監査 (2026/02/24)

実行できるユーザーvs実行者: OCI IAMでのユーザー資格証明の監査 (2026/02/24)

https://www.ateam-oracle.com/who-can-vs-who-did-auditing-user-credentials-in-oci-iam

投稿者:Dinesh Maricherla | Principal Solution Engineer

前回の記事では、OCI IAM Identity Domainにおけるユーザー機能を一覧表示および分析する方法を検討し、「各ユーザーに何が許可されているか」という質問に答えました

例えば、ユーザーはAPIキー、認証トークン、SMTP認証情報、顧客秘密キーなどを作成できるでしょうか?この情報は有用ですが、あくまで 理論的なアクセス権限を示すものに過ぎません。多くの現実のシナリオでは、管理者や監査担当者は、より重要な追加の質問に答える必要があります。

このユーザーは資格情報を作成することが許可されていますが、実際に資格情報を作成したのでしょうか?

権限付与と認証情報の設定には重大な違いがあります。認証情報の実際の状態を検証しなければ、潜在的なアクセス状況しか把握できず、実際の使用状況は把握できません。

この投稿では、そのギャップを埋める小さなPythonユーティリティを紹介します。このユーティリティは、ユーザーを列挙し、有効化されている機能を検査し、対応する認証情報をアイデンティティドメインに照会し、結果をCSVファイルにエクスポートして簡単に確認できるようにします。

これは何の問題を解決するのか

テナント管理者とセキュリティ チームは、頻繁に次の操作を行う必要があります。

  • 機密性の高い資格情報を作成できるユーザーを特定する
  • 実際に資格情報が設定されているユーザーと区別する
  • 未使用の機能と資格情報の拡散を検出する
  • 具体的な証拠で監査とアクセスレビューをサポートする

ケイパビリティフラグだけに頼るのは can_use_* 不十分です。効果的なガバナンスには、権限 と 実際に設定されたオブジェクトを関連付ける必要があります。このスクリプトは、その関連付けを実行します。

アプローチ

スクリプトはシンプルで透過的なワークフローに従います。

  1. SCIM機能拡張を含む、アイデンティティドメインのすべてのユーザーを一覧表示します
  2. 各ユーザーについて、有効になっている資格情報関連の機能を抽出します。
  3. 有効になっている機能ごとに次の操作を行います。
    • list_* Identity Domains クライアントで対応する API を呼び出します 。
    • ユーザー別に結果をフィルタリングします。
    • 資格情報の合計数のみを取得します。
  4. すべての結果を CSV ファイルに書き込みます。

スクリプトを実行するプリンシパルが特定の資格情報の種類を一覧表示する権限を持っていない場合、スクリプトは UNAUTHORIZED それを黙ってスキップするのではなく、明示的に記録します。これにより、認可ギャップを可視化できます。

サンプル Python スクリプト

注: このサンプルスクリプトは、教育および説明目的のみに提供されています。お客様のテナントで使用する前に、非本番環境またはサンドボックス環境で十分にテストしてください。実際の使用に適応させる際には、適切なエラー処理、ログ記録、およびアクセス制御が確実に実施されていることを確認してください。


クリップボードにコピーされました
エラー: コピーできませんでした
クリップボードにコピーされました
エラー: コピーできませんでした
import argparse
import csv
import logging
import oci
from oci.exceptions import ServiceError

CONFIG_PATH = "~/.oci/config"
PROFILE_NAME = "DEFAULT"
SCIM_COUNT = 1

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

def get_clients(domain_url):
    config = oci.config.from_file(CONFIG_PATH, PROFILE_NAME)
    idc = oci.identity_domains.IdentityDomainsClient(config, domain_url)
    return idc

USER_ATTRS = (
    "id,userName,"
    "urn:ietf:params:scim:schemas:oracle:idcs:extension:capabilities:User"
)

def iter_all_users(idc):
    pages = oci.pagination.list_call_get_all_results_generator(
        idc.list_users,
        "response",
        attributes=USER_ATTRS,
        count=200
    )
    for resp in pages:
        for u in getattr(resp.data, "resources", []) or []:
            yield u

def scim_count(idc, method_name, user_id):
    try:
        method = getattr(idc, method_name)
    except AttributeError:
        return None, f"NO_METHOD:{method_name}"
    try:
        r = method(filter=f'user.value eq "{user_id}"', count=SCIM_COUNT, attributes="id")
        total = getattr(r.data, "total_results", None)
        if total is None:
            total = getattr(r.data, "totalResults", None)
        return int(total or 0), None
    except ServiceError as e:
        if e.status in (401, 403):
            return None, "UNAUTHORIZED"
        return None, f"ERROR:{e.status}"

def get_caps(user):
    caps = getattr(user, "urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user", None)
    def b(name): return bool(getattr(caps, name, False)) if caps else False
    return {
        "api_keys": b("can_use_api_keys"),
        "auth_tokens": b("can_use_auth_tokens"),
        "customer_secret_keys": b("can_use_customer_secret_keys"),
        "db_credentials": b("can_use_db_credentials"),
        "oauth2_client_credentials": b("can_use_o_auth2_client_credentials"),
        "smtp_credentials": b("can_use_smtp_credentials"),
    }

LIST_METHODS = {
    "api_keys": "list_api_keys",
    "auth_tokens": "list_auth_tokens",
    "customer_secret_keys": "list_customer_secret_keys",
    "db_credentials": "list_user_db_credentials",
    "oauth2_client_credentials": "list_o_auth2_client_credentials",
    "smtp_credentials": "list_smtp_credentials",
}

def audit(domain_url, write_csv=True, csv_path="user_capabilities_audit.csv"):
    idc = get_clients(domain_url)
    rows = []
    saw_unauth = set()
    for user in iter_all_users(idc):
        caps = get_caps(user)
        result = {
            #"user_id": user.id,
            "user_name": getattr(user, "user_name", "") or getattr(user, "userName", ""),
            "can_use_api_keys": caps["api_keys"],
            "can_use_auth_tokens": caps["auth_tokens"],
            "can_use_customer_secret_keys": caps["customer_secret_keys"],
            "can_use_db_credentials": caps["db_credentials"],
            "can_use_oauth2_client_credentials": caps["oauth2_client_credentials"],
            "can_use_smtp_credentials": caps["smtp_credentials"],
            "api_keys_count": 0,
            "auth_tokens_count": 0,
            "customer_secret_keys_count": 0,
            "db_credentials_count": 0,
            "oauth2_client_credentials_count": 0,
            "smtp_credentials_count": 0,
            "api_keys_status": "",
            "auth_tokens_status": "",
            "customer_secret_keys_status": "",
            "db_credentials_status": "",
            "oauth2_client_credentials_status": "",
            "smtp_credentials_status": "",
        }
        for key, enabled in caps.items():
            method_name = LIST_METHODS[key]
            col = f"{key}_count"
            status_col = f"{key}_status"
            cnt, err = scim_count(idc, method_name, user.id) if enabled else (0, None)
            if err == "UNAUTHORIZED":
                result[col] = "UNAUTHORIZED"
                result[status_col] = "UNAUTHORIZED"
                saw_unauth.add(key)
            elif err:
                result[col] = err
                result[status_col] = err
            else:
                result[col] = cnt
                if enabled and cnt > 0:
                    result[status_col] = "ENABLED & CONFIGURED"
                elif enabled and cnt == 0:
                    result[status_col] = "ENABLED, None CONFIGURED"
                elif (not enabled) and cnt > 0:
                    result[status_col] = "Check user profile, Feature is DISABLED "
                else:
                    result[status_col] = "DISABLED"
        rows.append(result)
    if write_csv and rows:
        fieldnames = [
            "user_name",
            "can_use_api_keys",
            "can_use_auth_tokens",
            "can_use_customer_secret_keys",
            "can_use_db_credentials",
            "can_use_oauth2_client_credentials",
            "can_use_smtp_credentials",
            "api_keys_count",
            "auth_tokens_count",
            "customer_secret_keys_count",
            "db_credentials_count",
            "oauth2_client_credentials_count",
            "smtp_credentials_count",
            "api_keys_status",
            "auth_tokens_status",
            "customer_secret_keys_status",
            "db_credentials_status",
            "oauth2_client_credentials_status",
            "smtp_credentials_status",
        ]
        with open(csv_path, "w", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=fieldnames)
            w.writeheader()
            w.writerows(rows)
        logging.info("Wrote %d users to %s", len(rows), csv_path)
    if saw_unauth:
        logging.error("UNAUTHORIZED for: %s", ", ".join(sorted(saw_unauth)))

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--domain-url", required=True, help="Identity Domain base URL (e.g., https://idcs-xxxx.identity.oraclecloud.com)")
    p.add_argument("--csv", default="user_capabilities_audit.csv")
    args = p.parse_args()
    audit(args.domain_url, write_csv=True, csv_path=args.csv)

スクリプトの実行方法


クリップボードにコピーされました
エラー: コピーできませんでした
クリップボードにコピーされました
エラー: コピーできませんでした
python audit_capabilities.py --domain-url https://idcs-xxxxx.identity.oraclecloud.com --csv capabilities_audit.csv

これにより、ユーザーごとに1行のCSVファイルが生成されます。
以下はサンプル出力の一部です。

出力の解釈
CSV の各行で私たちにとって重要なのは次の点です。

  1. 機能列 ( can_use_*) : これらの列は、IAM がユーザーに許可する操作 (API キー、認証トークン、SMTP 認証情報の作成が許可されているかどうかなど) を反映します。
  2. カウント列 ( *_count) : これらの列には、ユーザーに対して実際に設定されている各タイプの資格情報の数が表示されます。
  3. ステータス列 ( *_status) : これらは、人間が判読できる簡単な解釈を提供します。
    • 有効かつ構成済み – 権限が付与され、資格情報が存在する
    • 有効、なし 構成済み – 機能は付与されているが使用されていない
    • 許可されていません – スクリプトは現在のプリンシパルを使用してこの資格情報の種類を一覧表示できませんでした

これにより何が可能になるか

このレポートを使用すると、次のことが簡単に行えます。

  • 権限を付与されたものの、一度も使用していないユーザーを特定する
  • 現在機密性の高い資格情報を保持しているユーザーを特定する
  • 環境でリスト可能な認証情報の種類を確認する
  • 具体的なデータで監査とアクセスレビューをサポートする
  • ターゲットのクリーンアップと最小権限の取り組み

これにより、可視性は許可ベースの想定から使用量ベースの現実へと移行します。

まとめ:

誰が 認証情報を作成できるかを理解することは 、話の半分に過ぎません。誰が認証情報を作成したかを理解することで、  は完了します

このスクリプトは、以前の機能リストのアプローチと組み合わせて、次の両方を提供します。

  • 構成の可視性 (誰が何を実行できるか)
  • 使用状況の可視性 (実際に誰が行ったか)

この組み合わせは、OCI 環境における監査、セキュリティ レビュー、資格情報ガバナンスに不可欠な場合が多くあります。

このスクリプトは簡単に拡張できます。例えば、CSVではなくJSONを出力したり、SMTP認証情報など特定の認証情報タイプに焦点を絞ったりできます。基本的な形式でも、OCI IAMアイデンティティドメイン全体の認証情報の使用状況を明確かつ実用的なビューで確認できます。

コメント

このブログの人気の投稿

Oracle Database 19cサポート・タイムラインの重要な更新 (2024/11/20)

ミリ秒の問題: BCCグループとOCIが市場データ・パフォーマンスを再定義する方法(AWSに対するベンチマークを使用) (2025/11/13)

OCIサービスを利用したWebサイトの作成 その4~Identity Cloud Serviceでサイトの一部を保護 (2021/12/30)