Implementa de forma segura en Cloud Run

1. Descripción general

Modificarás los pasos predeterminados para implementar un servicio en Cloud Run para mejorar la seguridad y, luego, verás cómo acceder a la app implementada de forma segura. La app es un "servicio de registro de socios" de la aplicación Cymbal Eats, que usan las empresas que trabajan con Cymbal Eats para procesar pedidos de comida.

Qué aprenderás

Puedes aumentar su seguridad de forma significativa si realizas algunos cambios pequeños en los pasos predeterminados mínimos para implementar una app en Cloud Run. Tomarás una app existente y las instrucciones de implementación, y cambiarás los pasos para mejorar la seguridad de la app implementada.

Luego, verás cómo autorizar el acceso a la app y realizar solicitudes autorizadas.

Esta no es una visión exhaustiva de la seguridad en la implementación de aplicaciones, sino un análisis de los cambios que puedes realizar en todas las implementaciones futuras de tus apps que mejorarán su seguridad con muy poco esfuerzo.

2. Configuración y requisitos

Cómo configurar el entorno a tu propio ritmo

  1. Accede a Google Cloud Console y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla en cualquier momento.
  • El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. por lo general, no te importa qué es. En la mayoría de los codelabs, deberás hacer referencia al ID del proyecto (por lo general, se identifica como PROJECT_ID). Si no te gusta el ID generado, puedes generar otro aleatorio. También puedes probar el tuyo propio y ver si está disponible. No se puede cambiar después de este paso y se mantendrá mientras dure el proyecto.
  • Para tu información, hay un tercer valor, un número de proyecto, que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.
  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no debería costar mucho, tal vez nada. Para cerrar recursos y evitar que se te facture más allá de este instructivo, puedes borrar los recursos que creaste o borrar todo el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 300.

Activar Cloud Shell

  1. En la consola de Cloud, haz clic en Activar Cloud Shell853e55310c205094.png.

55efc1aaa7a4d3ad.png

Si nunca iniciaste Cloud Shell, aparecerá una pantalla intermedia (mitad inferior de la página) que describe de qué se trata. Si ese es el caso, haz clic en Continuar (y no volverás a verlo). Así es como se ve la pantalla única:

9c92662c6a846a5c.png

El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.

9f0e51b578fecce5.png

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitas. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer simplemente con un navegador o tu Chromebook.

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu ID del proyecto.

  1. En Cloud Shell, ejecuta el siguiente comando para confirmar que está autenticado:
gcloud auth list

Resultado del comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Ejecuta el siguiente comando en Cloud Shell para confirmar que el comando de gcloud conoce tu proyecto:
gcloud config list project

Resultado del comando

[core]
project = <PROJECT_ID>

De lo contrario, puedes configurarlo con el siguiente comando:

gcloud config set project <PROJECT_ID>

Resultado del comando

Updated property [core/project].

Configuración del entorno

En este lab, ejecutarás comandos en la línea de comandos de Cloud Shell. Por lo general, puedes copiar los comandos y pegarlos como están, aunque en algunos casos deberás cambiar los valores de marcador de posición por los correctos.

  1. Establece una variable de entorno para el ID del proyecto para usarla en comandos posteriores:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
  1. Habilita la API del servicio de Cloud Run que ejecutará tu app, la API de Firestore que proporcionará almacenamiento de datos NoSQL, la API de Cloud Build que usará el comando de implementación y Artifact Registry que se usará para almacenar el contenedor de la aplicación cuando se compile:
gcloud services enable \
  run.googleapis.com \
  firestore.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  1. Inicializa la base de datos de Firestore en modo nativo. Ese comando usa la API de App Engine, por lo que primero se debe habilitar.

El comando debe especificar una región para App Engine, que no usaremos, pero debemos crear por razones históricas, y una región para la base de datos. Usaremos us-central para App Engine y nam5 para la base de datos. nam5 es la ubicación multirregional de los Estados Unidos. Las ubicaciones multirregionales maximizan la disponibilidad y durabilidad de la base de datos.

gcloud services enable appengine.googleapis.com

gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
  1. Clona el repositorio de la app de ejemplo y navega al directorio
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. Revisa el archivo README

Abre el editor y observa los archivos que componen la aplicación. Consulta README.md, que describe los pasos necesarios para implementar esta app. Algunos de estos pasos pueden implicar decisiones de seguridad implícitas o explícitas que se deben considerar. Cambiarás algunas de estas opciones para mejorar la seguridad de la app implementada, como se describe a continuación:

Paso 3: Ejecuta npm install

