La seguridad de AlpinaShop se ha construido a trozos. En 03-01 se cerró el firewall. En 03-04 se diseñó IAM con grupos, roles personalizados y cuentas de servicio sin claves. En 03-05 se puso Cloud Armor delante. En 03-06 llegaron Secret Manager y las claves gestionadas por el cliente. En 03-07 se publicó con TLS. En 06-02 se protegió la rama principal. En 07-03 se levantó un perímetro alrededor de los datos.

Cada pieza se decidió bien en su momento. Y nadie ha mirado nunca el conjunto.

Esa es la diferencia entre tener controles de seguridad y tener seguridad. Un atacante no ataca el firewall: ataca el eslabón más débil de una cadena que tú nunca has recorrido entera. Y en una empresa de cuarenta personas con tres personas técnicas, ese eslabón casi nunca es el que uno se imagina — no es una vulnerabilidad exótica en el kernel, es una clave JSON de hace dos años que sigue en el portátil de un becario que ya no trabaja aquí.

Esta lección es la revisión completa. Recorre la arquitectura capa por capa, aplica los principios que hacen que las capas se sostengan, revisa la cadena de suministro del software —que es hoy el vector de ataque que más crece—, cierra el ciclo de los datos, monta detección y respuesta, aclara qué parte del cumplimiento normativo es de Google y cuál es tuya, y termina con dos cosas que no suelen aparecer en los cursos: un checklist de endurecimiento que se puede ejecutar, y la lista honesta de la deuda de seguridad que AlpinaShop todavía tiene, priorizada.

Aviso, y va en serio. Esta lección es formación, no una auditoría. El modelo que se presenta es sólido y aplicable, pero cualquier arquitectura que trate datos personales, de pago o de salud debe ser revisada por un profesional de seguridad y de cumplimiento normativo antes de ir a producción, y con la periodicidad que exija el sector. Ningún curso, ninguna lista de comprobación y ninguna herramienta automática sustituye ese trabajo.

Contenido

  1. Defensa en profundidad aplicada a AlpinaShop
  2. Los seis principios que sostienen todo lo demás
  3. Confianza cero y BeyondCorp, con IAP como implementación práctica
  4. Identidad: revisión completa del diseño IAM
  5. Elevación temporal de privilegios y federación
  6. Cadena de suministro del software
  7. Datos: clasificación, cifrado, seudonimización y borrado
  8. Detección: Security Command Center y los logs de auditoría
  9. Respuesta: las dos primeras horas de un incidente
  10. Cumplimiento: qué aporta Google y qué sigue siendo tuyo
  11. Checklist de endurecimiento
  12. La deuda de seguridad de AlpinaShop, priorizada

  1. Defensa en profundidad aplicada a AlpinaShop

Defensa en profundidad significa que ningún control es el único. Si uno falla, otro detrás limita el daño. No es acumular herramientas: es diseñar de forma que el fallo de cualquier capa no sea catastrófico.

flowchart TB
    ATK["Atacante / error humano"]

    subgraph L1["CAPA 1 - Borde"]
      A1["Cloud Armor pol-catalogo-web<br/>WAF, geo, limite de tasa"]
      A2["TLS alpinashop-cert<br/>+ HSTS"]
      A3["Cloud CDN<br/>absorbe volumen"]
    end

    subgraph L2["CAPA 2 - Identidad"]
      B1["IAM: grupos, sin roles basicos"]
      B2["MFA obligatorio + IAP"]
      B3["Cuentas de servicio sin claves"]
      B4["Elevacion temporal PAM"]
    end

    subgraph L3["CAPA 3 - Red"]
      C1["VPC compartida, firewall central"]
      C2["Sin IP publicas en VM"]
      C3["Cloud NAT solo de salida"]
      C4["VPC Service Controls"]
    end

    subgraph L4["CAPA 4 - Carga de trabajo"]
      D1["Cloud Run sin privilegios"]
      D2["Cuenta de servicio minima"]
      D3["ingress solo desde el balanceador"]
      D4["Binary Authorization"]
    end

    subgraph L5["CAPA 5 - Datos"]
      E1["CMEK con alpinashop-keyring"]
      E2["Secret Manager"]
      E3["Cloud SQL sin IP publica"]
      E4["Cifrado en transito y reposo"]
    end

    subgraph L6["CAPA 6 - Deteccion"]
      F1["Security Command Center"]
      F2["Cloud Audit Logs"]
      F3["Alertas de Cloud Monitoring"]
      F4["Retencion inmutable de logs"]
    end

    ATK --> L1 --> L2 --> L3 --> L4 --> L5
    L6 -.->|"observa todas"| L1
    L6 -.-> L3
    L6 -.-> L5

    style L1 fill:#fef7e0,stroke:#fbbc04
    style L5 fill:#fce8e6,stroke:#ea4335
    style L6 fill:#e6f4ea,stroke:#34a853

La forma de comprobar si la defensa en profundidad es real es hacerse la pregunta del fallo único en cada capa:

Si falla… ¿Qué lo contiene? ¿Suficiente?
Cloud Armor deja pasar una inyección SQL Consultas parametrizadas en el código; la cuenta de la BD solo tiene permisos sobre tienda Sí
Alguien roba la sesión de Dani MFA en el acceso; Dani no es owner de producción; los cambios pasan por revisión Sí
Se filtra la clave de sa-catalogo-web Esa cuenta solo lee un secreto, un bucket y una BD; VPC-SC impide sacar datos fuera Parcialmente: podría leer pedidos
Un contenedor tiene una vulnerabilidad crítica Sin privilegios, sin acceso al nodo, cuenta mínima Sí
Se cifra por ransomware el bucket del catálogo Versionado de objetos + retención + copia en otra región Hay que verificarlo (07-06)
Marta pierde el portátil MFA con llave física; sesiones caducan Depende de si hay llave

Las filas con respuesta dudosa son deuda, y van al apartado 12. Esa es la utilidad real del ejercicio: no confirmar lo que está bien, sino encontrar lo que no.

  1. Los seis principios que sostienen todo lo demás

Las herramientas cambian; los principios no. Estos seis explican por qué cada decisión del curso fue la correcta.

Mínimo privilegio

Cada identidad tiene exactamente los permisos que necesita para su función, y ni uno más.

La aplicación honesta duele un poco: significa que cuando alguien pide "dame Editor para probar una cosa", la respuesta es no y hay que dedicar quince minutos a averiguar qué permiso necesita de verdad. El atajo de decir que sí es lo que ha llenado el mundo de proyectos donde todos son editores.

La herramienta que lo hace llevadero es el Recommender de IAM (03-04), que compara permisos concedidos con permisos usados en 90 días:

gcloud recommender recommendations list \
  --project=alpinashop-prod \
  --recommender=google.iam.policy.Recommender \
  --location=global \
  --format="table(content.overview.member, content.overview.removedRole, priority)"

Separación de funciones

Quien construye no despliega solo; quien despliega no aprueba solo; quien administra no audita.

En AlpinaShop: Dani escribe el código, Cloud Build lo construye, Marta aprueba producción, y los logs de auditoría los custodia un proyecto al que ninguno de los dos puede escribir (07-07). Ninguna persona puede, por sí sola, poner código en producción y borrar la evidencia.

Denegar por defecto

Lo que no está explícitamente permitido, está prohibido.

Se ha aplicado sistemáticamente: el firewall de 03-01 con denegación implícita; Cloud Run con --no-allow-unauthenticated; el ingress restringido al balanceador; el perímetro de VPC-SC; las políticas de organización de 07-07. La alternativa —permitir por defecto y bloquear lo malo conocido— es una carrera que se pierde siempre.

Radio de explosión limitado

Cuando algo se comprometa —no si—, ¿hasta dónde llega?

Es el principio que justifica separar en proyectos, dar cuentas de servicio distintas a cada carga, y no reutilizar credenciales. La pregunta operativa: si esta credencial se filtra hoy, ¿qué puede hacer el que la tenga? Si la respuesta es larga, el diseño está mal.

No confiar en la red

Estar dentro de la VPC no es una credencial.

El modelo clásico —perímetro duro, interior blando— falla porque en cuanto alguien entra, lo tiene todo. Se desarrolla en el apartado 3.

Asumir la brecha

Diseña como si ya estuvieran dentro.

Cambia las prioridades: se invierte tanto en detectar y limitar como en prevenir. Es lo que justifica los logs de auditoría inmutables, las alertas de comportamiento anómalo y el ensayo de la respuesta a incidentes.

  1. Confianza cero y BeyondCorp, con IAP como implementación práctica

BeyondCorp es el modelo de seguridad que Google adoptó internamente tras el ataque conocido como Operación Aurora en 2009, y que dio origen al término confianza cero.

Modelo perimetral clásico Confianza cero
Premisa La red interna es de confianza Ninguna red es de confianza
Acceso VPN → estás dentro → tienes acceso Cada petición se autoriza por separado
Señales Origen de la IP Identidad + dispositivo + contexto + riesgo
Trabajo remoto VPN obligatoria Igual desde cualquier sitio
Compromiso interno Acceso lateral libre Cada salto vuelve a autorizarse

En Google Cloud, la implementación práctica es IAP (Identity-Aware Proxy), que ya se usó en 03-04 y que aquí conviene mirar como pieza de arquitectura.

flowchart LR
    U["Empleado<br/>desde cualquier sitio"] --> LB["Balanceador global"]
    LB --> IAP{"IAP"}
    IAP -->|"identidad OK<br/>+ nivel de acceso OK"| APP["Panel interno<br/>Cloud Run privado"]
    IAP -->|"rechazo"| X["403"]
    CTX["Access Context Manager<br/>dispositivo gestionado,<br/>cifrado, region, hora"] -.-> IAP

