Autenticação entre serviços

É possível implementar autenticação entre serviços usando uma conta de serviço em um serviço gRPC. Nesta página, mostramos como usar a autenticação serviço a serviço com um exemplo completo, inclusive como configurar o Extensible Service Proxy (ESP) em um serviço gRPC para dar suporte a solicitações autenticadas e como chamar o serviço em um cliente gRPC.

Para que qualquer serviço faça chamadas autenticadas para uma API do Cloud Endpoints, ele precisa ter uma conta de serviço e enviar um token de autenticação na chamada. O autor da chamada precisa usar um token de ID do Google ou um JSON Web Token (JWT) personalizado, assinado somente pela conta de serviço do autor da chamada. O ESP verifica se a declaração iss no JWT corresponde a issuer na configuração do serviço. O ESP não verifica as permissões de gerenciamento de identidade e acesso que foram concedidas na conta de serviço.

Em nosso exemplo, você configura e usa a forma mais simples de autenticação de serviço em serviço, em que o cliente usa a conta de serviço do Google Cloud para gerar JWTs de autenticação. A abordagem por outros métodos de autenticação é semelhante, mas o processo no lado do cliente para conseguir tokens de autenticação válidos depende do método usado para isso.

Antes de começar

Este guia usa o exemplo Bookstore que aparece em nossos tutoriais.

  1. Clone o repositório "git" no local em que o código de exemplo do gRPC está hospedado:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
    
  2. Altere o diretório de trabalho:

    cd python-docs-samples/endpoints/bookstore-grpc/
    
  3. Siga as instruções nos tutoriais para configurar um projeto caso ainda não tenha um.

Neste exemplo, usaremos a implantação no Google Kubernetes Engine, ainda que a configuração de autenticação seja a mesma para o Compute Engine.

No exemplo, são mencionados dois projetos do Google Cloud Platform:

  • o projeto de produtor de serviços, que é proprietário do serviço Cloud Endpoints para gRPC;
  • o projeto de consumidor de serviços, que é proprietário do cliente gRPC.

Como criar a conta de serviço e a chave do consumidor

Para criar a conta de serviço e chave para o projeto de consumidor:

  1. No Console do Google Cloud Platform, acesse "APIs e serviços".

    APIs e serviços

    Verifique se você está no seu projeto de consumidor.
  2. Na página Credenciais, na lista suspensa Criar credenciais, selecione Chave da conta de serviço.
  3. Na página Criar chave da conta de serviço, selecione a conta de serviço atual que você quer usar. Caso contrário, na lista suspensa da Conta de serviço, selecione Nova conta de serviço e digite um nome de conta.

    Será criado um código de conta de serviço correspondente. Anote o código, que será necessário nas próximas seções. Exemplo:

    service-account-name@YOUR_PROJECT_ID.iam.gserviceaccount.com
    
  4. Clique na lista suspensa Papel e selecione o seguinte:

    • Contas de serviço > Usuário da conta de serviço
    • Contas de serviço > Criador do token da conta de serviço
  5. Verifique se o tipo de chave JSON está selecionado.

  6. Clique em Criar. O download do arquivo de chave JSON da conta de serviço é feito em sua máquina local. Observe a localização e verifique se o arquivo está armazenado com segurança. Ele será necessário posteriormente para a geração de tokens.

Como configurar a autenticação do serviço

Use o projeto de produtor em todas as etapas nesta seção.

Configurar a autenticação na configuração da API gRPC

A autenticação para o ESP é configurada na seção authentication do arquivo YAML de configuração da API gRPC. A configuração com autenticação para este exemplo de serviço está em api_config_auth.yaml (em inglês).

authentication:
  providers:
  - id: google_service_account
    # Replace SERVICE-ACCOUNT-ID with your service account's email address.
    issuer: SERVICE-ACCOUNT-ID
    jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-ID
  rules:
  # This auth rule will apply to all methods.
  - selector: "*"
    requirements:
      - provider_id: google_service_account