Es importante conocer el origen y la integridad del software de terceros que se usa en una app. Administrar la seguridad de la cadena de suministro del software es relevante para compilar cualquier software, no solo las apps implementadas en Cloud Run. Este lab se enfocó en la implementación, por lo que no aborda esta área, pero quizás debas investigar el tema por separado.

Pasos 4 y 5: Edita y ejecuta deploy.sh

Con estos pasos, se implementa la app en Cloud Run y se dejan la mayoría de las opciones con sus valores predeterminados. Modificarás este paso para que la implementación sea más segura de dos maneras clave:

  1. No permitas el acceso sin autenticación. Puede ser conveniente permitir eso para probar cosas durante la exploración, pero este es un servicio web para que lo usen socios comerciales y siempre debe autenticar a sus usuarios.
  2. Especifica que la aplicación debe usar una cuenta de servicio dedicada y personalizada solo con los privilegios necesarios, en lugar de una predeterminada que probablemente tenga más acceso a la API y los recursos de lo necesario. Esto se conoce como el principio de privilegio mínimo y es un concepto fundamental de la seguridad para aplicaciones.

Pasos del 6 al 11: Realiza solicitudes web de muestra para verificar el comportamiento correcto

Dado que la implementación de la aplicación ahora requiere autenticación, estas solicitudes deben incluir una prueba de la identidad del solicitante. En lugar de modificar estos archivos, realizarás solicitudes directamente desde la línea de comandos.

4. Implementa el servicio de forma segura

Se identificaron dos cambios necesarios en la secuencia de comandos de deploy.sh: no permitir el acceso no autenticado y usar una cuenta de servicio dedicada con privilegios mínimos.

Primero, crearás una nueva cuenta de servicio. Luego, editarás la secuencia de comandos deploy.sh para hacer referencia a ella y deshabilitar el acceso no autenticado. Luego, implementarás el servicio ejecutando la secuencia de comandos modificada antes de que podamos ejecutar la secuencia de comandos deploy.sh modificada.

Crear una cuenta de servicio y otorgarle el acceso necesario a Firestore/Datastore

gcloud iam service-accounts create partner-sa

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role=roles/datastore.user

Editar deploy.sh

Modifica el archivo deploy.sh para inhabilitar el acceso no autenticado(–no-allow-unauthenticated) y especificar la cuenta de servicio nueva(–service-account) para la app implementada. Corrige GOOGLE_PROJECT_ID para que sea el ID de tu propio proyecto.

Eliminarás las dos primeras líneas y cambiarás otras tres líneas, como se muestra a continuación.

gcloud run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region ${REGION} \
  --no-allow-unauthenticated \
  --project=$PROJECT_ID \
  --service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com

Implemente el servicio

Desde la línea de comandos, ejecuta la secuencia de comandos deploy.sh:

./deploy.sh

Cuando se complete la implementación, la última línea del resultado del comando mostrará la URL de Service de la app nueva. Guarda la URL en una variable de entorno:

export SERVICE_URL=<URL from last line of command output>

Ahora intenta recuperar un pedido desde la app con la herramienta curl:

curl -i -X GET $SERVICE_URL/partners

La marca -i del comando curl le indica que incluya encabezados de respuesta en el resultado. La primera línea del resultado debería ser la siguiente:

HTTP/2 403

La app se implementó con la opción Inhabilitar las solicitudes no autenticadas. Este comando curl no contiene información de autenticación, por lo que Cloud Run lo rechaza. La aplicación implementada en sí ni siquiera ejecuta ni recibe datos de esta solicitud.

5. Realiza solicitudes autenticadas

La app implementada se invoca a través de solicitudes web, que ahora se deben autenticar para que Cloud Run las permita. Para autenticar las solicitudes web, se incluye un encabezado Authorization con el siguiente formato:

Authorization: Bearer identity-token

El token de identidad es una cadena codificada con firma criptográfica de corto plazo y emitida por un proveedor de autenticación de confianza. En este caso, se requiere un token de identidad válido y vigente emitido por Google.

Cómo realizar una solicitud como tu cuenta de usuario

La herramienta de Google Cloud CLI puede proporcionar un token para el usuario autenticado predeterminado. Ejecuta este comando para obtener un token de identidad para tu cuenta y guárdalo en la variable de entorno ID_TOKEN:

export ID_TOKEN=$(gcloud auth print-identity-token)

De forma predeterminada, los tokens de identidad emitidos por Google son válidos por una hora. Ejecuta el siguiente comando curl para crear la solicitud que se rechazó antes porque no estaba autorizada. Este comando incluirá el encabezado necesario:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

