プロキシ経由でgcloudコマンドが失敗。サービスアカウントが使えない

記事タイトルとURLをコピーする

G-gen の杉村です。Compute Engine の VM で Squid 等の HTTP プロキシサーバ経由でインターネットへ出るような構成を取った際、gcloud コマンドが不可解なエラーメッセージと共に失敗しました。今回はその事象と、解決方法をご紹介します。

事象

やろうとしたこと

ある Compute Engine の Linux VM があります。この VM のログインユーザのプロファイルでは HTTP_PROXY および HTTPS_PROXY 環境変数を設定しており、他の VM 上で動作するプロキシサーバ (Squid) 経由でインターネットへ出られるようになっています。

この Linux VM にログインし、gcloud storage コマンドを実行しようとしました。なお認証情報として VM にアタッチされているサービスアカウントを利用する想定です。

エラーメッセージ

この環境で gcloud storage ls gs://my-test-bucket を実行しました。

すると、以下のようなエラーメッセージが出力され、コマンドが失敗しました。※バケット名やサービスアカウント名などは仮の値に置き換えてあります。

$ gcloud storage ls gs://my-test-bucket
ERROR: (gcloud.storage.ls) There was a problem refreshing your current auth tokens: ('Failed to retrieve http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true from the Google Compute Engine metadata service. Status: 403 Response:\nb\'<!DOCTYPE html>\\n<html lang=en>\\n  <meta charset=utf-8>\\n  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">\\n  <title>Error 403 (Forbidden)!!1</title>\\n  <style>\\n    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}\\n  </style>\\n  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>\\n  <p><b>403.</b> <ins>That\\xe2\\x80\\x99s an error.</ins>\\n  <p>Your client does not have permission to get URL <code>/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true</code> from this server.  <ins>That\\xe2\\x80\\x99s all we know.</ins>\\n\'', <google.auth.transport.requests._Response object at 0x7f0701657970>)
Please run:

  $ gcloud auth login

to obtain new credentials.

If you have already logged in with a different account:

    $ gcloud config set account ACCOUNT

to select an already authenticated account to use.

上記コードブロックだと改行がなく読みづらいのと、記号がエンコードされてしまっているので、以下に要所だけ抜き出して整形したメッセージを記載します。

