別アカウントの EC2 インスタンスにクレデンシャルをセットしてクロスアカウントでないアクセスをしてみた

実行元の EC2 インスタンスと実行先の S3 バケットが別のアカウントの場合、それはクロスアカウントと呼べるのか?という話です。

コンバンハ、千葉(幸)です。

突然ですが問題です。

以下のような構成で EC2 インスタンスから S3 にアクセスを行うとします。EC2 インスタンスと S3 バケットは別々のアカウントに属しています。

S3 バケットはパブリックアクセスを許可しておらず、何らか認証情報を使用してアクセスが必要な状態です。

この時、この構成を「クロスアカウントでのアクセス」と呼ぶでしょうか?また、その理由も合わせて考えてみてください。

答えはちょっと後に出てきます。クロスアカウントって何だっけ……というのを考えてみるのが本エントリの趣旨です。

AWS のクロスアカウントアクセスとは何か

AWS におけるクロスアカウントアクセスとは何を指すのかを考えます。明確に定義された記述はありませんが、アクションの実行元のプリンシパル(エンティティ)実行先の AWS リソースが別のアカウントに属している場合のアクセスを指すと考えればよいでしょう。

以下のような構成はわかりやすくクロスアカウントアクセスです。アクションが成功するためにはプリンシパルと AWS リソース、双方のパーミッションで許可が与えられている必要があります。

cross_account_access_direct

一方で、一度対向のアカウントにある IAM ロールを引き受けてからリソースにアクセスする、という一連の流れをクロスアカウントアクセスと呼ぶこともあります。

cross_account_access_switch_role

ここでのプロセスを分解すると以下のようになります。

  1. 別アカウントにある IAM ロールを引き受ける(クロスアカウントアクセス)
  2. IAM ロールを引き受けたセッションでリソースにアクセスする(同一アカウントアクセス)

マネジメントコンソールでスイッチロールと呼ばれる仕組みを使用しているのであれば、こちらのパターンのクロスアカウントアクセスを行なっていることになります。IAM ロールはプリンシパルでありながら同時に実行先の AWS リソースでもある、ちょっと特殊な存在です。そんな所が好きです。

別アカウントの EC2 からのアクセスはクロスアカウントアクセスか

ここで冒頭の問題に戻ります。果たしてこの構成はクロスアカウントアクセスなのかどうか、というのが観点です。

結果からいうと「この情報だけでは判断できない(どちらもあり得る)」なのですが、「クロスアカウントアクセスである」と考えた方も多いのではないでしょうか。

先程のおさらいをすると、クロスアカウントアクセスとはアクションの実行元のプリンシパル(エンティティ)実行先の AWS リソースが別のアカウントに属している場合を指します。

「クロスアカウントアクセスである」と考えた方は、暗黙的に以下のような構成を思い浮かべたのではないかと思います。

cross_account_access

EC2 インスタンスに IAM ロール(インスタンスプロファイル)をアタッチし、その認証情報を使用するという構成です。永続的な認証方法をセットする必要がなく、一時的な認証情報を IAM ロール経由で取得することになるため、ベストプラクティスに則った構成です。

一方で、永続的なアクセスキーを使用する構成も可能です。

cross_account_access (1)

別アカウント(ここでは実行先の S3 バケットのアカウントと同一のアカウント)の IAM ユーザーが発行した永続的なアクセスキーを EC2 インスタンスにセットアップすれば、あくまで同一アカウントでのアクセスとしてアクションを実行できます。

今回はたまたま実行環境が別アカウントの EC2 インスタンスであったためややこしくなりますが、アクセスキーをローカルの操作端末にセットアップした場合を考えれば、「同一アカウントでのアクセスである」ということがわかりやすくなるかと思います。

別アカウントの EC2 インスタンスからクロスアカウントじゃないアクセスをやってみた

今回は以下の構成で行います。

cross_account_access_yattemita

  • AWS アカウント000000000000に存在する IAM ユーザーChibayukiでアクセスキーを発行
  • AWS アカウント999999999999に存在する EC2 インスタンスには元々 IAM ロールがアタッチされているが、上記のアクセスキーを AWS CLI プロファイルで設定して上書き
  • AWS アカウント000000000000に存在する S3 バケットchibayuki-testに同一アカウントアクセス

同一アカウントアクセスであるため、プリンシパルか AWS リソースどちらかで許可が与えられていればアクセスは成功します。今回は S3 バケットポリシー側でのみ許可を与えます。

IAM ユーザーの作成

IAM ユーザーChibayukiを作成し、IAM ポリシーは何もアタッチしていない状態にしました。アクセスキーを払い出しておきます。

IAM_User_Chibayuki

S3 バケットの作成

以下のバケットポリシーを持つ S3 バケットを作成しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:user/Chibayuki"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::chibayuki-test",
                "arn:aws:s3:::chibayuki-test/*"
            ]
        }
    ]
}

プリンシパルとして IAM ユーザーChibayukiを指定しているため、当該ユーザーに IAM ポリシーがアタッチされていなくともアクションが成功する状態です。