El resultado del comando debe comenzar con HTTP/2 200, lo que indica que la solicitud es aceptable y se está respetando. Si esperas una hora y vuelves a realizar esta solicitud, esta fallará porque el token habrá vencido. El cuerpo de la respuesta se encuentra al final del resultado, después de una línea en blanco:

{"status":"success","data":[]}

Aún no hay socios.

Registra socios usando los datos JSON de muestra en el directorio con dos comandos curl:

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner.json" \
  $SERVICE_URL/partner

y

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner2.json" \
  $SERVICE_URL/partner

Repita la solicitud GET anterior para ver ahora a todos los socios registrados:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Deberías ver datos JSON con mucho más contenido, lo que brinda información sobre los dos socios registrados.

Cómo hacer una solicitud como una cuenta no autorizada

La solicitud autenticada que se realizó en el último paso tuvo éxito no solo porque se autenticó, sino también porque se autorizó al usuario autenticado (tu cuenta). Es decir, la cuenta tenía permiso para invocar a la app. No todas las cuentas autenticadas tienen autorización para realizar esta acción.

Se autorizó la cuenta predeterminada que se usó en la solicitud anterior porque es la que creó el proyecto que contiene la app y, de forma predeterminada, la que le otorgó permiso para invocar cualquier aplicación de Cloud Run en la cuenta. Ese permiso se puede revocar si es necesario, lo cual sería conveniente en una aplicación de producción. En lugar de hacerlo ahora, crearás una nueva cuenta de servicio sin privilegios ni roles asignados y la usarás para intentar acceder a la app implementada.

  1. Crea una cuenta de servicio llamada tester.
gcloud iam service-accounts create tester
  1. Obtendrás un token de identidad para esta cuenta nueva de la misma forma en que obtuviste uno para tu cuenta predeterminada anteriormente. Sin embargo, esto requiere que tu cuenta predeterminada tenga permiso para actuar en nombre de las cuentas de servicio. Otorgue este permiso a su cuenta.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="user:$USER_EMAIL" \
  --role=roles/iam.serviceAccountTokenCreator
  1. Ahora, ejecuta el siguiente comando a fin de guardar un token de identidad para esta cuenta nueva en la variable de entorno TEST_IDENTITY. Si el comando muestra un mensaje de error, espera uno o dos minutos y vuelve a intentarlo.