Lo que hace IAP, y por lo que merece la pena:

  • La aplicación nunca ve tráfico no autenticado. El rechazo ocurre en el borde de Google.
  • No hay VPN que mantener, ni concentrador que se cae, ni cliente que instalar.
  • Funciona igual desde la oficina y desde casa, lo que elimina el incentivo de saltarse el control.
  • Se combina con niveles de acceso por contexto: dispositivo corporativo verificado, disco cifrado, pantalla bloqueada, país permitido.

Y su límite honesto: IAP protege el acceso a aplicaciones HTTP y a SSH/RDP por túnel. No es un sustituto de la segmentación de red ni de IAM: es una capa más.

Aplicado a AlpinaShop, resuelve el problema del ejercicio de 07-03 —Lucía trabajando desde casa— mejor que ninguna lista de IP:

# Nivel de acceso basado en el dispositivo, no en la IP
gcloud access-context-manager levels create dispositivo_gestionado \
  --title="Dispositivo corporativo verificado" \
  --basic-level-spec=dispositivo.yaml \
  --policy=POLICY_ID
# dispositivo.yaml
- devicePolicy:
    requireScreenlock: true
    requireCorpOwned: true
    allowedEncryptionStatuses:
      - ENCRYPTED
    osConstraints:
      - osType: DESKTOP_MAC
        minimumVersion: "14.0"
      - osType: DESKTOP_WINDOWS
        minimumVersion: "10.0"
  regions:
    - ES
    - FR
    - PT

Se lee así: puede entrar quien use un equipo propiedad de la empresa, cifrado, con bloqueo de pantalla, con sistema operativo actualizado, desde España, Francia o Portugal. La IP ha dejado de importar, que es exactamente el punto.

  1. Identidad: revisión completa del diseño IAM

La identidad es el nuevo perímetro. En una nube bien montada, la inmensa mayoría de los incidentes graves empiezan por una credencial, no por un puerto abierto.

Los roles básicos: eliminarlos

Owner, Editor y Viewer existen desde antes de que IAM tuviera roles granulares y no deberían usarse nunca en producción:

Rol básico Qué incluye realmente
Viewer Lectura de casi todo, incluidos muchos datos
Editor Modificar y borrar casi cualquier recurso del proyecto
Owner Editor + gestionar permisos + vincular facturación

Auditoría inmediata:

for P in alpinashop-prod alpinashop-dev alpinashop-datos alpinashop-cicd alpinashop-red; do
  echo "=== $P ==="
  gcloud projects get-iam-policy "$P" --format=json \
    | jq -r '.bindings[] | select(.role|test("^roles/(owner|editor|viewer)$")) |
             "\(.role): \(.members|join(", "))"'
done

Lo que hay que encontrar y por qué:

Hallazgo Riesgo Acción
Una persona con Owner en producción Puede borrarlo todo y quitar la auditoría Sustituir por roles concretos
<numero>[email protected] con Editor Cuenta por defecto de Compute. Cualquier carga que la herede tiene permiso para todo Desactivarla y usar cuentas propias
<numero>@cloudbuild.gserviceaccount.com con Editor El pipeline puede modificar cualquier cosa Rol acotado
allUsers o allAuthenticatedUsers en cualquier vínculo Público. Es una fuga, no un riesgo Quitar inmediatamente

La cuenta por defecto de Compute merece un párrafo propio porque es, con diferencia, el fallo más extendido de Google Cloud. Existe en todos los proyectos, históricamente venía con Editor, y cualquier VM, función o servicio que no especifique otra cuenta la hereda. Una vulnerabilidad en tu aplicación web pasa de "leer una base de datos" a "administrar el proyecto entero". La política de organización automaticIamGrantsForDefaultServiceAccounts (07-07) lo previene en proyectos nuevos; en los existentes hay que arreglarlo a mano.

El diseño objetivo de AlpinaShop

Identidad Producción Desarrollo Datos CI/CD Red
gcp-infra@ (Marta) Roles de operación; owner nadie editor viewer builds.editor networkAdmin, securityAdmin
gcp-desarrollo@ (Dani) run.viewer, logging.viewer, errorreporting.viewer editor — builds.viewer networkViewer
gcp-datos@ (Lucía) — — bigquery.dataEditor, bigquery.jobUser — networkViewer
gcp-seguridad@ securityReviewer, logging.viewer ídem ídem ídem ídem
gcp-facturacion@ billing.viewer ídem ídem ídem ídem
sa-catalogo-web 3 permisos concretos — — — —
sa-deploy-prod run.developer + serviceAccountUser — — — —

Nadie es Owner de producción. Es la decisión de 03-04 que más discusión genera y la que más valor tiene: si nadie puede saltarse los controles, los controles son reales. Para las emergencias existe el procedimiento de acceso de ruptura de cristal del apartado 5, que es auditado y temporal.

Cuentas de servicio sin claves

La regla, ya establecida en 03-04 y que aquí se cierra:

Una clave JSON de cuenta de servicio descargada es una contraseña permanente sin caducidad que cualquiera puede copiar.

Alternativas, en orden de preferencia:

Situación Solución sin claves
Carga dentro de GCP Identidad adjunta: la VM, el servicio de Cloud Run o el pod la lleva puesta
GKE Workload Identity
CI/CD desde GitHub Workload Identity Federation (ya en uso desde 06-02)
Persona que necesita actuar como una cuenta Impersonación con credenciales de corta duración
Sistema local que no puede federar Clave, con rotación automatizada y alerta de uso

Búsqueda de claves existentes en toda la organización:

for P in $(gcloud projects list --format='value(projectId)'); do
  for SA in $(gcloud iam service-accounts list --project="$P" --format='value(email)' 2>/dev/null); do
    gcloud iam service-accounts keys list --iam-account="$SA" --project="$P" \
      --managed-by=user --format="value(name,validAfterTime)" 2>/dev/null \
      | sed "s|^|$P $SA |"
  done
done

--managed-by=user es la clave del comando: filtra las claves descargables y descarta las que gestiona Google internamente, que son inofensivas y llenarían la salida de ruido.

  1. Elevación temporal de privilegios y federación

El problema del permiso permanente

Marta necesita compute.securityAdmin para tocar el firewall. Lo usa una vez al mes. El resto del tiempo, ese permiso solo aporta riesgo: si le roban la sesión un martes cualquiera, el atacante puede abrir el firewall.

Privileged Access Manager (PAM) resuelve esto con permisos que se piden, se justifican, se aprueban y caducan solos.

gcloud pam entitlements create firewall-emergencia \
  --location=global \
  --project=alpinashop-red \
  --entitlement-file=derecho.yaml
# derecho.yaml
privilegedAccess:
  gcpIamAccess:
    resourceType: cloudresourcemanager.googleapis.com/Project
    resource: projects/alpinashop-red
    roleBindings:
      - role: roles/compute.securityAdmin

maxRequestDuration: 7200s          # maximo 2 horas

eligibleUsers:
  - principals:
      - group:[email protected]

requesterJustificationConfig:
  notMandatory: {}                  # en emergencias no se exige texto largo

approvalWorkflow:
  manualApprovals:
    requireApproverJustification: true
    steps:
      - approvers:
          - principals:
              - group:[email protected]

Lo que cambia:

Permiso permanente Elevación temporal
Ventana de exposición Siempre 2 horas al mes
Trazabilidad Un log entre miles Una solicitud con justificación
Aprobación Ninguna Otra persona
Revocación Alguien tiene que acordarse Automática

Y el matiz importante para una pyme: si el aprobador es la única persona que puede aprobar y está de vacaciones, el proceso bloquea una emergencia. Tiene que haber un camino de ruptura de cristal: un derecho con aprobación automática, duración de una hora, y alerta inmediata al canal de seguridad cuando se usa. No se impide; se hace ruidoso. Es la misma filosofía que el procedimiento de emergencia de Terraform en 06-07: cuando no puede haber control preventivo, tiene que haber control detectivo.

Federación de identidad

Workload Identity Federation permite que una identidad externa —GitHub Actions, un clúster de otra nube, un proveedor OIDC— obtenga credenciales de corta duración de Google Cloud sin clave alguna. AlpinaShop ya lo usa desde 06-02.

El punto de seguridad que casi todo el mundo configura mal es la condición de atributos:

# MAL: cualquier repositorio de GitHub del mundo puede obtener credenciales
--attribute-condition="assertion.repository_owner=='alpinashop'"

Esa condición parece restrictiva y no lo es tanto: si alguien crea una organización de GitHub llamada alpinashop, entra. Y aunque no fuera así, cualquier rama o pull request del repositorio podría desplegar en producción.

# BIEN: repositorio concreto, por ID numerico, y solo la rama principal
--attribute-condition="assertion.repository_id=='123456789' &&
                       assertion.ref=='refs/heads/main' &&
                       assertion.repository_visibility=='private'"

repository_id es numérico e inmutable: no se puede suplantar renombrando. Este es el detalle que separa una federación segura de una que solo lo parece.

  1. Cadena de suministro del software

Es el vector que más ha crecido en los últimos años, y el que menos atención recibe en las pymes. No hace falta atacar tu aplicación si se puede atacar una biblioteca que tu aplicación instala.

flowchart LR
    DEP["Dependencias<br/>PyPI, npm"] --> CODE["Codigo en GitHub"]
    CODE --> BUILD["Cloud Build"]
    BUILD --> IMG["Imagen en<br/>Artifact Registry"]
    IMG --> RUN["Cloud Run"]

    S1["Assured OSS<br/>+ escaneo de dependencias"] -.-> DEP
    S2["Revision obligatoria<br/>+ firma de commits"] -.-> CODE
    S3["Compilacion reproducible<br/>+ procedencia SLSA"] -.-> BUILD
    S4["Escaneo de vulnerabilidades<br/>+ atestado"] -.-> IMG
    S5["Binary Authorization"] -.-> RUN

    style S5 fill:#e6f4ea,stroke:#34a853

Escaneo de vulnerabilidades en Artifact Registry

gcloud services enable containerscanning.googleapis.com --project=alpinashop-prod