EC2 インスタンスでの作業

ここまで見てきた IAM ユーザーや S3 バケットとは別アカウントにある EC2 インスタンスで作業していきます。EC2 インスタンスには IAM ロールをアタッチしており、Systems Manager セッションマネージャーで接続ができる状態です。

接続し、アクセスキーのセットアップ前に AWS CLI の実行主体を確認してみます。

sh-4.2$ aws sts get-caller-identity
{
    "Account": "999999999999",
    "UserId": "AROAQ3BIIH735AQHQPIOW:i-047875da17caa9cc2",
    "Arn": "arn:aws:sts::999999999999:assumed-role/AmazonSSMRoleForInstancesQuickSetup/i-047875da17caa9cc2"
}

↑ EC2 インスタンスにアタッチされている IAM ロールを引き受けたセッションが実行主体であることがわかります。この状態のまま S3 バケットchibayuki-testにアクセスを試みるのであればクロスアカウントアクセスとなり、IAM ロールと S3 バケットの双方で許可が必要です。

今回は S3 バケット側で許可が与えられていないため、S3 へのアクセスは拒否されました。

sh-4.2$ aws s3 cp /tmp/test.txt s3://chibayuki-test
upload failed: ../../tmp/test.txt to s3://chibayuki-test/test.txt
An error occurred (AccessDenied) when calling the PutObjectoperation: Access Denied

ここで、AWS CLI のプロファイルをセットアップします。IAM ユーザーChibayukiのアクセスキー/シークレットアクセスキーを入力します。

sh-4.2$ aws configure
AWS Access Key ID [None]: AKIAXXXXXXXXXXXX
AWS Secret Access Key [None]: Ez+xYYYYYYYYYYYYYYYYYY
Default region name [None]: ap-northeast-1
Default output format [None]: json

AWS CLI プロファイルの方が優先度が高く解釈されるため、以降の AWS CLI コマンドは(特に指定しない限り)IAM ユーザーChibaykiとして実行されます。

sh-4.2$ aws sts get-caller-identity
{
    "Account": "000000000000",
    "UserId": "AIDARXXXXXXXXXXXXXXXX",
    "Arn": "arn:aws:iam::000000000000:user/Chibayuki"
}

この状態で S3 バケットchibayuki-testにアクセスを試みると、問題なく成功しました。

sh-4.2$ aws s3 cp /tmp/test.txt s3://chibayuki-test
upload: ../../tmp/test.txt to s3://chibayuki-test/test.txt
sh-4.2$ aws s3 ls chibayuki-test
2021-10-25 11:43:05          0 test.txt

クロスアカウントかどうかを判断する上で「実行環境がどこか」は関係なく、「どのアカウントの認証情報を使用するか」が意識すべき点であると分かりました。

EC2 インスタンスが存在するアカウント側では制御できないのか?

上記の試行では権限の制御は IAM ユーザーと S3 バケットでのみ行われます。EC2 インスタンスにアタッチされた IAM ロールの権限をどういじっても、「同一アカウントアクセス」を制御することはできません。

悪意を持ったユーザーが EC2 インスタンスに侵入し自身のアカウントのアクセスキーをそこでセットアップした場合、機密情報が外部の S3 バケットに Put されてしまうということも起こり得ます。

認証情報は切り替えができますので、「インスタンスプロファイルの権限を使用し S3 バケットからデータを Get し、アクセスキーを使用して外部の S3 バケットへ Put」ということもできてしまいます。

cross_account_access_incident

この事象を、EC2 インスタンスが存在するアカウント側で防ぐ手立てはないのでしょうか?

あります。VPC エンドポイントです。

S3 向けの VPC エンドポイントはエンドポイントポリシーを設定できます。そのポリシーの中でアクセス可能なリソースとして必要最小限の S3 バケットを指定していれば、外部の S3 バケットに Put されるという事態は防げます。

cross_account_access_vpc_endpoint

S3 向けの通信が VPC エンドポイントを向くように設定されていれば、そこでブロックできます。(基本的にはサブネットルートテーブルでの制御になります。)

仮に EC2 インスタンスに侵入されても、そのユーザーは VPC 周りの設定変更が可能な権限は有していないはずです。VPC エンドポイントが、プリンシパル / アクション実行先の AWS リソースとは別の防御レイヤとして働くことが期待できます。(EC2 インスタンスにアタッチされた IAM ロールに VPC の変更権限がついていた場合は突破されてしまいます。)

どこまでのセキュリティレベルを求めたいかを考え、各種ポリシーを設計するとよいでしょう。

終わりに

別アカウントの EC2 インスタンスからクロスアカウントでないアクセスを試みてみました。

それは EC2 インスタンス上で使用する認証情報として自アカウントのクレデンシャルを使用することで実現しました。クロスアカウントかどうかに実行環境は関係なく、プリンシパルと実行先 AWS リソースがアカウントを跨いでいるかどうかで考える、というのを覚えておいてください。

クロスアカウントってなんだっけ……となった時の参考になれば幸いです。

以上、 チバユキ (@batchicchi) がお送りしました。