VM の ID を確認する


アプリケーションが仮想マシン(VM)インスタンスに機密情報を送信する前に、Google によって署名されたインスタンス ID トークンを使用してインスタンスの ID を確認できます。各インスタンスには、インスタンスの詳細と Google の RS256 署名を含む一意の JSON Web Token(JWT)が存在します。アプリケーションは、署名と Google の公開 OAuth2 証明書を照合して、接続を確立したインスタンスの ID を確認できます。

Compute Engine が署名付きインスタンス トークンを生成するのは、インスタンスがインスタンス メタデータからこのトークンをリクエストした場合のみです。インスタンスは、各自が持っている一意のトークンにのみアクセスでき、他のインスタンスのトークンにはアクセスできません。

インスタンスの ID 確認が必要になるのは、たとえば次のような状況です。

  • インスタンスを初めて起動した際、そのインスタンスに機密情報を送信する前に、接続しているインスタンスの ID の有効性確認がアプリケーションで必要になることがあります。
  • Compute Engine 環境の外部に認証情報を保存するようポリシーで定められていて、それらの認証情報を一時的に使用するために定期的にインスタンスに送信する場合。アプリケーションは、認証情報を送信する必要があるたびにインスタンスの ID を確認できます。

Google のインスタンス認証方法には次の利点があります。

  • Compute Engine は、インスタンスがトークンをリクエストするたびに一意のトークンを作成し、各トークンは 1 時間以内に期限切れになります。インスタンスの ID トークンを 1 回のみ受け入れるようにアプリケーションを構成すると、許可されていないシステムによってトークンが再利用されるリスクが減少します。
  • 署名付きメタデータ トークンは、RFC 7519 のオープン業界標準と OpenID Connect 1.0 の ID レイヤを使用するため、既存のツールとライブラリは ID トークンとシームレスに連携します。

始める前に

  • インスタンス メタデータ値を取得する方法を理解します。
  • JSON Web Token の基本を理解して、アプリケーションで JSON Web Token を使用する方法を把握します。
  • インスタンスでサービス アカウントを作成して有効にする方法を理解します。インスタンスが ID トークンを取得できるように、インスタンスにサービス アカウントを関連付けておく必要があります。サービス アカウントには、これらの ID トークンを取得するための IAM 権限は必要ありません。
  • まだ設定していない場合は、認証を設定します。認証とは、Google Cloud サービスと API にアクセスするために ID を確認するプロセスです。ローカル開発環境からコードまたはサンプルを実行するには、次のように Compute Engine に対する認証を行います。

    このページの Python サンプルをローカル開発環境から使用するには、gcloud CLI をインストールして初期化し、自身のユーザー認証情報を使用してアプリケーションのデフォルト認証情報を設定してください。

    1. Google Cloud CLI をインストールします。
    2. gcloud CLI を初期化するには:

      gcloud init
    3. Google アカウントのローカル認証情報を作成します。

      gcloud auth application-default login

    詳細については、 ローカル開発環境の認証の設定 をご覧ください。

インスタンスの ID の確認

アプリケーションが Compute Engine で実行されているインスタンスにセンシティブ データを送信する前に、そのインスタンスの ID を確認しなければならない場合があります。典型的な例は、システムが Compute Engine の外部で実行され(このシステムを「Host1」と呼びます)、Compute Engine にインスタンス(「VM1」と呼びます)が存在する場合です。VM1 は次のプロセスに沿って Host1 に接続し、そのインスタンスの ID を確認できます。

  1. VM1 は、HTTPS などの安全な接続プロトコルを使用して Host1 との安全な接続を確立します。

  2. VM1 は、メタデータ サーバーに一意の ID トークンをリクエストし、トークンのオーディエンスを指定します。この例では、オーディエンス値は Host1 の URI です。メタデータ サーバーへのリクエストにオーディエンスの URI を含めて、後でトークンを確認する間に Host1 が値を確認できるようにします。

  3. Google は、JWT 形式で新しい一意のインスタンス ID トークンを生成し、これを VM1 に提供します。トークンのペイロードには、インスタンスに関する詳細が含まれ、オーディエンスの URI も含まれます。トークンの内容の詳細については、トークンの内容をご覧ください。

  4. VM1 は、既存の安全な接続を介して Host1 に ID トークンを送信します。

  5. Host1 は ID トークンをデコードし、トークン ヘッダーとペイロードの値を取得します。

  6. Host1 はオーディエンス値を確認し、証明書の署名と公開 Google 証明書を照合することによって、トークンが Google によって署名されていることを確認します。

  7. トークンが有効な場合、Host1 は送信を開始し、送信が終了すると接続を閉じます。VM1 への後続の接続についても、Host1 と他のシステムはそのたびに新しいトークンをリクエストする必要があります。

インスタンス ID トークンの取得

仮想マシン インスタンスは、ID トークンを提供してほしいというリクエストを受け取ると、通常のインスタンス メタデータの取得プロセスに従ってメタデータ サーバーにトークンをリクエストします。たとえば、次のいずれかの方法を使用できます。

cURL