# Consultar las vulnerabilidades de la imagen desplegada
gcloud artifacts docker images list-vulnerabilities \
  europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
  --format="table(vulnerability.severity, vulnerability.packageIssue[0].affectedPackage,
                  vulnerability.packageIssue[0].fixedVersion)"

Y el paso que convierte el escaneo en un control: fallar la compilación si hay vulnerabilidades críticas con parche disponible.

# Paso de cloudbuild.yaml
- id: comprobar-vulnerabilidades
  name: gcr.io/google.com/cloudsdktool/cloud-sdk
  entrypoint: bash
  args:
    - -c
    - |
      set -e
      sleep 60   # dar tiempo al escaneo
      CRITICAS=$(gcloud artifacts docker images list-vulnerabilities \
        "${_IMAGEN}:$COMMIT_SHA" --format=json \
        | jq '[.[] | select(.vulnerability.severity=="CRITICAL")
                   | select(.vulnerability.packageIssue[0].fixedVersion.fullName != "")] | length')
      echo "Vulnerabilidades criticas CON PARCHE: $$CRITICAS"
      [ "$$CRITICAS" -eq 0 ] || { echo "COMPILACION BLOQUEADA"; exit 1; }

El matiz de fixedVersion no es un tecnicismo: bloquear por vulnerabilidades sin parche disponible paraliza el desarrollo sin mejorar la seguridad, porque no hay nada que hacer salvo cambiar de dependencia. Se bloquea lo que se puede arreglar; lo demás se registra, se valora el riesgo y se decide.

Imágenes base mínimas y reproducibles

Imagen base Tamaño típico Paquetes Superficie
ubuntu:22.04 ~78 MB ~100 Alta: shell, gestor de paquetes, utilidades
python:3.12 ~1 GB ~400 Muy alta
python:3.12-slim ~130 MB ~120 Media
gcr.io/distroless/python3 ~50 MB Mínimos Baja: sin shell
scratch (binarios estáticos) Tamaño del binario 0 Nula

Sin shell, un atacante que consiga ejecución de código no puede lanzar sh, ni curl, ni wget. No es invulnerable, pero la mayoría de las herramientas de post-explotación dejan de funcionar.

Y la reproducibilidad, que es lo que hace verificable todo lo anterior:

# MAL: "latest" cambia bajo tus pies. La imagen de hoy no es la de ayer
FROM python:3.12-slim

# BIEN: digest inmutable. Esta imagen es exactamente esta, siempre
FROM python:3.12-slim@sha256:2d3f4a1b9c8e7d6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f

WORKDIR /app
COPY requirements.txt .
# Instalar con hashes verificados: si una dependencia cambia, falla
RUN pip install --no-cache-dir --require-hashes -r requirements.txt
COPY . .
USER 1000:1000                      # NUNCA root
CMD exec gunicorn --bind :$PORT --workers 4 --threads 20 main:app

Las tres líneas que importan: digest en lugar de etiqueta, --require-hashes para que una dependencia manipulada haga fallar la instalación, y USER 1000 para no ejecutar como root.

SLSA y procedencia

SLSA (Supply-chain Levels for Software Artifacts) es un marco con niveles de garantía sobre cómo se construyó un artefacto. Cloud Build genera procedencia verificable de forma nativa:

gcloud artifacts docker images describe \
  europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
  --show-provenance

La procedencia responde con firma criptográfica a: qué commit exacto, qué instrucciones de compilación, qué máquina, qué momento. Sirve para lo que en un incidente es una pregunta de horas: ¿esta imagen que está corriendo en producción se construyó desde nuestro código, o alguien la publicó a mano?

Nivel SLSA Qué garantiza Cómo se alcanza en GCP
1 Existe procedencia Cloud Build la genera sola
2 Procedencia firmada por un servicio de compilación Cloud Build gestionado
3 Compilación aislada y no falsificable Cloud Build con trabajadores aislados

Binary Authorization: cerrar el círculo

Ya se presentó en 07-01. Aquí es donde encaja: es el control que impide que llegue a producción una imagen que no pasó por el proceso.

gcloud container binauthz policy import - <<'EOF'
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: DRYRUN_AUDIT_LOG_ONLY      # empezar SIEMPRE aqui
  requireAttestationsBy:
    - projects/alpinashop-cicd/attestors/escaneo-superado
    - projects/alpinashop-cicd/attestors/aprobacion-marta
EOF

Y aplica a Cloud Run, no solo a GKE, lo que lo hace directamente relevante para AlpinaShop tras 07-02:

gcloud run services update alpinashop-web --region=europe-west1 \
  --binary-authorization=default

Dependencias: Assured Open Source

Assured Open Source Software es un servicio por el que Google distribuye paquetes de código abierto que él mismo usa: los compila en su infraestructura, los escanea, los firma y les da procedencia SLSA.

Su valor es acotado y conviene decirlo: cubre un catálogo de paquetes populares de Java y Python, no todo lo que instalas. Para AlpinaShop, con una decena de dependencias comunes, la mayoría estarían cubiertas. No es una bala de plata; es reducir la superficie donde tienes que confiar ciegamente.

  1. Datos: clasificación, cifrado, seudonimización y borrado

Clasificar antes de proteger

No se puede proteger lo que no se sabe qué es. La clasificación de AlpinaShop:

Nivel Qué incluye Controles exigidos
Público Catálogo, precios, fotos Ninguno especial
Interno Métricas de ventas, inventario IAM por grupo
Confidencial Pedidos, direcciones, correos IAM + CMEK + auditoría de acceso a datos
Restringido Datos de pago, documentos de identidad No se almacenan (los tiene la pasarela)

La cuarta fila es la mejor decisión de seguridad de todo el curso, y no es técnica: el dato que no guardas no se puede filtrar. AlpinaShop no almacena números de tarjeta porque la pasarela devuelve un token. Eso elimina de golpe el alcance completo de PCI-DSS sobre su infraestructura.

Descubrimiento con Sensitive Data Protection

En 04-07 se presentó DLP —hoy Sensitive Data Protection— para el gobierno del dato. Aquí se usa como control de seguridad: encontrar datos personales donde no deberían estar.

gcloud dlp jobs create inspect \
  --project=alpinashop-datos \
  --inspect-job-file=inspeccion.json
{
  "inspectJob": {
    "storageConfig": {
      "bigQueryOptions": {
        "tableReference": {
          "projectId": "alpinashop-datos",
          "datasetId": "alpinashop_analitica",
          "tableId": "eventos_web"
        },
        "sampleMethod": "RANDOM_START"
      }
    },
    "inspectConfig": {
      "infoTypes": [
        {"name": "EMAIL_ADDRESS"},
        {"name": "PHONE_NUMBER"},
        {"name": "CREDIT_CARD_NUMBER"},
        {"name": "SPAIN_DNI_NUMBER"},
        {"name": "IBAN_CODE"}
      ],
      "minLikelihood": "LIKELY",
      "includeQuote": false
    },
    "actions": [
      {"saveFindings": {"outputConfig": {"table": {
        "projectId": "alpinashop-datos", "datasetId": "seguridad", "tableId": "hallazgos_dlp"}}}}
    ]
  }
}

Dos decisiones del fichero:

  • includeQuote: false: no guardar el fragmento encontrado. Guardar el número de tarjeta detectado en la tabla de hallazgos sería crear una segunda copia del problema. Un error real y frecuente.
  • SPAIN_DNI_NUMBER: existen detectores específicos por país. Usar solo los genéricos deja fuera lo más relevante en España.

Lo que se encuentra siempre, y AlpinaShop no fue excepción: correos electrónicos en campos de texto libre de comentarios, teléfonos en el campo "observaciones del pedido", y direcciones completas en logs de aplicación de hace meses. Ninguno era intencionado. Los tres son datos personales a efectos del RGPD.

Cifrado y CMEK

Google cifra todo en reposo por defecto, sin configurar nada. CMEK (03-06) añade que la clave la controlas tú en Cloud KMS:

Cifrado por defecto CMEK
Quién controla la clave Google Tú, en alpinashop-keyring
Rotación Automática de Google Tú defines el periodo
Revocar el acceso a los datos No es posible Deshabilitando la clave
Registro del uso de la clave No Sí, en los logs de KMS
Coste Incluido Coste de KMS + operaciones
Complejidad Ninguna Gestión del ciclo de vida

El "botón rojo" que da CMEK. Si se detecta una intrusión en curso, deshabilitar la clave hace ilegibles los datos cifrados con ella en segundos, incluso para quien tenga permisos de lectura. Es una capacidad de contención que sin CMEK no existe.

Con su contrapartida, que hay que decir claramente: si pierdes la clave, pierdes los datos. Definitivamente. Por eso KMS impide el borrado inmediato e impone un periodo de destrucción programada.

Seudonimización

Para que Lucía analice comportamiento de compra no necesita saber quién es cada cliente:

-- Vista seudonimizada: analitica sin identificar a nadie
CREATE OR REPLACE VIEW `alpinashop-datos.alpinashop_analitica.pedidos_seudonimos` AS
SELECT
  TO_HEX(SHA256(CONCAT(cliente_email, @sal_secreta))) AS cliente_id,
  DATE(fecha_pedido)                                   AS fecha,
  SUBSTR(codigo_postal, 1, 2)                          AS provincia,
  categoria_producto,
  importe_total,
  canal
FROM `alpinashop-datos.alpinashop_analitica.pedidos`;

Tres técnicas en cinco líneas: hash con sal para que el mismo cliente sea el mismo cliente_id sin poder revertirlo; generalización del código postal a los dos primeros dígitos, que da la provincia sin señalar el barrio; y omisión de nombre, dirección y teléfono, que no aportan nada al análisis.

Y la advertencia honesta: la seudonimización no es anonimización. Con la sal, el dato sigue siendo reidentificable y por tanto sigue siendo un dato personal a efectos del RGPD. Reduce el riesgo; no elimina la obligación.

Retención y borrado

El RGPD obliga a no conservar datos personales más de lo necesario, y a poder borrarlos a petición del interesado.

