VM ID 확인


애플리케이션은 민감한 정보를 가상 머신(VM) 인스턴스에 전송하기 전에 Google에서 서명한 인스턴스 ID 토큰을 사용하여 인스턴스의 ID를 확인할 수 있습니다. 각 인스턴스에는 인스턴스에 대한 세부정보와 Google의 RS256 서명이 포함된 고유한 JSON 웹 토큰(JWT)이 있습니다. 애플리케이션은 Google의 공개 Oauth2 인증서와 대조해 서명을 확인하여 연결을 설정한 인스턴스의 ID를 확인합니다.

인스턴스가 인스턴스 메타데이터에 요청하는 경우에만 Compute Engine은 서명된 인스턴스 토큰을 생성합니다. 인스턴스는 자체의 고유 토큰에만 액세스할 수 있으며 다른 인스턴스의 토큰에는 액세스할 수 없습니다.

다음과 같은 시나리오에서 인스턴스의 ID를 확인해야 할 수 있습니다.

  • 인스턴스를 처음 시작할 때 애플리케이션은 민감한 정보를 인스턴스에 전송하기 전에 애플리케이션에 연결된 인스턴스에 유효한 ID가 있는지 확인해야 할 수 있습니다.
  • 정책에 의거하여 Compute Engine 환경 외부에 사용자 인증 정보를 저장하고 정기적으로 해당 사용자 인증 정보를 임시 사용 용도로 인스턴스에 전송하는 경우 애플리케이션은 사용자 인증 정보를 전송해야 할 때마다 인스턴스의 ID를 확인할 수 있습니다.

Google의 인스턴스 인증 방법은 다음과 같은 이점이 있습니다.

  • Compute Engine은 인스턴스에서 요청할 때마다 고유한 토큰을 만들며, 각 토큰은 1시간 이내에 만료됩니다. 인스턴스의 ID 토큰을 한 번만 허용하도록 애플리케이션을 구성할 수 있으며, 이렇게 하면 승인받지 않은 시스템이 토큰을 재사용할 위험이 줄어듭니다.
  • 서명된 메타데이터 토큰은 개방형 업계 표준인 RFC 7519와 ID 레이어인 OpenID Connect 1.0을 사용하므로, 기존의 도구와 라이브러리가 ID 토큰과 원활하게 작동합니다.

시작하기 전에

  • 인스턴스 메타데이터 값 검색 방법을 이해합니다.
  • JSON 웹 토큰의 기본사항을 이해하여 애플리케이션에서 이 토큰을 사용하는 방법을 익힙니다.
  • 인스턴스에서 서비스 계정 만들기 및 사용 설정 방법을 이해합니다. 인스턴스에는 연결된 서비스 계정이 있어야 인스턴스가 ID 토큰을 검색할 수 있습니다. 서비스 계정은 IAM 권한 없이도 이러한 ID 토큰을 검색할 수 있습니다.
  • 아직 인증을 설정하지 않았다면 설정합니다. 인증은 Google Cloud 서비스 및 API에 액세스하기 위해 ID를 확인하는 프로세스입니다. 로컬 개발 환경에서 코드 또는 샘플을 실행하려면 다음과 같이 Compute Engine에 인증하면 됩니다.

    이 페이지의 Python 샘플을 로컬 개발 환경에서 사용하려면 gcloud CLI를 설치 및 초기화한 다음 사용자 인증 정보로 애플리케이션 기본 사용자 인증 정보를 설정하세요.

    1. Install the Google Cloud CLI.
    2. To initialize the gcloud CLI, run the following command:

      gcloud init
    3. If you're using a local shell, then create local authentication credentials for your user account:

      gcloud auth application-default login

      You don't need to do this if you're using Cloud Shell.

    자세한 내용은 다음을 참조하세요: Set up authentication for a local development environment.

인스턴스 ID 확인