Na seção providers, especificamos os provedores de autenticação que você quer usar. Nesse caso, que você quer usar uma conta de serviço do Google como um provedor de autenticação. A seção rules especifica que você requer tokens desse provedor para conceder acesso a todos os métodos do seu serviço.

Em sua cópia desse arquivo do repositório clonado:

  • Altere MY_PROJECT_ID para o ID do projeto do produtor.
  • Altere SERVICE-ACCOUNT-ID na seção authentication (nos valores issuer e jwks_uri) para o ID da conta de serviço de consumidor anotado na seção anterior. Isso informa ao ESP que você quer conceder acesso ao serviço aos usuários que informarem tokens válidos a partir desta conta de serviço específica.
  • Opcionalmente, adicione jwt_locations no elemento providers. Use esse valor para definir um local personalizado do JWT. Os locais padrão do JWT são os metadados Authorization (prefixados por "Bearer ") e os metadados X-Goog-Iap-Jwt-Assertion.

Salve o arquivo para a próxima etapa.

Implantar a configuração e o serviço

Estas etapas são as mesmas da Introdução ao gRPC no GKE:

  1. Implemente sua configuração de serviço no Endpoints: como essa é uma configuração diferente, faça isso mesmo que já o tenha feito para o tutorial. Observe o nome de serviço retornado:

    gcloud endpoints services deploy api_descriptor.pb api_config_auth.yaml --project PRODUCER_PROJECT
    
  2. Crie um cluster de contêiner e autentique kubectl no cluster, caso ainda não tenha feito isso.

  3. Implante a API de amostra e o ESP no cluster. Se você usa projetos separados de produtor e consumidor, primeiro verifique se definiu o projeto apropriado na ferramenta de linha de comando gcloud:

    gcloud config set project PRODUCER_PROJECT
    

Como chamar métodos autenticados a partir de um cliente gRPC

Por fim, no lado do cliente, é possível usar a chave da conta de serviço para gerar um token JWT e usá-lo para chamar um método de Bookstore autenticado. Primeiro instale os requisitos apropriados do Python para gerar o token e executar o cliente de exemplo. Confira se você está na pasta python-docs-samples/endpoints/bookstore-grpc do seu cliente clonado e em seguida:

virtualenv bookstore-env
source bookstore-env/bin/activate
pip install -r requirements.txt

Gerar um token JWT

Neste exemplo, a Bookstore está usando a autenticação serviço a serviço em que o serviço que faz a chamada é simplesmente autenticado pela respectiva conta de serviço. Isso simplifica a criação de um token apropriado para enviar com nossas solicitações. Observe que também é possível exigir uma autenticação serviço a serviço mais restrita, em que o token gerado precisa ser autenticado também pelo Google (usando um token de código do Google).

Neste exemplo, o script Python fornecido gera um token do arquivo de chave JSON, de que foi feito o download anteriormente, usando e-mail de usuário e código fictícios.

#!/usr/bin/env python

# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Example of generating a JWT signed from a service account file."""

import argparse
import json
import time

import google.auth.crypt
import google.auth.jwt

"""Max lifetime of the token (one hour, in seconds)."""
MAX_TOKEN_LIFETIME_SECS = 3600