# Ciclo de vida del data lake
resource "google_storage_bucket" "datalake" {
  name     = "alpinashop-datalake"
  location = "EUROPE-WEST1"

  lifecycle_rule {
    condition { age = 90 }
    action { type = "SetStorageClass" storage_class = "NEARLINE" }
  }
  lifecycle_rule {
    condition { age = 365 }
    action { type = "SetStorageClass" storage_class = "COLDLINE" }
  }
  lifecycle_rule {
    condition { age = 2555 }        # 7 anos: obligacion fiscal
    action { type = "Delete" }
  }

  versioning { enabled = true }     # proteccion contra borrado accidental y ransomware
}
-- Caducidad automatica de particiones en BigQuery
ALTER TABLE `alpinashop-datos.alpinashop_analitica.eventos_web`
SET OPTIONS (partition_expiration_days = 400);

Y el borrado a petición, que es donde la mayoría de empresas descubre que su arquitectura no lo contemplaba:

-- Procedimiento de derecho de supresion
CREATE OR REPLACE PROCEDURE `alpinashop-datos.alpinashop_analitica.borrar_cliente`(email STRING)
BEGIN
  -- 1. Anonimizar los pedidos: se conservan por obligacion fiscal, sin identificar
  UPDATE `alpinashop-datos.alpinashop_analitica.pedidos`
  SET cliente_email = 'BORRADO', cliente_nombre = 'BORRADO',
      direccion = 'BORRADO', telefono = NULL
  WHERE cliente_email = email;

  -- 2. Borrar de las tablas donde no hay obligacion de conservacion
  DELETE FROM `alpinashop-datos.alpinashop_analitica.eventos_web` WHERE usuario_email = email;
  DELETE FROM `alpinashop-datos.alpinashop_analitica.carritos`    WHERE cliente_email = email;

  -- 3. Registrar la ejecucion del derecho (sin el dato borrado)
  INSERT INTO `alpinashop-datos.cumplimiento.solicitudes_rgpd`
  VALUES (TO_HEX(SHA256(email)), CURRENT_TIMESTAMP(), 'SUPRESION', SESSION_USER());
END;

Nótese el punto 1: los pedidos no se borran, se anonimizan, porque la normativa fiscal obliga a conservar las facturas. El derecho de supresión no es absoluto y colisiona con otras obligaciones legales; resolver esa colisión no es una decisión técnica y debe documentarse con asesoramiento jurídico.

  1. Detección: Security Command Center y los logs de auditoría

Security Command Center

SCC es el centro de seguridad de Google Cloud. Detecta configuraciones inseguras, vulnerabilidades y amenazas activas.

Nivel Qué incluye Coste
Estándar Security Health Analytics básico, hallazgos de configuración, inventario Gratuito
Premium Event Threat Detection, Container Threat Detection, VM Threat Detection, cumplimiento (CIS, PCI, ISO), simulación de rutas de ataque De pago, orientado a empresa
Enterprise Premium + multinube + gestión de casos + inteligencia de Mandiant De pago

Qué detecta de verdad el nivel gratuito, que es el que va a usar una pyme:

Hallazgo Gravedad Frecuencia con la que aparece
Bucket accesible públicamente Crítica Constante
Cuenta de servicio con rol de administrador Alta Muy frecuente
Regla de firewall que permite 0.0.0.0/0 a un puerto de administración Crítica Frecuente
Instancia de Cloud SQL con IP pública sin SSL Alta Frecuente
Claves de cuenta de servicio de más de 90 días Media Casi siempre
MFA no activado en cuentas de administrador Crítica Frecuente
Registro de auditoría desactivado Alta Ocasional

Con Premium se añade Event Threat Detection, que analiza los logs en tiempo real buscando comportamiento —no configuración—: minería de criptomonedas en una VM, exfiltración de datos de BigQuery, uso de credenciales desde una geografía imposible, creación de una cuenta de servicio seguida inmediatamente de una concesión de permisos.

El criterio honesto para AlpinaShop: empezar con el nivel gratuito y atender de verdad sus hallazgos, que ya es más de lo que hace la mayoría. Premium se justifica cuando hay algo que vigilar activamente, un requisito de cumplimiento que lo exija, o alguien con tiempo para responder a lo que detecte. Comprar detección que nadie mira es peor que no comprarla, porque genera la sensación de estar protegido.

gcloud scc findings list ORGANIZATION_ID \
  --filter='state="ACTIVE" AND severity="CRITICAL"' \
  --format="table(category, resourceName, eventTime)"

Los logs de auditoría como fuente de verdad

Se desarrollan a fondo en 07-07. Aquí, lo mínimo indispensable:

Tipo Qué registra ¿Activado por defecto?
Actividad de administrador Cambios de configuración y permisos Sí, y no se puede desactivar
Acceso a datos Lecturas y escrituras de datos No. Hay que activarlo y cuesta
Eventos del sistema Acciones de Google sobre tus recursos Sí
Denegaciones de política Peticiones rechazadas por políticas Sí

Que "acceso a datos" esté desactivado por defecto tiene una consecuencia que hay que asumir conscientemente: sin activarlo, no puedes saber quién leyó los datos de tus clientes. Para AlpinaShop, activarlo en alpinashop-datos y en los buckets con datos personales no es opcional si quiere responder a una brecha.

Tres consultas que hay que tener escritas antes de necesitarlas:

-- 1. Cambios de permisos IAM en los ultimos 7 dias
SELECT timestamp,
       protopayload_auditlog.authenticationInfo.principalEmail AS quien,
       protopayload_auditlog.methodName                        AS que,
       protopayload_auditlog.resourceName                      AS donde
FROM `alpinashop-prod.auditoria.cloudaudit_googleapis_com_activity`
WHERE protopayload_auditlog.methodName LIKE '%setIamPolicy%'
  AND DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
ORDER BY timestamp DESC
-- 2. Uso de credenciales desde fuera de Espana
SELECT DISTINCT
       protopayload_auditlog.authenticationInfo.principalEmail AS quien,
       protopayload_auditlog.requestMetadata.callerIp          AS ip,
       protopayload_auditlog.requestMetadata.callerSuppliedUserAgent AS agente,
       COUNT(*) AS peticiones
FROM `alpinashop-prod.auditoria.cloudaudit_googleapis_com_activity`
WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
  AND NET.IP_TO_STRING(NET.IP_FROM_STRING(
        protopayload_auditlog.requestMetadata.callerIp)) NOT LIKE '88.20.145.%'
GROUP BY quien, ip, agente
HAVING peticiones > 10
ORDER BY peticiones DESC
-- 3. Quien ha creado o descargado claves de cuenta de servicio
SELECT timestamp,
       protopayload_auditlog.authenticationInfo.principalEmail AS quien,
       protopayload_auditlog.resourceName                      AS cuenta
FROM `alpinashop-prod.auditoria.cloudaudit_googleapis_com_activity`
WHERE protopayload_auditlog.methodName =
      'google.iam.admin.v1.CreateServiceAccountKey'
ORDER BY timestamp DESC

La tercera merece una alerta permanente: en una organización que federa identidades y usa impersonación, crear una clave JSON es un evento excepcional que siempre debe mirarse.

  1. Respuesta: las dos primeras horas de un incidente

Aquí está lo que casi ningún curso incluye y lo que de verdad marca la diferencia. Un guion realista para un equipo de tres personas.

Escenario: son las 23:40 de un jueves de octubre, en plena campaña. Llega una alerta de SCC: "Anomalous IAM grant: la cuenta sa-informes-nocturnos ha concedido roles/owner a una cuenta externa".

Minutos 0-10: valorar, no actuar

No se toca nada todavía. El primer impulso —borrar la cuenta, cortar accesos— destruye evidencia y a veces avisa al atacante. Lo primero es responder tres preguntas:

  1. ¿Es real o un falso positivo? ¿Hay un cambio programado esta noche? ¿Alguien está trabajando?
  2. ¿Está en curso? ¿Sigue habiendo actividad de esa identidad ahora mismo?
  3. ¿Qué alcance tiene? ¿Qué puede tocar esa credencial?
# Actividad de esa identidad en la ultima hora
gcloud logging read '
  protoPayload.authenticationInfo.principalEmail="[email protected]"
  AND timestamp>="'$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)'"' \
  --project=alpinashop-prod --limit=200 \
  --format="table(timestamp, protoPayload.methodName, protoPayload.requestMetadata.callerIp)"

Minutos 10-30: contener

Si es real, cortar sin destruir. El orden importa:

# 1. Quitar el permiso concedido indebidamente
gcloud projects remove-iam-policy-binding alpinashop-prod \
  --member="user:[email protected]" --role="roles/owner"

# 2. DESHABILITAR la cuenta comprometida (no borrarla: se pierde la evidencia)
gcloud iam service-accounts disable \
  [email protected]

# 3. Revocar los tokens vivos: sin esto, los tokens ya emitidos siguen valiendo
#    hasta una hora aunque la cuenta este deshabilitada
gcloud iam service-accounts keys list \
  --iam-account=sa-informes-nocturnos@alpinashop-prod.iam.gserviceaccount.com

# 4. Congelar la evidencia: copiar los logs relevantes a un bucket con retencion
gcloud logging read 'timestamp>="'$INICIO'"' --project=alpinashop-prod \
  --format=json > /tmp/incidente-$(date +%F).json
gcloud storage cp /tmp/incidente-*.json gs://alpinashop-evidencia-forense/

El paso 3 es el que se olvida siempre. Deshabilitar una cuenta no invalida los tokens de acceso ya emitidos, que viven hasta una hora. Durante esa hora, el atacante sigue dentro aunque el panel diga que la cuenta está deshabilitada.

Minutos 30-60: evaluar el alcance

Las preguntas, en este orden:

Pregunta Cómo se responde
¿Qué hizo esa identidad en los últimos 30 días? Logs de actividad de administrador
¿Accedió a datos personales? Logs de acceso a datos (si estaban activados)
¿Salió información? Flow Logs, logs de VPC-SC, transferencias de Cloud Storage
¿Creó persistencia? Buscar cuentas nuevas, claves nuevas, reglas de firewall nuevas, funciones nuevas
¿Cómo entró? Origen de las credenciales: ¿clave filtrada? ¿repositorio público? ¿portátil?

La búsqueda de persistencia es la parte que más se descuida y la que hace que un incidente se repita a la semana siguiente:

# Recursos creados en las ultimas 48 horas en toda la organizacion
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --query="createTime>$(date -u -d '48 hours ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --format="table(name, assetType, createTime)"

Hora 1-2: comunicar

Destinatario Cuándo Qué se dice
Equipo interno Inmediato Qué ha pasado y quién hace qué
Dirección En cuanto se confirme Impacto en el negocio, en lenguaje de negocio
Asesor jurídico / DPO Antes de las 24 h Si hay datos personales implicados
AEPD Antes de 72 h desde el conocimiento Si hay riesgo para los derechos de las personas
Clientes afectados Según valoración jurídica Si el riesgo es alto
Aseguradora Según póliza Suele haber plazos estrictos

Las 72 horas del RGPD para notificar a la autoridad de control no son negociables y el reloj empieza cuando tienes conocimiento, no cuando terminas la investigación. Es la razón número uno por la que hay que llamar al asesor jurídico en la primera hora, aunque todavía no se sepa el alcance.

Lo que hay que tener preparado ANTES

Un incidente no es momento de improvisar. La carpeta que hay que tener escrita hoy:

  • Teléfonos: interno, dirección, asesor jurídico, aseguradora, soporte de Google Cloud.
  • El comando de contención ya escrito, probado y con permisos verificados.
  • Un bucket de evidencia con retención bloqueada y creado fuera del proyecto que puede estar comprometido.
  • Un canal de comunicación alternativo — si el correo corporativo está comprometido, no puedes coordinarte por correo.
  • El acceso de emergencia probado: que Marta pueda entrar aunque su cuenta habitual esté bloqueada.
  • Un simulacro al año. Media hora en una sala, sobre un escenario escrito. Descubre más agujeros que cualquier auditoría de configuración.

  1. Cumplimiento: qué aporta Google y qué sigue siendo tuyo

El reparto de 01-05, revisado ahora con todo el contexto:

Aspecto Google Tú
Seguridad física de los centros de datos ✅ —
Hardware, red física, hipervisor ✅ —
Cifrado en reposo por defecto ✅ —
Parcheo del sistema operativo ✅ en servicios gestionados ❌ en Compute Engine
Configuración de IAM — ❌ Tuyo
Reglas de firewall — ❌ Tuyo
Seguridad del código de tu aplicación — ❌ Tuyo
Clasificación de los datos — ❌ Tuyo
Gestión de accesos de empleados — ❌ Tuyo
Copias de seguridad y su prueba — ❌ Tuyo (07-06)
Respuesta a incidentes en tu capa — ❌ Tuyo

Las certificaciones de Google —ISO 27001, ISO 27017, ISO 27018, SOC 1/2/3, PCI-DSS como proveedor, ENS en España, esquemas sectoriales— acreditan su infraestructura. Se descargan del Compliance Reports Manager.

Y aquí está el malentendido más caro del cumplimiento en la nube:

Que Google esté certificado en ISO 27001 no certifica tu empresa. Que su infraestructura sea apta para PCI-DSS no hace conforme tu tienda. Lo que te dan es que la capa de abajo ya está auditada, lo que reduce mucho tu alcance — pero tu configuración, tu código, tus procesos y tus personas siguen siendo tuyos y siguen siendo auditables.

Para AlpinaShop, como tienda online española:

Obligación Situación Pendiente
RGPD y LOPDGDD Aplica plenamente Registro de actividades de tratamiento; contrato de encargado con Google (las Cloud Data Processing Addendum); análisis de transferencias internacionales
PCI-DSS Alcance mínimo: no almacena tarjetas Mantenerlo así; cuestionario SAQ-A
LSSI-CE Aviso legal, cookies Revisión periódica
Ley de servicios digitales Según tamaño Revisar con asesoría
Esquema Nacional de Seguridad No aplica (no es sector público) —

La documentación que hay que tener, y que en una inspección se pide antes que cualquier configuración técnica:

  1. Registro de actividades de tratamiento (art. 30 RGPD).
  2. Contrato de encargado del tratamiento con Google Cloud.
  3. Evaluación de impacto (EIPD) si hay tratamiento de alto riesgo.
  4. Política de seguridad de la información, aunque sean cuatro páginas.
  5. Procedimiento de respuesta a brechas, con los plazos del apartado 9.
  6. Registro de accesos a datos personales — que es justo lo que dan los logs de acceso a datos.
  7. Análisis de transferencias internacionales y garantías aplicadas.

Se repite porque importa: esta tabla es orientativa y docente. Un profesional de cumplimiento debe validar qué aplica a tu caso concreto, especialmente en lo relativo al RGPD, a las transferencias internacionales y al alcance de PCI-DSS.

  1. Checklist de endurecimiento

Accionable sobre todo lo construido en el curso. La columna de estado es la de AlpinaShop hoy.

Identidad y acceso

# Control Estado
1 MFA obligatorio para todas las cuentas, con llave física para administradores ⚠️ MFA sí, llaves no
2 Ningún rol básico (owner/editor/viewer) en producción ⚠️ Editor en cloudbuild
3 Permisos solo a grupos, nunca a personas ✅
4 Cero claves JSON de cuentas de servicio ⚠️ Una pendiente
5 Cuenta por defecto de Compute desactivada o sin permisos ❌
6 Federación de identidad con condición por repository_id y rama ✅
7 Elevación temporal (PAM) para permisos administrativos ❌
8 Revisión trimestral de accesos documentada ❌
9 Proceso de baja de empleados que revoca en 24 h ⚠️ Informal

Red

# Control Estado
10 Sin IP públicas en VM; salida por Cloud NAT ✅
11 Firewall con denegación por defecto y reglas por etiqueta ✅
12 Cloud SQL solo con IP privada ✅
13 VPC compartida con permisos por subred ✅ (07-03)
14 VPC Service Controls alrededor de los datos ⚠️ En modo de prueba
15 Flow Logs activados con muestreo ✅
16 Cloud Armor con reglas gestionadas y límite de tasa ✅
17 Acceso administrativo solo por IAP, sin SSH público ✅

Carga de trabajo

# Control Estado
18 Contenedores sin root y sin privilegios ✅
19 Cuenta de servicio propia y mínima por servicio ✅
20 ingress restringido al balanceador ✅ (07-02)
21 Imágenes base mínimas fijadas por digest ⚠️ Slim, por etiqueta
22 Escaneo de vulnerabilidades que bloquea la compilación ❌ Escanea, no bloquea
23 Binary Authorization en modo de prueba o aplicado ❌
24 Sin secretos en variables de entorno ni en el código ✅

Datos

# Control Estado
25 Clasificación de datos documentada ✅
26 CMEK en los datos confidenciales ✅
27 Sin datos de tarjeta almacenados ✅
28 Escaneo periódico con Sensitive Data Protection ⚠️ Una vez
29 Políticas de retención y borrado automatizadas ⚠️ Parcial
30 Procedimiento de derecho de supresión probado ❌
31 Versionado y protección contra borrado en buckets críticos ✅
32 Copias de seguridad con restauración probada ❌ (07-06)

Detección y respuesta

# Control Estado
33 Security Command Center activo y sus hallazgos atendidos ⚠️ Activo, sin proceso
34 Logs de acceso a datos activados en los datos sensibles ❌
35 Logs de auditoría exportados a un proyecto separado e inmutable ❌ (07-07)
36 Alertas sobre cambios de IAM y creación de claves ❌
37 Plan de respuesta escrito, con teléfonos y comandos ❌
38 Simulacro anual de incidente ❌
39 Bucket de evidencia forense con retención bloqueada ❌

Gobierno

# Control Estado
40 Políticas de organización aplicadas ❌ (07-07)
41 Toda la infraestructura en Terraform y revisada ✅
42 Registro de actividades de tratamiento ⚠️ Desactualizado
43 Contrato de encargado con Google firmado ✅

Resultado: 20 ✅, 11 ⚠️, 12 ❌. No está mal para una empresa de cuarenta personas. Y no es suficiente.

  1. La deuda de seguridad de AlpinaShop, priorizada

Ninguna empresa está al 100 %. Lo que distingue a una organización madura no es no tener deuda, sino saber cuál tiene, haberla priorizado y estar pagándola. Priorización por riesgo (probabilidad × impacto) frente a esfuerzo.

Prioridad 1 — Esta semana

# Deuda Riesgo Esfuerzo
1 La clave JSON pendiente. Existe una clave de sa-dataflow-pedidos creada hace 14 meses para una prueba. No se sabe dónde está. Puede leer todo el data lake Crítico: credencial permanente perdida 2 h
2 Cuenta por defecto de Compute con Editor. Cualquier carga que no especifique cuenta la hereda Crítico: escalada de privilegios trivial 4 h
3 Cloud Build con Editor en producción. Un compromiso del pipeline es un compromiso total Alto 4 h
4 Alertas de cambios de IAM y creación de claves. Hoy nadie se enteraría Alto: sin detección no hay respuesta 2 h

Los cuatro suman menos de dos días de trabajo y son lo que más reduce el riesgo. Empezar por aquí no es opinable.

Prioridad 2 — Este mes

# Deuda Riesgo Esfuerzo
5 Logs de acceso a datos desactivados. Ante una brecha, AlpinaShop no puede decir quién leyó qué. Es un problema legal, no solo técnico Alto 1 día + coste recurrente
6 Sin plan de respuesta escrito. El apartado 9 es teoría hasta que existe la carpeta Alto 1 día
7 VPC-SC en modo de prueba desde hace semanas. Un control a medias no protege Medio-alto 2 días
8 Restauración de copias nunca probada. Se trata en 07-06 Crítico si ocurre 1 día
9 Escaneo que no bloquea. Se despliegan imágenes con vulnerabilidades críticas parcheables Medio 4 h

