Tot el que AlpinaShop ha construït en set mòduls funciona perquè tres persones se'n recorden. La Marta es recorda de no crear VM amb IP pública. En Dani es recorda d'etiquetar els recursos. La Lucía es recorda de no exportar dades a un bucket extern. Els SLO viuen en un tauler que algú manté, els runbooks s'actualitzen perquè algú els revisa, i la política de pressupost d'error es respecta perquè l'equip la va acordar una tarda.

Res d'això no està malament. Tot això és fràgil.

Perquè avui, tècnicament, res no impedeix que demà algú creï una màquina amb IP pública a Iowa, desactivi un registre d'auditoria, comparteixi un bucket amb allUsers, o descarregui una clau JSON d'un compte de servei. Les bones pràctiques del curs són acords, no controls. I un acord es trenca el dia que entra una persona nova, el dia que algú té pressa, o el dia que l'empresa passa de tres persones tècniques a vuit.

Govern és exactament això: convertir els acords en controls que s'apliquen sols, i tenir registre fiable de tot el que passa. És la diferència entre «confiem que ningú no ho faci» i «no es pot fer».

I hi ha un malentès que convé desfer des de la primera línia: el govern no és una cosa que s'implanta quan s'és gran. S'implanta abans, perquè adaptar polítiques a posteriori sobre una organització amb cinquanta projectes i mil recursos és una obra de mesos, mentre que aplicar-les sobre cinc projectes és una tarda. AlpinaShop arriba tard, però no massa tard. Aquesta és l'única raó per la qual aquesta lliçó encara és factible en un dia de feina.

En acabar sabràs estructurar una organització i entendre les conseqüències de cada criteri, aplicar polítiques d'organització que s'hereten i bloquegen de debò, inventariar tot el que existeix i monitorar els canvis en temps real, activar i llegir els registres d'auditoria que importen, muntar retenció immutable en un projecte on ni els administradors puguin escriure, i desplegar tot això de cop com una zona d'aterratge amb Terraform.

I tancaràs el mòdul 7 i, amb ell, el recorregut complet d'AlpinaShop.

Contingut

  1. Què és el govern al núvol i per què es necessita abans del que sembla
  2. L'estructura de l'organització: criteris i conseqüències
  3. Polítiques d'organització: què són i com s'hereten
  4. Les polítiques que AlpinaShop aplica, una a una
  5. Mode de prova i el perill de bloquejar l'equip
  6. Restriccions personalitzades i la seva relació amb Policy Controller
  7. Quotes i límits a nivell d'organització
  8. Cloud Asset Inventory: saber què existeix
  9. Feeds: monitorar canvis en temps real
  10. Cloud Audit Logs de debò
  11. Les consultes d'auditoria que cal tenir preparades
  12. Retenció immutable en un projecte separat
  13. Zones d'aterratge i l'ordre que importa
  14. Gestió del canvi: revisions, cicle de vida i excepcions
  15. La maduresa d'AlpinaShop, i el que li queda

  1. Què és el govern al núvol i per què es necessita abans del que sembla

Govern és el conjunt d'estructures, polítiques i processos que asseguren que l'ús del núvol és coherent amb el que l'organització vol. Cobreix quatre preguntes:

Pregunta Mecanisme
Què es pot crear, on i com? Polítiques d'organització
Què existeix ara mateix? Cloud Asset Inventory
Qui ha fet què i quan? Cloud Audit Logs
Qui paga què? Estructura de projectes i etiquetes

I la raó per la qual es necessita abans de «ser gran», que és purament aritmètica:

Moment Projectes Recursos Cost d'aplicar polítiques
Ara (AlpinaShop) 5 ~120 Un dia
D'aquí a dos anys 15 ~600 Una setmana, amb negociacions
D'aquí a cinc anys 60 ~5.000 Mesos, i probablement no es farà

El cost no creix amb el nombre de recursos: creix amb el nombre d'excepcions que cal negociar. Aplicar avui la política «prohibit crear VM amb IP pública» afecta zero recursos existents. Aplicar-la d'aquí a cinc anys significa trobar les quaranta VM que la incompleixen, esbrinar per què, parlar amb sis equips i acceptar vint excepcions permanents que buiden la política de contingut.

La regla: les polítiques es posen quan no molesten ningú. Després ja no es posen.

  1. L'estructura de l'organització: criteris i conseqüències

La jerarquia de 01-04, revisada ara amb tot el context:

flowchart TB
    ORG["Organització<br/>alpinashop.example"]
    ORG --> F1["Carpeta produccion"]
    ORG --> F2["Carpeta desarrollo"]
    ORG --> F3["Carpeta compartido"]
    ORG --> F4["Carpeta seguridad"]

    F1 --> P1["alpinashop-prod"]
    F2 --> P2["alpinashop-dev"]
    F3 --> P3["alpinashop-datos"]
    F3 --> P4["alpinashop-cicd"]
    F3 --> P5["alpinashop-red"]
    F4 --> P6["alpinashop-auditoria"]

    style F4 fill:#e6f4ea,stroke:#34a853
    style P6 fill:#e6f4ea,stroke:#34a853

Dos canvis respecte de 01-04: el projecte alpinashop-red de 07-03 viu a compartido, i apareix una carpeta nova, seguridad, amb alpinashop-auditoria — el projecte de registres immutables de l'apartat 12. Està separat a propòsit: és l'únic on ni la Marta pot escriure.

Els tres criteris per estructurar

Poques vegades se n'usa un de sol; l'habitual és combinar dos nivells.

Criteri Estructura Avantatge Inconvenient
Per entorn produccion / desarrollo / pruebas Polítiques molt diferents per entorn; separació de permisos neta Un equip amb diverses aplicacions les té repartides
Per equip o unitat tienda / analitica / corporativo Facturació per equip directa; autonomia Producció i desenvolupament barrejats: mateixes polítiques per a tots dos
Per aplicació Una carpeta per producte Aïllament màxim Explosió de carpetes; duplicació

Conseqüències de l'elecció, que és el que gairebé mai no es pensa abans de decidir:

Aspecte Efecte de l'estructura
Permisos S'hereten cap avall. Un rol a la carpeta produccion s'aplica a tots els seus projectes
Polítiques d'organització S'hereten igual. Per això separar entorns permet polítiques més dures en producció
Facturació S'agrega per carpeta i projecte. L'estructura és el desglossament de costos
Quotes Són del projecte, no de la carpeta. Un projecte per aplicació aïlla l'esgotament de quota
Radi d'explosió El projecte és la frontera d'esborrat. Esborrar un projecte s'endú tot el que hi ha dins

L'elecció d'AlpinaShop és per entorn al primer nivell, i és la correcta per a la seva mida per un motiu concret: la diferència d'exigència entre producció i desenvolupament és molt més gran que la diferència entre equips. Ningú no és owner de producció, però en desenvolupament es pot ser editor sense drama. Amb una estructura per equips, aquesta distinció seria impossible d'expressar.

Quan AlpinaShop tingui quatre productes, l'evolució natural és un segon nivell: produccion/tienda, produccion/alpinapro, desarrollo/tiendaL'estructura de carpetes es pot canviar; moure projectes entre carpetes és una comanda. El que no es pot canviar és el projectId.

  1. Polítiques d'organització: què són i com s'hereten

L'Organization Policy Service és el que converteix els acords en controls.

IAM Polítiques d'organització
Respon a Qui pot fer què? Què es pot fer, amb independència de qui?
Subjecte Identitats Recursos i configuracions
Exemple «La Marta pot crear VM» «Ningú no pot crear VM amb IP pública»
Se salta amb Un rol més alt Res. Ni un owner d'organització

L'última fila és l'essència: una política d'organització no és un permís. Encara que tinguis roles/owner al projecte, si la política prohibeix IP públiques, no en pots crear una. Només la pot aixecar qui tingui roles/orgpolicy.policyAdmin al nivell on està definida.

Tipus de restricció

Tipus Què fa Exemple
Booleana Activa o desactiva un comportament compute.requireOsLogin, storage.uniformBucketLevelAccess
De llista Permet o denega valors concrets gcp.resourceLocations amb la llista de regions
Personalitzada CEL propi sobre atributs del recurs «Les VM han de ser de tipus e2 o n2»

L'herència, que és on hi ha la subtilesa

Les polítiques s'hereten d'organització → carpeta → projecte, i a cada nivell es pot:

Acció Efecte
Heretar (per defecte) S'aplica el del pare
Combinar (inheritFromParent: true) El del pare més el propi
Substituir (inheritFromParent: false) Només el propi; s'ignora el pare
Restaurar el valor per defecte Elimina la política en aquest nivell
flowchart TB
    O["Organització<br/>regions: europe-west1, europe-southwest1"] --> F1["Carpeta produccion<br/>hereta"]
    O --> F2["Carpeta desarrollo<br/>hereta + afegeix europe-west4"]
    F1 --> P1["alpinashop-prod<br/>només europe-west1, europe-southwest1"]
    F2 --> P2["alpinashop-dev<br/>3 regions"]

    style F1 fill:#e6f4ea,stroke:#34a853
    style F2 fill:#fef7e0,stroke:#fbbc04

I la regla que sorprèn tothom: per a les restriccions de llista, una denegació en qualsevol nivell guanya sempre. Un fill no pot permetre el que el pare denega. És el que fa que el model sigui segur: no es pot escapar d'una política cap avall.

Conseqüència pràctica: les polítiques es defineixen al nivell més alt on tinguin sentit, i es relaxen cap avall només amb allow addicionals sobre restriccions que el pare no va denegar explícitament. Si et trobes necessitant «des-denegar» alguna cosa, la política estava mal posada.

  1. Les polítiques que AlpinaShop aplica, una a una

Aquestes són les nou, amb el seu motiu, el seu nivell i el seu risc.

4.1 Prohibir IP públiques a les VM

gcloud resource-manager org-policies enable-enforce \
  constraints/compute.vmExternalIpAccess \
  --organization=ORG_ID

Amb l'excepció declarada, si alguna VM legítima la necessités:

# politica-ip-externa.yaml
constraint: constraints/compute.vmExternalIpAccess
listPolicy:
  deniedValues:
    - "under:organizations/ORG_ID"
  # Excepcio explicita i documentada, si calgues:
  # allowedValues:
  #   - "projects/alpinashop-dev/zones/europe-west1-b/instances/bastion-pruebas"

Motiu: a AlpinaShop no queda cap VM a la ruta de servei (07-02), i les de desenvolupament surten per Cloud NAT. Una IP pública és superfície d'atac directa des d'internet. Nivell: organització. Risc de bloqueig: baix.

4.2 Restringir les regions a la UE

