Verificar a identidade da VM


Antes de enviar informações confidenciais a uma instância de máquina virtual (VM, na sigla em inglês), o aplicativo pode verificar a identidade da instância usando tokens de identidade da instância assinados pelo Google. Cada instância tem um JSON Web Token (JWT) exclusivo que inclui detalhes sobre ela, bem como a assinatura RS256 do Google. Seus aplicativos podem verificar a assinatura dos certificados OAuth2 públicos do Google para confirmar a identidade da instância com que estabeleceram uma conexão.

O Compute Engine gera tokens assinados somente quando uma instância os solicita a partir dos metadados dela. Uma instância só pode acessar o próprio token exclusivo, e não os de outras instâncias.

Verifique as identidades das suas instâncias nos seguintes cenários:

  • Quando a instância é iniciada pela primeira vez, os aplicativos podem precisar confirmar se a identidade dela é válida antes de transmitirem informações confidenciais para a instância.
  • Quando as políticas exigem o armazenamento das credenciais fora do ambiente do Compute Engine, e você as envia regularmente às instâncias para uso temporário. Os aplicativos podem confirmar a identidade das instâncias sempre que precisarem transmitir credenciais.

Os métodos de autenticação de instâncias do Google têm os seguintes benefícios:

  • O Compute Engine cria um token exclusivo sempre que uma instância o solicita. Cada token expira em até uma hora. É possível configurar seus aplicativos para aceitarem um token de identidade somente uma vez. Isso reduz o risco de que o token seja reutilizado por um sistema não autorizado.
  • Os tokens de metadados assinados usam o padrão aberto do setor RFC 7519 e a camada de identidade OpenID Connect 1.0 (links em inglês). Assim, as ferramentas e bibliotecas atuais funcionam perfeitamente com os tokens de identidade.

Antes de começar

  • Entenda como recuperar os valores de metadados da instância.
  • Entenda os conceitos básicos dos JSON Web Tokens para saber como usá-los nos seus aplicativos.
  • Entenda como criar e ativar contas de serviço nas instâncias. Elas precisam ter uma conta de serviço associada para recuperar tokens de identidade. Para esse processo, não é necessário ter uma permissão IAM na conta de serviço.
  • Configure a autenticação, caso ainda não tenha feito isso. A autenticação é o processo de verificação da sua identidade para acesso a serviços e APIs do Google Cloud. Para executar códigos ou amostras de um ambiente de desenvolvimento local, autentique-se no Compute Engine da seguinte maneira.

    Para usar os exemplos Python desta página em um ambiente de desenvolvimento local, instale e inicialize o gcloud CLI e e configure o Application Default Credentials com suas credenciais de usuário.

    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.

    Confira mais informações em Set up authentication for a local development environment.

Como verificar a identidade de uma instância

Em alguns cenários, os aplicativos precisam verificar a identidade de uma instância em execução no Compute Engine antes de transmitir dados confidenciais para ela. Vejamos um exemplo comum, em que há uma instância do Compute Engine chamada "VM1" e um sistema em execução fora dele chamado "Host1". A VM1 pode se conectar ao Host1 e validar a identidade da instância com o seguinte processo:

  1. A VM1 estabelece uma conexão segura com o Host1 por meio do protocolo de conexão protegida que você escolher, como HTTPS.

  2. A VM1 solicita o token de identidade exclusivo do servidor de metadados e especifica o público do token. Nesse exemplo, o valor do público é o URI do Host1. A solicitação ao servidor de metadados inclui o URI do público para que o Host1 possa confirmar o valor mais tarde, durante a etapa de verificação do token.

  3. O Google gera um novo token exclusivo de identidade da instância no formato JWT e o fornece à VM1. O payload do token inclui vários detalhes sobre a instância, além do URI do público. Veja uma descrição completa sobre o Conteúdo do token na seção correspondente.

  4. A VM1 envia o token de identidade ao Host1 por meio da conexão segura atual.

  5. O Host1 decodifica o token de identidade para receber os valores do cabeçalho e payload.

  6. Para verificar se o token é assinado pelo Google, o Host1 compara o valor do público e a assinatura do certificado com o certificado público do Google.

  7. Se o token for válido, o Host1 prossegue com a transmissão e encerra a conexão quando terminar. O Host1 e quaisquer outros sistemas precisam solicitar um novo token para conexões posteriores com a VM1.

Como receber o token de identidade da instância

Quando recebe uma solicitação para fornecer o token de identidade, a instância de máquina virtual o solicita do servidor de metadados. Para isso, ela usa o processo normal de recebimento de metadados da instância. Por exemplo, você pode usar um dos seguintes métodos:

cURL

Crie uma solicitação curl e inclua um valor no parâmetro audience. Outra opção é incluir o parâmetro format para especificar se você quer incluir os detalhes do projeto e da instância no payload ou não. Se estiver usando o formato full, inclua o parâmetro licenses para especificar se quer incluir códigos de licença no payload ou não.

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

Substitua:

  • AUDIENCE: o URI exclusivo aceito pela instância e pelo sistema que verifica a identidade dela. Por exemplo, o público pode ser um URL da conexão entre os dois sistemas;
  • FORMAT: o parâmetro opcional que especifica se os detalhes do projeto e da instância são incluídos no payload. Especifique full para incluir essas informações ou standard para omiti-las do payload. O valor padrão é standard. Para mais informações, consulte Formato do token de identidade.
  • LICENSES: um parâmetro opcional que especifica se os códigos de licença para imagens associadas a essa instância estão incluídos no payload. Especifique TRUE para incluir essas informações ou FALSE para omiti-las do payload. O valor padrão é FALSE. Não tem efeito, exceto se format for full.