Prioridad 3 — Este trimestre

# Deuda Riesgo Esfuerzo
10 Políticas de organización sin aplicar (07-07) Medio 3 días
11 Sin revisión periódica de accesos Medio Proceso
12 Llaves físicas para administradores Medio 1 día + compra
13 Elevación temporal con PAM Medio 2 días
14 Binary Authorization Bajo-medio 3 días
15 Imágenes por digest y --require-hashes Bajo-medio 1 día
16 Simulacro de incidente Medio 4 h
17 Registro de tratamiento actualizado Legal Con asesoría

Deuda aceptada conscientemente

Y esto también forma parte de la madurez: decidir no hacer algo, por escrito, con motivo.

Decisión Motivo
No contratar SCC Premium No hay nadie que pueda atender lo que detecte. Se revisará al superar los 10 empleados técnicos
No adoptar Cloud Service Mesh para mTLS entre servicios Hay un servicio (DA-004). El tráfico interno ya va cifrado por la red de Google
No cifrar a nivel de aplicación sobre CMEK El modelo de amenaza no incluye a un administrador de Google. Se revisaría con datos de salud
No hacer pruebas de penetración externas este año Coste frente a madurez actual. Primero hay que pagar la prioridad 1 y 2

Un riesgo aceptado y documentado no es un fallo de seguridad: es una decisión de gestión. Un riesgo que nadie ha mirado, sí.

Errores Comunes y Consejos

  • Confundir tener herramientas con tener seguridad. SCC activado y nadie mirando los hallazgos es peor que no tenerlo: genera falsa confianza.
  • Dejar la cuenta por defecto de Compute con Editor. Es el fallo más extendido de Google Cloud y convierte cualquier vulnerabilidad en un compromiso total del proyecto.
  • Crear claves JSON "para probar". Nunca se borran. Búscalas periódicamente con --managed-by=user.
  • Federar identidad sin condición de atributos estricta. Sin repository_id y ref, cualquier rama o repositorio con el mismo nombre de organización puede desplegar en producción.
  • Borrar la cuenta comprometida durante un incidente. Destruye evidencia. Se deshabilita.
  • Olvidar que los tokens vivos sobreviven a la deshabilitación hasta una hora.
  • Guardar el fragmento detectado por DLP. Crear una tabla con los números de tarjeta encontrados es duplicar el problema, no resolverlo.
  • Creer que la seudonimización es anonimización. Con la sal, sigue siendo dato personal a efectos del RGPD.
  • Pensar que las certificaciones de Google certifican tu empresa. Certifican la capa de abajo. Tu configuración es tuya.
  • Bloquear compilaciones por vulnerabilidades sin parche disponible. Paraliza al equipo sin mejorar nada. Se bloquea lo que se puede arreglar.
  • Activar todos los controles a la vez. VPC-SC, Binary Authorization y políticas de organización el mismo día es garantía de caída. Uno cada vez, en modo de prueba primero.
  • Dejar el plan de respuesta para cuando haga falta. El incidente es el peor momento para escribirlo.
  • Consejo: haz la pregunta del fallo único en cada capa. Es media hora y encuentra más que cualquier escáner.
  • Consejo: la mejor medida de seguridad es el dato que no guardas. Antes de proteger un dato, pregunta si hace falta almacenarlo.
  • Consejo: un simulacro de media hora al año descubre más agujeros reales que una auditoría de configuración.
  • Consejo: escribe la deuda aceptada. Convierte un agujero en una decisión, y hace que se revise.

Ejercicios

Ejercicio 1 — Auditoría de identidad

Escribe un script de auditoría que, sobre los cinco proyectos de AlpinaShop, detecte y reporte:

  1. Cualquier vínculo con roles básicos (owner, editor, viewer).
  2. Vínculos con allUsers o allAuthenticatedUsers en proyectos y en buckets.
  3. Cuentas de servicio con claves gestionadas por el usuario, con su antigüedad.
  4. Cuentas de servicio que no se han usado en 90 días.
  5. Cuentas de servicio con roles de administrador.

Para cada hallazgo, indica gravedad y acción recomendada. Explica además por qué el punto 4 es más importante de lo que parece.

Ejercicio 2 — Diseñar la respuesta a un incidente concreto

Escenario. Un lunes por la mañana, un desarrollador externo publica en un foro público que ha encontrado un repositorio de GitHub de AlpinaShop con un fichero credenciales.json en el historial de commits. El fichero es una clave de sa-dataflow-pedidos, borrada del repositorio hace ocho meses pero presente en el historial. Esa cuenta tiene roles/bigquery.dataViewer sobre alpinashop_analitica y roles/storage.objectAdmin sobre alpinashop-datalake.

Escribe el plan de respuesta completo con marcas de tiempo: qué se hace en los primeros 10 minutos, en los primeros 30, en la primera hora y en las primeras 24 horas. Incluye los comandos, las decisiones de comunicación (con quién y cuándo), cómo se determina si hubo acceso indebido, y qué cambios estructurales se hacen después para que no vuelva a pasar.

Ejercicio 3 — Priorizar con presupuesto limitado

La dirección de AlpinaShop aprueba 10 días de trabajo de Marta y 3.000 € para seguridad este trimestre. No hay más.

De la lista de deuda del apartado 12, elige qué se hace y qué no, justificando cada decisión con criterio de riesgo frente a esfuerzo. Elabora un plan por semanas y, lo más importante, escribe qué le dices a dirección sobre lo que queda sin hacer, en lenguaje que un director general entienda.

Soluciones

Solución 1 — Auditoría de identidad

#!/usr/bin/env bash
# auditoria-identidad.sh — revision de identidad de AlpinaShop
set -uo pipefail

PROYECTOS=(alpinashop-prod alpinashop-dev alpinashop-datos alpinashop-cicd alpinashop-red)
HOY=$(date +%s)

echo "===== 1. ROLES BASICOS ====="
for P in "${PROYECTOS[@]}"; do
  gcloud projects get-iam-policy "$P" --format=json 2>/dev/null \
    | jq -r --arg p "$P" '
        .bindings[]
        | select(.role | test("^roles/(owner|editor|viewer)$"))
        | .role as $r
        | .members[]
        | "\($p) | \($r) | \(.)"'
done | while IFS='|' read -r PROY ROL MIEMBRO; do
  GRAV="MEDIA"
  [[ "$ROL" == *owner*  ]] && GRAV="CRITICA"
  [[ "$ROL" == *editor* ]] && GRAV="ALTA"
  [[ "$PROY" == *prod*  ]] && GRAV="CRITICA"
  echo "[$GRAV] $PROY $ROL -> $MIEMBRO"
done

echo
echo "===== 2. ACCESO PUBLICO ====="
for P in "${PROYECTOS[@]}"; do
  gcloud projects get-iam-policy "$P" --format=json 2>/dev/null \
    | jq -r --arg p "$P" '.bindings[] | select(.members[]? | test("^all(Users|AuthenticatedUsers)$"))
                          | "[CRITICA] \($p) PROYECTO PUBLICO: \(.role)"'
done
for B in $(gcloud storage buckets list --format='value(name)' 2>/dev/null); do
  gcloud storage buckets get-iam-policy "gs://$B" --format=json 2>/dev/null \
    | jq -r --arg b "$B" '.bindings[]? | select(.members[]? | test("^all(Users|AuthenticatedUsers)$"))
                          | "[CRITICA] BUCKET PUBLICO gs://\($b): \(.role)"'
done

echo
echo "===== 3. CLAVES JSON ====="
for P in "${PROYECTOS[@]}"; do
  for SA in $(gcloud iam service-accounts list --project="$P" --format='value(email)' 2>/dev/null); do
    gcloud iam service-accounts keys list --iam-account="$SA" --project="$P" \
      --managed-by=user --format='value(name,validAfterTime)' 2>/dev/null \
    | while read -r NOMBRE FECHA; do
        [ -z "$NOMBRE" ] && continue
        DIAS=$(( (HOY - $(date -d "$FECHA" +%s)) / 86400 ))
        GRAV="ALTA"; [ "$DIAS" -gt 90 ] && GRAV="CRITICA"
        echo "[$GRAV] $P $SA clave de $DIAS dias"
      done
  done
done

echo
echo "===== 4. CUENTAS DE SERVICIO SIN USO (90 dias) ====="
for P in "${PROYECTOS[@]}"; do
  gcloud recommender insights list --project="$P" --location=global \
    --insight-type=google.iam.serviceAccount.Insight \
    --filter='insightSubtype=SERVICE_ACCOUNT_USAGE' \
    --format="value(description)" 2>/dev/null | sed "s|^|[MEDIA] $P |"
done

echo
echo "===== 5. CUENTAS DE SERVICIO CON ROLES DE ADMINISTRADOR ====="
for P in "${PROYECTOS[@]}"; do
  gcloud projects get-iam-policy "$P" --format=json 2>/dev/null \
    | jq -r --arg p "$P" '
        .bindings[]
        | select(.role | test("[Aa]dmin$|^roles/owner$|^roles/iam\\.securityAdmin$"))
        | .role as $r | .members[]
        | select(startswith("serviceAccount:"))
        | "[ALTA] \($p) \($r) -> \(.)"'
done

Tabla de hallazgos, gravedad y acción:

Hallazgo Gravedad Acción
owner en producción a una persona Crítica Sustituir por roles concretos; usar PAM para lo excepcional
editor a cloudbuild en producción Crítica Acotar a run.developer + serviceAccountUser
allUsers en cualquier sitio Crítica Eliminar en el momento y revisar los logs de acceso
Clave JSON de más de 90 días Crítica Migrar a federación o impersonación y borrar la clave
Cuenta de servicio sin uso en 90 días Media Deshabilitar 30 días; si nada se rompe, borrar
Cuenta de servicio con rol *Admin Alta Rol personalizado con los permisos reales