constraint: constraints/gcp.resourceLocations
listPolicy:
  allowedValues:
    - in:europe-west1-locations
    - in:europe-southwest1-locations
    - in:eu-locations            # multiregio EU: BigQuery, buckets

Motiu: residència de la dada a la UE (07-01, 07-04) i control de costos de trànsit intercontinental (07-05). Nivell: organització. Risc: mitjà-alt, i és la política que més disgustos dona. Molts serveis tenen components globals o multiregió, i in:eu-locations és imprescindible perquè BigQuery multiregió i alguns buckets continuïn funcionant. Aquesta es prova en dry-run sí o sí.

4.3 Desactivar la creació de claus de comptes de servei

gcloud resource-manager org-policies enable-enforce \
  constraints/iam.disableServiceAccountKeyCreation \
  --organization=ORG_ID

Motiu: és el deute #1 de 07-04. Amb federació d'identitat i impersonació ja en ús, no hi ha cap cas legítim que necessiti una clau descarregable. Nivell: organització. Risc: mitjà. Pot trencar integracions antigues — per això primer l'inventari de l'apartat 8, després la política.

I la seva companya, que impedeix que una clau existent valgui eternament:

constraint: constraints/iam.serviceAccountKeyExpiryHours
listPolicy:
  allowedValues:
    - "24h"     # si excepcionalment se n permet una, caduca en 24 hores

4.4 Exigir OS Login

gcloud resource-manager org-policies enable-enforce \
  constraints/compute.requireOsLogin \
  --organization=ORG_ID

Motiu: OS Login vincula l'accés SSH a la identitat de Google en lloc de a claus SSH soltes a les metadades. Això significa que donar de baixa una persona al directori li treu l'accés a totes les màquines, que és exactament el que no passa amb les claus a les metadades. Nivell: organització. Risc: baix (amb prou feines queden VM).

4.5 Prohibir buckets públics

gcloud resource-manager org-policies enable-enforce \
  constraints/storage.publicAccessPrevention \
  --organization=ORG_ID

Motiu: un bucket públic és la fuita de dades més comuna del núvol, en tots els proveïdors. Nivell: organització. Risc: baix… amb un parany que cal resoldre abans: el bucket alpinashop-catalogo serveix imatges a la botiga. Si aquestes imatges se serveixen a través del balancejador amb bb-catalogo-imagenes (03-02), el bucket no necessita ser públic i la política no trenca res. Si algú les estigués servint per URL directa de Cloud Storage, sí. Comprovar-ho és part de la feina prèvia.

4.6 Accés uniforme a nivell de bucket

gcloud resource-manager org-policies enable-enforce \
  constraints/storage.uniformBucketLevelAccess \
  --organization=ORG_ID

Motiu: desactiva les ACL per objecte, que són un sistema de permisos paral·lel a IAM, invisible a les auditories i responsable d'innombrables fuites. Amb accés uniforme, IAM és l'única veritat.

4.7 Requerir CMEK i limitar els projectes de clau

# Exigir clau gestionada pel client als serveis que ho admeten
constraint: constraints/gcp.restrictNonCmekServices
listPolicy:
  deniedValues:
    - bigquery.googleapis.com
    - sqladmin.googleapis.com
    - storage.googleapis.com
# I que la clau vingui NOMES del nostre keyring
constraint: constraints/gcp.restrictCmekCryptoKeyProjects
listPolicy:
  allowedValues:
    - projects/alpinashop-prod      # on viu alpinashop-keyring

Motiu: tanca el control de 03-06. La segona política és la que gairebé ningú no posa i evita que algú xifri dades amb una clau d'un projecte que no controles. Nivell: carpeta produccion. Risc: alt. Exigir CMEK trenca la creació de recursos si la clau no té els permisos correctes concedits a l'agent de servei corresponent. dry-run obligatori.

4.8 Impedir concessions automàtiques als comptes per defecte

gcloud resource-manager org-policies enable-enforce \
  constraints/iam.automaticIamGrantsForDefaultServiceAccounts \
  --organization=ORG_ID

Motiu: és el deute #2 de 07-04. Impedeix que els projectes nous neixin amb el compte per defecte de Compute amb rol Editor. No arregla els projectes existents, que cal corregir a mà.

4.9 Restringir l'ús compartit per domini

constraint: constraints/iam.allowedPolicyMemberDomains
listPolicy:
  allowedValues:
    - "C03xxxxxx"        # ID de client de Cloud Identity d alpinashop.example

Motiu: impedeix concedir permisos a comptes de Google alienes a l'organització. És la política que evita l'error de donar accés a [email protected] «temporalment». Risc: alt. Trenca qualsevol col·laboració legítima amb externs, i trenca alguns agents de servei de Google. Requereix excepcions acurades i dry-run llarg.

Resum

# Restricció Nivell Risc Deute que tanca
1 compute.vmExternalIpAccess Organització Baix
2 gcp.resourceLocations Organització Mitjà-alt Residència UE
3 iam.disableServiceAccountKeyCreation Organització Mitjà 07-04 #1
4 compute.requireOsLogin Organització Baix
5 storage.publicAccessPrevention Organització Baix
6 storage.uniformBucketLevelAccess Organització Baix
7 gcp.restrictNonCmekServices + projectes de clau Carpeta produccion Alt Tanca 03-06
8 iam.automaticIamGrants... Organització Baix 07-04 #2
9 iam.allowedPolicyMemberDomains Organització Alt

  1. Mode de prova i el perill de bloquejar l'equip

Les polítiques d'organització admeten mode de prova (dry run), igual que Cloud Armor (03-05), Policy Controller (07-01) i VPC Service Controls (07-03). És el mateix patró per quarta vegada al curs, i no és casualitat: tot control que denega s'ha de mesurar abans d'aplicar-se.

# Aplicar NOMES en mode de prova
gcloud org-policies set-policy politica-regiones.yaml --update-mask=dryRunSpec
# politica-regiones.yaml
name: organizations/ORG_ID/policies/gcp.resourceLocations
dryRunSpec:
  rules:
    - values:
        allowedValues:
          - in:europe-west1-locations
          - in:europe-southwest1-locations
          - in:eu-locations

I la consulta del que hauria bloquejat:

SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.methodName                        AS operacio,
  protopayload_auditlog.resourceName                      AS recurs,
  protopayload_auditlog.metadata.dryRun                   AS en_proves,
  protopayload_auditlog.metadata.constraint               AS restriccio
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_policy`
WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 14 DAY)
  AND protopayload_auditlog.metadata.dryRun = TRUE
ORDER BY timestamp DESC

El procediment segur, i l'ordre importa

Pas Què Durada
1 Inventariar el que ja incompleix (apartat 8) 1 h
2 Corregir o documentar cada incompliment Variable
3 Aplicar a dryRunSpec 10 min
4 Esperar 14 dies, cobrint processos setmanals i mensuals 14 dies
5 Revisar violacions i decidir excepcions 1 h
6 Aplicar de debò, començant per alpinashop-dev 10 min
7 Una setmana en desenvolupament sense incidències 7 dies
8 Aplicar en producció 10 min

El pas 1 és el que se salta tothom i el que evita el desastre. Aplicar dry-run sobre polítiques que ja s'incompleixen genera centenars de violacions que ningú no analitzarà, i el resultat pràctic és que s'ignoren totes.

Les cinc maneres de bloquejar-se a un mateix

Reals, totes vistes en producció:

Error Conseqüència Prevenció
allowedPolicyMemberDomains sense excepcions per als agents de servei de Google Serveis interns deixen de funcionar de maneres inexplicables dry-run llarg; llista d'agents
resourceLocations sense in:eu-locations BigQuery multiregió i alguns buckets deixen de crear-se Incloure sempre les multiregió que facis servir
disableServiceAccountKeyCreation amb integracions antigues vives Un sistema extern deixa d'autenticar-se sense avís previ Inventariar claus primer
CMEK exigit sense permisos a l'agent de servei sobre la clau Els recursos nous no es poden crear Concedir cloudkms.cryptoKeyEncrypterDecrypter abans
Política aplicada un divendres El cap de setmana sencer sense poder desplegar Aplicar dimarts o dimecres al matí

I la salvaguarda que cal preparar abans de tot això: que almenys dues persones tinguin roles/orgpolicy.policyAdmin a l'organització, amb accés verificat. Si l'única persona que pot aixecar una política es bloqueja a si mateixa, la sortida passa pel suport de Google i per un mal dia.

  1. Restriccions personalitzades i la seva relació amb Policy Controller

Quan cap restricció predefinida no serveix, s'escriu una restricció personalitzada amb CEL (Common Expression Language):

# restriccion-tipos-maquina.yaml
name: organizations/ORG_ID/customConstraints/custom.tiposMaquinaPermitidos
resourceTypes:
  - compute.googleapis.com/Instance
methodTypes:
  - CREATE
  - UPDATE
condition: "resource.machineType.contains('e2-') || resource.machineType.contains('n2-')"
actionType: ALLOW
displayName: "Nomes families de maquina e2 i n2"
description: "Evita families cares o especialitzades sense justificacio (07-05)."
gcloud org-policies set-custom-constraint restriccion-tipos-maquina.yaml
gcloud resource-manager org-policies enable-enforce \
  custom.tiposMaquinaPermitidos --organization=ORG_ID

Un altre exemple, aquesta vegada per a les etiquetes obligatòries de 07-05:

name: organizations/ORG_ID/customConstraints/custom.etiquetasObligatorias
resourceTypes:
  - compute.googleapis.com/Instance
  - storage.googleapis.com/Bucket
methodTypes: [CREATE]
condition: "has(resource.labels['centro-coste']) && has(resource.labels['entorno'])"
actionType: ALLOW
displayName: "Etiquetes centro-coste i entorno obligatories"

Polítiques d'organització davant de Policy Controller

Dos sistemes que fan coses semblants en àmbits diferents. La confusió és habitual:

Polítiques d'organització Policy Controller (07-01)
Àmbit Recursos de Google Cloud Recursos de Kubernetes
On actua API de Google Cloud Webhook d'admissió del clúster
Llenguatge CEL Rego (OPA/Gatekeeper)
Abast Tota l'organització Els clústers de la flota
Exemple «Cap VM amb IP pública» «Cap pod privilegiat»
Cost Inclòs Nivell base de GKE / GKE Enterprise

No competeixen: es complementen. Una política d'organització no pot impedir que un pod s'executi com a root, i Policy Controller no pot impedir que es creï un bucket públic. Una organització amb Kubernetes necessita totes dues.

Per a AlpinaShop, que va retirar les càrregues de producció de GKE a 07-02, les polítiques d'organització són les que importen. Policy Controller queda com a control del clúster de proves, cosa coherent amb el que es va decidir a DA-004.

  1. Quotes i límits a nivell d'organització

Les quotes es gestionen per projecte, però es poden governar des de dalt mitjançant polítiques i automatització.

Palanca Què fa
Ajust de quotes per projecte Baixar la quota de vCPU en desenvolupament (07-05)
compute.quotaOverrides Restringir famílies de màquines cares
Quotes de cost de BigQuery Bytes per usuari i dia
Quotes de taxa d'API Contenir bucles descontrolats
# Baixar la quota de CPU en desenvolupament: fre DUR a la despesa
gcloud alpha services quota update \
  --service=compute.googleapis.com \
  --consumer=projects/alpinashop-dev \
  --metric=compute.googleapis.com/cpus \
  --unit=1/{project}/{region} \
  --dimensions=region=europe-west1 \
  --value=8

La distinció essencial, ja vista a 07-05 i que aquí es col·loca al seu lloc: els pressupostos avisen, les quotes impedeixen. En una organització governada calen totes dues, i les quotes són l'únic mecanisme que no depèn que algú reaccioni.

  1. Cloud Asset Inventory: saber què existeix

No es pot governar el que no se sap que existeix. Cloud Asset Inventory és el catàleg de tots els recursos, polítiques IAM i configuracions de l'organització, amb el seu historial.

gcloud services enable cloudasset.googleapis.com --project=alpinashop-auditoria

Buscar recursos

# Tot el que existeix a l organitzacio
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --format="table(name, assetType, location, project)"

# VM amb IP publica: l inventari PREVI a la politica 4.1
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --asset-types=compute.googleapis.com/Instance \
  --query="natIP:*" \
  --format="table(name, project, location)"

# Recursos fora d Europa: l inventari previ a la politica 4.2
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --query="NOT location:europe AND NOT location:eu AND NOT location:global" \
  --format="table(name, assetType, location, project)"

# Recursos SENSE etiqueta de centre de cost (07-05)
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --query="NOT labels.centro-coste:*" \
  --format="table(name, assetType, project)"

Buscar polítiques IAM

# Qui te owner en qualsevol lloc de l organitzacio
gcloud asset search-all-iam-policies --scope=organizations/ORG_ID \
  --query="policy:roles/owner" \
  --format="table(resource, policy.bindings.members)"

# Qualsevol cosa concedida a un compte extern
gcloud asset search-all-iam-policies --scope=organizations/ORG_ID \
  --query="policy:gmail.com" \
  --format="table(resource, policy.bindings.role, policy.bindings.members)"

Aquestes dues consultes, executades abans d'aplicar les polítiques 4.1, 4.2 i 4.9, són exactament el pas 1 del procediment de l'apartat 5. Sense elles, s'aplica a cegues.

Exportar a BigQuery

gcloud asset export --organization=ORG_ID \
  --content-type=resource \
  --bigquery-table=projects/alpinashop-auditoria/datasets/inventario/tables/recursos \
  --output-bigquery-force

gcloud asset export --organization=ORG_ID \
  --content-type=iam-policy \
  --bigquery-table=projects/alpinashop-auditoria/datasets/inventario/tables/politicas_iam \
  --output-bigquery-force

Amb exportació diària programada (Cloud Scheduler + Workflows, 04-06), s'obté una cosa molt valuosa: l'històric de l'inventari, que permet respondre «què hi havia el 3 de juny?» — una pregunta que apareix en tota investigació i en tota auditoria.

-- Recursos creats en els ultims 7 dies, per tipus i projecte
SELECT
  asset_type,
  SPLIT(name, '/')[SAFE_OFFSET(4)] AS projecte,
  COUNT(*) AS nous
FROM `alpinashop-auditoria.inventario.recursos`
WHERE DATE(update_time) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
GROUP BY asset_type, projecte
ORDER BY nous DESC

  1. Feeds: monitorar canvis en temps real

L'inventari diu què hi ha. Els feeds avisen quan alguna cosa canvia, en el moment.

# 1. Topic on arribaran les notificacions
gcloud pubsub topics create cambios-criticos --project=alpinashop-auditoria

# 2. Feed sobre canvis de politiques IAM a TOTA l organitzacio
gcloud asset feeds create feed-iam \
  --organization=ORG_ID \
  --content-type=iam-policy \
  --asset-types="cloudresourcemanager.googleapis.com/Project,cloudresourcemanager.googleapis.com/Folder" \
  --pubsub-topic=projects/alpinashop-auditoria/topics/cambios-criticos

# 3. Feed sobre creacio o canvi de recursos sensibles
gcloud asset feeds create feed-recursos-sensibles \
  --organization=ORG_ID \
  --content-type=resource \
  --asset-types="compute.googleapis.com/Firewall,storage.googleapis.com/Bucket,iam.googleapis.com/ServiceAccountKey" \
  --pubsub-topic=projects/alpinashop-auditoria/topics/cambios-criticos

I la funció que decideix què mereix un avís immediat (Cloud Functions 2a gen, 06-03):

# main.py — evaluar-cambio-critico
import base64, json, logging

CRITICS = [
    ("roles/owner",             "S ha concedit OWNER"),
    ("roles/editor",            "S ha concedit EDITOR"),
    ("allUsers",                "ACCES PUBLIC concedit"),
    ("allAuthenticatedUsers",   "ACCES SEMIPUBLIC concedit"),
    ("roles/iam.securityAdmin", "S ha concedit SECURITY ADMIN"),
]

def avaluar_canvi(esdeveniment, context):
    dades = json.loads(base64.b64decode(esdeveniment["data"]).decode("utf-8"))
    actiu = dades.get("asset", {})
    nom   = actiu.get("name", "")
    tipus = actiu.get("assetType", "")

    # 1. Creacio d una clau de compte de servei: SEMPRE excepcional (07-04)
    if tipus == "iam.googleapis.com/ServiceAccountKey":
        alertar("CRITICAL", "Clau de compte de servei creada", nom, dades)
        return

    # 2. Concessions perilloses
    text = json.dumps(actiu.get("iamPolicy", {}))
    for patro, missatge in CRITICS:
        if patro in text:
            alertar("CRITICAL", missatge, nom, dades)
            return

    # 3. Regla de tallafoc oberta a tot internet en un port d administracio
    if tipus == "compute.googleapis.com/Firewall":
        r = actiu.get("resource", {}).get("data", {})
        rangs = r.get("sourceRanges", [])
        if "0.0.0.0/0" in rangs and r.get("direction") == "INGRESS":
            for permes in r.get("allowed", []):
                ports = permes.get("ports", [])
                if any(p in ("22", "3389", "3306", "5432") for p in ports):
                    alertar("CRITICAL", "Tallafoc obert a internet en port sensible",
                            nom, dades)
                    return

def alertar(gravetat, missatge, recurs, dades):
    logging.log_struct({
        "severity": gravetat,
        "message": missatge,
        "recurso": recurs,
        "momento": dades.get("window", {}).get("startTime"),
        "alerta_gobierno": True,
    })

Amb una alerta de Cloud Monitoring sobre jsonPayload.alerta_gobierno=true i gravetat CRITICAL, aquests esdeveniments arriben al canal de seguretat en segons.

Això tanca el deute #4 de 07-04 —alertes de canvis d'IAM i creació de claus— i és la diferència entre assabentar-se d'un canvi perillós en el moment o descobrir-lo a la revisió trimestral, sis setmanes després.

  1. Cloud Audit Logs de debò

S'han esmentat a 03-04, 06-06 i 07-04. Aquí, complets.

Tipus Què registra Per defecte? Cost Es pot desactivar
Activitat d'administrador Canvis de configuració i permisos Gratis No
Accés a dades Lectures i escriptures de dades No (llevat de BigQuery) De pagament, i pot ser molt
Esdeveniments del sistema Accions de Google sobre els teus recursos Gratis No
Denegacions de política Peticions rebutjades per polítiques Gratis No

Les dues dades que cal retenir:

  1. L'activitat d'administrador és gratis i no es pot desactivar. Ni tan sols un atacant amb permisos d'organització pot esborrar l'evidència dels seus canvis de configuració. És el fonament de tota investigació.
  2. L'accés a dades està desactivat per defecte. I sense ell, com va demostrar l'exercici de 07-04, no es pot saber qui va llegir les dades dels clients — cosa que converteix una credencial filtrada en una bretxa d'abast indeterminable.

Activar l'accés a dades on importa

Es fa amb criteri, perquè el volum pot ser enorme:

# politica-auditoria.yaml — aplicada a la CARPETA produccion
auditConfigs:
  # BigQuery: lectures i escriptures de dades de clients
  - service: bigquery.googleapis.com
    auditLogConfigs:
      - logType: DATA_READ
      - logType: DATA_WRITE

  # Cloud Storage: nomes escriptures i administracio.
  # DATA_READ en un bucket que serveix imatges generaria milions d entrades.
  - service: storage.googleapis.com
    auditLogConfigs:
      - logType: DATA_WRITE
      - logType: ADMIN_READ

  # Secret Manager: TOT. Cada acces a un secret es rellevant
  - service: secretmanager.googleapis.com
    auditLogConfigs:
      - logType: DATA_READ
      - logType: DATA_WRITE

  # Cloud KMS: cada us de clau
  - service: cloudkms.googleapis.com
    auditLogConfigs:
      - logType: DATA_READ
      - logType: DATA_WRITE

  # IAM: canvis d identitat
  - service: iam.googleapis.com
    auditLogConfigs:
      - logType: DATA_WRITE
gcloud resource-manager folders set-iam-policy FOLDER_PRODUCCION_ID politica-auditoria.yaml

Les tres decisions que fan això assequible, i són les que converteixen un cost inacceptable en uns 35 € al mes (07-05):

  • Cloud Storage sense DATA_READ: el bucket del catàleg serveix milions d'imatges; registrar cada lectura costaria més que tota la plataforma. Es registren escriptures i lectures administratives, que és on hi ha el risc.
  • Secret Manager amb tot: són pocs accessos i cadascun importa.
  • Només a la carpeta produccion: en desenvolupament no hi ha dades reals.

I una exclusió imprescindible perquè el volum no es dispari:

gcloud logging sinks update _Default \
  --add-exclusion=name=ruido-lecturas-repetitivas,\
filter='protoPayload.methodName="google.storage.objects.get"
        AND protoPayload.authenticationInfo.principalEmail=~"^service-.*gserviceaccount.com$"'

S'exclouen les lectures que fan els mateixos agents de servei de Google, que són soroll pur i d'altíssim volum.

Com es llegeix una entrada

{
  "protoPayload": {
    "@type": "type.googleapis.com/google.cloud.audit.AuditLog",
    "authenticationInfo": {
      "principalEmail": "[email protected]",
      "serviceAccountDelegationInfo": [
        {"firstPartyPrincipal": {"principalEmail": "sa-deploy-prod@..."}}
      ]
    },
    "requestMetadata": {
      "callerIp": "88.20.145.33",
      "callerSuppliedUserAgent": "google-cloud-sdk gcloud/latest"
    },
    "serviceName": "cloudresourcemanager.googleapis.com",
    "methodName": "SetIamPolicy",
    "authorizationInfo": [
      {"resource": "projects/alpinashop-prod",
       "permission": "resourcemanager.projects.setIamPolicy",
       "granted": true}
    ],
    "resourceName": "projects/alpinashop-prod",
    "serviceData": {"policyDelta": {"bindingDeltas": [
      {"action": "ADD", "role": "roles/owner", "member": "user:[email protected]"}
    ]}}
  },
  "severity": "NOTICE",
  "timestamp": "2026-08-04T18:23:11.004Z"
}

Els camps que cal saber llegir, per ordre d'importància:

Camp Què diu
authenticationInfo.principalEmail Qui
serviceAccountDelegationInfo Qui de debò, si hi va haver impersonació (03-04)
methodName Què
resourceName Sobre què
requestMetadata.callerIp Des d'on
authorizationInfo.granted Si es va permetre o es va denegar
serviceData.policyDelta El canvi exacte, en els canvis d'IAM
timestamp Quan

serviceAccountDelegationInfo és el camp que gairebé ningú no mira i el que més importa en una organització que fa servir impersonació: sense ell, un canvi fet per la Marta impersonant sa-deploy-prod apareix atribuït al compte de servei, i es perd el rastre de la persona.

  1. Les consultes d'auditoria que cal tenir preparades

Escrites i desades com a vistes abans de necessitar-les. Un incident no és moment d'aprendre SQL.

1. Qui va canviar una política IAM?

SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.resourceName                      AS recurs,
  delta.action                                            AS accio,
  delta.role                                              AS rol,
  delta.member                                            AS membre,
  protopayload_auditlog.requestMetadata.callerIp          AS ip
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`,
  UNNEST(JSON_QUERY_ARRAY(protopayload_auditlog.servicedata_v1_iam.policyDelta.bindingDeltas)) AS d,
  UNNEST([STRUCT(
    JSON_VALUE(d, '$.action') AS action,
    JSON_VALUE(d, '$.role')   AS role,
    JSON_VALUE(d, '$.member') AS member)]) AS delta
