Inyecta Secretos en Cloud Run sin exponer credenciales
Diseñar un microservicio seguro en Google Cloud no consiste simplemente en esconder una contraseña. La arquitectura correcta separa configuración, identidad y material secreto para que las credenciales de base de datos, API keys y tokens permanezcan fuera del código fuente, de las imágenes de contenedor y de los pipelines de CI/CD.
En esta guía de nivel Senior / Staff / Architect construiremos el patrón Cloud Run + Google Secret Manager + IAM + Terraform, analizaremos sus trade-offs y veremos por qué el verdadero control de seguridad está en quién puede leer cada secreto, no en ocultar una variable de entorno.
Lo que aprenderás
Separar secretos, configuración e identidad de ejecución.
Conceder acceso únicamente al secreto que necesita cada workload.
Declarar Cloud Run, Secret Manager e IAM sin incrustar credenciales.
Detectar estados Terraform, logs, imágenes y pipelines como superficies de riesgo.
Un secreto no es una variable de configuración normal
Una URL de una API pública puede formar parte de la configuración de una aplicación. Una contraseña de PostgreSQL, un token privado o una clave API no debería tratarse igual. Su ciclo de vida incluye creación, autorización, consumo, rotación, auditoría y revocación.
En Cloud Run, la identidad de ejecución del servicio es una pieza central del diseño: es esa identidad la que debe tener permiso para acceder al secreto. Google recomienda utilizar una cuenta de servicio administrada por el usuario con el conjunto mínimo de permisos necesario, en lugar de ampliar innecesariamente los permisos de la identidad predeterminada.
Secret Manager
Almacena el material sensible y permite controlar el acceso mediante IAM. El secreto debe vivir fuera del repositorio y de la imagen del contenedor.
Cloud Run
Ejecuta el workload con una identidad explícita. Cloud Run puede consumir secretos como variables de entorno o mediante volúmenes montados.
IAM
Define la frontera real de seguridad: qué principal puede acceder a qué secreto y a qué nivel de la jerarquía de recursos.
¿Dónde debe vivir el secreto?
El error arquitectónico más frecuente es confundir mecanismo de entrega con sistema de gestión de secretos. Una variable de entorno no es un gestor de secretos: es simplemente una superficie de exposición durante el ciclo de ejecución.
| Patrón | Seguridad | Latencia | Operación | Coste | Uso recomendado |
|---|---|---|---|---|---|
| Secret Manager → ENV | Alta si IAM está correctamente restringido | Muy baja durante el runtime tras la inicialización | Baja | Bajo | Credenciales de DB, API keys, tokens y configuración sensible estable. |
| Secret Manager → volumen | Alta | Muy baja para el proceso consumidor | Media | Bajo | Aplicaciones que esperan archivos o necesitan separar físicamente el contenido. |
| Secret embebido en imagen | Crítica / mala práctica | Muy baja | Alta deuda de seguridad | Bajo | No recomendado. El secreto queda ligado al artefacto. |
| Secreto en repositorio | Crítica / mala práctica | Irrelevante | Alta deuda de seguridad | Bajo | No recomendado. Git no es un sistema de gestión de secretos. |
| Secreto como texto en CI/CD | Variable | Baja | Media / alta | Variable | Solo cuando el proveedor y el pipeline están diseñados para manejar secretos correctamente. |
El error no es usar variables de entorno: es perder el control de su origen
⚠ Antipatrón: copiar el secreto dentro del pipeline
Un patrón aparentemente cómodo consiste en almacenar una contraseña como variable del sistema de CI/CD y ejecutar un comando que la inyecta directamente durante el despliegue.
El problema es que ahora hay que auditar otra cadena completa: configuración del runner, logs, masking, comandos ejecutados, artefactos, debugging y permisos del pipeline.
El patrón más limpio es que Terraform declare la referencia al secreto y que la identidad de ejecución de Cloud Run sea quien tenga acceso al payload mediante IAM.
☠ Antipatrón FinOps + Security: permisos a nivel de proyecto sin necesidad
Conceder roles/secretmanager.secretAccessor a una cuenta de servicio sobre todo el proyecto puede funcionar técnicamente, pero rompe el principio de mínimo privilegio.
Si el workload solo necesita leer DB_PASSWORD, la autorización debería limitarse a ese secreto cuando el diseño lo permita. De esta forma, comprometer una aplicación no implica automáticamente comprometer todos los secretos accesibles en el proyecto.
Cloud Run + Secret Manager + Terraform
El patrón de referencia tiene cuatro piezas: secreto, versión del secreto, cuenta de servicio de Cloud Run y binding IAM que permite a esa cuenta acceder únicamente al secreto requerido.
Para variables de entorno, Cloud Run puede resolver el secreto antes de iniciar la instancia. Google recomienda fijar una versión concreta del secreto en lugar de depender de latest, especialmente cuando se busca reproducibilidad y control operacional.
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 7.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
type = string
description = "Google Cloud project ID"
}
variable "region" {
type = string
default = "europe-west1"
}
# -------------------------------------------------------------------
# 1. Secret Manager
# -------------------------------------------------------------------
resource "google_secret_manager_secret" "db_password" {
secret_id = "orders-db-password"
replication {
auto {}
}
}
# En un entorno real, evita introducir valores productivos
# directamente en el código Terraform.
resource "google_secret_manager_secret_version" "db_password" {
secret = google_secret_manager_secret.db_password.name
secret_data = var.db_password
}
variable "db_password" {
type = string
sensitive = true
}
# -------------------------------------------------------------------
# 2. Identidad específica para Cloud Run
# -------------------------------------------------------------------
resource "google_service_account" "cloud_run" {
account_id = "orders-api-runtime"
display_name = "Runtime identity for Orders API"
}
# -------------------------------------------------------------------
# 3. Mínimo privilegio: acceso únicamente al secreto necesario
# -------------------------------------------------------------------
resource "google_secret_manager_secret_iam_member" "db_password_accessor" {
secret_id = google_secret_manager_secret.db_password.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cloud_run.email}"
}
# -------------------------------------------------------------------
# 4. Cloud Run
# -------------------------------------------------------------------
resource "google_cloud_run_v2_service" "orders_api" {
name = "orders-api"
location = var.region
template {
service_account = google_service_account.cloud_run.email
containers {
image = "europe-west1-docker.pkg.dev/PROJECT_ID/apps/orders-api:1.0.0"
env {
name = "DB_PASSWORD"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_password.id
version = google_secret_manager_secret_version.db_password.version
}
}
}
env {
name = "APP_ENV"
value = "production"
}
}
}
depends_on = [
google_secret_manager_secret_iam_member.db_password_accessor
]
}
Nota de arquitectura: el ejemplo anterior demuestra el patrón completo, pero para producción conviene analizar cuidadosamente cómo se suministra var.db_password. Marcar una variable como sensitive evita exposiciones accidentales en ciertas salidas, pero no convierte automáticamente el estado de Terraform en un almacén de secretos seguro.
La arquitectura correcta va más allá del Terraform
Identidad dedicada
No reutilices una única cuenta de servicio con acceso a decenas de secretos para todos los microservicios. Una identidad por workload o por dominio de confianza reduce el blast radius.
Mínimo privilegio
El rol roles/secretmanager.secretAccessor permite acceder al payload. Concederlo al nivel del secreto es preferible cuando el workload solo necesita un conjunto reducido de secretos.
Versionado explícito
Referenciar una versión concreta permite relacionar una revisión de Cloud Run con una versión conocida del secreto. Esto mejora rollback, reproducibilidad y troubleshooting.
Rotación sin acoplamiento
La aplicación no debería necesitar que el secreto aparezca en Git para poder desplegarse. Secret Manager debe ser la fuente de verdad del material secreto, mientras que Cloud Run recibe una referencia.
Logs sin secretos
Nunca registres variables sensibles para diagnosticar errores. Evita excepciones, dumps, trazas HTTP y mensajes de debug que puedan imprimir headers o credenciales.
Separación de responsabilidades
El equipo que despliega el servicio no tiene por qué ser el mismo principal que administra el contenido de todos los secretos. IAM permite construir fronteras operativas más precisas.
¿Variable de entorno o secreto montado como archivo?
Ambas estrategias son válidas. La elección debe depender del contrato de la aplicación y de cómo consume el secreto.
| Criterio | Variable de entorno | Volumen de secretos |
|---|---|---|
| Integración | Excelente para aplicaciones que leen configuración desde ENV. | Excelente para software que espera un archivo. |
| Inicialización | Cloud Run obtiene el secreto antes de iniciar la instancia. | El proceso consume el contenido desde el volumen. |
| Uso típico | DB_PASSWORD, API_TOKEN, JWT_SECRET. | Certificados, claves o aplicaciones con configuración basada en archivos. |
| Consideración | Evitar imprimir el entorno completo en logs o diagnósticos. | Controlar cuidadosamente permisos y rutas de lectura. |
Consumir el secreto desde Python sin acoplarse a GCP
Una buena aplicación no debería conocer la implementación concreta del secreto. Desde el punto de vista del proceso, DB_PASSWORD es simplemente una configuración sensible proporcionada por el entorno de ejecución.
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class Settings:
database_url: str
db_password: str
environment: str
def load_settings() -> Settings:
"""
Lee configuración inyectada por el entorno de ejecución.
La aplicación no necesita llamar directamente a Secret Manager.
Cloud Run resuelve el secreto antes de iniciar la instancia.
"""
password = os.getenv("DB_PASSWORD")
if not password:
raise RuntimeError(
"DB_PASSWORD is required but was not provided"
)
database_url = os.getenv(
"DATABASE_URL",
"postgresql://orders-db:5432/orders"
)
environment = os.getenv(
"APP_ENV",
"development"
)
return Settings(
database_url=database_url,
db_password=password,
environment=environment,
)
settings = load_settings()
Esta separación es importante: el código de negocio no necesita importar un SDK de Secret Manager únicamente para obtener una contraseña que Cloud Run ya puede proporcionar como parte de la configuración del proceso.
Checklist para llevarlo a producción
-
Clasifica el dato. Determina qué valores son secretos y cuáles son configuración pública.
-
Crea el secreto. Utiliza Google Secret Manager como sistema de almacenamiento del material sensible.
-
Define una identidad de runtime. Crea una cuenta de servicio dedicada para Cloud Run.
-
Aplica IAM sobre el recurso más pequeño posible. Si el servicio necesita un único secreto, evita conceder acceso a todos los secretos del proyecto.
-
Referencia el secreto desde Cloud Run. Usa variables de entorno o volúmenes según el contrato de la aplicación.
-
Fija versiones. Usa una versión explícita cuando necesites reproducibilidad y control de rollbacks.
-
Audita Terraform. Comprueba dónde puede aparecer material sensible, especialmente en el estado y en automatizaciones.
-
Audita CI/CD. El pipeline debe poder desplegar referencias sin convertir el secreto en texto de logs o artefactos.
-
Elimina secretos de logs. Revisa debugging, tracing, errores HTTP, dumps y métricas.
-
Diseña rotación y revocación. Un secreto seguro también debe poder cambiarse y revocarse sin rediseñar el servicio.
El patrón que recomiendo para microservicios empresariales
Para un servicio Cloud Run estándar, mi arquitectura de referencia es:
Secret Manager
Fuente de verdad del material secreto, con versiones y políticas IAM.
Cloud Run Identity
Cuenta de servicio dedicada que recibe únicamente los permisos necesarios para ejecutar el workload.
Terraform
Describe infraestructura, IAM y referencias sin convertir el repositorio en un almacén de contraseñas.
Principio clave
El secreto debe viajar lo menos posible. El objetivo no es simplemente evitar que aparezca en Git. El objetivo es reducir el número de sistemas, identidades, artefactos, procesos y personas que pueden llegar a observar su valor.
Preguntas frecuentes sobre Secret Manager y Cloud Run
¿Cuál es la forma recomendada de pasar secretos a Cloud Run?
Una arquitectura habitual consiste en almacenar el secreto en Google Secret Manager, conceder a la identidad de ejecución de Cloud Run el rol roles/secretmanager.secretAccessor sobre el recurso necesario y referenciar el secreto desde Cloud Run como variable de entorno o volumen. La documentación oficial de Google contempla ambas modalidades.
¿Debo guardar contraseñas directamente en Terraform?
No es una buena práctica tratar el código Terraform como almacén de secretos. Incluso cuando una variable se marca como sensible, hay que analizar el ciclo de vida completo del valor y, especialmente, la gestión del estado de Terraform. La configuración declarativa debe separar referencias y metadatos de los valores secretos siempre que sea posible.
¿Qué permisos necesita la cuenta de servicio de Cloud Run?
Necesita permiso para acceder al payload del secreto utilizado por el servicio. El rol roles/secretmanager.secretAccessor permite acceder a los valores de las versiones de secretos. Para aplicar mínimo privilegio, es preferible concederlo al nivel más bajo compatible con la arquitectura.
Secretos seguros en GCP = identidad + IAM + ciclo de vida
La solución profesional para gestionar credenciales en Cloud Run no consiste en esconder una contraseña dentro de otra capa de configuración. Consiste en construir una cadena de confianza explícita: Secret Manager → IAM → identidad de Cloud Run → aplicación.
Terraform completa el modelo declarando la infraestructura y las relaciones entre recursos, pero no elimina la necesidad de gobernar el estado, los pipelines y las identidades que participan en el despliegue.
Cuando esta separación se mantiene, un microservicio puede cambiar de imagen, escalar horizontalmente y evolucionar su pipeline sin convertir las credenciales de producción en parte del artefacto de software.
Google Cloud Documentation · Cloud Run · Secret Manager · IAM.
La implementación descrita sigue el modelo documentado por Google para configurar secretos en Cloud Run y conceder a la identidad de ejecución el acceso mediante roles/secretmanager.secretAccessor.