Por qué el punto 4 es más importante de lo que parece. Una cuenta de servicio sin uso es un objetivo silencioso: nadie vigila su actividad, nadie notaría un uso anómalo, y como no se usa, nadie recuerda por qué existe ni qué permisos tiene. Además de eliminar superficie de ataque, la limpieza aporta algo más valioso: cada cuenta que se elimina reduce el ruido de la auditoría, y una lista de veinte cuentas de las que se entienden las veinte es infinitamente más segura que una de sesenta de las que se entienden veinte.

La técnica correcta es deshabilitar antes de borrar (gcloud iam service-accounts disable): si algo se rompe, se reactiva en un segundo; si se borró, hay que recrearla y reconstruir todos sus permisos. Treinta días de espera es un plazo razonable, porque captura los procesos mensuales.

Solución 2 — Diseñar la respuesta a un incidente concreto

T+0 a T+10 min — Valorar sin actuar.

La primera decisión: esto ya es público. A diferencia de una detección interna, aquí el atacante potencial no solo existe: hay un foro entero que sabe de la clave. La urgencia es máxima y el sigilo ya no aporta nada, lo que cambia el cálculo respecto al apartado 9.

# 1. Confirmar que la clave existe y sigue activa
gcloud iam service-accounts keys list \
  --iam-account=sa-dataflow-pedidos@alpinashop-datos.iam.gserviceaccount.com \
  --managed-by=user --format="table(name, validAfterTime, validBeforeTime)"

# 2. Actividad de esa cuenta en las ultimas 48 horas
gcloud logging read '
  protoPayload.authenticationInfo.principalEmail="[email protected]"
  AND timestamp>="'$(date -u -d '48 hours ago' +%Y-%m-%dT%H:%M:%SZ)'"' \
  --project=alpinashop-datos --limit=500 \
  --format="table(timestamp, protoPayload.methodName, protoPayload.requestMetadata.callerIp)"

Se busca una sola cosa: peticiones desde IP que no sean las de Dataflow. Si aparecen, el incidente pasa de "credencial expuesta" a "acceso confirmado", con implicaciones legales inmediatas.

T+10 a T+30 min — Contener.

# 1. Borrar la clave (aqui SI se borra: es la credencial expuesta, no la evidencia)
gcloud iam service-accounts keys delete KEY_ID \
  --iam-account=sa-dataflow-pedidos@alpinashop-datos.iam.gserviceaccount.com

# 2. Deshabilitar la cuenta hasta entender el alcance
#    (rompe el pipeline de Dataflow: se asume conscientemente)
gcloud iam service-accounts disable \
  [email protected]

# 3. Congelar evidencia FUERA del proyecto afectado
gcloud logging read 'timestamp>="'$(date -u -d '9 months ago' +%Y-%m-%dT%H:%M:%SZ)'"
  AND protoPayload.authenticationInfo.principalEmail=~"sa-dataflow-pedidos"' \
  --project=alpinashop-datos --format=json > /tmp/evidencia.json
gcloud storage cp /tmp/evidencia.json gs://alpinashop-evidencia-forense/incidente-clave/

# 4. Activar VPC-SC en modo aplicado sobre alpinashop-datos
#    Aunque robaran mas credenciales, los datos ya no pueden salir

La decisión difícil de estos veinte minutos: deshabilitar la cuenta rompe el pipeline de pedidos hacia BigQuery. Se hace igualmente. Ante una credencial pública con acceso al data lake, la analítica puede esperar unas horas. Y es exactamente la decisión que hay que haber pensado antes, no en caliente.

