Google CloudのCloud Shellでアクセスキー情報なしで AWSのリソースを操作してみる
概要
AWSのアクセスキーを使用せずに、Google CloudからS3などのリソースにアクセスする必要があり、色々試した方法の一つとしてAssumeRoleWithWebIdentityを用いてGCEインスタンスからAWS CLIを実行する方法を最近考えました。
(※Cloud Shellで行なっていますが、ローカル環境でも可能です)
AssumeRoleWithWebIdentityとは
AssumeRoleWithWebIdentity
は、AWS Security Token Service (STS)のAPIで、Web IDプロバイダー(OIDCプロバイダー)を使用して認証されたユーザーに一時的なセキュリティ認証情報(アクセスキー、シークレットキー、セッショントークン)を発行します。
AWSリソースへのアクセスを許可するために使用されます。この仕組みを用いることで、Google CloudからAWSのリソース(S3など)を操作することができます。
(AWSのAssumeRoleを、AWS外からも使えるようにしたイメージの認証方法です。)
認証をアクセスキーの情報ではなくロール(AssumeRoleWithWebIdentity)で行うことができ、アクセスキーの発行を行わないのでキーの流出に怯える日々から解放されます。
認証フロー
ざっくりとした認証フローは以下となります。
- Web IDプロバイダー(Google Cloud)で認証を行いIDトークン(JWT)を取得
- AWS STSにリクエスト。取得したJWTを使用して、AWS STSのAssumeRoleWithWebIdentityAPIを呼び出し、IAMロールを引き受ける。AWS STSは、リクエストされたIAMロールの信頼ポリシーを検証し、条件が一致する場合に一時的な認証情報 (アクセスキー、シークレットキー、セッショントークン)を発行
- AWSリソースにアクセス。発行された一時的な認証情報を使用して、AWSリソースにアクセスできるようになります
やりかた
AWS CLIをアクセスキーなしで操作することが目標です。そのための手順としては以下となります。
※サービスアカウントを使用する方法です
- AWSでIAMロールを作成
- Cloud ShellにAWS CLIをインストール
- config情報を設定
- AWS(S3)を操作
それでは一つ一つやってみます。
1. AWSでIAMロールを作成
まずAWSでIAMロールを作成し、GoogleのOIDCプロバイダーを設定します。
手順
- AWSマネジメントコンソールにログインします。
[IAM > ロール]
に移動します。- 「ロールを作成」をクリックします。
- カスタム信頼ポリシーを選択します。
以下のポリシーを設定します。accounts.google.com:sub
は後述の方法で取得した値を設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "accounts.google.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"accounts.google.com:oaud": "Audience値",
"accounts.google.com:sub": "サービスアカウントの一意なID"
}
}
}
]
}
少し内容に触れます。
以下の記載は、このロールを引き受けることができるエンティティを指定します。Federatedフィールドでaccounts.google.com
を指定するとGoogleのIDトークンを使用するエンティティが対象となります。
そして、許可するアクションにsts:AssumeRoleWithWebIdentity
を指定することでWeb ID プロバイダー((この場合は Google)を使用してこのIAMロールを引き受けることができるようになります
"Principal": {
"Federated": "accounts.google.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
accounts.google.com:sub
に設定する値はCloud Shellでgcloud auth print-identity-token
でJWTを取得してデコードして確認します。
--impersonate-service-account
を用いてサービスアカウントの借用を行いJWTを発行しデコードすることで取得できます。
# サービスアカウントを借用してJWTを発行する
gcloud auth print-identity-token --impersonate-service-account=サービスアカウントのメールアドレス --audiences="Audience値"
レスポンスはBase64URL形式となっているのでデコードが必要となります。
JWT(のペイロード部分)をデコードするには、トークンを3つの部分に分割し2番目をBase64URLデコードします。レスポンスのJWTは以下の形式になっています。
#トークンの形式
<ヘッダー>.<ペイロード>.<署名>
今回欲しい値はペイロード
の部分にあるので、そこだけデコードします。トークン取得とデコードと整形をワンライナーで書くと以下です。
以下のコマンドを実行してsub
を取得して信頼ポリシーに入力します。
# デコード方法と出力例
gcloud auth print-identity-token --impersonate-service-account=サービスアカウント名 --audiences="Audience値" | cut -d '.' -f2 | tr '_-' '/+' | sed -E 's/={0,2}$/==/' | base64 -d | jq .
{
"aud": "Audience値",
"azp": "サービスアカウントの一意なID",
"exp": 1737630653,
"iat": 1737627053,
"iss": "https://accounts.google.com",
"sub": "サービスアカウントの一意なID"
}
設定できたら必要なポリシーをアタッチしてロールのARNを控えておきます。Audience値に関しては一意な値やURIなど任意の値を設定してください。
また、azp
が存在していることに注意してください。Google CloudのJWTクレームでazp
が存在していない場合はAWS STS側条件キーaccounts.google.com:aud
とマッピングされるのはaud
となりますが、Google CloudのJWTクレームでazp
が存在している場合にはoaud
クレームとSTS側でマッピングされるSTS条件キーはaccounts.google.com:oaud
となります。
azp(Authorized Party)
クレームは、トークンが特定のクライアント(アプリケーションやサービス)によって発行された場合に含まれるフィールドです。このクレームは、トークンを発行したクライアントIDを示します。
--impersonate-service-accoun
でトークンを発行すると、azp
クレームが作成されます。メタデータサーバからトークンを取得した場合は含まれません。
取得方法 | azp クレーム | 説明 | Audience(aud)に対応するAWS STS側条件キー |
---|---|---|---|
サービスアカウントを借用 (OAuth) | 含まれる | トークンを発行したクライアントID (サービスアカウント) を指す | accounts.google.com:oaud |
メタデータサーバを使用 | 含まれない | トークンはGoogle Cloudのメタデータサーバによって直接発行されます | accounts.google.com:aud |
IAMユーザで取得 | 含まれる | トークンを発行したクライアントIDを指す | accounts.google.com:oaud |
今回はサービスアカウントを借用しているので、azp
クレームが作成されます。よってGoogle CloudのJWTクレームのaud
に対応するSTS条件キーにはaccounts.google.com:oaud
を指定する必要があります。ご注意ください。
上記の理由により、(1.)に記載した信頼ポリシーの条件にはaccounts.google.com:oaud
を設定しています。
# STS条件キー
"accounts.google.com:oaud": "***",
つまりはメタデータサーバを使用してトークンを作成する場合はSTS条件キーにはaud
を指定する必要がある、ということでもあります。ご注意ください。
exp
、iat
に関しては以下の記事をご参照ください。
2. Cloud ShellにAWS CLIをインストール
Cloud Shellを開いてAWS CLIをインストールします。
homeディレクトリ配下以外はCloud Shellを終了すると(タブを閉じると)、リセットされてしまうのでhomeディレクトリ配下にインストールします。
リセットされても良いという方はパスの設定などは不要です。
まずはダウンロードして、homeディレクトリ配下にインストールします。
# AWS CLIをhomeディレクトリ配下にインストールする
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "$HOME/awscliv2.zip"
unzip $HOME/awscliv2.zip -d $HOME
$HOME/aws/install --install-dir $HOME/aws-cli --bin-dir $HOME/bin
~/.bashrc
ファイルを編集して$HOME/bin
をPATH に追加します。
# PATHの追加
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
.bashrc
を再読み込みします。
# .bashrc再読み込み
source ~/.bashrc
バージョン情報が出力されればインストール成功です。
# AWS CLIのバージョン確認
aws --version
3. configの設定
AWS CLIの設定ファイルを作成し、IAMロールを使用するように設定します。
- AWS CLIの設定ファイルを編集します。regionだけ入力します。configファイルを作成するため。
# AWS CLIのconfig作成
aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]:
- 作成したconfigを開いて以下の内容を追加します:
# AWS CLIのconfig設定
vi ~/.aws/config
[profile google-role]
role_arn = arn:aws:iam::<AWS_ACCOUNT_ID>:role/***
web_identity_token_file = /home/ユーザ名/token.json
region = ap-northeast-1
role_arn
: 手順1で作成したIAMロールのARN。web_identity_token_file
: JWTの保存先。出力先のパスは適宜変更してください。
JWTが保存されたファイルを作成します。
# JWTが保存されたファイル作成
gcloud auth print-identity-token --impersonate-service-account=サービスアカウントのメールアドレス --audiences="Audience値" > token.json
上記コマンドで指定したサービスアカウントを借用してそのサービスアカウントのJWTを取得します。
このJWTとAWS CLIのコンフィグに指定したARNをもとにAWSのIAMロールを引き受けることができます。
準備完了、あとはAWS CLIを実行するだけです。
4. S3を操作
AWS CLIを使用してS3バケットを操作します。
- AWS CLIでS3バケットのリストを取得します
aws s3 ls --profile google-role
- S3バケット内のオブジェクトを確認します
aws s3 ls s3://バケット名 --profile google-role
- 必要に応じて、ファイルのアップロードやダウンロードを行います
aws s3 cp local-file.txt s3://バケット名/ --profile google-role
問題なく操作できていれば、成功です。アクセスキーを発行することなくCloud ShellからAWSのリソースをCLIやboto3で操作することができます。
補足
web_identity_token_file
AWS CLIのconfig設定時に設定したweb_identity_token_file
に関してですが、この項目にファイルパスを設定するとAWS CLIはファイル内容をロードして、AssumeRoleWithWebIdentity
オペレーションに渡しAWS STSとの認証につながります。
# web_identity_token_file
web_identity_token_file = /home/ユーザ名/token.json
OAuth 2.0 アクセストークンまたは ID プロバイダによって提供される OpenID Connect ID トークンを含むファイルへのパスを指定します。AWS CLI はこのファイルをロードし、その内容を WebIdentityToken 引数として AssumeRoleWithWebIdentity オペレーションに渡します。
JWTの構造
以下に今回のJWTのクレームの内容を表形式で示しました。
クレーム名 | 値 | 説明 |
---|---|---|
aud |
Audience (受信者) | トークンが向けられたリソースやサービスを示す。この値は、トークンがどのリソースで使用されるべきかを指定。AWS STS条件キーではaccounts.google.com:oaud またはaccounts.google.com:aud にマッピング |
azp |
Authorized Party (認可されたクライアント) | トークンを発行したクライアント (サービスアカウントやサービス) を示す。このクレームは、トークンが特定のクライアントや環境によって発行された場合に含まれる。AWS STS条件キーでは accounts.google.com:oaud にマッピング |
exp |
Expiration Time (有効期限) | トークンの有効期限を示す。この値はUNIXタイムスタンプ形式で記録されており、この時刻を過ぎるとトークンは無効になる |
iat |
Issued At (発行時刻) | トークンが発行された時刻。この値もUNIXタイムスタンプ形式で記録されている |
iss |
Issuer (発行者) | トークンを発行した主体。今回の場合、トークンはGoogleの認証基盤 (https://accounts.google.com ) によって発行 |
sub |
Subject (主体) | トークンが関連付けられているエンティティ(通常はIAMユーザーやサービスアカウント)示す。この値はトークンの所有者を一意に識別する。AWS STS条件キーではaccounts.google.com:sub にマッピング |
JWTの有効期間
デフォルトでは1時間です。gcloudコマンドでは有効期間を変更することはできません。
REST API を使用していて、トークンの有効期間を延長できるようにシステムが構成されている場合は、有効期間がデフォルトより長いトークンを作成できます。Google Cloud CLI では、トークンの有効期間の設定はサポートされていません。
有効期間を変更したい場合は、REST APIやクライアントライブラリを用いる必要があります。また最大設定可能有効期間は12時間(43200秒)です。
流出時のリスク
JWTが流出した場合、有効期間内であれば不正に使用される可能性があります。JWTは自己完結型のトークンであり、トークン自体に認証情報や権限が含まれているため、流出したJWTが有効期限内であれば、攻撃者が使用してリソースにアクセスすることが可能です。ただし、今回の仕組みではAWSのロールを用いるので、AWSのconfig情報も流出しなければAWSリソースにはアクセスすることはできません。
今回のJWTには有効期間(exp
)が設定されています。この有効期間内であれば、流出したJWTは攻撃者によって使用される可能性があります。今回の仕組みだと有効期間は1時間です。変更するには前述の通りRESTやクライアントライブラリを使用する必要があります。
流出時のリスクに対しては、AWS側でポリシーでIP制限を設定したり、Google Cloud側でJWTの有効期間を短くするなど様々な方法があります。ワークロードを踏まえて検討する必要があります。
所感
今回の仕組みもトークン更新の仕組みが入っていません。なのでトークン有効期間(1時間)経過後は再度トークンを取得し直す必要がある(gcloud auth print-identity-token > token.json
を実行)ので注意が必要です。
Cloud ShellからアクセスキーなしでAWS CLIを使用できるので、ワークロードによっては有用かなと思います。Audience
やSubject
が流出しても、JWTの署名はGoogle Cloudの秘密鍵で生成されており、攻撃者がその秘密鍵を持っていない限り、トークンを偽造することはできません。サービスアカウントにアクセスできる権限がない限り発行することはできません。
この仕組み、ローカル環境でもCloud Shell同様に動作します(IP制限などは考慮していませんのでご注意ください。個々のワークロードによって動作が異なる可能性はあると思います)。
これ以外にもアクセスキーなしでAWSにGoogle Cloudからアクセスする方法はあるのでまた機会があればご紹介したいと思います。
それではまた。ナマステー
参考