O servidor de metadados usa o algoritmo RS256 para responder a essa solicitação com um JSON Web Token assinado. Ele inclui uma assinatura do Google e informações adicionais no payload. É possível enviar esse token para outros sistemas e aplicativos para que eles façam a verificação dele e confirmem a identidade da instância.

Python

Use os métodos na biblioteca requests do Python para enviar uma solicitação simples da instância para o servidor de metadados. No exemplo a seguir, um token de identidade da instância é solicitado e depois impresso. Ele é exclusivo para a instância que faz a solicitação.

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

O servidor de metadados usa o algoritmo RS256 para responder a essa solicitação com um JSON Web Token assinado. Ele inclui uma assinatura do Google e informações adicionais no payload. É possível enviar esse token para outros sistemas e aplicativos para que eles façam a verificação dele e confirmem a identidade da instância.

Como verificar o token

Depois que seu aplicativo receber um token de identidade de uma instância do Compute Engine, ele poderá verificá-lo usando o processo a seguir.

  1. Receba o token da instância de máquina virtual, faça a decodificação dele usando um decodificador RS256 JWT e leia o conteúdo do cabeçalho para conseguir o valor kid.

  2. Compare o token com o certificado público do Google para verificar se ele está assinado. Todo certificado público tem um valor kid que corresponde a kid no cabeçalho do token.

  3. Se o token for válido, compare o conteúdo do payload com os valores esperados. Se o payload do token incluir detalhes sobre a instância e o projeto, o aplicativo poderá verificar os valores instance_id, project_id e zone. Eles são uma tupla globalmente exclusiva que confirma se o aplicativo se comunica com a instância correta no projeto esperado.

É possível decodificar e verificar o token usando a ferramenta que você quiser. No entanto, um método comum é usar as bibliotecas do seu idioma. Por exemplo, é possível usar o método verify_token da biblioteca do Google OAuth 2.0 para Python. O verify_token combina o valor kid ao certificado apropriado, verifica a assinatura, confere a declaração do público e retorna o conteúdo do payload do token.

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

Depois de verificar o token e seu conteúdo, o aplicativo poderá se comunicar com essa instância em uma conexão segura e depois encerrar a conexão quando terminar. Para as conexões posteriores, solicite um novo token da instância e volte a verificar a identidade dela.

Conteúdo do token

O token de identidade da instância contém três partes principais:

O cabeçalho inclui o valor kid para identificar quais certificados OAuth2 públicos são necessários para verificar a assinatura. Ele também inclui o valor alg para confirmar que a assinatura foi gerada usando o algoritmo RS256 (em inglês).

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

Payload

O payload contém a declaração do público aud. Se a instância especificou format=full ao solicitar o token, o payload também incluirá declarações sobre a instância de máquina virtual e o projeto dela. Ao solicitar um token de formato completo, a especificação licenses=TRUE também incluirá declarações sobre as licenças associadas à instância.

{
   "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]"
      ]
    }
  }
}

Em que:

  • [TOKEN_ISSUER]: um URL que identifica o emissor do token. Para o Compute Engine, esse valor é https://accounts.google.com;
  • [ISSUED_TIME]: um carimbo de data/hora Unix que indica quando o token foi emitido. Esse valor é atualizado sempre que a instância solicita um token do servidor de metadados;
  • [EXPIRED_TIME]: um carimbo de data/hora Unix que indica quando o token expira;
  • [AUDIENCE]: o URI exclusivo aceito pela instância e pelo sistema que verifica a identidade dela. Por exemplo, o público pode ser um URL da conexão entre os dois sistemas;
  • [SUBJECT]: o assunto do token, que é o ID exclusivo da conta de serviço associada à instância.
  • [AUTHORIZED_PARTY]: o grupo para o qual o ID do token foi emitido, que é um ID exclusivo da conta de serviço associada à instância.
  • [PROJECT_ID]: o ID do projeto em que você criou a instância;
  • [PROJECT_NUMBER]: o número exclusivo do projeto em que a instância foi criada;
  • [ZONE]: a zona em que a instância está localizada.
  • [INSTANCE_ID]: o ID exclusivo da instância a que esse token pertence. Esse ID é exclusivo no projeto e na zona.
  • [INSTANCE_NAME]: o nome da instância a que esse token pertence. Se o projeto usar o DNS zonal, ele poderá ser reutilizado nas zonas, portanto, use uma combinação de valores project_id, zone e instance_id para identificar um ID de instância exclusivo. Projetos com DNS global ativado têm um nome de instância exclusivo em todo o projeto.
  • [CREATION_TIMESTAMP]: um carimbo de data/hora Unix que indica quando a instância foi criada;
  • [INSTANCE_CONFIDENTIALITY]: 1 se a instância for uma VM confidencial.
  • [LICENSE_1] até [LICENSE_N]: os códigos de licença para imagens associadas a essa instância.

Veja o exemplo de payload a seguir:

{
  "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"
      ]
    }
  }
}

Assinatura

O Google gera a assinatura por Base64url, codificando o cabeçalho e o payload, além de concatenar os dois valores. É possível comparar esse valor com os certificados OAuth2 públicos para verificar o token.

A seguir