Principalに指定したリソースがないとIAM Roleの作成に失敗する


概要

AWSでクロスアカウントでアクセスする際にAssumeRoleを行うための設定をします。
例えばアクセス元のアカウントをA、アクセス先のアカウントをBとすると、アカウントBのAWSアカウントにAssumeRoleを許可するためのIAM Roleを作成します。
今回、このアカウントBに該当するアカウントで、IAM Roleを作成しようとして失敗しました。オチとしてはタイトルの通りなのですが、なぜ失敗したのか原因を調べたところ、IAMってちゃんと考えられてるな、と感心したので記事にしました。

構成

今回はAWS Organizationsでアカウント管理をしている環境において、メンバーアカウントのLambdaからマスターアカウントのOrganizationsサービスが提供しているAPIを実行する場合を例として取り上げたいと思います。Organizationsの部分が例としては少し特殊かと思いますが、基本的にクロスアカウントでAPIを実行する場合は同じような構成になるかと思います。

試したこと

メンバーアカウント側のLambdaの実装は後回しにして、ひとまずマスターアカウント側でIAMRoleを作成するためにCFnで以下のテンプレートを用意しました。
パラメータのMemberAccountIdにはアクセス元のAWSアカウント番号が入ります。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  PrincipalOrgID:
    Type: 'String'
  MemberAccountId:
    Type: 'String'
Resources:
  IAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2008-10-17'
        Statement:
        - Effect: 'Allow'
          Principal:
            AWS: !Sub 'arn:aws:iam::${MemberAccountId}:role/ichi0124-hogehoge-fugafuga'
          Action: 'sts:AssumeRole'
      Policies:
      - PolicyDocument:
          Version: 2012-10-17'
          Statement:
            - Effect: 'Allow'
              Action:
                - 'organizations:DescribeAccount'
              Resource: !Sub 'arn:aws:organizations::${AWS::AccountId}:account/${PrincipalOrgID}/${MemberAccountId}'
        PolicyName: 'get-organizations'
      RoleName: !Sub 'organizational-information-for-${MemberAccountId}'

マスターアカウントでこのテンプレートからCFnスタックを作成し、これでメンバーアカウント側の実装に集中できるぞ、と思ったのですが、作成時にエラーが発生しました。エラー内容は以下の通りです。

エラーの原因

Invalid principal in policy とイベントにあるのでprincipalの定義を中心にCFnテンプレートを見返したのですが、書き方的にはおかしいところはなかったため、ドキュメントを読み返しました。

そして、こちらのドキュメントにたどり着きました。注目すべきは以下の記載です。

ロールのPrincipal要素に、特定のIAMロールを指し示すARNが含まれている場合、そのARNはポリシーを保存するときにロールの一意のプリンシパルIDに変換されます

今回エラーになった原因は、Principal要素に記載したIAMロールのARNをプリンシパルIDに変換しようとしたが、対象のIAMロールが存在しないために変換に失敗した、ということになります。明記はされていませんでしたが、Principal要素に記載したARNをプリンシパルIDに変換する際に実体が存在するかどうかの確認をIAMロールの作成時に行っているようです。この事実を知りませんでした。

なぜこのような仕様になっているか

これについては以下の記載に集約されていました。

ロールを削除して再作成することにより、誰かがそのユーザーの特権をエスカレートするリスクを緩和できます。通常、この ID はコンソールには表示されません。これは、信頼ポリシーが表示されるときに、ロールの ARN への逆変換が行われるためです。ただし、ロールを削除すると、関係が壊れます。ロールを再作成した場合でも、ポリシーは適用されません。これは、新しいロールは信頼ポリシーに保存されている ID と一致しない新しいプリンシパル ID を持っているためです。この場合、プリンシパル ID はコンソールに表示されます。これは、AWS が有効な ARN に ID をマッピングできなくなるためです。その結果、信頼ポリシーの Principal エレメントで参照されているロールを削除して再作成する場合は、ロールを編集して正しくなくなったプリンシパル ID を正しい ARN に置き換える必要があります。ポリシーを保存するときに、ARN は再びロールの新しいプリンシパル ID に変換されます。

概要をかいつまむと、単純にIAMロールのARNの文字列をチェックする場合だとロールを同名で再作成することで意図しない誰かに権限を奪われるリスクがある。しかし、ARNを独自のプリンシパルIDに変換することで、再作成時には同名のIAMロールであっても別のプリンシパルIDに変換されるため、上記の可能性を潰すことができる、ということのようです。

おわりに

今回初めて知ったことではありますが、この動作仕様はかなり重要なもので、なるほどと思いました。
色々と考えて作られており、知らないうちに守られているんだなということを実感しました。