ERROR: (gcloud.storage.ls) There was a problem refreshing your current auth tokens: ('Failed to retrieve http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true from the Google Compute Engine metadata service. Status: 403


403. That's an error.

Your client does not have permission to get URL /computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true from this server. That's all we know.


Please run:
$ gcloud auth login
to obtain new credentials.
If you have already logged in with a different account:
$ gcloud config set account ACCOUNT
to select an already authenticated account to use.

There was a problem refreshing your current auth tokens ... や Your client does not have permission to get URL ...というメッセージから想像するに、認証情報に関連するエラーメッセージのようです。

設定を確認したところ、VM には正しくサービスアカウントがアタッチされており、またサービスアカウントは適切に IAM ロールが付与されています。VM の アクセススコープ設定 も適切でした。

また Please run: $ gcloud auth login とありますが、このコマンドは gcloud コマンドに明示的に Google アカウントの認証情報を設定するコマンドであり、今回は VM にアタッチしたサービスアカウントを使いたいため、要件と合わなくなってしまいます。

前提知識

VM とプロキシサーバについて

今回の事象を理解するためには、前提となる環境を理解する必要があります。

Compute Engine VM からインターネットに出るには、いくつかの方法があります。

  1. VM のパブリック IP アドレスを使って VPC の Default Internet Gateway から直接インターネットへ出る
  2. パブリック IP アドレスを持たない VM から Cloud NAT 経由でインターネットへ出る
  3. パブリック IP アドレスを持たない VM から HTTP プロキシサーバ経由でインターネットへ出る

このうち 3. のプロキシサーバを使う場合、以下のような方法でプロキシサーバの宛先を設定することになります。

  • (Linux の場合) HTTP_PROXY / HTTPS_PROXY 環境変数を設定する
  • (Windows の場合) コントロールパネルのインターネットオプションで設定する
  • アプリケーション固有の設定でプロキシサーバを指定する

今回の事象は Linux サーバで起きており、上記のうち HTTP_PROXY / HTTPS_PROXY 環境変数を使用する方法でプロキシサーバを指定しています。また gcloud コマンドは、これらの環境変数が設定されている場合は、変数の内容を読み取ってプロキシサーバを利用してくれる仕様となっています。

サービスアカウントと VM

サービスアカウントとは、Google Cloud で管理されるアカウントの一種です。

サービスアカウントは人間が使う Google アカウントとは区別され、プログラムが Google API や Google Cloud API を呼び出すために用いるアカウントを指します。

Compute Engine の VM にはそのサービスアカウントをアタッチ (日本語ドキュメントでは “接続” と表現) することができます。

サービスアカウントをアタッチすると、VM 上で動作する gcloud 等のプログラムは、そのサービスアカウントの権限を使って各種 Google Cloud サービスの API を実行することができます。Google アカウントを使って認証したり、サービスアカウントキーをダウンロードする必要はありません。

余談ですが Amazon Web Services (AWS) でも「EC2 インスタンスへの IAM ロールのアタッチ」というよく似た概念が存在しています。

原因調査

サービスアカウントは認識できている

エラーメッセージには以下の文言が含まれています。

Your client does not have permission to get URL /computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true from this server.

1234567890-compute@developer.gserviceaccount.com はこの VM にアタッチされているサービスアカウント名であり、VM の中からはサービスアカウントの名称は正しく認識できていることが分かります。

それにも関わらず「認証情報が得られていない」ようなエラーメッセージが出力されていることになります。

エラーメッセージ内の URL

ヒントは、引用した上記メッセージにありました。 /computeMetadata で始まる URL に注目します。

Cloud SDK は、前述のように ADC (Application Default Credentials) の仕組みにより認証情報を探します。

今回の実行時は GOOGLE_APPLICATION_CREDENTIALS 環境変数に認証情報を設定しておらず、gcloud コマンドにも認証情報を設定していなかったため、VM にアタッチされたサービスアカウントへ認証情報を取りにいきます。

実は gcloud 等が VM のサービスアカウントから認証情報を取得するときは、VM の メタデータサーバ というサーバへ HTTP リクエストを投げることで認証情報を取得する仕様です。

メタデータサーバは http://metadata.google.internal/ という URL であり、これを VM 上で名前解決すると 169.254.169.254 というリンクローカル IP アドレスへ解決されます (hosts ファイルで静的に指定されている)。

このメタデータサーバ (169.254.169.254) は VM のローカルからのみアクセス可能な特殊なサーバとなっており、我々ユーザも curl コマンドなどを用いて VM 内からリクエストすることで、VM やプロジェクトの情報を得ることができます。

改めてエラーの1行目を見てみると以下のようになっています。

ERROR: (gcloud.storage.ls) There was a problem refreshing your current auth tokens: ('Failed to retrieve http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/?recursive=true from the Google Compute Engine metadata service.

前述のエラーメッセージと合わせて見てみても、どうもこのメタデータサーバへのクエリが失敗している (Your client does not have permission) らしいことが分かります。

メタデータサーバはローカルからのみクエリできる

実は、VM のメタデータサーバへは、その VM の ローカルからしかアクセスできない という制限があります。プロキシサーバ等、他のノードを経由してメタデータサーバへアクセスしようとすると、セキュリティ上の理由から拒否されます。

今回利用しようとしていたプロキシサーバ (Squid) のアクセスログを見てみると、以下のように、当該 VM からメタデータサーバへ プロキシ経由で接続しようと していました。

1671421064.582      1 10.146.x.x TCP_MISS/403 2037 GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/? - HIER_DIRECT/169.254.169.254 text/html

本来ローカルでルーティングされるべき 169.254.169.254 へのリクエストがプロキシ経由でリクエストされてしまっていたのです。

今回はプロキシサーバも Compute Engine VM でしたが、ローカルからのアクセスではないためメタデータサーバ側で弾かれていました。なおプロキシサーバがオンプレミスであれば、169.254.169.254 は HTTP レスポンスを受け付けないはずなので、タイムアウト等になるかもしれません。

解決策

以下のように NO_PROXY 環境変数を設定し「プロキシへフォワードしない IP アドレス/ドメイン名一覧」を定義してあげます。

$ export NO_PROXY=127.0.0.1,localhost,169.254.169.254,metadata,metadata.google.internal

これにより localhost (127.0.0.1) や metadata.google.internal (169.254.169.254) への HTTP リクエストはプロキシサーバに転送されなくなり、ローカルから直接リクエストされるようになります。

上記を設定したところ、以下のように正しく gcloud コマンドが使えるようになりました。

$ gcloud storage ls gs://my-test-bucket
gs://my-test-bucket/my-private-obj-01.txt
gs://my-test-bucket/my-private-obj-02.txt

さらに深堀り

curl でメタデータサーバへクエリしてみる

メタデータサーバの挙動を理解するために、curl コマンドを使い手動でメタデータサーバをクエリしてみます。

curl コマンドは HTTP_PROXY / HTTPS_PROXY / NO_PROXY 環境変数に影響を受けません。代わりに -x オプションで明示的にプロキシサーバを指定できます。

まずは以下のように、プロキシを使わずにメタデータをクエリしてみます。 metadata.google.internal は hosts ファイルにより 169.254.169.254 に解決され、メタデータサーバへルートされます。

$ curl -v -H 'Metadata-Flavor:Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email
*   Trying 169.254.169.254:80...
* Connected to metadata.google.internal (169.254.169.254) port 80 (#0)
> GET /computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email HTTP/1.1
> Host: metadata.google.internal
> User-Agent: curl/7.74.0
> Accept: */*
> Metadata-Flavor:Google
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Metadata-Flavor: Google
< Content-Type: application/text
< ETag: ae98be0f811e76b9
< Date: Mon, 19 Dec 2022 23:23:36 GMT
< Server: Metadata Server for VM
< Content-Length: 48
< X-XSS-Protection: 0
< X-Frame-Options: SAMEORIGIN
< 
* Connection #0 to host metadata.google.internal left intact
1234567890-compute@developer.gserviceaccount.com

上記のようにメタデータサーバから正しくレスポンスが返ってきました。

なお -H オプションで Metadata-Flavor:Google を付与するのは、メタデータをクエリするための決まりごとであり、必須です。

プロキシ経由でメタデータサーバへクエリしてみる

次にオプション -x http://myproxy.local:3128 を指定して、あえてプロキシ経由でメタデータサーバにクエリを投げてみます。 myproxy.local は Squid が動作している別の VM です。

$ curl -v -x http://myproxy.local:3128 -H 'Metadata-Flavor:Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email
*   Trying 10.146.15.212:3128...
* Connected to myproxy.local (10.146.15.212) port 3128 (#0)
> GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email HTTP/1.1
> Host: metadata.google.internal
> User-Agent: curl/7.74.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> Metadata-Flavor:Google
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Metadata-Flavor: Google
< Date: Mon, 19 Dec 2022 23:24:18 GMT
< Content-Type: text/html; charset=UTF-8
< Server: Metadata Server for VM
< Content-Length: 1678
< X-XSS-Protection: 0
< X-Frame-Options: SAMEORIGIN
< X-Cache: MISS from squid
< X-Cache-Lookup: MISS from squid:3128
< Via: 1.1 squid (squid/4.13)
< Connection: keep-alive
< 
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 403 (Forbidden)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>403.</b> <ins>That’s an error.</ins>
  <p>Your client does not have permission to get URL <code>/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email</code> from this server.  <ins>That’s all we know.</ins>
* Connection #0 to host myproxy.local left intact

上記のように Your client does not have permission to get URL <code>/computeMetadata/v1/instance/service-accounts/1234567890-compute@developer.gserviceaccount.com/email</code> from this server. というメッセージが表示されました。ステータスコードは 403 Forbidden です。

これは、当初 gcloud コマンドで出力されたエラーメッセージと全く同じです。この検証によって、今回の事象がよく理解できるのではないでしょうか。

杉村 勇馬 (記事一覧)

執行役員 CTO / クラウドソリューション部 部長

元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。