WHERE protopayload_auditlog.methodName LIKE '%SetIamPolicy%'
  AND DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
ORDER BY timestamp DESC

2. Qui va accedir a dades de clients?

SELECT
  DATE(timestamp) AS dia,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.resourceName                      AS taula,
  COUNT(*) AS accessos
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_data_access`
WHERE protopayload_auditlog.serviceName = 'bigquery.googleapis.com'
  AND protopayload_auditlog.resourceName LIKE '%pedidos%'
  AND DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY dia, qui, taula
ORDER BY dia DESC, accessos DESC

3. Qui va apagar o modificar un registre?

SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.methodName                        AS operacio,
  protopayload_auditlog.resourceName                      AS recurs
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`
WHERE (protopayload_auditlog.methodName LIKE '%UpdateSink%'
    OR protopayload_auditlog.methodName LIKE '%DeleteSink%'
    OR protopayload_auditlog.methodName LIKE '%UpdateBucket%'
    OR protopayload_auditlog.methodName LIKE '%SetIamPolicy%'
       AND protopayload_auditlog.serviceName = 'logging.googleapis.com'
    OR protopayload_auditlog.methodName LIKE '%UpdateExclusion%')
  AND DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
ORDER BY timestamp DESC

Aquesta tercera és la més important de les tres, i la que menys gent té. Manipular el registre és el primer que fa un atacant competent i el primer que fa algú que vol amagar un error. Ha de tenir alerta permanent.

4. Activitat fora d'horari.

SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.methodName                        AS operacio,
  protopayload_auditlog.requestMetadata.callerIp          AS ip
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`
WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
  AND NOT REGEXP_CONTAINS(
        protopayload_auditlog.authenticationInfo.principalEmail, r'^(service-|.*gserviceaccount)')
  AND (EXTRACT(HOUR FROM timestamp AT TIME ZONE 'Europe/Madrid') NOT BETWEEN 7 AND 21
       OR EXTRACT(DAYOFWEEK FROM timestamp AT TIME ZONE 'Europe/Madrid') IN (1, 7))
ORDER BY timestamp DESC

El filtre que exclou comptes de servei és essencial: el programari treballa de nit, i sense aquest filtre la consulta retorna milers de línies irrellevants.

5. Denegacions de política: el que algú va intentar i no va poder.

SELECT
  DATE(timestamp) AS dia,
  protopayload_auditlog.authenticationInfo.principalEmail AS qui,
  protopayload_auditlog.methodName                        AS operacio,
  protopayload_auditlog.status.message                    AS motiu,
  COUNT(*) AS intents
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_policy`
WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY dia, qui, operacio, motiu
ORDER BY intents DESC

Aquesta consulta té un doble ús que convé entendre: detecta intents maliciosos, i també detecta polítiques mal calibrades que estan destorbant l'equip. Si en Dani apareix vint vegades intentant una cosa legítima, el problema és la política, no en Dani.

  1. Retenció immutable en un projecte separat

Aquí hi ha la peça que fa que l'auditoria sigui creïble.

Un registre que l'administrador pot esborrar no serveix com a evidència.

Si la Marta té permisos per modificar els registres d'auditoria, aleshores davant de qualsevol investigació —interna, d'un client o de l'AEPD— la resposta a «podria algú haver alterat això?» és «sí». I això buida de valor tot el registre.

La solució és un projecte separat amb permisos que ni tan sols els administradors tenen.

flowchart LR
    subgraph ORG["Organització"]
      P1["alpinashop-prod"]
      P2["alpinashop-dev"]
      P3["alpinashop-datos"]
      P4["alpinashop-red"]
    end
    SINK["Abocador agregat<br/>a nivell d'ORGANITZACIÓ"]
    P1 & P2 & P3 & P4 --> SINK
    SINK --> B["Bucket de registres<br/>alpinashop-auditoria<br/>retenció BLOQUEJADA 400 dies"]
    SINK --> BQ["BigQuery<br/>consultes d'auditoria"]
    SINK --> GCS["Bucket GCS<br/>bloqueig de retenció 7 anys"]

    style B fill:#e6f4ea,stroke:#34a853
    style GCS fill:#e6f4ea,stroke:#34a853

Pas 1: el projecte i els seus permisos

gcloud projects create alpinashop-auditoria --folder=FOLDER_SEGURIDAD_ID
gcloud services enable logging.googleapis.com bigquery.googleapis.com storage.googleapis.com \
  --project=alpinashop-auditoria

I el repartiment de permisos, que és la clau de tot:

Identitat Rol Pot
gcp-seguridad@ roles/logging.viewer, roles/bigquery.dataViewer Llegir, no escriure ni esborrar
gcp-infra@ (Marta) roles/logging.viewer Només llegir
Auditor extern roles/logging.viewer acotat, temporal Llegir durant l'auditoria
Ningú roles/logging.admin sobre els abocadors

La Marta, que administra tota la plataforma, aquí només pot llegir. És la separació de funcions de 07-04 portada a la seva conclusió: qui administra no custodia l'evidència del que administra.

Pas 2: l'abocador agregat a nivell d'organització

gcloud logging sinks create sumidero-auditoria-org \
  logging.googleapis.com/projects/alpinashop-auditoria/locations/eu/buckets/auditoria \
  --organization=ORG_ID \
  --include-children \
  --log-filter='logName:"cloudaudit.googleapis.com"'

--include-children és el que el fa agregat: recull els registres de tots els projectes i carpetes de l'organització, inclosos els que es creïn en el futur. Sense aquesta bandera, cada projecte nou quedaria fora i ningú no se n'adonaria.

Després cal concedir a l'escriptor de l'abocador permís sobre la destinació:

ESCRITOR=$(gcloud logging sinks describe sumidero-auditoria-org \
  --organization=ORG_ID --format='value(writerIdentity)')

gcloud projects add-iam-policy-binding alpinashop-auditoria \
  --member="$ESCRITOR" --role=roles/logging.bucketWriter

Aquest pas s'oblida constantment i el símptoma és desconcertant: l'abocador existeix, apareix a la consola, i no arriba res. Sense errors.

Pas 3: retenció bloquejada

# Bucket de registres amb retencio de 400 dies i BLOQUEIG
gcloud logging buckets create auditoria \
  --location=eu --retention-days=400 \
  --project=alpinashop-auditoria

# EL PAS IRREVERSIBLE
gcloud logging buckets update auditoria \
  --location=eu --locked \
  --project=alpinashop-auditoria

⚠️ --locked és irreversible. A partir d'aquest moment ningú, ni Google, ni l'owner de l'organització, ni el suport, pot reduir la retenció ni esborrar el bucket abans que expiri. Aquest és exactament el punt: la garantia és que ni tu pots.

I per a conservació a llarg termini, amb bloqueig de retenció de Cloud Storage:

gcloud storage buckets create gs://alpinashop-auditoria-archivo \
  --location=EU --uniform-bucket-level-access \
  --project=alpinashop-auditoria

gcloud storage buckets update gs://alpinashop-auditoria-archivo \
  --retention-period=7y

# BLOQUEIG DEFINITIU: cap objecte no es podra esborrar abans de 7 anys
gcloud storage buckets update gs://alpinashop-auditoria-archivo --lock-retention-period

Això és el que es coneix com a emmagatzematge WORM (Write Once, Read Many), i és el que exigeixen molts marcs de compliment normatiu. També és, dit sigui de passada, la millor defensa contra ransomware que existeix: un atacant amb permisos totals no pot xifrar ni esborrar el que està sota bloqueig de retenció.

Amb una contrapartida que cal assumir conscientment: si es bloquegen 7 anys, es paguen 7 anys d'emmagatzematge, sense excepció. Abans de bloquejar, es calcula el volum i es comprova que el número té sentit.

  1. Zones d'aterratge i l'ordre que importa

Una zona d'aterratge (landing zone) és l'organització preparada abans que arribi la primera càrrega de treball: jerarquia, polítiques, xarxa, identitat, auditoria i facturació, tot desplegat amb codi.

AlpinaShop ho ha fet a l'inrevés, com gairebé tothom: primer les càrregues, després el govern. Ha funcionat perquè és petita. La forma correcta, i la que cal conèixer per al pròxim projecte, és aquesta.

L'ordre de desplegament, i per què importa

