Lab práctico · Semana 7: Seguridad en Google Cloud: identidad, perímetros, cifrado y cadena de suministro
SSH por IAP sin IP pública e impersonación de cuentas de servicio
Qué vas a construir
Una VM sin IP pública, a la que entrarás por SSH a través de IAP TCP forwarding, que corre con una cuenta de servicio dedicada y con permisos mínimos. Después operarás como esa cuenta desde Cloud Shell mediante impersonación, sin descargar ninguna clave.
flowchart LR
CS["Cloud Shell (tu usuario)"] -->|"gcloud compute ssh --tunnel-through-iap"| IAP["IAP (35.235.240.0/20)"]
IAP -->|"TCP 22"| VM["vm-lab15 sin IP pública"]
VM -->|"token del servidor de metadatos"| API["Cloud Storage (solo lectura)"]
CS -.->|"--impersonate-service-account"| SA["sa-lab15"]
Antes de empezar
- Proyecto dedicado con facturación y la red
default(o una VPC con una subred en Madrid; verlab-06-vpc-firewall-nat). - Cloud Shell abierto.
export PROJECT_ID=$(gcloud config get-value project)
export REGION=europe-southwest1
export ZONE=europe-southwest1-a
export SA=sa-lab15@${PROJECT_ID}.iam.gserviceaccount.com
export BUCKET=gs://${PROJECT_ID}-lab15
export YO=$(gcloud config get-value account)
gcloud services enable compute.googleapis.com iap.googleapis.com iamcredentials.googleapis.com
Si tu red no se llama default, sustituye --network=default en los comandos.
Paso 1: cuenta de servicio con mínimo privilegio
Creamos una cuenta de servicio para la VM y un bucket de prueba sobre el que solo podrá leer.
gcloud iam service-accounts create sa-lab15 --display-name="VM del lab 15"
gcloud storage buckets create $BUCKET --location=$REGION --uniform-bucket-level-access
echo "hola desde el bucket" > saludo.txt
gcloud storage cp saludo.txt $BUCKET/
gcloud storage buckets add-iam-policy-binding $BUCKET \
--member="serviceAccount:$SA" --role="roles/storage.objectViewer"
Fíjate en que el rol se concede en el bucket, no en el proyecto. Comprueba que la cuenta no tiene claves gestionadas por el usuario (y que así seguirá):
gcloud iam service-accounts keys list --iam-account=$SA --managed-by=user
La lista debe estar vacía.
Paso 2: VM sin IP pública que corre como esa cuenta
Una VM sin IP externa no puede llegar a las APIs de Google (Cloud Storage, por ejemplo) salvo que la subred tenga Private Google Access. Lo activamos primero en la subred de Madrid:
gcloud compute networks subnets update default --region=$REGION --enable-private-ip-google-access
--no-address impide la IP externa. --service-account y --scopes=cloud-platform hacen que los permisos los decida solo IAM (la forma recomendada; los scopes heredados limitaban por API). Activamos también OS Login para que el acceso SSH dependa de IAM y no de claves en metadatos.
gcloud compute instances create vm-lab15 \
--zone=$ZONE --machine-type=e2-micro \
--image-family=debian-12 --image-project=debian-cloud \
--network=default --no-address \
--service-account=$SA --scopes=cloud-platform \
--metadata=enable-oslogin=TRUE \
--shielded-secure-boot
Para crear una VM que corre como sa-lab15, necesitas iam.serviceAccounts.actAs sobre ella (rol roles/iam.serviceAccountUser); como Owner ya lo tienes, pero en un equipo real es el permiso que controla quién puede «prestar» una identidad a una VM.
Paso 3: permitir IAP en el firewall y conceder el acceso
IAP llega a tus VMs desde el rango 35.235.240.0/20. Abrimos solo el puerto 22 y solo desde ese rango. Si existe la regla default-allow-ssh (abierta a 0.0.0.0/0), bórrala: con IAP ya no hace falta.
gcloud compute firewall-rules create allow-ssh-from-iap \
--network=default --direction=INGRESS --action=allow \
--rules=tcp:22 --source-ranges=35.235.240.0/20
gcloud compute firewall-rules delete default-allow-ssh --quiet 2>/dev/null || true
Permisos necesarios para el usuario (como Owner ya los tienes; en producción se concederían a un grupo de administradores):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$YO" --role="roles/iap.tunnelResourceAccessor"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$YO" --role="roles/compute.osAdminLogin"
En la consola: Security → Identity-Aware Proxy → SSH and TCP resources muestra las VMs y quién puede crear túneles hacia ellas.
Paso 4: conectarte por SSH a través de IAP
gcloud compute ssh vm-lab15 --zone=$ZONE --tunnel-through-iap
Ya dentro de la VM, comprueba la identidad que usa y que el servidor de metadatos le da tokens sin ninguna clave en disco:
curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email; echo
gcloud storage cat gs://PROYECTO-lab15/saludo.txt # sustituye PROYECTO: funciona (objectViewer)
echo x | gcloud storage cp - gs://PROYECTO-lab15/x.txt # falla con 403: no puede escribir
exit
La VM no tiene IP pública ni Cloud NAT: llega a las APIs de Google gracias a Private Google Access, que activaste en la subred en el paso 2. Sin él, gcloud storage se quedaría esperando hasta agotar el tiempo.
Paso 5: impersonar la cuenta de servicio desde Cloud Shell
Para probar exactamente lo que puede hacer la cuenta, sin claves, date permiso para generar tokens en su nombre:
gcloud iam service-accounts add-iam-policy-binding $SA \
--member="user:$YO" --role="roles/iam.serviceAccountTokenCreator"
Ahora ejecuta órdenes como sa-lab15 (espera uno o dos minutos si IAM aún no ha propagado):
gcloud storage cat $BUCKET/saludo.txt --impersonate-service-account=$SA # funciona
gcloud compute instances list --impersonate-service-account=$SA # falla: sin permisos de Compute
gcloud auth print-access-token --impersonate-service-account=$SA | cut -c1-20; echo "..."
El token impreso es de corta duración (por defecto una hora) y lo emite iamcredentials.googleapis.com. Cada emisión queda registrada en los audit logs de IAM, así que sabes qué persona actuó como la cuenta de servicio, algo imposible de saber con una clave JSON compartida.
También puedes impersonar para toda la sesión y volver atrás:
gcloud config set auth/impersonate_service_account $SA
gcloud storage ls $BUCKET
gcloud config unset auth/impersonate_service_account
Comprueba que funciona
gcloud compute instances describe vm-lab15 --zone=$ZONE --format="value(networkInterfaces[0].accessConfigs)"devuelve vacío: no hay IP pública.- El SSH por IAP funciona. Si borras la regla
allow-ssh-from-iap, deja de funcionar: el único camino de entrada es el rango de IAP. (Nota: las versiones recientes degcloud compute sshusan IAP automáticamente cuando la VM no tiene IP externa, pero conviene indicar--tunnel-through-iapde forma explícita.) - Dentro de la VM y por impersonación, la cuenta lee el bucket pero no escribe ni lista VMs.
gcloud iam service-accounts keys list --iam-account=$SA --managed-by=usersigue vacío.- En Logging → Logs Explorer, busca
protoPayload.methodName="GenerateAccessToken"para ver las impersonaciones (son Data Access logs de IAM; si no aparecen, activa los audit logs de lectura de datos para Identity and Access Management API en IAM → Audit Logs).
Limpieza
gcloud compute instances delete vm-lab15 --zone=$ZONE --quiet
gcloud compute firewall-rules delete allow-ssh-from-iap --quiet
gcloud storage rm --recursive $BUCKET
gcloud iam service-accounts delete $SA --quiet
gcloud compute networks subnets update default --region=$REGION --no-enable-private-ip-google-access # solo si lo activaste para este lab
gcloud projects remove-iam-policy-binding $PROJECT_ID \
--member="user:$YO" --role="roles/iap.tunnelResourceAccessor"
gcloud projects remove-iam-policy-binding $PROJECT_ID \
--member="user:$YO" --role="roles/compute.osAdminLogin"
rm -f saludo.txt
Si borraste default-allow-ssh y otros labs la necesitan, vuelve a crearla solo si de verdad hace falta (mejor usa IAP también en ellos).
Preguntas para pensar como arquitecto
¿Por qué IAP TCP forwarding es mejor que un bastión con IP pública?
No expone ningún puerto a Internet (solo al rango de IAP), la autorización es IAM por usuario o grupo (iap.tunnelResourceAccessor), puede exigir niveles de acceso de contexto (dispositivo, IP, país), cada conexión queda auditada y no hay una VM bastión que parchear y vigilar. Combinado con OS Login, tampoco hay claves SSH repartidas en metadatos.
Un script de un administrador usa hoy una clave JSON de una cuenta de servicio con rol Editor. ¿Qué propones?
Eliminar la clave, conceder al grupo de administradores roles/iam.serviceAccountTokenCreator sobre una cuenta de servicio con roles mínimos (no Editor) y ejecutar el script con --impersonate-service-account o con ADC impersonada. Después, aplicar la política de organización que impide crear claves. Si el script corre fuera de Google Cloud de forma desatendida, usar Workload Identity Federation.
¿Qué diferencia hay entre los roles que usaste en el paso 2 y en el paso 5?
En el paso 2 hace falta roles/iam.serviceAccountUser (permiso actAs) para adjuntar la cuenta a un recurso: la VM obtiene los tokens. En el paso 5, roles/iam.serviceAccountTokenCreator permite generar tokens directamente y actuar como la cuenta. Ambos son sensibles: quien los tiene hereda de facto los permisos de la cuenta de servicio.
¿Cómo garantizarías en toda la organización que ninguna VM nueva tenga IP pública?
Con la política de organización constraints/compute.vmExternalIpAccess en modo denegar todo (o con una lista mínima de excepciones), aplicada en la organización o en la carpeta de producción. IAM no sirve para esto, porque no controla la configuración del recurso sino quién puede crearlo.