【実務・中級編】 クロスアカウントアクセスにおける信頼関係の厳格化 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

【AWS・GCP】クロスアカウントアクセスの「穴」を塞ぐ:Confused Deputy問題を無力化する実践的ガードレール

現場で数多のインシデントを見てきたが、クラウドにおける「信頼関係」ほど、設計者の性善説が裏切られやすい場所はない。

今日話すのは、クロスアカウントアクセス、つまり「他人のアカウントから自分のリソースを触らせる」時のセキュリティだ。特にAWSにおいて、IAMロールの信頼ポリシー(Trust Policy)を適当に書いているなら、それは「鍵付きのドアを、鍵を差し込んだまま放置している」のと同じだ。

今回は、攻撃者が好む「Confused Deputy(混乱した代理人)」攻撃を防ぎ、堅牢な環境を作るための実務的なアプローチを共有する。

—

1. 誰もが陥る「Confused Deputy」の罠

クロスアカウントアクセスを実装する際、多くのエンジニアは以下のようなポリシーを書く。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": "sts:AssumeRole"
    }
  ]
}

一見、問題ないように見えるだろう。だが、これがなぜ危険なのか?

攻撃者は、あなたが信頼しているアカウント(123456789012)に既に侵入しているとする。そのアカウント内の権限を悪用し、あなたの環境のロールをAssumeRoleしようとする。もしあなたのロールが「外部ID(External ID)」というガードレールを持っていなければ、攻撃者はあなたの環境に潜り込み、データへ自由にアクセスできる。

これが「Confused Deputy問題」だ。「信頼できるアカウント」であっても、「そのアカウント内の誰が、どの目的でアクセスしているか」を検証しなければ、セキュリティは崩壊する。

—

2. 実装:External IDによる「合言葉」の強制

これを防ぐ唯一の解は、sts:ExternalId を信頼ポリシーに組み込むことだ。これは、第三者ベンダーにリソースを触らせる時や、別アカウントから操作する際に必須となる「共通の合言葉」だ。

修正後のセキュアな信頼ポリシー

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "MY-SECRET-UNIQUE-ID-2023" // ここに一意の文字列を設定
        }
      }
    }
  ]
}

この設定により、たとえ攻撃者が123456789012アカウントの操作権限を持っていたとしても、MY-SECRET-UNIQUE-ID-2023 という合言葉を知らなければ、あなたのロールを奪うことはできない。

—

3. Pythonによる安全なAssumeRoleの実装例

クライアント側(操作する側)の実装も重要だ。boto3 を使って安全に接続するサンプルコードを置いておく。

import boto3

def assume_role_with_security():
    sts_client = boto3.client('sts')
    
    # 信頼ポリシーで設定したExternalIdを必ず付与する
    assumed_role_object = sts_client.assume_role(
        RoleArn="arn:aws:iam::987654321098:role/TargetRole",
        RoleSessionName="AssumeRoleSession",
        ExternalId="MY-SECRET-UNIQUE-ID-2023" # ここを一致させる
    )
    
    # 一時的な認証情報を取得
    credentials = assumed_role_object['Credentials']
    
    # これ以降は取得したクレデンシャルで操作を行う
    return boto3.client(
        's3',
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )

—

4. 運用のためのチェックリスト

技術的な実装以上に、以下の運用ルールをチームで徹底してほしい。

1. 最小権限の原則(Least Privilege):
AssumeRole を許可するロールには、必要最小限のアクションしか許可しないこと。AdministratorAccess をクロスアカウントで渡すのは論外だ。
2. External IDの使い回し禁止:
IDは接続先ごとにユニークなものを生成し、パスワード管理ツールやシークレットマネージャーで保護すること。
3. CloudTrailの監視:
AssumeRole のログには requestParameters に externalId が含まれる。これをアラート監視対象に入れ、不正なIDでのアクセス試行を検知し、即座に遮断するフローを確立すること。

—

最後に:なぜ「泥臭い」確認が必要なのか

セキュリティは、ツールを導入して終わりではない。今回紹介した ExternalId も、運用側の意識が低ければ単なる「長い文字列」として放置される。

「この信頼関係は本当に必要か?」「この権限は広すぎないか?」と、コードをコミットする前に一度立ち止まる。この泥臭い自問自答こそが、最大の防御壁になる。

君たちの環境が、攻撃者にとって「割に合わないターゲット」になることを願っている。もし設定に不安があれば、いつでもコードを見せてくれ。一緒にレビューしよう。

コメント

タイトルとURLをコピーしました