curl リクエストを作成し、audience パラメータに値を含めます。必要に応じて、format パラメータを含めて、ペイロードにプロジェクトとインスタンスの詳細を含めるかどうかを指定できます。full 形式を使用する場合は、licenses パラメータを含めて、ペイロードにライセンス コードを含めるかどうかを指定できます。

curl -H "Metadata-Flavor: Google" \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=AUDIENCE&format=FORMAT&licenses=LICENSES'

次のように置き換えます。

  • AUDIENCE: インスタンスとインスタンスの ID を確認するシステムとの両方によって合意された一意の URI。たとえば、2 つのシステムの接続に使われる URL などです。
  • FORMAT: 省略可能なパラメータ。プロジェクトとインスタンスの詳細をペイロードに含めるかどうかを指定します。この情報をペイロードに含める場合は full を指定し、含めない場合は standard を指定します。デフォルト値は standard です。詳細については、ID トークンの形式をご覧ください。
  • LICENSES: 省略可能なパラメータ。このインスタンスに関連付けられたイメージのライセンス コードがペイロードに含まれるかどうかを指定します。この情報を含める場合は TRUE を指定し、含めない場合は FALSE を指定します。デフォルト値は FALSE です。formatfull でない限り、効果はありません。

メタデータ サーバーは、RS256 アルゴリズムを使用して署名された JSON Web Token でこのリクエストに応答します。このトークンは、Google の署名と追加情報をペイロードに含めます。このトークンを他のシステムやアプリケーションに送信すると、インスタンスのトークンを確認し ID を確認してもらうことができます。

Python

Python requests ライブラリのメソッドを使用して、インスタンスからメタデータ サーバーに単純なリクエストを送信できます。次の例では、インスタンス ID トークンをリクエストして出力します。このトークンは、このリクエストを行うインスタンスに固有です。

import requests

AUDIENCE_URL = "http://www.example.com"
METADATA_HEADERS = {"Metadata-Flavor": "Google"}
METADATA_VM_IDENTITY_URL = (
    "http://metadata.google.internal/computeMetadata/v1/"
    "instance/service-accounts/default/identity?"
    "audience={audience}&format={format}&licenses={licenses}"
)
FORMAT = "full"
LICENSES = "TRUE"


def acquire_token(
    audience: str = AUDIENCE_URL, format: str = "standard", licenses: bool = True
) -> str:
    """
    Requests identity information from the metadata server.

    Args:
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity. For example, the audience
            could be a URL for the connection between the two systems.
        format: the optional parameter that specifies whether the project and
            instance details are included in the payload. Specify `full` to
            include this information in the payload or standard to omit the
            information from the payload. The default value is `standard`.
        licenses: an optional parameter that specifies whether license
            codes for images associated with this instance are included in the
            payload. Specify TRUE to include this information or FALSE to omit
            this information from the payload. The default value is FALSE.
            Has no effect unless format is `full`.

    Returns:
        A JSON Web Token signed using the RS256 algorithm. The token includes a
        Google signature and additional information in the payload. You can send
        this token to other systems and applications so that they can verify the
        token and confirm that the identity of your instance.
    """
    # Construct a URL with the audience and format.
    url = METADATA_VM_IDENTITY_URL.format(
        audience=audience, format=format, licenses=licenses
    )

    # Request a token from the metadata server.
    r = requests.get(url, headers=METADATA_HEADERS)
    # Extract and return the token from the response.
    r.raise_for_status()
    return r.text

メタデータ サーバーは、RS256 アルゴリズムを使用して署名された JSON Web Token でこのリクエストに応答します。このトークンは、Google の署名と追加情報をペイロードに含めます。このトークンを他のシステムやアプリケーションに送信すると、インスタンスのトークンを確認し ID を確認してもらうことができます。

トークンの確認

アプリケーションが Compute Engine インスタンスからインスタンス ID トークンを受信すると、次のプロセスを使用してトークンを確認できます。

  1. 仮想マシン インスタンスからトークンを受信した後、RS256 JWT デコーダーでトークンをデコードし、ヘッダーの内容を読み取って kid 値を取得します。

  2. トークンと公開 Google 証明書を照合して、トークンが署名されていることを確認します。各公開証明書には、トークン ヘッダーの kid 値に対応する kid 値が含まれています。

  3. トークンが有効な場合は、ペイロードの内容と期待値を比較します。トークン ペイロードにインスタンスとプロジェクトに関する詳細が含まれている場合、アプリケーションは instance_idproject_idzone の値を確認できます。これらの値はグローバルに一意のタプルで、アプリケーションが目的のプロジェクトの適切なインスタンスと通信していることを立証するものです。

トークンのデコードと確認には任意のツールを使用できますが、一般に使用言語のライブラリが使用されます。たとえば、Python 用の Google OAuth 2.0 ライブラリに含まれている verify_token メソッドを使用できます。verify_token メソッドは、kid 値を適切な証明書と照合して署名を確認し、オーディエンス クレームを確認すれば、トークンからペイロードの内容を返します。

import google.auth.transport.requests
from google.oauth2 import id_token