일부 시나리오에서 애플리케이션은 Compute Engine에서 실행 중인 인스턴스에 민감한 정보를 전송하기 전에 해당 인스턴스의 ID를 확인해야 합니다. 한 가지 일반적인 예로, Compute Engine 외부에서 실행 중인 시스템('Host1') 하나와 Compute Engine 인스턴스('VM1') 하나가 있는 경우 다음 프로세스에 따라 VM1을 Host1에 연결하고 해당 인스턴스의 ID를 검증할 수 있습니다.

  1. 원하는 보안 연결 프로토콜(예: HTTPS)을 사용하여 VM1에서 Host1으로 보안 연결을 설정합니다.

  2. VM1이 메타데이터 서버에 고유한 ID 토큰을 요청하고 토큰의 대상을 지정합니다. 이 예시에서 대상 값은 Host1의 URI입니다. 메타데이터 서버 요청에는 이후 Host1이 토큰 검증 단계 도중에 값을 확인할 수 있도록 대상 URI가 포함됩니다.

  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입니다. 예를 들어 이 두 시스템 사이의 연결에 사용되는 URL이 대상일 수 있습니다.
  • FORMAT: 페이로드에 프로젝트 및 인스턴스 세부정보를 포함할지 여부를 지정하는 선택적인 매개변수입니다. 이 정보를 페이로드에 포함하려면 full를 지정하고 포함하지 않으려면 standard를 지정합니다. 기본값은 standard입니다. 자세한 내용은 ID 토큰 형식을 참조하세요.
  • LICENSES: 이 인스턴스와 연관된 이미지의 라이선스 코드를 페이로드에 포함할지 여부를 지정하는 선택적인 매개변수입니다. 이 정보를 페이로드에 포함하려면 TRUE를 지정하고 포함하지 않으려면 FALSE를 지정합니다. 기본값은 FALSE입니다. formatfull이면 영향을 주지 않습니다.

이 요청에 대해 메타데이터 서버는 RS256 알고리즘을 사용하여 서명한 JSON 웹 토큰으로 응답합니다. 이 토큰에는 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 웹 토큰으로 응답합니다. 이 토큰에는 Google 서명과 페이로드의 추가 정보가 포함됩니다. 이 토큰을 다른 시스템 및 애플리케이션에 전송하여 토큰 검증 및 인스턴스 ID 확인을 수행하도록 할 수 있습니다.

토큰 검증

애플리케이션은 Compute Engine 인스턴스에서 인스턴스 ID 토큰을 수신한 후 다음 프로세스에 따라 이 토큰을 검증할 수 있습니다.

  1. 가상 머신 인스턴스에서 토큰을 수신하고 RS256 JWT 디코더를 사용하여 토큰을 디코딩한 다음 헤더 콘텐츠를 읽어 kid 값을 얻습니다.

  2. 공개 Google 인증서와 대조해 토큰을 확인하여 토큰이 서명되었는지 확인합니다. 각 공개 인증서에는 토큰 헤더의 kid 값에 해당하는 kid 값이 있습니다.

  3. 토큰이 유효한 경우 페이로드 콘텐츠와 예상 값을 비교합니다. 토큰 페이로드에 인스턴스 및 프로젝트에 대한 세부정보가 있는 경우 애플리케이션이 instance_id, project_id, zone 값을 확인할 수 있습니다. 이러한 값은 애플리케이션이 원하는 프로젝트 내의 적절한 인스턴스와 통신하는지 확인하는, 전역적으로 고유한 튜플입니다.

토큰을 디코딩하고 검증하는 데 임의의 도구를 사용할 수 있지만, 일반적인 방법은 원하는 언어의 라이브러리를 사용하는 것입니다. 예를 들어 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입니다. 예를 들어 이 두 시스템 사이의 연결에 사용되는 URL이 대상일 수 있습니다.
  • [SUBJECT]: 토큰의 제목으로, 인스턴스와 연결한 서비스 계정의 고유 ID입니다.
  • [AUTHORIZED_PARTY]: ID 토큰을 발급받는 주체로, 인스턴스와 연결한 서비스 계정의 고유 ID입니다.
  • [PROJECT_ID]: 만든 인스턴스가 속한 프로젝트의 ID입니다.
  • [PROJECT_NUMBER]: 만든 인스턴스가 속한 프로젝트의 고유 번호입니다.
  • [ZONE]: 인스턴스가 있는 영역입니다.
  • [INSTANCE_ID]: 이 토큰이 속한 인스턴스의 고유 ID입니다. 이 ID는 프로젝트와 영역 내에서 고유합니다.
  • [INSTANCE_NAME]: 이 토큰이 속한 인스턴스의 이름입니다. 프로젝트에서 영역 DNS를 사용하는 경우 이 이름은 영역 간에 재사용할 수 있으므로 고유한 인스턴스 ID를 식별하려면 project_id, zone, instance_id 값의 조합을 사용합니다. 전역 DNS가 사용 설정된 프로젝트는 프로젝트 전체에서 고유한 인스턴스 이름을 가집니다.
  • [CREATION_TIMESTAMP]: 인스턴스를 만든 시간을 나타내는 Unix 타임스탬프입니다.
  • [INSTANCE_CONFIDENTIALITY]: 인스턴스가 컨피덴셜 VM이면 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로 인코딩하고 이 두 값을 연결하여 서명을 생성합니다. 이 값을 공개 Oauth2 인증서와 대조하여 토큰을 검증할 수 있습니다.

다음 단계