flowchart TB
    A["1. Organització i Cloud Identity<br/>grups, MFA, domini"] --> B["2. Jerarquia de carpetes"]
    B --> C["3. Facturació<br/>compte, exportació, pressupostos"]
    C --> D["4. Polítiques d'organització<br/>en DRY-RUN"]
    D --> E["5. Projecte d'auditoria<br/>abocador agregat + retenció"]
    E --> F["6. Projecte de xarxa<br/>VPC compartida"]
    F --> G["7. IAM: grups i rols"]
    G --> H["8. Projectes de càrrega"]
    H --> I["9. Polítiques en mode APLICAT"]
    I --> J["10. Càrregues de treball"]

    style E fill:#e6f4ea,stroke:#34a853
    style D fill:#fef7e0,stroke:#fbbc04
    style I fill:#fef7e0,stroke:#fbbc04

Les quatre raons per les quals aquest ordre concret i no un altre:

  1. L'auditoria (5) va abans que la xarxa i les càrregues. Si l'abocador es crea després, els registres de tot el que es va fer mentrestant no estan capturats. La construcció de la plataforma és precisament el període amb més canvis de permisos, i és el que més interessa tenir registrat.
  2. Les polítiques en dry-run (4) van abans que els projectes de càrrega (8). Així es mesuren sobre recursos que es van creant, i les violacions es corregeixen sobre la marxa en lloc d'acumular-se.
  3. La xarxa (6) abans que els projectes de servei (8). Un projecte de servei necessita que existeixi l'amfitrió.
  4. Les polítiques aplicades (9) abans que les càrregues (10). És l'únic moment en què aplicar una política no trenca res, perquè no hi ha res per trencar.

En Terraform

# modules/landing-zone/main.tf — estructura del modul

module "carpetas" {
  source = "./modulos/carpetas"
  organizacion = var.org_id
  carpetas     = ["produccion", "desarrollo", "compartido", "seguridad"]
}

module "auditoria" {
  source     = "./modulos/auditoria"
  depends_on = [module.carpetas]

  org_id             = var.org_id
  carpeta_seguridad  = module.carpetas.ids["seguridad"]
  retencion_dias     = 400
  bloquear_retencion = true          # IRREVERSIBLE: es posa a ma la primera vegada
}

module "politicas" {
  source     = "./modulos/politicas-organizacion"
  depends_on = [module.carpetas]

  org_id  = var.org_id
  dry_run = var.politicas_en_pruebas   # true al principi, false despres de mesurar

  regiones_permitidas = ["in:europe-west1-locations",
                         "in:europe-southwest1-locations",
                         "in:eu-locations"]
}

module "red" {
  source     = "./modulos/vpc-compartida"
  depends_on = [module.politicas]

  carpeta_compartido = module.carpetas.ids["compartido"]
  proyecto_host      = "alpinashop-red"
  subredes           = var.subredes
}

module "proyectos" {
  source     = "./modulos/proyectos"
  depends_on = [module.red]

  for_each = var.proyectos
  # ...
}

depends_on explícit a cada mòdul, encara que no hi hagi referències entre ells. És el cas legítim de depends_on que 06-07 assenyalava com a excepció: l'ordre aquí no ve de dependències de dades, sinó d'una seqüència lògica que Terraform no pot inferir.

Google publica els blueprints de fonamentació (Cloud Foundation Fabric, Terraform Example Foundation), que implementen tot això amb criteri. Mereixen un advertiment honest: estan pensats per a organitzacions grans i porten molta més estructura de la que necessita una pime. Fes-los servir com a referència i com a font de decisions ben pensades, no com a plantilla per copiar.

El que costaria fer-ho avui a AlpinaShop

Fase Feina Durada
Inventariar incompliments Consultes de l'apartat 8 2 h
Crear alpinashop-auditoria i l'abocador Terraform 4 h
Polítiques en dry-run Terraform 3 h
Espera i anàlisi 14 dies
Corregir incompliments i excepcions Variable 1-2 dies
Aplicar en desenvolupament 1 h
Espera 7 dies
Aplicar en producció 1 h
Feeds i alertes Terraform + funció 4 h
Total de feina efectiva ~4 dies
Total de calendari ~4 setmanes

Quatre dies de feina repartits en quatre setmanes. Aquest és el preu de governar la plataforma, i explica per què l'aritmètica de l'apartat 1 importa: d'aquí a dos anys serien quatre setmanes de feina i sis mesos de calendari.

  1. Gestió del canvi: revisions, cicle de vida i excepcions

Les polítiques tècniques no basten. Calen processos, i per a un equip de tres persones han de ser lleugers o no es faran.

Revisió periòdica d'accessos

Periodicitat Què es revisa Qui Durada
Mensual Comptes de servei nous; claus creades; rols bàsics Marta 30 min
Trimestral Tots els permisos davant de les funcions reals; recomanacions del Recommender Marta + Dani 2 h
Semestral Comptes externs; excepcions de política; accessos de tercers Marta + direcció 1 h
Quan algú canvia de lloc o se'n va Tots els seus accessos Immediat
#!/usr/bin/env bash
# revision-trimestral.sh — genera l informe per a la reunio
echo "=== 1. Rols basics a l organitzacio ==="
gcloud asset search-all-iam-policies --scope=organizations/$ORG_ID \
  --query="policy:(roles/owner OR roles/editor)" \
  --format="table(resource, policy.bindings.role, policy.bindings.members)"

echo "=== 2. Comptes externs ==="
gcloud asset search-all-iam-policies --scope=organizations/$ORG_ID \
  --query="policy:(gmail.com OR hotmail.com OR outlook.com)" \
  --format="table(resource, policy.bindings.members)"

echo "=== 3. Permisos concedits i no usats en 90 dies ==="
for P in $PROYECTOS; do
  gcloud recommender recommendations list --project="$P" --location=global \
    --recommender=google.iam.policy.Recommender \
    --format="table(content.overview.member, content.overview.removedRole)"
done

echo "=== 4. Claus de comptes de servei ==="
gcloud asset search-all-resources --scope=organizations/$ORG_ID \
  --asset-types=iam.googleapis.com/ServiceAccountKey \
  --format="table(name, createTime)"

echo "=== 5. Excepcions de politica vigents ==="
gcloud org-policies list --organization=$ORG_ID --format="table(name, spec.rules)"

Cicle de vida dels projectes

Fase Què s'exigeix
Sol·licitud Propòsit, responsable, entorn, centre de cost, data de revisió
Creació Sempre per Terraform, mai des de la consola. Etiquetes obligatòries, pressupost, carpeta correcta
Operació Revisió trimestral de cost i permisos
Caducitat Tot projecte de proves té data de caducitat
Retirada Exportar dades → desvincular facturació → 30 dies d'espera → esborrar

La data de caducitat obligatòria als projectes de prova és la mesura que més despesa fantasma evita (07-05), perquè ataca la causa en lloc del símptoma. S'implementa amb una etiqueta caduca=2026-12-31 i una feina setmanal que avisa dels vençuts.

I els 30 dies d'espera abans d'esborrar no són burocràcia: esborrar un projecte és irreversible passat el període de gràcia, i sempre apareix algú que necessitava alguna cosa d'allà.

El procés d'excepcions

Tota política acabarà tenint excepcions. Sense procés, es concedeixen de paraula i es converteixen en permanents.

Element Regla
Sol·licitud Per escrit, amb motiu tècnic i alternatives descartades
Aprovació Marta + una persona més. Mai una de sola
Abast El mínim possible: un recurs, no un projecte
Caducitat Obligatòria. Màxim 90 dies, renovable amb justificació
Registre A alpinashop-infra/EXCEPCIONES.md, amb Terraform
Revisió Semestral: continua fent falta?
# EXCEPCIONES.md

## EXC-001 — IP pública a `bastion-migracion`
- **Política:** `constraints/compute.vmExternalIpAccess`
- **Abast:** `projects/alpinashop-dev/zones/europe-west1-b/instances/bastion-migracion`
- **Motiu:** el proveïdor de l'ERP exigeix connexió entrant des de la seva IP per a la
  migració de dades; no admet connexió sortint ni PSC.
- **Alternatives descartades:** IAP (el proveïdor no pot fer servir el túnel),
  VPN (fora de termini del projecte).
- **Mitigació:** tallafoc restringit a la IP del proveïdor; la VM s'apaga
  fora de les sessions de treball; registre de connexions activat.
- **Sol·licitada per:** Dani · **Aprovada per:** Marta + direcció
- **Concedida:** 2026-08-05 · **CADUCA: 2026-11-03**
- **Revisió:** en acabar la migració, s'elimina la VM i l'excepció

La data de caducitat és el que separa una excepció d'una derogació. Sense ella, la política queda buida de contingut per sempre i ningú no recorda per què.

  1. La maduresa d'AlpinaShop, i el que li queda

On és

Domini Estat Comentari
Estructura organitzativa 🟢 Bona Jerarquia per entorn, projectes amb propòsit clar, carpeta de seguretat separada
Identitat 🟢 Bona Grups, sense rols bàsics, sense claus, federació, ningú owner de producció
Xarxa 🟢 Bona VPC compartida, permisos per subxarxa, híbrid resolt, perímetre de dades
Còmput 🟢 Bona Sense servidors per mantenir, escalat a zero, canari i reversió en segons
Dades 🟡 Acceptable Classificades, xifrades, amb retenció; falta provar el dret de supressió
CI/CD 🟢 Bona Trunk-based, sense claus, aprovació manual, canari verificat
Observabilitat 🟢 Bona Mètriques, registres, traces, SLO, pressupost d'error, alertes per consum
Fiabilitat 🟡 Acceptable HA regional, DR amb RTO/RPO, restauració provada; falta multiregió
Cost 🟢 Bona Visibilitat, atribució, pressupostos, rutina mensual, −60 %
Seguretat 🟡 Acceptable Defensa en profunditat; deute prioritari pagat; falta detecció activa
Govern 🟡 En marxa El d'aquesta lliçó: polítiques, inventari, auditoria immutable
Compliment normatiu 🟠 Millorable Documentació desactualitzada; falta validació professional

El que li queda, amb honestedat

A curt termini (aquest trimestre):

  1. Acabar d'aplicar les polítiques d'organització. Estan en dry-run; cal travessar les 4 setmanes de l'apartat 13.
  2. Documentació de compliment normatiu actualitzada, amb assessoria professional. És la casella més endarrerida i l'única amb conseqüències legals.
  3. Provar el procediment de dret de supressió d'extrem a extrem, amb un client de prova.

A mitjà termini (aquest any):

  1. Multiregió per a Cloud SQL, que és l'única cosa que impedeix l'alta disponibilitat regional completa (07-06).
  2. Detecció activa d'amenaces, quan hi hagi algú que la pugui atendre (07-04).
  3. Elevació temporal de privilegis amb PAM.
  4. Revisió externa de seguretat sobre l'arquitectura ja endurida.