def generate_jwt(service_account_file, issuer, audiences):
    """Generates a signed JSON Web Token using a Google API Service Account."""
    with open(service_account_file) as fh:
        service_account_info = json.load(fh)

    signer = google.auth.crypt.RSASigner.from_string(
        service_account_info['private_key'],
        service_account_info['private_key_id'])

    now = int(time.time())

    payload = {
        'iat': now,
        'exp': now + MAX_TOKEN_LIFETIME_SECS,
        # aud must match 'audience' in the security configuration in your
        # swagger spec. It can be any string.
        'aud': audiences,
        # iss must match 'issuer' in the security configuration in your
        # swagger spec. It can be any string.
        'iss': issuer,
        # sub and email are mapped to the user id and email respectively.
        'sub': issuer,
        'email': 'user@example.com'
    }

    signed_jwt = google.auth.jwt.encode(signer, payload)
    return signed_jwt

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--file',
                        help='The path to your service account json file.')
    parser.add_argument('--issuer', default='', help='issuer')
    parser.add_argument('--audiences', default='', help='audiences')

    args = parser.parse_args()

    signed_jwt = generate_jwt(args.file, args.issuer, args.audiences)
    print(signed_jwt.decode('utf-8'))

Para gerar um token usando o script:

  • Gere um token JWT e atribua-o à variável $JWT_TOKEN:

    JWT_TOKEN=$(python jwt_token_gen.py \
        --file=[SERVICE_ACCOUNT_FILE] \
        --audiences=[SERVICE_NAME] \
        --issuer=[SERVICE-ACCOUNT-ID])
    

    onde:

    • [SERVICE_ACCOUNT_FILE] é o arquivo de chave JSON da conta de serviço de consumidor salvo;
    • [SERVICE_NAME] é o nome do serviço Bookstore retornado quando você implantou sua configuração de serviço atualizada no Endpoints;
    • [SERVICE-ACCOUNT-ID] é o ID completo da conta de serviço do consumidor quando você gerou sua conta de serviço.

Fazer uma chamada gRPC autenticada

Esta última etapa usa bookstore_client.py (em inglês), que é o mesmo cliente usado nos tutoriais. Para fazer uma chamada autenticada, o cliente transmite o JWT como metadados (em inglês) com a respectiva chamada de método.

def run(host, port, api_key, auth_token, timeout, use_tls, servername_override, ca_path):
    """Makes a basic ListShelves call against a gRPC Bookstore server."""

    if use_tls:
        with open(ca_path, 'rb') as f:
            creds = grpc.ssl_channel_credentials(f.read())
        channel_opts = ()
        if servername_override:
            channel_opts += ((
                        'grpc.ssl_target_name_override', servername_override,),)
        channel = grpc.secure_channel(f'{host}:{port}', creds, channel_opts)
    else:
        channel = grpc.insecure_channel(f'{host}:{port}')

    stub = bookstore_pb2_grpc.BookstoreStub(channel)
    metadata = []
    if api_key:
        metadata.append(('x-api-key', api_key))
    if auth_token:
        metadata.append(('authorization', 'Bearer ' + auth_token))
    shelves = stub.ListShelves(empty_pb2.Empty(), timeout, metadata=metadata)
    print(f'ListShelves: {shelves}')

Para executar o exemplo:

  1. Use kubectl get services para receber o endereço IP externo para o Bookstore implantado:

    #kubectl get services
    NAME                 CLUSTER-IP      EXTERNAL-IP      PORT(S)           AGE
    echo                 10.11.246.240   104.196.186.92   80/TCP            10d
    endpoints            10.11.243.168   104.196.210.50   80/TCP,8090/TCP   10d
    esp-grpc-bookstore   10.11.254.34    104.196.60.37    80/TCP            1d
    kubernetes           10.11.240.1     <none>           443/TCP           10d
    

    Nesse caso, é o serviço esp-grpc-bookstore e seu IP externo é 104.196.60.37.

  2. Atribua o endereço IP à variável EXTERNAL_IP:

    EXTERNAL_IP=104.196.60.37
    
  3. Liste todas as estantes do serviço Bookstore:

    python bookstore_client.py --port=80 --host=$EXTERNAL_IP --auth_token=$JWT_TOKEN
    

    O serviço retorna todas as estantes no Bookstore atual. É possível confirmar isso: se você não fornecer um token ou se especificar um ID incorreto da conta de serviço ao gerar o JWT, haverá uma falha no comando.

A seguir