Autenticación entre servicios

Para implementar la autenticación entre servicios, usa una cuenta de servicio en un servicio de gRPC. En esta página, se muestra la autenticación entre servicios mediante una explicación con un ejemplo completo, que incluye cómo configurar el proxy de servicio extensible (ESP) en un servicio de gRPC para admitir solicitudes autenticadas y cómo llamar al servicio desde un cliente de gRPC.

Para que cualquier servicio pueda hacer llamadas autenticadas en una API de Cloud Endpoints, el servicio de llamadas debe tener una cuenta de servicio y enviar un token de autorización en la llamada. El emisor debe usar un token de ID de Google o un token web JSON (JWT) personalizado que solo firma su cuenta de servicio. El ESP valida que la reclamación iss coincida con el parámetro issuer en la configuración de servicio. El ESP no verifica los permisos de la administración de identidades y accesos que se otorgaron en la cuenta de servicio.

En nuestro ejemplo, debes configurar y usar la forma más simple de autenticación entre servicios, en la que el cliente usa su cuenta de servicio de Google Cloud para generar los JWT de autenticación. El enfoque de otros métodos de autenticación es similar, aunque el proceso del cliente para obtener tokens de autenticación válidos depende del método usado.

Antes de comenzar

En esta guía, se utiliza el ejemplo de librería empleado en nuestros Instructivos.

  1. Clona el repositorio de Git donde se aloja el código de ejemplo de gRPC:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
    
  2. Cambia tu directorio de trabajo:

    cd python-docs-samples/endpoints/bookstore-grpc/
    
  3. Sigue las instrucciones que se mencionan en los Instructivos para configurar un proyecto si aún no tienes uno.

En este ejemplo, utilizas la implementación en Google Kubernetes Engine, aunque la configuración de autenticación es la misma para Compute Engine.

En el ejemplo, hay dos proyectos de Google Cloud Platform a los que se hace referencia:

  • El proyecto del productor del servicio, que es el proyecto que posee Cloud Endpoints para el servicio de gRPC.
  • El proyecto de consumidor del servicio, que es el proyecto que posee el cliente de gRPC.

Cómo crear la clave y la cuenta de servicio de consumidor

Para crear la cuenta de servicio y la clave del proyecto de consumidor, sigue estos pasos:

  1. En la consola de Google Cloud, ve a API y servicios.

    API y servicios

    Asegúrate de que estés en tu proyecto de consumidor.
  2. En la página Credenciales, en la lista desplegable Crear credenciales, selecciona Clave de cuenta de servicio.
  3. En la página Crear clave de cuenta de servicio, si tienes una cuenta de servicio existente que deseas usar, selecciónala. De lo contrario, en la lista desplegable Cuenta de servicio, selecciona Cuenta de servicio nueva y escribe el nombre de la cuenta.

    Se creará el ID de cuenta de servicio correspondiente. Anota el ID, ya que será necesario en las siguientes secciones. Por ejemplo:

    service-account-name@YOUR_PROJECT_ID.iam.gserviceaccount.com
    
  4. Haz clic en la lista desplegable Función y selecciona las siguientes funciones:

    • Cuentas de servicio > Usuario de cuenta de servicio
    • Cuentas de servicio > Creador de token de cuenta de servicio
  5. Asegúrate de que esté seleccionado el tipo de clave JSON.

  6. Haz clic en Crear. El archivo de claves JSON de la cuenta de servicio se descargará en tu equipo local. Toma nota de la ubicación y asegúrate de guardarlo en un lugar seguro, ya que lo necesitarás luego para generar tokens.

Cómo configurar la autenticación para el servicio

Utiliza el proyecto del productor para todos los pasos de esta sección.

Establece la autenticación en la configuración de la API de gRPC

La autenticación para el ESP se configura en la sección authentication del archivo YAML de configuración de la API de gRPC. La configuración con la autenticación para este servicio de ejemplo se encuentra en api_config_auth.yaml.

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

En la sección providers, se especifican los proveedores de autenticación que deseas usar; en este caso, quieres usar una cuenta de servicio de Google como proveedor de autenticación. En la sección rules, se especifica que necesitas tokens de este proveedor para acceder a todos los métodos del servicio.