El punto 4 merece atención: si el perímetro hubiera estado aplicado en lugar de en modo de prueba (deuda #7), la clave filtrada no habría podido sacar un solo byte fuera de la organización. El incidente ilustra por qué un control a medias no es medio control.

T+30 min a T+1 h — Determinar si hubo acceso.

-- Todo el uso de la cuenta desde la creacion de la clave, con IP y metodo
SELECT
  DATE(timestamp) AS dia,
  protopayload_auditlog.requestMetadata.callerIp AS ip,
  protopayload_auditlog.methodName               AS metodo,
  COUNT(*) AS n
FROM `alpinashop-datos.auditoria.cloudaudit_googleapis_com_data_access`
WHERE protopayload_auditlog.authenticationInfo.principalEmail
      = '[email protected]'
GROUP BY dia, ip, metodo
ORDER BY dia DESC

Y aquí aparece el problema que convierte este ejercicio en la mejor demostración de por qué la deuda #5 importa: los logs de acceso a datos están desactivados. AlpinaShop tiene los logs de actividad de administrador —sabrá si alguien cambió permisos— pero no puede saber si alguien leyó la tabla de pedidos.

Consecuencia práctica, y es grave: al no poder demostrar que no hubo acceso, jurídicamente hay que tratar el caso como si pudiera haberlo habido. Se pasa de "credencial expuesta sin evidencia de uso" a "posible brecha de datos personales cuyo alcance no se puede acotar". Eso cambia por completo la notificación a la AEPD.

Vías alternativas de evidencia, todas peores:

Fuente Qué aporta Limitación
Logs de facturación Consultas de BigQuery anómalas por bytes procesados Solo si el volumen fue grande
Métricas de Cloud Storage Picos de descarga del data lake Granularidad gruesa
Historial del repositorio Cuándo se subió y cuándo se hizo público No dice quién lo usó
Actividad de administrador Si crearon persistencia No cubre lectura de datos

T+1 a T+2 h — Comunicar.

Momento A quién Qué
T+35 min Dirección "Credencial con acceso a datos de clientes expuesta públicamente. Contenida. Investigando alcance. Posible obligación de notificar a la AEPD."
T+45 min Asesor jurídico / DPO Los hechos, y la limitación de la evidencia
T+1 h Equipo Reparto: Marta contiene, Dani revisa el repositorio, Lucía valida qué datos había
T+2 h Soporte de Google Cloud Abrir caso: pueden aportar datos y ayudar en el análisis

T+2 a T+24 h — Alcance y erradicación.

# 1. Buscar OTRAS credenciales en el historial de TODOS los repositorios
#    Casi nunca hay una sola
for REPO in alpinashop-catalogo alpinashop-infra alpinashop-datos alpinashop-ml; do
  git clone --mirror "[email protected]:alpinashop/$REPO.git" "/tmp/$REPO"
  trufflehog git "file:///tmp/$REPO" --json > "/tmp/hallazgos-$REPO.json"
done

# 2. Buscar persistencia creada en los ultimos 8 meses
gcloud asset search-all-iam-policies --scope=organizations/ORG_ID \
  --query="policy:serviceAccount" --format=json > /tmp/todas-politicas.json

# 3. Rotar TODO lo que estuviera en el mismo repositorio
gcloud secrets versions add db-password-catalogo --data-file=- <<< "$NUEVA"
gcloud secrets versions add api-key-pasarela-pago --data-file=- <<< "$NUEVA_API"

T+24 a T+72 h — Notificación. Si el asesor jurídico determina que hay riesgo para los derechos de los interesados, notificación a la AEPD dentro de las 72 horas. Al no poder descartar el acceso, lo más probable es que haya que notificar. La notificación describe: qué ocurrió, qué datos podrían estar afectados, qué medidas se han tomado, y por qué no se puede acotar el alcance — lo cual, dicho sea con claridad, es en sí mismo un hallazgo desfavorable sobre la organización.

Cambios estructurales después:

Cambio Previene
Activar logs de acceso a datos en alpinashop-datos La imposibilidad de acotar el alcance. El más importante de todos
Escaneo de secretos en cada push (Secret Scanning de GitHub + gancho de pre-commit) Que vuelva a subirse una credencial
Eliminar todas las claves JSON y migrar a federación e impersonación La existencia misma de credenciales permanentes
VPC-SC en modo aplicado Que una credencial filtrada pueda sacar datos
Alerta sobre CreateServiceAccountKey Que se cree una clave sin que nadie se entere
Reescritura del historial de los repositorios (BFG) o rotación de todo lo que contengan Que el histórico siga siendo explotable
Post-mortem sin culpables (07-06) Que la lección se pierda

La reflexión final del ejercicio. La clave se subió hace ocho meses, se borró del repositorio, y todo el mundo pensó que el problema estaba resuelto. Borrar un fichero de Git no borra su historial: cualquiera con acceso al repositorio —o al repositorio público, o a un fork— podía recuperarlo con git log -p. Es un malentendido tan extendido que merece una regla:

Un secreto que ha estado en un repositorio, aunque sea un minuto, está comprometido. La única respuesta correcta es rotarlo. Borrarlo del historial es limpieza, no remediación.

Solución 3 — Priorizar con presupuesto limitado

Restricciones: 10 días de Marta, 3.000 €, un trimestre. Y una premisa que hay que respetar: Marta también tiene que operar la plataforma, así que 10 días de seguridad son 10 días reales, no elásticos.

Lo que se hace:

Semana Tarea Días € Por qué
1 Localizar y eliminar la clave JSON; migrar a federación 0,5 0 Credencial permanente perdida. Riesgo máximo, coste mínimo
1 Cuenta por defecto de Compute: quitar Editor y desactivar 0,5 0 Escalada trivial. Media jornada
1 Cloud Build: acotar a roles concretos 0,5 0 Compromiso del pipeline = compromiso total
1 Alertas de cambios de IAM y creación de claves 0,5 0 Sin detección no hay respuesta. Dos horas
2 Activar logs de acceso a datos en alpinashop-datos y buckets sensibles 1 ~600/año Requisito legal de facto. Sin esto no se puede acotar una brecha
2-3 Plan de respuesta a incidentes escrito y probado en simulacro 1,5 0 Convierte la teoría en capacidad
3-4 Ensayo de restauración de alpinashop-pedidos 1,5 ~100 Una copia no probada no existe. Riesgo existencial
5 VPC-SC de modo de prueba a aplicado 2 0 Ya está el trabajo hecho a medias; terminarlo es barato y muy eficaz
6 Escaneo de vulnerabilidades bloqueante en el pipeline 0,5 0 Medio día
7 Llaves físicas para las 3 personas técnicas 0,5 ~200 Elimina el phishing de credenciales, que es el vector número uno
8 Revisión de accesos + proceso trimestral documentado 1 0 Proceso, no herramienta
— Total 10 ~900 €

Sobran 2.100 €. Y aquí viene la propuesta que da más valor por euro:

Contratar una revisión externa de seguridad de medio día (~1.500-2.000 €) al final del trimestre, sobre la arquitectura ya endurecida.

El razonamiento: Marta ha construido esta plataforma y tiene los puntos ciegos de quien la construyó. Una mirada externa sobre un sistema ya ordenado encuentra cosas distintas de las que encontraría sobre uno desordenado — y por eso se contrata después de pagar la deuda de prioridad 1 y 2, no antes. Además aporta algo que dentro no se puede fabricar: un informe firmado por un tercero, que es lo que pide un cliente corporativo, una aseguradora o una inspección.

Lo que NO se hace, y por qué:

No se hace Motivo
SCC Premium ~1.000 €/mes y nadie puede atender lo que detecte. Comprar detección sin capacidad de respuesta es gastar en tranquilidad, no en seguridad
Binary Authorization 3 días para un riesgo bajo-medio con la cadena de suministro ya controlada por otras vías
PAM 2 días. Valioso, pero con 3 personas y nadie siendo owner de producción, la exposición es menor
Políticas de organización Van en 07-07 con la zona de aterrizaje. Hacerlo dos veces es desperdiciar días
Pruebas de penetración externas 5.000-15.000 € y encontraría lo que ya sabemos. Se hace cuando la deuda conocida esté pagada, no antes

Lo que se le dice a dirección — y esta es la parte que más cuesta y más importa:

"Con estos diez días cerramos los cuatro agujeros que hoy nos exponen más: una contraseña permanente que perdimos hace más de un año y que sigue funcionando, una cuenta interna que da permiso para borrar cualquier cosa, un sistema automático que puede tocarlo todo, y la falta de aviso cuando alguien cambia permisos.

Además hacemos dos cosas que hoy no tenemos y que son obligación legal o de sentido común: saber quién accede a los datos de nuestros clientes —hoy no podríamos responder a esa pregunta ante la Agencia de Protección de Datos, y no poder responderla es en sí mismo un problema— y comprobar que nuestras copias de seguridad se pueden restaurar de verdad, que nunca lo hemos verificado. Y ensayamos qué haríamos si mañana pasa algo, porque el día del incidente es el peor día para improvisar.

Lo que queda sin hacer, y quiero que conste por escrito: no vamos a tener vigilancia activa de amenazas —si alguien entra sin hacer ruido, nos enteraríamos tarde—; no vamos a impedir técnicamente que se despliegue software no aprobado; los permisos administrativos seguirán siendo permanentes en lugar de temporales; y no vamos a contratar un ataque simulado profesional. Cada una de estas cuatro cosas es una decisión de presupuesto, no un descuido, y la revisamos el trimestre que viene.

Con lo que hacemos pasamos de un riesgo que yo calificaría de alto a uno medio. Llegar a bajo requiere una persona dedicada a seguridad o un servicio gestionado, y eso es una conversación distinta que conviene tener cuando seamos sesenta personas o cuando un cliente grande nos lo exija por contrato."

Los tres elementos que hacen buena esta comunicación, y que se pueden reutilizar en cualquier empresa: se habla de riesgos de negocio y no de nombres de productos; se dice explícitamente lo que queda sin cubrir en lugar de dejar que la dirección asuma que todo está resuelto; y se fija cuándo se revisa, lo que convierte un recorte presupuestario en una decisión consciente con fecha en lugar de en un silencio.

Conclusión

AlpinaShop ha pasado de tener controles de seguridad a tener seguridad revisada, y —lo que es más útil— a saber exactamente qué le falta.

Has aplicado defensa en profundidad capa por capa —borde, identidad, red, carga de trabajo, datos, detección— y sobre todo has aprendido la pregunta del fallo único, que es la herramienta más barata y más eficaz de toda la lección: para cada capa, si esto falla, ¿qué lo contiene? Las respuestas dudosas son la deuda.

Tienes los seis principios que explican todas las decisiones del curso: mínimo privilegio con el Recommender que lo hace llevadero, separación de funciones —Dani escribe, Cloud Build construye, Marta aprueba, y nadie puede desplegar y borrar la evidencia—, denegar por defecto, radio de explosión limitado con su pregunta operativa, no confiar en la red, y asumir la brecha.

Entiendes confianza cero y BeyondCorp con IAP como implementación práctica, y has visto que un nivel de acceso basado en el dispositivo —corporativo, cifrado, bloqueado, actualizado, desde una región permitida— resuelve el teletrabajo mucho mejor que cualquier lista de IP, porque hace que la IP deje de importar.

Has revisado la identidad a fondo: eliminar los roles básicos, el script que los encuentra, y los tres hallazgos que aparecen siempre —una persona con Owner, Cloud Build con Editor y, sobre todo, la cuenta por defecto de Compute con Editor, que es el fallo más extendido de Google Cloud y el que convierte cualquier vulnerabilidad de aplicación en un compromiso total. Conoces las alternativas sin claves para cada situación y el comando con --managed-by=user que encuentra las que quedan. Sabes montar elevación temporal con PAM —con el camino de ruptura de cristal que no impide sino que hace ruido— y federación de identidad con la condición de atributos por repository_id y ref que separa una federación segura de una que solo lo parece.

Dominas la cadena de suministro: escaneo que bloquea solo lo que tiene parche, imágenes base mínimas —con la tabla que va de python:3.12 a distroless— fijadas por digest y con --require-hashes, USER 1000 en lugar de root, procedencia SLSA que responde a "¿esta imagen salió de nuestro código?", Binary Authorization —que también aplica a Cloud Run— y Assured Open Source con su alcance honesto.

Has cerrado el ciclo de los datos: clasificación en cuatro niveles con la mejor decisión de seguridad del curso —no almacenar tarjetas, porque el dato que no guardas no se filtra—, descubrimiento con Sensitive Data Protection y detectores españoles, includeQuote: false para no duplicar el problema, CMEK con su "botón rojo" de deshabilitar la clave y su contrapartida irreversible, seudonimización con hash salado y generalización —que no es anonimización— y retención con el procedimiento de supresión que anonimiza los pedidos en lugar de borrarlos porque la obligación fiscal manda.

Sabes detectar: Security Command Center con sus tres niveles, lo que el gratuito encuentra de verdad, y el criterio honesto de que comprar detección que nadie mira es peor que no comprarla. Y tienes los cuatro tipos de log de auditoría con el dato que cambia todo — acceso a datos está desactivado por defecto, y sin él no puedes saber quién leyó los datos de tus clientes— junto con las tres consultas que hay que tener escritas antes de necesitarlas.

Tienes un guion de respuesta realista: diez minutos para valorar sin tocar nada, veinte para contener sin destruir evidencia —deshabilitar y no borrar, y revocar los tokens vivos que sobreviven una hora—, media hora para el alcance incluyendo la búsqueda de persistencia, y la comunicación con las 72 horas del RGPD contando desde el conocimiento y no desde la conclusión. Con la carpeta que hay que preparar hoy, incluido el canal de comunicación alternativo y el simulacro anual.

Sabes qué cumplimiento aporta Google y cuál sigue siendo tuyo, con el malentendido más caro aclarado: sus certificaciones acreditan la capa de abajo, no tu empresa. Y tienes la lista de los siete documentos que se piden antes que cualquier configuración técnica.

Y te llevas dos cosas que valen más que cualquier apartado teórico: el checklist de 43 controles con el estado real de AlpinaShop —20 ✅, 11 ⚠️, 12 ❌— y la deuda priorizada en tres bloques, con cuatro tareas de prioridad 1 que suman menos de dos días y son lo que más riesgo elimina. Más la lista de deuda aceptada conscientemente, porque un riesgo documentado es una decisión de gestión y un riesgo que nadie ha mirado es un agujero.

El ejercicio de la clave filtrada ha dejado la lección más incómoda de todas, y conviene llevársela literal: un secreto que ha estado en un repositorio, aunque sea un minuto, está comprometido; borrarlo del historial es limpieza, no remediación.

Y el ejercicio del presupuesto ha dejado la otra: la seguridad de una pyme no se decide con la lista de lo ideal, sino eligiendo diez días bien gastados y diciéndole a dirección, por escrito y en su idioma, lo que queda sin cubrir.

Dos hilos de esta lección quedan explícitamente abiertos. Uno apunta a 07-06: la restauración de copias nunca se ha probado, y aparece como riesgo crítico tanto en el checklist como en la priorización. El otro apunta a 07-07: las políticas de organización, los logs de auditoría inmutables en un proyecto separado y la revisión periódica de accesos son gobierno, y ahí se resuelven.

Pero antes hay una conversación que lleva siete módulos aplazándose. Cada lección ha añadido servicios; ninguna ha cerrado la factura. Esta misma lección ha propuesto activar los logs de acceso a datos —que cuestan—, ha descartado SCC Premium por precio, y ha repartido 3.000 € como si fueran mucho dinero, que para una empresa de cuarenta personas lo son. Ninguna de esas tres decisiones se puede tomar bien sin entender de dónde sale cada euro de la factura de AlpinaShop. La siguiente lección abre la factura entera, línea a línea, y la explica.

Curso de Google Cloud Platform (GCP)

Módulo 1: Introducción a Google Cloud Platform

Módulo 2: Servicios principales de GCP

Módulo 3: Redes y seguridad

Módulo 4: Datos y análisis

Módulo 5: Aprendizaje automático e IA

Módulo 6: DevOps y monitoreo

Módulo 7: Temas avanzados de GCP

Módulo 8: Proyecto final

© Copyright 2026. Todos los derechos reservados