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
- Què és el govern al núvol i per què es necessita abans del que sembla
- L'estructura de l'organització: criteris i conseqüències
- Polítiques d'organització: què són i com s'hereten
- Les polítiques que AlpinaShop aplica, una a una
- Mode de prova i el perill de bloquejar l'equip
- Restriccions personalitzades i la seva relació amb Policy Controller
- Quotes i límits a nivell d'organització
- Cloud Asset Inventory: saber què existeix
- Feeds: monitorar canvis en temps real
- Cloud Audit Logs de debò
- Les consultes d'auditoria que cal tenir preparades
- Retenció immutable en un projecte separat
- Zones d'aterratge i l'ordre que importa
- Gestió del canvi: revisions, cicle de vida i excepcions
- La maduresa d'AlpinaShop, i el que li queda
- 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.
- 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/tienda… L'estructura de carpetes es pot canviar; moure projectes entre carpetes és una comanda. El que no es pot canviar és el projectId.
- 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
allowaddicionals sobre restriccions que el pare no va denegar explícitament. Si et trobes necessitant «des-denegar» alguna cosa, la política estava mal posada.
- 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_IDAmb 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, bucketsMotiu: 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_IDMotiu: é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 hores4.4 Exigir OS Login
gcloud resource-manager org-policies enable-enforce \
constraints/compute.requireOsLogin \
--organization=ORG_IDMotiu: 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_IDMotiu: 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_IDMotiu: 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-keyringMotiu: 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_IDMotiu: é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.exampleMotiu: 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 | — |
- 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-locationsI 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 DESCEl 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.policyAdmina 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.
- 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_IDUn 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.
- 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=8La 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.
- 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.
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-forceAmb 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
- 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-criticosI 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.
- 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 | Sí | Gratis | No |
| Accés a dades | Lectures i escriptures de dades | No (llevat de BigQuery) | De pagament, i pot ser molt | Sí |
| Esdeveniments del sistema | Accions de Google sobre els teus recursos | Sí | Gratis | No |
| Denegacions de política | Peticions rebutjades per polítiques | Sí | Gratis | No |
Les dues dades que cal retenir:
- 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ó.
- 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_WRITELes 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.
- 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 DESC2. 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 DESC3. 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 DESCAquesta 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 DESCEl 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 DESCAquesta 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.
- 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-auditoriaI 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.bucketWriterAquest 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-periodAixò é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.
- 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:
- 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.
- 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. - La xarxa (6) abans que els projectes de servei (8). Un projecte de servei necessita que existeixi l'amfitrió.
- 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.
- 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è.
- 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):
- Acabar d'aplicar les polítiques d'organització. Estan en
dry-run; cal travessar les 4 setmanes de l'apartat 13. - Documentació de compliment normatiu actualitzada, amb assessoria professional. És la casella més endarrerida i l'única amb conseqüències legals.
- Provar el procediment de dret de supressió d'extrem a extrem, amb un client de prova.
A mitjà termini (aquest any):
- Multiregió per a Cloud SQL, que és l'única cosa que impedeix l'alta disponibilitat regional completa (07-06).
- Detecció activa d'amenaces, quan hi hagi algú que la pugui atendre (07-04).
- Elevació temporal de privilegis amb PAM.
- 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-rundeixa de servir. - Oblidar
in:eu-locationsaresourceLocations. BigQuery multiregió i alguns buckets deixen de crear-se. allowedPolicyMemberDomainssense 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.bucketWritera lawriterIdentityde 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_READen 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-auditoriaPer 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.
- Cloud Identity, domini, grups (
at-desarrollo@,at-datos@,at-infra@,at-seguridad@), MFA obligatori des del minut u. - Organització i carpetes.
- Facturació: compte, exportació a BigQuery, pressupost global de 2.500 € i un per carpeta de client.
alpinatech-auditoriaamb abocador agregat i retenció bloquejada. Abans que res més, per capturar tota la construcció.- Polítiques 1-11 en
dry-run. alpinatech-redamb VPC compartida.alpinatech-cicdamb Workload Identity Federation.- Primer projecte de client com a plantilla, amb mòdul de Terraform reutilitzable.
- Polítiques en mode aplicat.
- 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 |
SetIamPolicy → CreateServiceAccount → CreateServiceAccountKey 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 timestampEls 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 timestampI 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 timestampSi 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 | Sí |
| 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:
- 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.
- 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.
- 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ó:
- 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.
- 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.
- 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.
- 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
- Què és Google Cloud Platform?
- Configuració del teu compte de GCP
- Descripció general de la consola de GCP
- Projectes, jerarquia de recursos i facturació
- Regions, zones i model de responsabilitat compartida
- Cloud Shell i la CLI de gcloud
Mòdul 2: Serveis principals de GCP
- Compute Engine: màquines virtuals a Google Cloud
- Cloud Storage: emmagatzematge d'objectes
- Cloud SQL: bases de dades relacionals gestionades
- App Engine: plataforma com a servei
- Google Kubernetes Engine (GKE)
- Bases de dades NoSQL: Firestore, Bigtable i Spanner
- Com triar el servei de còmput adequat
Mòdul 3: Xarxes i seguretat
- Xarxes VPC
- Balanceig de càrrega al núvol
- Cloud CDN
- Gestió d'identitat i accés (IAM)
- Cloud Armor
- Secrets i xifratge: Secret Manager i Cloud KMS
- Cloud DNS, certificats TLS i publicació segura de serveis
Mòdul 4: Dades i anàlisi
- BigQuery: el magatzem de dades analític
- Cloud Dataflow: processament de dades per lots i en temps real
- Cloud Dataproc: Spark i Hadoop gestionats
- Cloud Pub/Sub: missatgeria asíncrona
- Cloud Data Fusion: integració de dades sense codi
- Orquestració de pipelines amb Cloud Composer i Workflows
- Govern de les dades i taulers amb Dataplex i Looker Studio
Mòdul 5: Aprenentatge automàtic i IA
- Vertex AI: la plataforma d'aprenentatge automàtic de GCP
- AutoML: models a mida sense escriure codi
- TensorFlow a GCP: entrenament i servei de models
- API de llenguatge natural
- API de visió
- IA generativa a Vertex AI: models Gemini i incrustacions
- MLOps: del model al producte amb Vertex AI Pipelines
Mòdul 6: DevOps i monitoratge
- Cloud Build: integració contínua a GCP
- Cloud Source Repositories i gestió del codi font
- Cloud Functions: funcions sense servidor
- Cloud Monitoring (abans Stackdriver): mètriques, taulers i alertes
- Cloud Deployment Manager i infraestructura com a codi nativa
- Cloud Logging i Cloud Trace: registres, traces i diagnòstic
- Terraform a GCP: infraestructura com a codi a la pràctica
Mòdul 7: Temes avançats de GCP
- Híbrid i multinúvol amb Anthos
- Computació sense servidor amb Cloud Run
- Xarxes avançades: VPC compartida, aparellament i connectivitat híbrida
- Bones pràctiques de seguretat
- Gestió i optimització de costos
- Fiabilitat: SLO, alta disponibilitat i recuperació de desastres
- Govern a escala: organització, polítiques i auditoria
