Google CloudのCloud Shellでアクセスキー情報なしで AWSのリソースを操作してみる

Google CloudのCloud Shellでアクセスキー情報なしで AWSのリソースを操作してみる

Clock Icon2025.01.23

概要

AWSのアクセスキーを使用せずに、Google CloudからS3などのリソースにアクセスする必要があり、色々試した方法の一つとしてAssumeRoleWithWebIdentityを用いてGCEインスタンスからAWS CLIを実行する方法を最近考えました。
https://dev.classmethod.jp/articles/20250121-gce-assumerole-no-key/
しかしながらやはりGCEインスタンスを構築するのが若干負担に感じたためCloud Shellでできないものかと思い考え、結果としてできて良い方法だなと思ったので記事にしてみました。
(※Cloud Shellで行なっていますが、ローカル環境でも可能です)

AssumeRoleWithWebIdentityとは

AssumeRoleWithWebIdentity は、AWS Security Token Service (STS)のAPIで、Web IDプロバイダー(OIDCプロバイダー)を使用して認証されたユーザーに一時的なセキュリティ認証情報(アクセスキー、シークレットキー、セッショントークン)を発行します。
AWSリソースへのアクセスを許可するために使用されます。この仕組みを用いることで、Google CloudからAWSのリソース(S3など)を操作することができます。
(AWSのAssumeRoleを、AWS外からも使えるようにしたイメージの認証方法です。)
https://docs.aws.amazon.com/ja_jp/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html

認証をアクセスキーの情報ではなくロール(AssumeRoleWithWebIdentity)で行うことができ、アクセスキーの発行を行わないのでキーの流出に怯える日々から解放されます。

認証フロー

ざっくりとした認証フローは以下となります。

  1. Web IDプロバイダー(Google Cloud)で認証を行いIDトークン(JWT)を取得
  2. AWS STSにリクエスト。取得したJWTを使用して、AWS STSのAssumeRoleWithWebIdentityAPIを呼び出し、IAMロールを引き受ける。AWS STSは、リクエストされたIAMロールの信頼ポリシーを検証し、条件が一致する場合に一時的な認証情報 (アクセスキー、シークレットキー、セッショントークン)を発行
  3. AWSリソースにアクセス。発行された一時的な認証情報を使用して、AWSリソースにアクセスできるようになります

やりかた

AWS CLIをアクセスキーなしで操作することが目標です。そのための手順としては以下となります。
※サービスアカウントを使用する方法です

  1. AWSでIAMロールを作成
  2. Cloud ShellにAWS CLIをインストール
  3. config情報を設定
  4. AWS(S3)を操作

それでは一つ一つやってみます。

1. AWSでIAMロールを作成

まずAWSでIAMロールを作成し、GoogleのOIDCプロバイダーを設定します。
手順

  1. AWSマネジメントコンソールにログインします。
  2. [IAM > ロール]に移動します。
  3. 「ロールを作成」をクリックします。
  4. カスタム信頼ポリシーを選択します。
    以下のポリシーを設定します。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を取得してデコードして確認します。
https://cloud.google.com/sdk/gcloud/reference/auth/print-identity-token
--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となります。
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#:~:text=てください。-,Default,-Amazon Cognito

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を指定する必要がある、ということでもあります。ご注意ください。

expiatに関しては以下の記事をご参照ください。
https://dev.classmethod.jp/articles/20250121-gce-assumerole-no-key/

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ロールを使用するように設定します。

  1. 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]: 
  1. 作成した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バケットを操作します。

  1. AWS CLIでS3バケットのリストを取得します
aws s3 ls --profile google-role
  1. S3バケット内のオブジェクトを確認します
aws s3 ls s3://バケット名 --profile google-role
  1. 必要に応じて、ファイルのアップロードやダウンロードを行います
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 オペレーションに渡します。

https://docs.aws.amazon.com/ja_jp/cli/v1/userguide/cli-configure-role.html#cli-configure-role-oidc

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 では、トークンの有効期間の設定はサポートされていません。

https://cloud.google.com/iam/docs/create-short-lived-credentials-direct?hl=ja#create-access

有効期間を変更したい場合は、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を使用できるので、ワークロードによっては有用かなと思います。AudienceSubjectが流出しても、JWTの署名はGoogle Cloudの秘密鍵で生成されており、攻撃者がその秘密鍵を持っていない限り、トークンを偽造することはできません。サービスアカウントにアクセスできる権限がない限り発行することはできません。
この仕組み、ローカル環境でもCloud Shell同様に動作します(IP制限などは考慮していませんのでご注意ください。個々のワークロードによって動作が異なる可能性はあると思います)。

これ以外にもアクセスキーなしでAWSにGoogle Cloudからアクセスする方法はあるのでまた機会があればご紹介したいと思います。
それではまた。ナマステー

参考

https://cloud.google.com/sdk/gcloud/reference/auth/print-identity-token
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.