GetFederationToken は AssumeRole や GetSessionToken と何が違うのか

sts:GetFederationToken が好きです。でも sts:AssumeRole はもっと好きです。

コンバンハ、AssumeRole 大好きおじさんです。

AssumeRole とは、IAM ユーザーや AWS サービスといったエンティティが IAM ロールを引き受けるアクションを指します。引き受けた後は「IAM ロールを引き受けたセッション」がプリンシパルとなりアクションを実行します。 IAM ロール自身ではなくそれを引き受けたセッションが実行元である、という部分にえもいわれぬ浪漫を感じますね。このようなプリンシパルはセッションプリンシパルと呼ばれることもあります。

そしてセッションプリンシパルと呼ばれるのは「IAM ロールを引き受けたセッション」だけではありません。GetFederationToken によるフェデレーテッドユーザーもセッションプリンシパルのひとつです。

そうなると、AssumeRole を極めるためには GetFederationToken の履修も必要ということになってきます。そんなわけで今回は GetFederation と仲良くなってみます。名前が似ているので GetSessionToken もついでに調べてみます。

STS による一時的なセキュリティ認証情報の取得

GetFederationToken、AssumeRole、GetSessionToken に共通するのは、いずれも一時的なセキュリティ認証情報を取得するためのアクションであるという点です。

一時的でない認証情報は IAM ユーザーで発行するアクセスキー/シークレットアクセスキーのことを指し、そちらは永続的・あるいは長期的な認証情報と呼ばれます。

一時的なセキュリティ認証情報は AWS Security Token Service (AWS STS) に対してリクエストすることで生成され、アクセスキー/シークレットアクセスキーに加えてセッショントークンがセットになっています。

3 点セットを環境変数や AWS CLI プロファイルにセットすることで、許可された期限の中でアクションを実行できるようになります。

GetFederationToken と AssumeRole は何が違うか

それぞれのアクションの概要を掴んでおきましょう。

GetFederationToken のイメージ

GetFederationToken のイメージは以下です。

IAM ユーザー、もしくは root ユーザーが GetFederationToken を実行できます。(root ユーザーによるフェデレーションは非推奨のため以降は IAM ユーザー前提で記載します。)

GetFederationToken 実行時に任意のフェデレーテッドユーザー名を指定します。また、あわせてセッションポリシー(そのフェデレーテッドユーザーセッションでのみ有効なポリシー)を指定するのもほぼ必須です。

そうして生成されたフェデレーテッドユーザーは以下の両方で許可されたアクションのみ実行できます。(一部例外あり)

  • GetFederationToken を実行した IAM ユーザーのアイデンティティベースポリシー
  • セッションポリシー

平たく言えば「自身が持つ権限の範囲内で任意の権限を持つフェデレーテッドユーザーを生成する」のが GetFederationToken です。

AWS で「フェデレーテッドユーザー」と言えば外部の Idp から ID フェデレーションされる文脈においても使用されますが、GetFederationToken によるフェデレーションユーザーとは切り離して考えてください。GetFederationToken を使用するケースは以下の「カスタム ID ブローカーアプリケーションを作成するユーザーのフェデレーション」部が該当します。

AssumeRole のイメージ

続いて AssumeRole のイメージです。

IAM ロールはお面のようなもので、AssumeRole とはお面を被るようなもの、という表現を推しています。お面を被れる主体はさまざまいますが、ここでは IAM ユーザーが被るケースを考えます。

フェデレーテッドユーザーが持つ権限のベースとなるのは実行元の IAM ユーザーのアイデンティティベースポリシーでしたが、AssumeRole で生成されたセッションが持つ権限のベースは「IAM ロールのアイデンティティベースポリシー」です。

IAM ロールには信頼ポリシーというパラメータがあり、AssumeRole できるかどうかの評価に信頼ポリシーも加わる、というのが一つのポイントです。

GetFederationToken と AssumeRole の比較

両者を比較してみます。

細かい違いはもちろんたくさんありますが、ここでは以下を取り上げます。

  • ベースとなる権限
  • セッションポリシー無しの場合の挙動
  • 最大セッション時間

ベースとなる権限

両者ともセッションプリンシパルを生成するアクションであることは変わりありませんが、GetFederationToken では実行主体の IAM ユーザーがベースとなるのに対し、AssumeRole は任意の IAM ロールをベースにできます。