Realiza estas acciones enn tu copia de este archivo en el repositorio clonado:

  • Cambia MY_PROJECT_ID a tu ID del proyecto de productor.
  • Cambia SERVICE-ACCOUNT-ID en la sección authentication (en los valores issuer y jwks_uri) al ID de la cuenta de servicio de consumidor que anotaste en la sección anterior. Esto le indica a ESP que deseas otorgar acceso a tu servicio a los usuarios que proporcionan tokens válidos desde esta cuenta de servicio particular.
  • También puedes agregar jwt_locations debajo del elemento providers. Puedes usar este valor para definir una ubicación JWT personalizada. Las ubicaciones de JWT predeterminadas son los metadatos Authorization (con el prefijo “Bearer”) y los metadatos X-Goog-Iap-Jwt-Assertion.

Guarda el archivo para el siguiente paso.

Cómo implementar la configuración y el servicio

Estos pasos son los mismos que en Comenzar a usar gRPC en GKE:

  1. Implementa la configuración del servicio en Endpoints: Debes hacerlo incluso si lo hiciste para el instructivo, ya que esta es una configuración diferente. Anota el nombre de servicio que se muestra:

    gcloud endpoints services deploy api_descriptor.pb api_config_auth.yaml --project PRODUCER_PROJECT
    
  2. Crea un clúster de contenedor y autentica kubectl en el clúster si aún no lo hiciste.

  3. Implementa la API y el ESP de ejemplo en el clúster. Si usas proyectos de productor y consumidor diferentes, primero asegúrate de que estableciste el proyecto correspondiente en la herramienta de línea de comandos de gcloud.

    gcloud config set project PRODUCER_PROJECT
    

Llama a los métodos autenticados desde un cliente de gRPC

Por último, en el lado del cliente, puedes usar la clave de cuenta de servicio para generar un token JWT y, luego, usar el token a fin de llamar a un método de Bookstore autenticado. Primero, instala los requisitos de Python apropiados para generar el token y ejecutar el cliente de ejemplo. Asegúrate de que te encuentras en la carpeta python-docs-samples/endpoints/bookstore-grpc de tu cliente clonado, luego realiza lo siguiente:

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

Genera un token JWT

En este ejemplo, la librería utiliza la autenticación entre servicios donde la cuenta de servicio autentica el servicio de llamadas, por lo que es sencillo crear un token adecuado para enviar con las solicitudes. Ten en cuenta que también puedes necesitar una autenticación entre servicios más estricta, en cuyo caso Google debe autenticar el token generado (con un token de Id de Google).

Para este ejemplo, la secuencia de comandos proporcionada de Python puede generar un token del archivo de claves JSON que se descargó antes, con un ID de usuario y un correo electrónico de muestra.

#!/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 generar un token con la secuencia de comandos, realiza lo siguiente:

  • Genera un token JWT y asígnalo a la variable $JWT_TOKEN:

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

    Donde:

    • [SERVICE_ACCOUNT_FILE] es el archivo de claves JSON de la cuenta de servicio de consumidor que se descargó.
    • [SERVICE_NAME] es el nombre del servicio de Bookstore que se mostró cuando implementaste la configuración de servicio actualizada en Endpoints.
    • [SERVICE-ACCOUNT-ID] es el ID completo de la cuenta de servicio de consumidor que se creó cuando generaste tu cuenta de servicio.

Haz una llamada autenticada a gRPC

En este último paso, se usa bookstore_client.py, que es el mismo cliente que se usó en los instructivos. Para hacer una llamada autenticada, el cliente pasa el JWT como metadatos con la llamada al 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 ejecutar el ejemplo, sigue estos pasos:

  1. Usa kubectl get services para obtener la dirección IP externa de la Bookstore implementada:

    #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
    

    En este caso, es el servicio esp-grpc-bookstore y su IP externa es 104.196.60.37.

  2. Asigna la dirección IP a la variable EXTERNAL_IP

    EXTERNAL_IP=104.196.60.37
    
  3. Enumera todas las bibliotecas del servicio de Bookstore:

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

    El servicio muestra todas las bibliotecas en la Bookstore actual. Puedes verificarlo dos veces si no proporcionas un token o si especificas el ID de cuenta de servicio incorrecto cuando generes el JWT. El comando debería fallar.

¿Qué sigue?