El que NO farà, i està bé:

No farà Per què
GKE Enterprise / Google Distributed Cloud DA-004: no hi ha càrregues fora de GCP per gestionar
Malla de serveis Un servei. No hi ha res per mallar
Multinúvol No hi ha motiu de negoci
Actiu-actiu El cost no es justifica amb 1.200 comandes/mes (07-06)
Equip de seguretat dedicat Quaranta persones
Network Connectivity Center Una VPC i un enllaç

Aquesta última taula és la més valuosa de la lliçó, perquè una organització madura no és la que ha adoptat més tecnologia: és la que sap justificar el que ha decidit no adoptar. Les sis files tenen la seva decisió escrita, el seu motiu, i —en el cas de DA-004— les seves condicions de revisió.

El recorregut complet

Val la pena mirar enrere una vegada.

AlpinaShop va començar el mòdul 1 sense compte de Google Cloud, amb una botiga que funcionava en algun lloc i tres persones que no sabien què era una regió. Va acabar el mòdul 2 amb l'aplicació migrada i una decisió pendent. El mòdul 3 li va donar xarxa, balanceig, identitat i secrets. El 4, una plataforma de dades. El 5, models que prediuen. El 6, lliurament continu i observabilitat. I el 7 ha resolt el que quedava: l'híbrid, el còmput definitiu, la xarxa avançada, la seguretat revisada, la factura entesa, la fiabilitat mesurada i el govern aplicat.

Avui AlpinaShop té una plataforma que una persona pot operar, qualsevol pot entendre llegint un repositori, i es reconstrueix sencera amb terraform apply. No és perfecta —té una llista de deutes escrita i prioritzada, que és exactament el que ha de tenir— però és defensable davant d'un client, davant d'un auditor i davant de la persona que entri a treballar el mes que ve.

Errors Habituals i Consells

  • Deixar el govern per «quan siguem grans». El cost creix amb les excepcions que cal negociar, no amb el nombre de recursos. Es fa ara o no es fa.
  • Aplicar polítiques sense inventariar abans el que les incompleix. Es generen centenars de violacions que ningú no analitza i el dry-run deixa de servir.
  • Oblidar in:eu-locations a resourceLocations. BigQuery multiregió i alguns buckets deixen de crear-se.
  • allowedPolicyMemberDomains sense excepcions per als agents de servei de Google. Serveis interns fallen de maneres inexplicables.
  • Exigir CMEK sense donar permisos a l'agent de servei sobre la clau. Els recursos nous no es poden crear.
  • Aplicar polítiques un divendres. Cap de setmana sense poder desplegar.
  • Que només una persona tingui orgpolicy.policyAdmin. Si es bloqueja a si mateixa, la sortida és el suport de Google.
  • Abocador sense --include-children. Els projectes nous queden fora i ningú no se n'assabenta.
  • Oblidar donar logging.bucketWriter a la writerIdentity de l'abocador. L'abocador existeix i no arriba res, sense errors.
  • Crear el projecte d'auditoria al final. Els registres de la fase de construcció, que és la de més canvis, es perden.
  • Que l'administrador pugui escriure als registres d'auditoria. Aleshores no valen com a evidència.
  • Bloquejar la retenció sense calcular el volum. --locked és irreversible i pagues tots els anys que vas posar.
  • Activar DATA_READ en un bucket que serveix imatges. Milions d'entrades i una factura absurda.
  • No mirar serviceAccountDelegationInfo. En una organització amb impersonació, sense aquest camp es perd el rastre de la persona.
  • Excepcions sense data de caducitat. Deixen de ser excepcions i buiden la política.
  • Copiar un blueprint de fonamentació tal qual. Estan fets per a organitzacions grans i porten estructura que no necessites.
  • Consell: la consulta de «qui va tocar un registre» mereix alerta permanent. És el primer que manipula qui vol amagar alguna cosa.
  • Consell: posa data de caducitat obligatòria als projectes de prova. Ataca la causa de la despesa fantasma, no el símptoma.
  • Consell: fes servir les denegacions de política com a termòmetre. Si algú de l'equip apareix vint vegades, la política està mal calibrada.
  • Consell: escriu el que decideixes NO adoptar. És el que distingeix una organització madura d'una que simplement no hi va arribar.

Exercicis

Exercici 1 — Dissenyar el conjunt de polítiques d'una empresa nova

AlpinaTech, una consultora de 25 persones, arrenca a Google Cloud des de zero. Dades:

  • Tres equips: desenvolupament (12), dades (5), infraestructura (3). La resta no és tècnica.
  • Treballen per a clients externs, i alguns exigeixen que les seves dades no surtin de la UE.
  • Dos consultors autònoms amb compte de Gmail col·laboren en projectes concrets.
  • Un client exigeix certificació ISO 27001 en 18 mesos.
  • Pressupost: 2.500 €/mes de núvol.

Dissenya: la jerarquia de carpetes i projectes amb el seu criteri justificat, el conjunt complet de polítiques d'organització amb el seu nivell i el seu risc, com resols el cas dels autònoms sense desactivar allowedPolicyMemberDomains, i l'ordre de desplegament de la zona d'aterratge.

Exercici 2 — Investigar un canvi sospitós

Un dilluns al matí, la consulta d'auditoria mensual revela això:

timestamp             quien                                              operacion       recurso
2026-08-01T23:47:12Z  [email protected]...  SetIamPolicy    projects/alpinashop-prod
2026-08-01T23:47:44Z  [email protected]...  CreateServiceAccount  projects/alpinashop-prod
2026-08-01T23:48:03Z  [email protected]...  CreateServiceAccountKey  projects/alpinashop-prod
2026-08-01T23:51:20Z  [email protected]...  storage.buckets.update  alpinashop-datalake

Era dissabte a la nit. No hi havia cap desplegament programat. El compte sa-mantenimiento no apareix al Terraform d'AlpinaShop.

Escriu la investigació completa: quines consultes executes i en quin ordre, què busques a cadascuna, com determines si va ser un atac o una automatització legítima no documentada, què contens i en quin ordre, i quins controls de govern haurien detectat o impedit això abans.

Exercici 3 — Justificar el govern davant de direcció

La direcció d'AlpinaShop pregunta per què cal dedicar quatre dies a «posar regles» quan el sistema funciona perfectament i no hi ha hagut cap incident.

Escriu la resposta de la Marta: quin risc concret cobreix cada bloc de feina, què passaria sense ell amb exemples reals del curs, quin cost té fer-ho ara davant de fer-ho d'aquí a dos anys, i què li demanes a més del temps. Màxim una pàgina, en llenguatge no tècnic.

Solucions

Solució 1 — Dissenyar les polítiques d'AlpinaTech

Jerarquia, i el criteri que la decideix.

El factor determinant no és la mida ni els equips: és que treballen per a clients externs amb requisits diferents. Això fa que l'aïllament per client sigui més important que l'aïllament per entorn.

Organitzacio alpinatech.example
├── carpeta: clientes
│   ├── carpeta: cliente-acme
│   │   ├── alpinatech-acme-prod
│   │   └── alpinatech-acme-dev
│   └── carpeta: cliente-beta
│       ├── alpinatech-beta-prod
│       └── alpinatech-beta-dev
├── carpeta: interno
│   ├── alpinatech-web-corporativa
│   └── alpinatech-herramientas
├── carpeta: compartido
│   ├── alpinatech-red
│   └── alpinatech-cicd
└── carpeta: seguridad
    └── alpinatech-auditoria

Per què carpeta per client i entorn a dins:

Avantatge Detall
Aïllament contractual Les dades d'Acme no es poden barrejar amb les de Beta ni per accident
Polítiques per client Si Acme exigeix només UE i Beta no, s'aplica a la carpeta de cadascun
Facturació directa El cost per carpeta és el que es factura al client
Retirada neta Quan acaba el contracte, s'esborra la carpeta sencera
Permisos per projecte Només l'equip assignat a Acme té accés a Acme

L'última fila és la que més importa amb autònoms pel mig, i enllaça amb el punt següent.

Conjunt de polítiques.

# Restricció Nivell Risc Motiu
1 iam.allowedPolicyMemberDomains Organització Alt Treballen amb externs: és la més important i la més delicada
2 gcp.resourceLocations = UE Carpeta clientes Mitjà-alt Requisit contractual; a interno pot ser més lax
3 iam.disableServiceAccountKeyCreation Organització Mitjà Base de la ISO 27001
4 storage.publicAccessPrevention Organització Baix Dades de clients
5 storage.uniformBucketLevelAccess Organització Baix IAM com a única veritat
6 compute.vmExternalIpAccess Organització Baix
7 compute.requireOsLogin Organització Baix Baixa d'un autònom = perd accés a tot
8 iam.automaticIamGrantsForDefaultServiceAccounts Organització Baix Des del dia 1: no caldrà corregir-ho mai
9 sql.restrictPublicIp Organització Baix
10 compute.disableSerialPortAccess Organització Baix Evita un camí d'accés sense auditar
11 Personalitzada: etiquetes cliente i centro-coste obligatòries Organització Mitjà La facturació al client en depèn
12 gcp.restrictNonCmekServices Carpeta clientes Alt Si algun client ho exigeix

Fixa't en la política 2 a nivell de carpeta clientes i no d'organització. Aplicar-la a l'organització obligaria la web corporativa i les eines internes a ser a la UE, cosa que probablement estigui bé però és una restricció que no es necessita. Les polítiques es posen al nivell més alt on tinguin sentit, no al més alt possible.

El cas dels autònoms — la part interessant de l'exercici.

Desactivar allowedPolicyMemberDomains per donar accés a dos comptes de Gmail seria llençar per la borda la política més valuosa del conjunt. Hi ha quatre opcions, i l'última és la correcta:

Opció Valoració
No aplicar la política ❌ Qualsevol pot donar accés a qualsevol compte de Google del món
Excepció permanent per als seus dos correus de Gmail ❌ Comptes personals sense control de l'empresa: sense MFA obligatori, sense baixa centralitzada, sense poder revocar el correu
Excepció a la carpeta del client concret ⚠️ Millor, però continua recolzant-se en comptes personals
Comptes de Cloud Identity propis per als autònoms La correcta

La solució: [email protected] gestionat per l'empresa, amb MFA obligatori, condició d'IAM amb data de caducitat (03-04) coincident amb el final del contracte, i permisos únicament sobre el projecte on treballen.

resource "google_project_iam_member" "freelance_acme" {
  project = "alpinatech-acme-dev"
  role    = "roles/editor"
  member  = "user:[email protected]"

  condition {
    title      = "Contracte Acme fins al 2026-12-31"
    expression = "request.time < timestamp('2027-01-01T00:00:00Z')"
  }
}