対象の IAM ロールは実行元の IAM ユーザーが所属する AWS アカウントと別のアカウントに存在するものも指定できますので、より柔軟性に富んでいると言えます。

セッションポリシー無しの場合の挙動

先述のイメージでは AssumeRole におけるセッションポリシーの指定を省略しましたが、AssumeRole でも指定は可能です。

セッションポリシーは権限を絞るために機能するものであり、ベースとなるアイデンティティベースポリシーと組み合わせて評価されます。「アイデンティティベースポリシーで許可が無いがセッションポリシーでは許可がある」という場合、セッションポリシー単独での許可は与えられないため、結果は暗黙的な拒否となります。

ここでセッションポリシーが設定されていない場合、それぞれのセッションプリンシパルでの挙動は以下の通り異なります。

  • フェデレーテッドユーザーの場合:暗黙的な拒否となる
  • IAM ロールを引き受けたセッションの場合:評価論理に影響を与えない

GetFederationToken の場合はセッションポリシーを設定した上でそこで許可を与えるように考慮しないと最終評価が許可になりません。(とは言えリソースベースポリシー側で許可されていれば許可となる、という例外もあります。ややこしいですね。)

最大セッション時間

GetFederationToken の場合は最大 36 時間、AssumeRole の場合は最大 12時間がセッションの有効期限となります。

以下のように一時的なコンソールアクセス用に URL を払い出すケースにおいては、長い方が嬉しい場合が多いでしょう。

GetFederationToken と GetSessionToken は何が違うか

続いて GetSessionToken が何者かを見ていきます。

GetSessionToken のイメージ

GetSessionToken のイメージは以下です。

GetSessionToken も root ユーザーが呼び出すことは可能ですが、例に漏れず IAM ユーザーが実行する前提で扱います。

GetFederationToken や AssumeRole と異なり、 GetSessionToken ではセッションプリンシパルは生成されません。生成されたセッションにおいても、実行元は変わらず(GetSessionToken を呼び出した) IAM ユーザーです。

セッションが持つ権限は IAM ユーザーのそれと同一です。

GetFederationToken と GetSessionToken の比較

ふたつを比較してみます。トピックは以下です。

  • MFA サポートの有無
  • セッションポリシーサポートの有無
  • コンソールへの SSO の可否

MFA サポートの有無

GetSessionToken では実行時に MFA トークンを指定できます。IAM ユーザーで AWS CLI を実行する際に MFA の利用が必要な場合、GetSessionToken でセッションを生成してから実行することになります。

GetFederationToken では MFA はサポートされていないため、MFA を必須とするような IAM ポリシーが設定されている場合には使用できません。

セッションポリシーサポートの有無

GetFederationToken ではセッションポリシーを指定することで権限をコントロールできます。上限は「 GetFederationToken を呼び出した IAM ユーザーの権限」であるものの、セッションポリシーでより狭い範囲の権限を持たせるようにできます。

GetSessionToken ではセッションポリシーがサポートされていないため、セッションは呼び出し元の IAM ユーザーと同じ権限を持つことになります。

コンソールへの SSO の可否

GetFederationToken により生成されたフェデレーテッドユーザーはフェデレーションエンドポイントを使用してコンソールにシングルサインオンが可能です。

対して GetSessionToken のセッションは SSO がサポートされていません。両者の使い分けがなんとなく見えてきました。

その他の各種比較は以下を参考にしてください。

全部 AWS CLI で試してみる

理論は大体分かったので実際にコマンドを叩いてみましょう。

実行元はいずれも以下の IAM ユーザーで、AdministratorAccess がアタッチされています。

% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH733ZAKODZMW",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/chiba-cli"
}

いずれのアクションもアクセスキー ID 、シークレットアクセスキー、セッショントークンが払い出されるので、それを環境変数にセットして試してみます。

GetFederationToken

リファレンスは以下です。

用意されているオプションは以下の通りです。

  get-federation-token
--name <value>
[--policy <value>]
[--policy-arns <value>]
[--duration-seconds <value>]
[--tags <value>]
[--cli-input-json | --cli-input-yaml]
[--generate-cli-skeleton <value>]

必須のオプション--nametestを指定して実行、各種値を環境変数にセットしてみます。

OUTPUT=$(aws sts get-federation-token --name test)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

実行元がフェデレーテッドユーザーtestになったことを確認できました。

% aws sts get-caller-identity
{
    "UserId": "000000000000:test",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:federated-user/test"
}