def verify_token(token: str, audience: str) -> dict:
    """
    Verify token signature and return the token payload.

    Args:
        token: the JSON Web Token received from the metadata server to
            be verified.
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity.

    Returns:
        Dictionary containing the token payload.
    """
    request = google.auth.transport.requests.Request()
    payload = id_token.verify_token(token, request=request, audience=audience)
    return payload

アプリケーションがトークンとその内容を確認したら、安全な接続を介してそのインスタンスと通信し、通信が終了したら接続を閉じることができます。後続の接続ごとにインスタンスに新しいトークンを再度リクエストし、インスタンスの ID を再確認します。

トークンの内容

インスタンス ID トークンは、3 つの主要部分で構成されます。

ヘッダーには、公開 OAuth2 証明書を識別するための kid 値が含まれています。署名の確認には、この値で識別された証明書を使用する必要があります。ヘッダーにはさらに、署名が RS256 アルゴリズムを使用して生成されたことを確認するための alg 値も含まれています。

{
  "alg": "RS256",
  "kid": "511a3e85d2452aee960ed557e2666a8c5cedd8ae",
}

ペイロード

ペイロードには、aud オーディエンス クレームが含まれています。インスタンスがトークンをリクエストしたときに format=full を指定した場合、ペイロードには、仮想マシン インスタンスとそのプロジェクトに関するクレームも含まれます。 完全なフォーマットのトークンを要求するときに、licenses=TRUE と指定すると、インスタンスに関連付けられているライセンスに関するクレームも含まれます。

{
   "iss": "[TOKEN_ISSUER]",
   "iat": [ISSUED_TIME],
   "exp": [EXPIRED_TIME],
   "aud": "[AUDIENCE]",
   "sub": "[SUBJECT]",
   "azp": "[AUTHORIZED_PARTY]",
   "google": {
    "compute_engine": {
      "project_id": "[PROJECT_ID]",
      "project_number": [PROJECT_NUMBER],
      "zone": "[ZONE]",
      "instance_id": "[INSTANCE_ID]",
      "instance_name": "[INSTANCE_NAME]",
      "instance_creation_timestamp": [CREATION_TIMESTAMP],
      "instance_confidentiality": [INSTANCE_CONFIDENTIALITY],
      "license_id": [
        "[LICENSE_1]",
          ...
        "[LICENSE_N]"
      ]
    }
  }
}

ここで

  • [TOKEN_ISSUER]: トークンの発行者を識別する URL。Compute Engine の場合、この値は https://accounts.google.com です。
  • [ISSUED_TIME]: トークンが発行された時刻を示す UNIX タイムスタンプ。この値は、インスタンスがメタデータ サーバーにトークンをリクエストするたびに更新されます。
  • [EXPIRED_TIME]: トークンの有効期限が切れる時刻を示す UNIX タイムスタンプ。
  • [AUDIENCE]: インスタンスとインスタンスの ID を確認するシステムとの両方によって合意された一意の URI。たとえば、2 つのシステムの接続に使われる URL などです。
  • [SUBJECT]: トークンの主体者。インスタンスに関連付けられたサービス アカウントの一意の ID です。
  • [AUTHORIZED_PARTY]: ID トークンが発行された当事者。インスタンスに関連付けられたサービス アカウントの一意の ID です。
  • [PROJECT_ID]: インスタンスが作成されたプロジェクトの ID。
  • [PROJECT_NUMBER]: インスタンスが作成されたプロジェクトの一意の番号。
  • [ZONE]: インスタンスが配置されているゾーン。
  • [INSTANCE_ID]: このトークンが属するインスタンスの一意の ID。この ID はプロジェクトとゾーン内で一意です。
  • [INSTANCE_NAME]: このトークンが属するインスタンスの名前。プロジェクトでゾーン DNS を使用している場合、この名前はゾーン間で再利用できるため、project_idzoneinstance_id を組み合わせて利用して一意のインスタンス ID を識別します。グローバル DNS が有効なプロジェクトには、プロジェクト全体で一意のインスタンス名が付けられます。
  • [CREATION_TIMESTAMP]: インスタンスの作成時刻を示す UNIX タイムスタンプ。
  • インスタンスが Confidential VMs の場合、[INSTANCE_CONFIDENTIALITY]1 です。
  • [LICENSE_1] から [LICENSE_N]: このインスタンスに関連付けられているイメージのライセンス コード

ペイロードは次の例のようになります。

{
  "iss": "https://accounts.google.com",
  "iat": 1496953245,
  "exp": 1496956845,
  "aud": "https://www.example.com",
  "sub": "107517467455664443765",
  "azp": "107517467455664443765",
  "google": {
    "compute_engine": {
      "project_id": "my-project",
      "project_number": 739419398126,
      "zone": "us-west1-a",
      "instance_id": "152986662232938449",
      "instance_name": "example",
      "instance_creation_timestamp": 1496952205,
      "instance_confidentiality": 1,
      "license_id": [
        "1000204"
      ]
    }
  }
}

署名

Google は、ヘッダーとペイロードを base64url でエンコードし、これら 2 つの値を連結して署名を生成します。この値を公開 OAuth2 証明書と照合して、トークンを確認できます。

次のステップ