L'accés caduca sol. No depèn que ningú es recordi de revocar-lo el 31 de desembre, que és exactament el tipus de tasca que s'oblida. I el cost d'una llicència de Cloud Identity és ridícul comparat amb el risc que un consultor mantingui accés a dades d'un client durant dos anys després d'acabar.

Ordre de desplegament.

  1. Cloud Identity, domini, grups (at-desarrollo@, at-datos@, at-infra@, at-seguridad@), MFA obligatori des del minut u.
  2. Organització i carpetes.
  3. Facturació: compte, exportació a BigQuery, pressupost global de 2.500 € i un per carpeta de client.
  4. alpinatech-auditoria amb abocador agregat i retenció bloquejada. Abans que res més, per capturar tota la construcció.
  5. Polítiques 1-11 en dry-run.
  6. alpinatech-red amb VPC compartida.
  7. alpinatech-cicd amb Workload Identity Federation.
  8. Primer projecte de client com a plantilla, amb mòdul de Terraform reutilitzable.
  9. Polítiques en mode aplicat.
  10. Càrregues de treball.

Avantatge decisiu d'AlpinaTech sobre AlpinaShop: comença al pas 1 amb la casella buida. No haurà de negociar cap excepció, perquè quan apliqui les polítiques no hi haurà res que les incompleixi. És literalment el millor moment possible, i només passa una vegada.

Sobre la ISO 27001 a 18 mesos: pràcticament tot el d'aquesta lliçó és evidència directa per a la certificació —control d'accessos, registre d'auditoria immutable, gestió de canvis, revisió periòdica de permisos, classificació de les dades—. Fer-ho ara significa arribar a l'auditoria amb 18 mesos de registres; fer-ho d'aquí a un any significa arribar amb sis. L'auditor mira l'històric, no la foto del dia.

Solució 2 — Investigar el canvi sospitós

Lectura inicial dels indicis. Quatre senyals, i cap no és concloent per separat però juntes dibuixen un patró molt reconeixible:

Indici Per què preocupa
Dissabte 23:47 Fora de tot horari i sense desplegament programat
SetIamPolicyCreateServiceAccountCreateServiceAccountKey en 51 segons És la seqüència canònica d'establiment de persistència
CreateServiceAccountKey La política 4.3 ho hauria d'impedir. Existeix una clau descarregable nova
sa-mantenimiento no és al Terraform Recurs creat fora del procés: o és shadow IT o és un atacant

Pas 1 — Qui va actuar de debò? (5 minuts)

sa-deploy-prod és un compte de servei; no actua sol. La pregunta és qui el va fer servir:

SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS identitat,
  JSON_VALUE(protopayload_auditlog.authenticationInfo.serviceAccountDelegationInfo)
    AS delegacio,
  protopayload_auditlog.requestMetadata.callerIp          AS ip,
  protopayload_auditlog.requestMetadata.callerSuppliedUserAgent AS agent,
  protopayload_auditlog.methodName                        AS operacio
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`
WHERE protopayload_auditlog.authenticationInfo.principalEmail LIKE 'sa-deploy-prod%'
  AND timestamp BETWEEN TIMESTAMP('2026-08-01 22:00:00')
                    AND TIMESTAMP('2026-08-02 02:00:00')
ORDER BY timestamp

Els tres camps que decideixen el cas:

Camp Si és… Significa
delegacio Buit El compte es va fer servir directament, amb una credencial. D'on va sortir?
delegacio Un correu de persona Algú el va impersonar. Qui i per què un dissabte?
agent Google-Cloud-Build Va ser el pipeline: cal buscar quina compilació
agent gcloud/... Algú des d'un terminal
agent Una biblioteca genèrica o buit Molt sospitós
ip Rang de Google (35.x, 34.x) Compatible amb Cloud Build
ip IP d'oficina Algú de l'equip
ip Qualsevol altra 🚨 Incident confirmat

Pas 2 — Què es va concedir exactament? (5 minuts)

SELECT
  timestamp,
  protopayload_auditlog.resourceName AS recurs,
  JSON_VALUE(d, '$.action') AS accio,
  JSON_VALUE(d, '$.role')   AS rol,
  JSON_VALUE(d, '$.member') AS membre
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`,
  UNNEST(JSON_QUERY_ARRAY(
    protopayload_auditlog.servicedata_v1_iam.policyDelta.bindingDeltas)) AS d
WHERE protopayload_auditlog.methodName LIKE '%SetIamPolicy%'
  AND DATE(timestamp) = '2026-08-01'

Si el resultat inclou ADD roles/owner o ADD roles/editor a sa-mantenimiento, o qualsevol concessió a una identitat externa, és un incident i s'activa el guió de 07-04.

Pas 3 — Es va fer servir la clau creada? (10 minuts)

Aquesta és la pregunta que determina l'abast:

SELECT
  timestamp,
  protopayload_auditlog.methodName               AS operacio,
  protopayload_auditlog.resourceName             AS recurs,
  protopayload_auditlog.requestMetadata.callerIp AS ip
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`
WHERE protopayload_auditlog.authenticationInfo.principalEmail LIKE 'sa-mantenimiento%'
ORDER BY timestamp

I el canvi sobre el bucket del data lake, que és l'objectiu evident:

SELECT timestamp, protopayload_auditlog.methodName, protopayload_auditlog.request
FROM `alpinashop-auditoria.auditoria.cloudaudit_googleapis_com_activity`
WHERE protopayload_auditlog.resourceName LIKE '%alpinashop-datalake%'
  AND DATE(timestamp) BETWEEN '2026-08-01' AND CURRENT_DATE()
ORDER BY timestamp

Si buckets.update va afegir allUsers, el data lake va estar públic. Això converteix el cas en una bretxa de dades personals amb notificació a l'AEPD en 72 hores.

Pas 4 — Atac o automatització no documentada? (15 minuts)

Senyal Automatització legítima Atac
IP Rang de Google o d'oficina Externa, o d'un proveïdor de núvol aliè
Agent d'usuari Google-Cloud-Build, terraform Genèric, buit, python-requests
Delegació Una persona de l'equip Buit o desconegut
Compilació associada Existeix a Cloud Build No existeix
Rol concedit Acotat i coherent owner, editor
Clau creada Cap: el pipeline no les necessita
Nom del recurs Coherent amb la convenció Genèric i plausible: sa-mantenimiento
Activitat posterior Coherent amb la tasca Enumeració, lectures massives

L'indici que més pesa és la creació de la clau. El pipeline d'AlpinaShop fa servir Workload Identity Federation des de 06-02 i no necessita ni ha creat mai una clau JSON. Una clau nova en producció, un dissabte a les 23:48, és gairebé amb certesa persistència d'un atacant. I el nom sa-mantenimiento és exactament el tipus de nom plausible que es tria per passar desapercebut en una llista.

Pas 5 — Contenció, en aquest ordre.

# 1. ESBORRAR la clau: es la credencial exfiltrada
gcloud iam service-accounts keys delete KEY_ID \
  --iam-account=sa-mantenimiento@alpinashop-prod.iam.gserviceaccount.com

# 2. DESHABILITAR (no esborrar) el compte: es conserva l evidencia
gcloud iam service-accounts disable \
  [email protected]

# 3. Revertir les concessions IAM indegudes
gcloud projects remove-iam-policy-binding alpinashop-prod \
  --member="serviceAccount:[email protected]" \
  --role="roles/owner"

# 4. Tancar el bucket si va quedar obert
gcloud storage buckets update gs://alpinashop-datalake --no-public-access-prevention=false

# 5. Rotar les credencials de sa-deploy-prod i revisar el pipeline
#    (si el compromis va venir per aqui, tot el que toca esta en dubte)

# 6. Congelar evidencia fora del projecte afectat
gcloud logging read 'timestamp>="2026-07-01T00:00:00Z"' --project=alpinashop-prod \
  --format=json > /tmp/evidencia.json
gcloud storage cp /tmp/evidencia.json gs://alpinashop-evidencia-forense/

# 7. Buscar MES persistencia: mai n hi ha nomes una
gcloud asset search-all-resources --scope=organizations/ORG_ID \
  --query="createTime>2026-07-25" --format="table(name, assetType, createTime)"

L'ordre importa: primer la credencial (pas 1), perquè mentre existeixi l'atacant continua a dins; després el compte; després els permisos. I el pas 7 no s'omet mai: qui estableix persistència poques vegades ho fa en un sol lloc.

Quins controls de govern ho haurien detectat o impedit.

Control Efecte Apartat
iam.disableServiceAccountKeyCreation Ho hauria IMPEDIT. Sense clau, no hi ha persistència 4.3
Feed de Cloud Asset Inventory sobre ServiceAccountKey Alerta en segons, no 36 hores després 9
Feed sobre concessions d'owner Alerta immediata al SetIamPolicy 9
storage.publicAccessPrevention Hauria impedit obrir el bucket 4.5
Consulta d'activitat fora d'horari Detecció l'endemà, no a la revisió mensual 11
Terraform com a única via de creació + detecció de deriva Un plan hauria mostrat el compte no declarat 06-07
Retenció immutable de registres Garanteix que l'evidència no va ser alterada 12

I la conclusió que dona sentit a la lliçó sencera: de les set mesures, la primera hauria impedit l'atac per complet i la segona l'hauria detectat en segons. Totes dues són d'aquesta lliçó, totes dues costen menys d'una hora de configuració, i cap no estava posada el dissabte a la nit.

Un control preventiu que costa una hora val més que la investigació més brillant.

Solució 3 — Justificar el govern davant de direcció


Per què dediquem quatre dies a posar regles al núvol Marta Ruiz, responsable d'infraestructura

És veritat que el sistema funciona i que no hem tingut cap incident greu. També és veritat que funciona perquè les tres persones de l'equip ens recordem de fer les coses bé, no perquè res ho impedeixi. Avui, qualsevol de nosaltres —o qualsevol que entri a treballar el mes que ve— pot, sense voler i sense que salti cap alarma, deixar un magatzem de dades obert a internet, crear una contrasenya permanent que ningú no sàpiga que existeix, o posar dades de clients en un servidor fora d'Europa.

No és una hipòtesi. Aquest any hem tingut dos casos. Vam trobar una contrasenya d'accés a dades que portava catorze mesos perduda i continuava funcionant. I vam descobrir dos serveis encesos que ningú no feia servir i que ens havien costat 1.500 €. Tots dos es van resoldre, però tots dos els vam trobar per casualitat, mesos després.

Què cobreix cada bloc de feina:

Regles automàtiques (dia 1). Impedeixen tècnicament el que avui només evitem per costum: res de servidors exposats a internet, res de dades fora de la UE, i —la més important— ningú no pot crear contrasenyes permanents. Sense això, el cas de la contrasenya perduda es pot repetir demà.

Registre protegit (dia 2). Desem en un lloc a part, i amb un cadenat que ni jo puc obrir, el registre de tot el que es fa a la plataforma. Serveix per a tres coses: si passa alguna cosa, sabem exactament què i qui; si un client o l'Agència de Protecció de Dades ens ho demana, ho podem demostrar; i com que ni tan sols l'administrador ho pot modificar, val com a prova. Un registre que jo pogués esborrar no serviria de res.

Avisos en el moment (dia 3). Avui, si algú es concedeix permisos d'administrador un dissabte a la nit, ens n'assabentaríem a la revisió del mes següent. Amb això ens n'assabentem en segons.

Inventari i revisions (dia 4). Saber en tot moment què existeix, qui té accés a què, i què està costant diners sense donar servei.

Per què ara i no més endavant. És una qüestió d'aritmètica, i és l'argument més important d'aquesta nota. Avui tenim cinc projectes i uns cent vint recursos: posar les regles afecta zero coses existents, perquè ja ho fem tot bé. D'aquí a dos anys, amb quinze projectes i sis-cents recursos, posar aquestes mateixes regles significarà trobar tot el que no les compleix, parlar amb cada equip i acceptar excepcions permanents que deixarien les regles gairebé buides. Quatre dies ara són quatre setmanes d'aquí a dos anys, i probablement ja no es faci.

I hi ha un motiu comercial que també convé tenir present: quan un client gran ens demani un qüestionari de seguretat —i ens el demanaran— la diferència entre respondre «sí, i aquí teniu el registre dels últims divuit mesos» i respondre «confiem en el nostre equip» pot ser la diferència entre signar el contracte o no.

El que demano a més del temps:

  1. Que una segona persona tingui les claus mestres. Avui soc l'única que pot aixecar aquestes regles. Si m'equivoco o no soc disponible, l'equip es queda bloquejat.
  2. Mitja hora al trimestre per revisar junts qui té accés a què. És ràpid i evita que els permisos s'acumulin sols, que és el que sempre acaba passant.
  3. Acceptar que durant dues setmanes anirem una mica més a poc a poc, mentre mesurem l'impacte de les regles abans d'activar-les. No encendrem res sense haver-ho provat primer: bloquejar l'equip per pressa seria el pitjor resultat possible.

Les quatre decisions de comunicació:

  1. Reconèixer que tenen raó abans de rebatre. «És veritat que funciona» desarma l'objecció en lloc d'enfrontar-s'hi. El que es corregeix és el perquè funciona, no el fet.
  2. Dos incidents reals en lloc de riscos hipotètics. «Podria passar» es descarta; «ens va passar al març i ho vam trobar al juliol» no. I tots dos casos es van resoldre, cosa que permet explicar-los sense semblar alarmista ni incompetent.
  3. L'argument aritmètic com a eix. Quatre dies ara davant de quatre setmanes d'aquí a dos anys és un raonament que un director financer entén immediatament i amb el qual no es pot discutir, perquè no depèn de valorar el risc: depèn de comptar.
  4. L'angle comercial al final. Converteix el govern de cost en habilitador d'ingressos. És l'argument que més pes té en un consell i per això va després dels altres, no abans: posat primer semblaria una excusa; posat al final, remata.

I una cinquena, que es veu a les peticions: es demana explícitament anar més a poc a poc dues setmanes. Anunciar el cost abans que es noti evita que la primera fricció s'interpreti com un error de planificació.

Conclusió

El mòdul 7 acaba on havia d'acabar: convertint en controls el que fins ara eren acords.

Saps què és el govern i respons a les seves quatre preguntes amb mecanismes concrets: què es pot crear (polítiques d'organització), què existeix (Asset Inventory), qui va fer què (Audit Logs) i qui paga (estructura i etiquetes). I tens l'argument que decideix quan implantar-lo: el cost no creix amb els recursos, creix amb les excepcions que cal negociar. Les polítiques es posen quan no molesten ningú; després ja no es posen.

Saps estructurar una organització amb els tres criteris —entorn, equip, aplicació— i, més important, amb les seves conseqüències en permisos, polítiques heretades, facturació, quotes i radi d'explosió. Entens per què AlpinaShop estructura per entorn i per què AlpinaTech, amb clients externs, ho ha de fer per client.

Domines les polítiques d'organització: què les distingeix d'IAM —no són permisos, i no se les salta ni un owner—, els seus tres tipus, i l'herència amb la seva regla clau: una denegació en qualsevol nivell guanya sempre. Tens les nou polítiques d'AlpinaShop amb el seu motiu, el seu nivell i el seu risc, incloses les dues que tanquen deutes crítics de 07-04.

Saps aplicar-les sense bloquejar l'equip: el patró dry-run per quarta vegada al curs, el procediment de vuit passos que comença per inventariar el que ja incompleix, les cinc maneres reals de bloquejar-se a un mateix, i la salvaguarda que cal preparar abans de tot: dues persones amb orgpolicy.policyAdmin.

Saps escriure restriccions personalitzades amb CEL i distingir-les de Policy Controller, que actua sobre Kubernetes i no competeix sinó que complementa. I saps que les quotes impedeixen on els pressupostos només avisen.

Manegues Cloud Asset Inventory per saber què existeix, buscar recursos i polítiques IAM, exportar a BigQuery i construir l'històric que respon «què hi havia el 3 de juny?». I muntes feeds que avisen en segons d'una concessió d'owner, un bucket públic o —el més important— la creació d'una clau de compte de servei.

Coneixes els Cloud Audit Logs de debò: els quatre tipus, amb les dues dades que cal retenir —l'activitat d'administrador és gratis i no es pot desactivar; l'accés a dades està apagat per defecte i sense ell no es pot acotar una bretxa— i les tres decisions que fan el seu cost assumible. Saps llegir una entrada camp a camp, inclòs serviceAccountDelegationInfo, que gairebé ningú no mira i sense el qual es perd el rastre de la persona darrere d'una impersonació.

Tens les cinc consultes d'auditoria desades abans de necessitar-les, amb la de «qui va tocar un registre» assenyalada com la més important i la menys freqüent, i amb les denegacions de política usades com a termòmetre de si les teves regles estan ben calibrades.

I has muntat la peça que fa creïble tota la resta: retenció immutable en un projecte separat, amb abocador agregat a nivell d'organització i --include-children perquè cap projecte futur no quedi fora, amb logging.bucketWriter concedit a la writerIdentity —el pas que s'oblida sempre—, i amb --locked irreversible. On la Marta, que administra tota la plataforma, només pot llegir. Perquè un registre que l'administrador pot esborrar no serveix com a evidència.

Saps què és una zona d'aterratge i —el que importa— per què l'ordre és el que és: l'auditoria abans que la xarxa i les càrregues, per no perdre els registres del període amb més canvis de tota la vida de la plataforma. Amb els depends_on explícits de Terraform com a cas legítim, i amb els blueprints de Google com a referència i no com a plantilla.

I tens els processos de gestió del canvi dimensionats per a tres persones: revisions amb la seva periodicitat i el seu script, cicle de vida de projectes amb data de caducitat obligatòria als de prova, i un procés d'excepcions on la data de caducitat és l'única cosa que separa una excepció d'una derogació permanent.


I aquí acaba el mòdul 7. Mira on és AlpinaShop.

Va començar aquest mòdul amb una aplicació que es construïa, es desplegava i s'observava sola, i amb una llista de converses ajornades. Avui la llista és buida. DA-004 va decidir no adoptar la plataforma híbrida i connectar Sabadell amb un túnel, amb les seves condicions de revisió escrites. DA-001, promesa al mòdul 2 i arrossegada durant cinc, està complerta: el catàleg corre a Cloud Run, el MIG està apagat, i no queda ni una sola màquina virtual a la ruta de servei. La xarxa és una VPC compartida amb permisos per subxarxa, connectivitat híbrida real i un perímetre que impedeix que les dades surtin. La seguretat s'ha revisat sencera capa per capa, amb una llista de comprovació de 43 controls i un deute prioritzat del qual ja està pagada la part crítica. La factura ha baixat un 60 % i —més important— s'entén, s'atribueix i se segueix per cost unitari. La fiabilitat té números: quatre SLI, quatre SLO, pressupost d'error amb política, pla de recuperació amb RTO i RPO, i una restauració provada que va trobar cinc problemes que ningú no sospitava. I el govern converteix tot això en controls que no depenen que ningú se'n recordi.

Queda deute, escrit i prioritzat. I queda una llista de coses que AlpinaShop ha decidit no fer, amb el seu motiu — que és el que de debò distingeix una organització madura d'una que simplement no hi va arribar.

Has recorregut set mòduls seguint la Marta, en Dani i la Lucía. Has vist crear un compte i una jerarquia, migrar una aplicació, triar entre cinc serveis de còmput, muntar xarxes, identitats i secrets, construir una plataforma de dades, entrenar i servir models, automatitzar el lliurament, observar el sistema, resoldre un incident d'extrem a extrem, escriure la infraestructura com a codi, i ara assegurar-la, mesurar-la, abaratir-la i governar-la. Has vist prendre decisions i també desfer-les: App Engine es va descartar, GKE es va retirar, Anthos es va rebutjar, un endpoint es va apagar. Has vist quatre decisions d'arquitectura documentades, revisades i —algunes— complertes anys després d'escriure's.

El que encara no has fet és decidir tu.

El mòdul 8 és el teu projecte. Ja no hi ha una AlpinaShop a qui seguir: hi ha una empresa, un problema i un conjunt de restriccions que hauràs de resoldre de principi a fi. Recolliràs requisits i els traduiràs a decisions (08-01), dissenyaràs l'arquitectura completa justificant cada elecció igual que es va justificar DA-001 (08-02), la implementaràs amb infraestructura com a codi i lliurament continu (08-03), la provaràs i la desplegaràs amb canari, SLO i pla de recuperació (08-04), i la presentaràs i defensaràs davant de qui et pregunti per què vas triar el que vas triar (08-05). I acabaràs mirant endavant, cap a les certificacions de Google Cloud i cap al que ve després d'aquest curs (08-06).

Tot el que necessites és als set mòduls que acabes de recórrer. Ara et toca a tu.

Curs de Google Cloud Platform (GCP)

Mòdul 1: Introducció a Google Cloud Platform

Mòdul 2: Serveis principals de GCP

Mòdul 3: Xarxes i seguretat

Mòdul 4: Dades i anàlisi

Mòdul 5: Aprenentatge automàtic i IA

Mòdul 6: DevOps i monitoratge

Mòdul 7: Temes avançats de GCP

Mòdul 8: Projecte final

© Copyright 2026. Tots els drets reservats