適当なコマンドを叩いてみますが必要な権限を持っていないことが分かります。これはセッションポリシーを指定していないためです。

% aws ec2 describe-instances

An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.

--policy-arnsオプションで既存の管理ポリシーの ARN を指定し再度施行します。独自のポリシーをインラインポリシーとしてセッションポリシーに指定したい(ややこしい)場合は--policyオプションを使用します。

OUTPUT=$(aws sts get-federation-token --name test --policy-arns arn=arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

セッションポリシーとして許可されている範囲であれば、フェデレーテッドユーザーでもアクションを実行できました。

% aws sts get-caller-identity
{
    "UserId": "000000000000:test",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:federated-user/test"
}
% aws ec2 describe-instances
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
     .....

AssumeRole

リファレンスは以下です。

使用可能なオプションは以下です。

  assume-role
--role-arn <value>
--role-session-name <value>
[--policy-arns <value>]
[--policy <value>]
[--duration-seconds <value>]
[--tags <value>]
[--transitive-tag-keys <value>]
[--external-id <value>]
[--serial-number <value>]
[--token-code <value>]
[--source-identity <value>]
[--cli-input-json | --cli-input-yaml]
[--generate-cli-skeleton <value>]

同じアカウントにある IAM ロールtestを指定し、セッション名をtestsessionとして引き受けてみます。

% OUTPUT=$(aws sts assume-role --role-arn arn:aws:iam::000000000000:role/test --role-session-name testsession)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

実行元がロールを引き受けたセッションに変わりました。

% aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH73U2LGAXTZQ:testsession",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:assumed-role/test/testsession"
}

IAM ロールtestには予めAmazonEC2ReadOnlyAccessをアタッチしています。セッションポリシーは指定しませんでしたが、問題なくコマンドが実行できました。

% aws ec2 describe-instances
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
     ...

GetSessionToken

リファレンスは以下です。

用意されているオプションは以下の通り。

  get-session-token
[--duration-seconds <value>]
[--serial-number <value>]
[--token-code <value>]
[--cli-input-json | --cli-input-yaml]
[--generate-cli-skeleton <value>]

特にオプションは指定せず実行してみます。

% OUTPUT=$(aws sts get-session-token)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

実行元は元の IAM ユーザーと変わらないので、セッションが有効なのかどうかが判別しづらいですね。

% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH733ZAKODZMW",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/chiba-cli"
}

セッションの状態でさらに GetSessionToken を呼び出そうとするとエラーが出るため、きちんとセッションクレデンシャルを使用していることが分かります。

% aws sts get-session-token

An error occurred (AccessDenied) when calling the GetSessionToken operation: Cannot call GetSessionToken with session credentials

Cannot call GetSessionToken with session credentials

このようにセッションが受ける制限はいくつかあるため、以下から確認してください。

終わりに

GetFederationToken について深掘りしてみました。

GetFederationToken は AssumeRole と同じくセッションプリンシパルを生成できる浪漫あふれるアクションであることが分かりました。とは言え AssumeRole と違って「ある程度の権限を持った IAM ユーザーの永続的な認証情報」を用意する必要がある点が要注意です。

例えばオンプレのアプリケーションに IAM ユーザーの永続的な認証情報を持たせて一時的な認証情報を生成する場合、AssumeRole であれば IAM ユーザーはsts:AsuumeRoleだけができれば事足ります。その後の操作に必要な権限は IAM ロール側に持たせればいいので、IAM ユーザーの権限は必要最小限で済みます。一方 GetFederationToken の場合はフェデレーテッドユーザーが必要となる権限も IAM ユーザーに持たせる必要があります。アクセスキーが漏洩するリスクなどを考えると、AssumeRole パターンを採用する方がよいでしょう。

また、GetFederationToken と名前が似ている GetSessionToken は全く別のものであることも分かりました。 GetSessionToken を利用する主なケースとしては AWS CLI で MFA を利用したいシチュエーションが多いかと思いますが、これも AssumeRole で賄えます。AssumeRole 時に MFA トークンを指定すれば、IAM ロールを引き受けたセッションでは MFA が有効なものとして扱われます。同じく IAM ユーザーの権限を最小にする、という観点では AssumeRole を採用した方が楽ちんです。

結局 AssumeRole 最高だな、という答えに辿り着きました。AssumeRole はいいぞ。

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

参考