export TEST_TOKEN=$( \
  gcloud auth print-identity-token \
    --impersonate-service-account \
    "tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
  1. Realiza la solicitud web autenticada como antes, pero con este token de identidad:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

El resultado del comando comenzará otra vez con HTTP/2 403 porque la solicitud, aunque autenticada, no está autorizada. La cuenta de servicio nueva no tiene permiso para invocar esta app.

Cómo autorizar una cuenta

Un usuario o una cuenta de servicio debe tener el rol de Invocador de Cloud Run en un servicio de Cloud Run para hacerle solicitudes. Asigna ese rol a la cuenta de servicio del verificador con el siguiente comando:

export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
  --member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
  --role=roles/run.invoker \
  --region=${REGION}

Espera uno o dos minutos para que se actualice el rol nuevo, repite la solicitud autenticada. Guarda un TEST_TOKEN nuevo si transcurrió una hora o más desde que se guardó por primera vez.

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

El resultado del comando ahora comienza con HTTP/1.1 200 OK y la última línea contiene la respuesta JSON. Cloud Run aceptó esta solicitud y la app la procesó.

6. Diferencias entre autenticar programas y autenticar usuarios

En las solicitudes autenticadas que realizaste ahora se usa la herramienta de línea de comandos de curl. Hay otras herramientas y lenguajes de programación que podrían haberse usado en su lugar. Sin embargo, las solicitudes autenticadas de Cloud Run no se pueden realizar con un navegador web con páginas web simples. Si un usuario hace clic en un vínculo o en un botón para enviar un formulario en una página web, el navegador no agregará el encabezado Authorization que Cloud Run requiere para las solicitudes autenticadas.

El mecanismo de autenticación integrado de Cloud Run está diseñado para que lo usen los programas, no los usuarios finales.

Nota:

Cloud Run puede alojar aplicaciones web para usuarios, pero esas aplicaciones deben configurar Cloud Run para que permita las solicitudes no autenticadas de los usuarios navegadores web. Si las aplicaciones requieren autenticación del usuario, la aplicación debe controlarlo en lugar de pedirle a Cloud Run que lo haga. La aplicación puede hacerlo de la misma manera que lo hacen las aplicaciones web fuera de Cloud Run. Cómo hacerlo está fuera del alcance de este codelab.

Es posible que hayas notado que las respuestas a las solicitudes de ejemplo hasta ahora eran objetos JSON, no páginas web. Esto se debe a que este servicio de registro de socios está diseñado para que lo usen los programas, y JSON es un formato conveniente para que lo usen. A continuación, escribirás y ejecutarás un programa para consumir y usar estos datos.

Solicitudes autenticadas de un programa de Python

Un programa puede realizar solicitudes autenticadas de una aplicación protegida de Cloud Run a través de solicitudes web HTTP estándar, pero con un encabezado Authorization. El único desafío nuevo para esos programas es obtener un token de identidad válido y no vencido que se pueda colocar en ese encabezado. Cloud Run validará ese token con Google Cloud Identity and Access Management (IAM), por lo que una autoridad reconocida por IAM debe emitir y firmar el token. Hay bibliotecas cliente disponibles en muchos lenguajes que los programas pueden usar para solicitar la emisión de un token de este tipo. La biblioteca cliente que se usará en este ejemplo es la de Python google.auth. Existen varias bibliotecas de Python para realizar solicitudes web en general; En este ejemplo, se usa el popular módulo requests.

El primer paso es instalar las dos bibliotecas cliente:

pip install google-auth
pip install requests

El código de Python que solicita un token de identidad para el usuario predeterminado es el siguiente:

credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token

Si usas una shell de comandos como Cloud Shell o la shell de la terminal estándar en tu computadora, el usuario predeterminado es el que se haya autenticado dentro de esa shell. En Cloud Shell, por lo general, es el usuario que accedió a Google. En otros casos, es cualquier usuario autenticado con gcloud auth login o con otro comando gcloud. Si el usuario nunca accedió, no habrá ningún usuario predeterminado y este código fallará.

Para un programa que realiza solicitudes a otro programa, por lo general, no se recomienda usar la identidad de una persona, sino la del programa solicitante. Para eso se usan las cuentas de servicio. Implementaste el servicio de Cloud Run con una cuenta de servicio dedicada que proporciona la identidad que usa cuando realiza solicitudes a la API, como Cloud Firestore. Cuando un programa se ejecuta en una plataforma de Google Cloud, las bibliotecas cliente usarán automáticamente la cuenta de servicio asignada a él como su identidad predeterminada, por lo que el mismo código de programa funciona en ambas situaciones.

El código de Python para realizar una solicitud con un encabezado de autorización agregado es el siguiente:

auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)

El siguiente programa completo de Python realizará una solicitud autenticada al servicio de Cloud Run para recuperar todos los socios registrados y, luego, imprimir sus nombres y los IDs asignados. Copia y ejecuta el siguiente comando para guardar este código en el archivo print_partners.py.

cat > ./print_partners.py << EOF
def print_partners():
    import google.auth
    import google.auth.transport.requests
    import requests

    credentials, _ = google.auth.default()
    credentials.refresh(google.auth.transport.requests.Request())
    identity_token = credentials.id_token

    auth_header = {"Authorization": "Bearer " + identity_token}
    response = requests.get("${SERVICE_URL}/partners", headers=auth_header)

    parsed_response = response.json()
    partners = parsed_response["data"]

    for partner in partners:
        print(f"{partner['partnerId']}: {partner['name']}")


print_partners()
EOF

Ejecutarás este programa con un comando shell. Deberás autenticarte como el usuario predeterminado primero para que el programa pueda usar esas credenciales. Ejecuta el comando gcloud auth que se muestra a continuación:

gcloud auth application-default login

Sigue las instrucciones para completar el acceso. Luego, ejecuta el programa desde la línea de comandos:

python print_partners.py

El resultado se verá similar al siguiente:

10102: Zippy food delivery
67292: Foodful

La solicitud del programa llegó al servicio de Cloud Run porque se autenticó con tu identidad. Eres el propietario de este proyecto y, por lo tanto, tienes autorización para ejecutarlo de forma predeterminada. Sería más común que este programa se ejecutara bajo la identidad de una cuenta de servicio. Cuando se ejecuta en la mayoría de los productos de Google Cloud, como Cloud Run o App Engine, la identidad predeterminada sería una cuenta de servicio y se usará en lugar de una cuenta personal.

7. ¡Felicitaciones!

¡Felicitaciones! Completaste el codelab.

¿Qué sigue?

Explora otros codelabs de Cymbal Eats:

Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.