Hem protegit la vora amb cura: xarxa segmentada, balancejador, memòria cau, permisos mínims i un WAF calibrat amb paciència. I mentrestant, la contrasenya de l'usuari app_catalogo de la base de dades alpinashop-pedidos continua exactament on la vam deixar a 02-01: en una variable d'entorn definida dins de l'startup-script de la plantilla d'instància del MIG.

Val la pena enumerar per què això és deute de seguretat, i no una simple falta d'elegància:

  • És llegible per qualsevol amb compute.instances.get. Les metadades d'una instància, inclòs l'script d'arrencada, es llegeixen amb un gcloud compute instances describe. El Dani, que només té rol de lectura en producció, pot veure la contrasenya de producció.
  • És a l'historial de Git, perquè la plantilla es genera des d'un script versionat. Esborrar-la del fitxer no l'esborra del repositori.
  • No es pot rotar sense tornar a desplegar. Canviar la contrasenya exigeix una plantilla nova i una actualització progressiva del MIG. A la pràctica, això significa que no es rota mai.
  • Està duplicada. A la plantilla, al .env del portàtil del Dani, a l'entorn de desenvolupament, probablement en un missatge de xat de fa vuit mesos.
  • No deixa rastre. No hi ha manera de saber qui l'ha llegit ni quan.

Aquesta lliçó resol aquest problema i respon a més la pregunta que ve just al darrere: si Google guarda les meves dades, qui les xifra i amb quina clau? Veuràs Secret Manager per als secrets de l'aplicació i Cloud KMS per al material criptogràfic, i entendràs quan té sentit assumir la responsabilitat de gestionar les teves pròpies claus i quan és una complicació que no compra seguretat real.

Avís. Xifratge i gestió de secrets toquen directament obligacions normatives: RGPD, PCI DSS si es processen pagaments, i requisits sectorials de residència de les dades. El que segueix és un model docent tècnicament correcte, però qualsevol disseny destinat a producció amb dades personals o de pagament l'ha de revisar un professional de seguretat i compliment normatiu abans de desplegar-se. Una fallada aquí no es mesura en minuts de caiguda.

Contingut

  1. El problema concret: la contrasenya d'app_catalogo
  2. Secret Manager: model de secrets i versions
  3. Crear els secrets d'AlpinaShop
  4. Accedir-hi des de l'aplicació Flask
  5. Permisos per secret: el mínim privilegi aplicat
  6. Rotació: desactivar en lloc de destruir
  7. Replicació automàtica davant de regional: residència de les dades
  8. Integració amb Cloud Run, GKE i Cloud Build
  9. Xifratge a Google Cloud: què passa per defecte
  10. La jerarquia de claus: DEK i KEK
  11. Google-managed, CMEK i CSEK: taula de decisió
  12. Cloud KMS: clauer, claus i rotació
  13. HSM i External Key Manager
  14. Aplicar CMEK al bucket i a Cloud SQL
  15. Xifratge en trànsit
  16. Què NO fer mai

  1. El problema concret: la contrasenya d'app_catalogo

Així està avui la plantilla del MIG, i així no ha de quedar-se:

# ANTIPATRO: la contrasenya a les metadades de la instancia
gcloud compute instance-templates create tpl-catalogo-v3 \
  --metadata=startup-script='#!/bin/bash
export DB_PASSWORD="Tr3kking!2026"     # <-- visible per a tothom
gunicorn --bind 0.0.0.0:8080 app:app'

Comprova tu mateix com n'està d'exposada:

gcloud compute instances describe alpinashop-web-1 --zone=europe-west1-b \
  --format="value(metadata.items[startup-script])"

Aquesta comanda només necessita compute.instances.get, un permís que forma part de roles/compute.viewer i que hem concedit al grup gcp-desarrollo@ en producció (03-04). És a dir: el nostre propi disseny de permisos, curosament construït, se salta completament per culpa d'on està desada la contrasenya.

La destinació és aquesta, i la resta de la lliçó explica cada peça:

flowchart LR
    APP["Aplicació Flask al MIG<br/>identitat: sa-catalogo-web"]
    SM["Secret Manager<br/>db-password-catalogo"]
    KMS["Cloud KMS<br/>xifra el secret en repòs"]
    SQL[("Cloud SQL<br/>alpinashop-pedidos")]
    AUD["Registres d'auditoria<br/>qui hi ha accedit i quan"]

    APP -->|"1. accessSecretVersion<br/>amb la seva identitat"| SM
    SM -->|2. desxifra| KMS
    SM -->|3. retorna el valor| APP
    APP -->|4. connecta| SQL
    SM -.->|registra cada accés| AUD

  1. Secret Manager: model de secrets i versions

Secret Manager guarda cadenes o binaris petits —contrasenyes, claus d'API, certificats, tokens— amb control d'accés per IAM, xifratge en repòs, versionatge i auditoria.

El model té dos nivells, i confondre'ls és la font de gairebé tots els errors:

Nivell Què és Conté
Secret El contenidor amb nom i la seva política IAM Metadades, etiquetes, política de replicació, cap valor
Versió Un valor concret, immutable, numerat des d'1 La dada real

Propietats que es deriven d'aquest model:

  • Les versions són immutables. No s'"actualitza" un secret: s'afegeix una versió nova. La 1 continua existint.
  • latest és un àlies mòbil que apunta sempre a la versió habilitada més recent.
  • Una versió pot estar en tres estats: habilitada (es pot llegir), deshabilitada (existeix però l'accés falla, i és reversible) i destruïda (el material s'esborra de forma irreversible; només en queden les metadades).
  • Els permisos es donen sobre el secret, no sobre la versió.

  1. Crear els secrets d'AlpinaShop

gcloud config set project alpinashop-prod
gcloud services enable secretmanager.googleapis.com

# 1) Contrasenya de la base de dades
gcloud secrets create db-password-catalogo \
  --replication-policy=automatic \
  --labels=entorno=produccion,equipo=plataforma,aplicacion=catalogo,centro-coste=tienda

# 2) Afegir la primera versio SENSE que quedi a l'historial del shell.
#    El guio final significa "llegir de l'entrada estandard".
printf '%s' 'Tr3kking!2026' | gcloud secrets versions add db-password-catalogo --data-file=-

# 3) Clau de la passarel·la de pagament
gcloud secrets create api-key-pasarela-pago \
  --replication-policy=automatic \
  --labels=entorno=produccion,equipo=plataforma,aplicacion=pagos,centro-coste=tienda

printf '%s' 'sk_live_9f2c...' | gcloud secrets versions add api-key-pasarela-pago --data-file=-

Detalls que importen d'aquestes comandes:

  • --data-file=- amb printf i sense salt de línia final. Si fas servir echo, afegeixes un \n al valor i la contrasenya que llegeix l'aplicació no és la que et penses. És una fallada clàssica que costa mitja tarda de depuració. Si el secret és en un fitxer, --data-file=fitxer i esborra el fitxer després amb shred -u.
  • No passis mai el valor per la línia de comandes. Existeix --data-file, però no un --data: és deliberat. Un valor a la línia de comandes queda a ~/.bash_history i a la llista de processos.
  • Etiqueta els secrets amb el mateix conveni que la resta de recursos (entorno, equipo, centro-coste, aplicacion). Serveix per a l'inventari i per a la revisió periòdica.

Operacions habituals:

gcloud secrets list --format="table(name, createTime, labels.aplicacion)"

gcloud secrets versions list db-password-catalogo \
  --format="table(name, state, createTime)"

# Llegir el valor (aixo queda registrat a auditoria)
gcloud secrets versions access latest --secret=db-password-catalogo

  1. Accedir-hi des de l'aplicació Flask

L'aplicació s'autentica amb la identitat de la VM —el compte de servei sa-catalogo-web— sense cap credencial al codi. És el patró que ja vas fer servir amb Cloud Storage a 02-02, aplicat ara als secrets.

# secretos.py — acces a Secret Manager amb memoria cau en memoria
import os
from functools import lru_cache
from google.cloud import secretmanager

PROJECTE = os.environ.get("GOOGLE_CLOUD_PROJECT", "alpinashop-prod")

# El client es car de crear: un per proces.
_client = secretmanager.SecretManagerServiceClient()


@lru_cache(maxsize=32)
def llegir_secret(nom: str, versio: str = "latest") -> str:
    """Retorna el valor d'un secret.

    lru_cache evita anar a l'API a cada peticio HTTP: sense ella,
    una botiga amb 500 req/s faria 500 crides per segon a
    Secret Manager. Aixo es lent, car i a mes topa amb la quota.

    El preu de la memoria cau es que un canvi de secret no es veu fins
    que es reinicia el proces. Es un compromis ACCEPTABLE i cal
    prendre'l conscientment: la rotacio s'acompanya d'un reinici
    progressiu del MIG.
    """
    ruta = f"projects/{PROJECTE}/secrets/{nom}/versions/{versio}"
    resposta = _client.access_secret_version(request={"name": ruta})
    return resposta.payload.data.decode("UTF-8")
# app.py — construccio de la cadena de connexio a l'arrencada
from secretos import llegir_secret

def crear_pool_connexions():
    password = llegir_secret("db-password-catalogo")
    return crear_pool(
        usuari="app_catalogo",
        password=password,
        host="10.10.1.5",          # IP privada, 03-01
        base_dades="tienda",
    )

Tres decisions de disseny explícites en aquest codi:

  1. Es llegeix a l'arrencada, no a cada petició. El cost i la latència d'una crida per petició són inacceptables.
  2. Es fa servir latest, cosa que fa que un reinici reculli automàticament la versió nova després d'una rotació. L'alternativa —fixar la versió 3— és més determinista però obliga a desplegar codi per rotar. Per a AlpinaShop, latest és l'elecció correcta; en un sistema on un secret equivocat sigui catastròfic, la versió fixa té el seu argument.
  3. El valor no es registra mai. Ni en un print, ni en un registre de depuració, ni en un missatge d'excepció. Si el valor arriba a Cloud Logging (06-06), l'has tornat a exposar, ara en un lloc que a més es conserva i s'exporta.

  1. Permisos per secret: el mínim privilegi aplicat

Aquí s'aplica de forma directa el que has après a 03-04. Els rols rellevants:

Rol Permet Per a qui
roles/secretmanager.secretAccessor Llegir el valor de les versions L'aplicació. Res més
roles/secretmanager.viewer Veure que el secret existeix i les seves metadades, sense llegir-lo Auditoria, inventari
roles/secretmanager.secretVersionAdder Afegir versions noves, sense poder llegir-les El procés de rotació
roles/secretmanager.secretVersionManager Habilitar, deshabilitar i destruir versions Operació
roles/secretmanager.admin Tot, inclòs crear i esborrar secrets Només gcp-infra@, i amb moderació
SA_WEB="[email protected]"

# L'aplicacio nomes pot LLEGIR, i nomes AQUEST secret
gcloud secrets add-iam-policy-binding db-password-catalogo \
  --member="serviceAccount:$SA_WEB" \
  --role="roles/secretmanager.secretAccessor"

gcloud secrets add-iam-policy-binding api-key-pasarela-pago \
  --member="serviceAccount:$SA_WEB" \
  --role="roles/secretmanager.secretAccessor"

# Auditoria: veu que existeixen, no els llegeix
gcloud secrets add-iam-policy-binding db-password-catalogo \
  --member="group:[email protected]" \
  --role="roles/secretmanager.viewer"

# Verificar qui pot llegir cada secret
gcloud secrets get-iam-policy db-password-catalogo --format=yaml

La regla que no s'ha de trencar: els permisos es concedeixen a nivell de secret, mai de projecte. Un roles/secretmanager.secretAccessor sobre alpinashop-prod donaria accés a tots els secrets presents i futurs, inclosa la clau de la passarel·la de pagament que es creï el mes que ve. És exactament el mateix raonament que vam aplicar amb el bucket i amb les condicions d'IAM.

Un detall que sorprèn i que convé saber: l'accés a un secret queda registrat als registres d'auditoria d'accés a dades, però aquests registres s'han d'activar explícitament perquè no vénen habilitats per defecte. Sense ells no sabràs qui va llegir què. La configuració dels registres d'auditoria es tracta a 07-07; per a un secret de producció, activa'ls.

  1. Rotació: desactivar en lloc de destruir

Rotar és substituir el valor per un altre de nou. El procediment correcte té un pas que gairebé tothom se salta, i és el que evita el tall de servei.

# PAS 1 — Crear la credencial nova al sistema de desti,
#          convivint amb la vella
gcloud sql users set-password app_catalogo \
  --instance=alpinashop-pedidos --password="$NUEVA"

# PAS 2 — Afegir la versio nova al secret (la vella continua viva)
printf '%s' "$NUEVA" | gcloud secrets versions add db-password-catalogo --data-file=-

# PAS 3 — Desplegar: reinici progressiu del MIG perque els processos
#          rellegeixin 'latest'. Sense tall, gracies al drenatge de connexions (03-02).
gcloud compute instance-groups managed rolling-action restart alpinashop-web-mig \
  --region=europe-west1 --max-unavailable=1

# PAS 4 — Verificar que ningu no fa servir ja la versio anterior, i DESACTIVAR-LA
gcloud secrets versions disable 1 --secret=db-password-catalogo

# PAS 5 — Nomes despres d'un periode de gracia (dies), destruir
gcloud secrets versions destroy 1 --secret=db-password-catalogo

El pas 4 és la clau de tota la lliçó en matèria d'operació. disable és reversible: si alguna cosa es trenca, un gcloud secrets versions enable 1 retorna el servei en segons. destroy és irreversible. La regla: deshabilitar sempre primer, destruir molt després, i mai les dues coses el mateix dia.

Secret Manager pot a més recordar-te que toca rotar:

gcloud secrets update db-password-catalogo \
  --next-rotation-time="2026-11-01T03:00:00Z" \
  --rotation-period="90d" \
  --add-topics="projects/alpinashop-prod/topics/rotacion-secretos"

Aquí hi ha un matís que es malinterpreta molt sovint: Secret Manager no rota res. Publica un missatge en un tema de Pub/Sub quan arriba la data. Qui rota és el teu automatisme —una Cloud Function subscrita a aquest tema (06-03) que genera la contrasenya, l'aplica a Cloud SQL, afegeix la versió i llança el reinici del MIG—. La rotació completament automàtica és un projecte en si mateix; el recordatori amb Pub/Sub i un procediment escrit és el mínim exigible.

També es pot posar caducitat a un secret, útil per a credencials temporals:

gcloud secrets update credencial-consultora-agosto --expire-time="2026-09-16T00:00:00Z"

  1. Replicació automàtica davant de regional: residència de les dades

En crear un secret es tria on s'emmagatzema, i la decisió és irreversible.

Política On viu Avantatges Quan
automatic Google decideix, replicat globalment Màxima disponibilitat, sense decisions Per defecte, tret de requisit contrari
user-managed A les regions que tu indiquis Compleix requisits de residència Quan la dada no pot sortir d'una zona geogràfica
# Secret restringit a Europa, amb dues regions per tolerar la fallada d'una
gcloud secrets create api-key-pasarela-pago-eu \
  --replication-policy=user-managed \
  --locations=europe-west1,europe-west4

Existeix a més la variant de secrets regionals, creats amb --location=europe-west1, que viuen íntegrament al pla de control regional i ofereixen l'aïllament més estricte. S'hi accedeix amb un punt final regional (secretmanager.europe-west1.rep.googleapis.com), cosa que cal tenir en compte al codi.

Per a AlpinaShop, la facturació i la clientela de la qual són europees, l'elecció raonable és user-managed amb regions europees per als secrets lligats a dades personals o de pagament, i automatic per a la resta. Quina de les dues exigeix el marc normatiu aplicable és una pregunta per al responsable de compliment normatiu, no per a l'equip de plataforma.

  1. Integració amb Cloud Run, GKE i Cloud Build

Fora de les VM, la integració és encara més neta: la plataforma injecta el secret i el teu codi ni tan sols crida l'API.

Cloud Run (que serà la destinació del catàleg segons DA-001, a 07-02) ofereix les dues formes:

# Com a variable d'entorn: comode
gcloud run deploy catalogo \
  --image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:1.4.0 \
  --region=europe-west1 \
  --service-account=sa-catalogo-web@alpinashop-prod.iam.gserviceaccount.com \
  --set-secrets=DB_PASSWORD=db-password-catalogo:latest

# Com a fitxer muntat: mes segur
gcloud run deploy catalogo \
  --image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:1.4.0 \
  --region=europe-west1 \
  --service-account=sa-catalogo-web@alpinashop-prod.iam.gserviceaccount.com \
  --set-secrets=/secretos/db-password=db-password-catalogo:latest
Variable d'entorn Fitxer muntat
Comoditat Alta: os.environ["DB_PASSWORD"] Requereix llegir un fitxer
Visibilitat accidental Alta: apareix en bolcats d'entorn, en traces d'excepció de molts frameworks i en eines de depuració Baixa
Actualització sense tornar a desplegar No, amb :latest es fixa en desplegar : fent servir :latest, el fitxer s'actualitza
Recomanació Acceptable per a secrets poc crítics Preferible per a contrasenyes i claus de pagament

GKE Autopilot (el clúster alpinashop-cluster, namespace tienda) fa servir el complement de Secret Manager amb el controlador CSI, que munta el secret com a volum:

# secret-provider.yaml — el secret es munta, no es copia a un Secret de Kubernetes
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: alpinashop-secretos
  namespace: tienda
spec:
  provider: gcp
  parameters:
    secrets: |
      - resourceName: "projects/alpinashop-prod/secrets/db-password-catalogo/versions/latest"
        path: "db-password"
# Al Deployment: es munta com a fitxer a /var/secretos/db-password
      volumes:
        - name: secretos
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "alpinashop-secretos"

Això tanca per fi l'advertiment que vam deixar a 02-05: un Secret de Kubernetes està codificat en base64, que no és xifratge. Qualsevol amb permís de lectura al namespace el descodifica en un segon. Amb el controlador CSI, el valor no existeix com a objecte de Kubernetes: el porta Secret Manager, autenticat amb Workload Identity, i només apareix al sistema de fitxers del pod.

Cloud Build (06-01) permet fer servir secrets al pipeline sense escriure'ls al fitxer de configuració:

# cloudbuild.yaml
availableSecrets:
  secretManager:
    - versionName: projects/alpinashop-prod/secrets/api-key-pasarela-pago/versions/latest
      env: 'API_KEY_PAGO'

steps:
  - name: python:3.12
    entrypoint: bash
    secretEnv: ['API_KEY_PAGO']
    args:
      - -c
      - |
        # Disponible com a $$API_KEY_PAGO. NO l'imprimeixis MAI:
        # els registres de Cloud Build son llegibles per tot l'equip.
        pytest tests/integracion

  1. Xifratge a Google Cloud: què passa per defecte

Canviem de tema, del secret d'aplicació al xifratge de les dades.

El primer, per treure ansietat innecessària: totes les dades en repòs de Google Cloud estan xifrades per defecte, sempre, sense que facis res i sense cost addicional. Cloud Storage, discos persistents, Cloud SQL, BigQuery, Firestore: tot. No hi ha una casella d'"activar xifratge" perquè no hi ha manera de desactivar-lo.

Per tant, la pregunta correcta no és "estan xifrades les meves dades?" sinó "qui controla la clau?". I aquesta pregunta rarament la respon la seguretat tècnica: la respon el compliment normatiu, el sector i, de vegades, el contracte amb un client gran.

  1. La jerarquia de claus: DEK i KEK

Entendre això explica de cop per què CMEK és barat d'activar i per què rotar una clau no reescriu petabytes.

flowchart TD
    D["Les teves dades<br/>trossejades en fragments"]
    DEK["DEK — Data Encryption Key<br/>una per fragment, AES-256"]
    KEK["KEK — Key Encryption Key<br/>viu a Cloud KMS, no en surt mai"]
    ROOT["Magatzem arrel de claus de Google<br/>o la teva clau CMEK"]

    D -->|xifrat amb| DEK
    DEK -->|"embolcallada (wrapped) per"| KEK
    KEK -->|protegida per| ROOT

El mecanisme, pas a pas:

  1. Les dades es trossegen en fragments i cada fragment es xifra amb una DEK pròpia i diferent.
  2. La DEK no es desa en clar: es xifra ("embolcalla") amb una KEK i s'emmagatzema embolcallada juntament amb el fragment.
  3. La KEK viu a Cloud KMS i no en surt mai. Per desxifrar, el servei envia la DEK embolcallada a KMS, que la desembolcalla i la retorna; la DEK en clar viu en memòria el mínim imprescindible.

Conseqüències que responen preguntes molt freqüents:

  • Rotar la KEK és instantani i no reescriu ni un byte de dades. Només canvia la clau amb la qual s'embolcallen les DEK noves; les antigues se segueixen desembolcallant amb la versió antiga, que es conserva.
  • Revocar l'accés a la KEK inutilitza les dades immediatament. Sense KEK no hi ha DEK, i sense DEK no hi ha dades. Això és alhora la garantia més potent de CMEK i el seu perill operatiu més gran.
  • CMEK és substituir la KEK, no xifrar tu les dades. Per això activar-lo té un cost computacional menyspreable.

  1. Google-managed, CMEK i CSEK: taula de decisió

Google-managed (per defecte) CMEK (claus gestionades pel client) CSEK (claus proporcionades pel client)
Qui crea la clau Google Tu, a Cloud KMS Tu, fora de Google
On viu Infraestructura de Google Cloud KMS, al teu projecte Enlloc de Google: l'envies a cada petició
La pots rotar No la veus Sí, manual o automàtica Tu t'ho munteges
La pots revocar No : inutilitza les dades a l'instant Deixant d'enviar-la
Auditoria d'ús de la clau No , cada operació als registres No
Si la perds No aplica Es pot recuperar mentre estigui programada per a destrucció Les dades es perden per sempre
Serveis compatibles Tots La majoria dels importants Només Cloud Storage i discos de Compute Engine
Cost 0 Cost de KMS: baix 0, però altíssim cost operatiu
Complexitat Cap Mitjana Alta

Com decidir, sense misticisme:

  • Google-managed cobreix bé la immensa majoria dels casos. Si ningú no t'ha demanat el contrari, això és el correcte i no és "menys segur".
  • CMEK quan necessites una d'aquestes tres coses: poder revocar l'accés a les dades de forma unilateral, auditar cada ús de la clau, o complir un requisit normatiu o contractual que ho exigeixi explícitament. Afegeix un mode de fallada nou —si la clau no està disponible, el servei no arrenca— i això cal assumir-ho conscientment.
  • CSEK és gairebé sempre la resposta equivocada. Assumeixes la custòdia, la rotació i la disponibilitat de la clau, i un error significa pèrdua definitiva de dades. Existeix per a requisits molt concrets on la clau no pot residir al núvol sota cap circumstància.

Per a AlpinaShop, la decisió raonada: CMEK a alpinashop-catalogo i a la base de dades de comandes, perquè contenen dades de clients i l'empresa vol poder demostrar control de la clau davant d'una auditoria; Google-managed a alpinashop-dev, on no hi ha dades reals i afegir un mode de fallada no compensa.

  1. Cloud KMS: clauer, claus i rotació

La jerarquia de KMS és: projecte → ubicació → clauer (keyring) → clau → versions de clau.

gcloud services enable cloudkms.googleapis.com

# El clauer agrupa claus i NO ES POT ESBORRAR. Tria be el nom i la ubicacio.
gcloud kms keyrings create alpinashop-keyring --location=europe-west1

# Clau simetrica amb rotacio automatica cada 90 dies
gcloud kms keys create clave-catalogo \
  --location=europe-west1 \
  --keyring=alpinashop-keyring \
  --purpose=encryption \
  --rotation-period=90d \
  --next-rotation-time=2026-11-01T03:00:00Z \
  --protection-level=software

gcloud kms keys create clave-pedidos \
  --location=europe-west1 \
  --keyring=alpinashop-keyring \
  --purpose=encryption \
  --rotation-period=90d \
  --next-rotation-time=2026-11-01T03:00:00Z

gcloud kms keys versions list --key=clave-catalogo \
  --keyring=alpinashop-keyring --location=europe-west1

Quatre coses que cal saber de KMS i que no són evidents:

  1. La ubicació de la clau ha de ser compatible amb la del recurs. Una clau a europe-west1 per a un bucket a europe-west1: correcte. Per a un bucket multiregió EU cal una clau a la ubicació europe. Si no coincideixen, l'operació falla amb un error poc clar.
  2. Els clauers i les claus no es poden esborrar. Mai. Només se'n destrueixen les versions. Això és deliberat: evita que un esborrat accidental deixi dades irrecuperables. Conseqüència pràctica: pensa la nomenclatura abans de crear, perquè viurà per sempre.
  3. Destruir una versió de clau té un període de gràcia, 30 dies per defecte, durant el qual es pot restaurar. És la xarxa de seguretat que impedeix que un error es converteixi en una catàstrofe.
  4. La rotació automàtica no torna a xifrar res. Crea una versió nova que passa a ser la primària per a les operacions futures. Les versions antigues es conserven habilitades per poder desxifrar el que ja està xifrat.

Permisos, un altre cop amb criteri de mínim privilegi:

Rol Permet
roles/cloudkms.cryptoKeyEncrypterDecrypter Xifrar i desxifrar amb la clau. És el que necessiten els agents de servei
roles/cloudkms.cryptoKeyEncrypter Només xifrar
roles/cloudkms.viewer Veure metadades, sense fer-la servir
roles/cloudkms.admin Gestionar claus. Mai a qui també accedeix a les dades

Aquesta última línia és separació de funcions aplicada al xifratge: qui administra les claus no ha de ser qui accedeix a les dades xifrades, perquè en aquest cas el xifratge no protegeix de res davant d'aquesta persona.

KMS serveix a més per xifrar dades petites directament:

echo -n "dada sensible" | gcloud kms encrypt \
  --location=europe-west1 --keyring=alpinashop-keyring --key=clave-catalogo \
  --plaintext-file=- --ciphertext-file=cifrado.bin

gcloud kms decrypt \
  --location=europe-west1 --keyring=alpinashop-keyring --key=clave-catalogo \
  --ciphertext-file=cifrado.bin --plaintext-file=-

Encara que per a contrasenyes i claus d'API, Secret Manager és l'eina adequada, no KMS. KMS és per a claus; Secret Manager és per a secrets. Internament, Secret Manager ja fa servir KMS.

  1. HSM i External Key Manager

El nivell de protecció determina on viu físicament el material de la clau:

Nivell On resideix la clau Certificació Cost relatiu Quan
SOFTWARE A la infraestructura de Google Base Per defecte
HSM Mòdul de maquinari dedicat FIPS 140-2 nivell 3 ~10-40× per versió de clau Requisit normatiu explícit
EXTERNAL Fora de Google, al teu proveïdor d'EKM Segons el proveïdor Alt + cost del proveïdor Sobirania de les dades, requisit contractual
gcloud kms keys create clave-pagos-hsm \
  --location=europe-west1 --keyring=alpinashop-keyring \
  --purpose=encryption --protection-level=hsm

External Key Manager (EKM) és el cas extrem: la clau viu en un gestor extern (Thales, Fortanix, Equinix i similars) i Google crida aquest sistema per a cada operació criptogràfica. Si talles l'accés, Google no pot desxifrar les teves dades, ni tan sols si volgués. És la resposta tècnica a "vull poder demostrar que ni el proveïdor de núvol pot llegir les meves dades".

El preu d'aquesta garantia és alt i cal dir-ho clar: afegeixes una dependència externa al camí crític d'accés a les teves dades. Si l'EKM no respon, els teus serveis no arrenquen. Per a una pime com AlpinaShop, SOFTWARE amb CMEK és l'elecció proporcionada; HSM només si la passarel·la de pagament o una auditoria PCI ho exigeixen per escrit.

  1. Aplicar CMEK al bucket i a Cloud SQL

Aquí apareix el concepte que més gent encalla: l'agent de servei. Quan Cloud Storage xifra un objecte amb la teva clau, qui crida KMS no ets tu: és un compte de servei intern del servei, gestionat per Google, que ha de tenir permís sobre la teva clau.

Bucket alpinashop-catalogo:

PROYECTO_NUM=$(gcloud projects describe alpinashop-prod --format="value(projectNumber)")
CLAVE="projects/alpinashop-prod/locations/europe-west1/keyRings/alpinashop-keyring/cryptoKeys/clave-catalogo"

# 1) Obtenir l'agent de servei de Cloud Storage del projecte
AGENTE_GCS=$(gcloud storage service-agent --project=alpinashop-prod)
echo "$AGENTE_GCS"   # service-<NUM>@gs-project-accounts.iam.gserviceaccount.com

# 2) Donar-li permis sobre la clau (i NOMES sobre aquesta clau)
gcloud kms keys add-iam-policy-binding clave-catalogo \
  --location=europe-west1 --keyring=alpinashop-keyring \
  --member="serviceAccount:$AGENTE_GCS" \
  --role="roles/cloudkms.cryptoKeyEncrypterDecrypter"

# 3) Establir la clau per defecte del bucket
gcloud storage buckets update gs://alpinashop-catalogo \
  --default-encryption-key="$CLAVE"

gcloud storage buckets describe gs://alpinashop-catalogo \
  --format="value(default_kms_key)"

Un matís essencial: això només afecta els objectes nous. Els 60 GB ja pujats continuen xifrats amb la clau de Google. Per migrar-los cal reescriure'ls:

# Reescriu els objectes existents amb la clau nova.
# En 60 GB aixo triga i genera operacions de classe A: fes-ho per lots i fora d'hora punta.
gcloud storage objects update "gs://alpinashop-catalogo/productos/**" \
  --encryption-key="$CLAVE"

Cloud SQL alpinashop-pedidos: aquí hi ha una restricció que canvia el pla de treball i que convé saber abans de prometre res.

CMEK a Cloud SQL només es pot establir en crear la instància. No es pot afegir a una instància existent. Migrar alpinashop-pedidos a CMEK significa crear una instància nova i migrar les dades, amb la finestra de manteniment que això implica.

AGENTE_SQL="service-${PROYECTO_NUM}@gcp-sa-cloud-sql.iam.gserviceaccount.com"

gcloud kms keys add-iam-policy-binding clave-pedidos \
  --location=europe-west1 --keyring=alpinashop-keyring \
  --member="serviceAccount:$AGENTE_SQL" \
  --role="roles/cloudkms.cryptoKeyEncrypterDecrypter"

# Instancia NOVA amb CMEK des del primer moment
gcloud sql instances create alpinashop-pedidos-cmek \
  --database-version=POSTGRES_16 \
  --region=europe-west1 \
  --availability-type=REGIONAL \
  --disk-encryption-key="projects/alpinashop-prod/locations/europe-west1/keyRings/alpinashop-keyring/cryptoKeys/clave-pedidos"

La migració es faria amb les eines de 02-03: còpia, restauració a la instància nova, sincronització i canvi de la cadena de connexió. La decisió d'AlpinaShop és fer-ho fora de la campanya de tardor, no abans.

I l'avís operatiu que tanca l'apartat, perquè és el risc real de CMEK:

Si deshabilites o destrueixes la versió de la clau, el bucket deixa de servir objectes i la base de dades deixa d'arrencar, de forma immediata. No és un avís teòric: és exactament el comportament dissenyat. Abans de tocar una clau en producció, comprova amb gcloud logging read què l'està fent servir, i tingues un procediment escrit de recuperació.

  1. Xifratge en trànsit

Menys configurable, però convé conèixer el mapa complet:

Trajecte Com es xifra Configures alguna cosa?
Client → balancejador TLS amb el teu certificat Sí: certificats i política SSL, a 03-07
Balancejador → backend TLS o xarxa privada de Google Opcional: HTTPS cap al backend
Entre serveis de Google ALTS, protocol propi d'autenticació i xifratge No
Entre centres de dades de Google Xifratge a nivell d'enllaç i d'aplicació No
Aplicació → Cloud SQL TLS, i amb l'Auth Proxy a més autenticació IAM (02-03) Sí: fes servir l'Auth Proxy
Aplicació → APIs de Google TLS 1.2 o superior No

El punt que sí que depèn de tu és el primer, i és el tema de la lliçó següent. El segon també mereix una decisió: el trànsit entre el balancejador i el MIG viatja per la xarxa privada de Google dins d'alpinashop-vpc, cosa que és raonable, però per a dades de pagament la pràctica recomanada és xifrar també aquest tram configurant el servei de backend amb protocol HTTPS.

  1. Què NO fer mai

Una llista curta, sense matisos, perquè cada punt ha causat una bretxa real en alguna empresa:

  • Secrets a Git. Ni en un .env, ni en un config.py, ni en un terraform.tfvars, ni en un fitxer de proves. Esborrar-los no n'hi ha prou: queden a l'historial. Si passa, cal rotar la credencial, no només reescriure l'historial. Fes servir git-secrets o l'escaneig de secrets de la teva plataforma per impedir-ho d'entrada.
  • Secrets en imatges de contenidor. Un ENV DB_PASSWORD=... o un fitxer copiat al Dockerfile queden a la capa de la imatge i són llegibles per qualsevol que la descarregui d'europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo.
  • Secrets a metadades o variables d'instància. El problema amb què va començar aquesta lliçó.
  • Secrets als registres. Una excepció que imprimeix la cadena de connexió completa deixa la contrasenya a Cloud Logging, on es conserva i de vegades s'exporta a BigQuery.
  • Secrets en Secrets de Kubernetes sense xifratge addicional. Base64 no és xifratge.
  • Secrets per xat o correu. Queden indexats i sincronitzats en dispositius que no controles.
  • La mateixa contrasenya en desenvolupament i en producció. L'entorn de desenvolupament té permisos més laxos per disseny; comprometre'l no ha de comprometre producció.
  • Claus JSON de comptes de servei descarregades (03-04). Són secrets que no caduquen.

Errors habituals i consells

  • Fer servir echo en lloc de printf en crear una versió. Afegeix un salt de línia al valor i l'autenticació falla amb un missatge que no ajuda.
  • Concedir secretAccessor a nivell de projecte. Dona accés a tots els secrets, inclosos els futurs. Sempre a nivell de secret.
  • Llegir el secret a cada petició HTTP. Latència, cost i quota. Desa'l en memòria cau al procés i accepta conscientment que la rotació exigeix reiniciar.
  • Destruir una versió sense haver-la deshabilitat abans. disable és reversible; destroy no. Mai les dues coses el mateix dia.
  • Creure que Secret Manager rota els secrets. Només avisa per Pub/Sub. La rotació la programes tu.
  • Triar la política de replicació sense pensar. És irreversible i pot tenir implicacions de residència de les dades.
  • Registrar el valor del secret. Un print de depuració el porta a Cloud Logging, que es conserva i s'exporta.
  • Oblidar l'agent de servei en activar CMEK. L'error és un permís denegat sobre la clau, no sobre el bucket, i despista molt.
  • Esperar que CMEK xifri el que ja existeix. Només s'aplica a les dades noves; l'anterior s'ha de reescriure.
  • Prometre CMEK en una instància de Cloud SQL existent. Només es pot en crear-la; implica migració.
  • Desactivar una versió de clau en producció "per provar". El bucket deixa de servir i la base de dades deixa d'arrencar a l'instant.
  • Confondre KMS amb Secret Manager. KMS gestiona claus; Secret Manager gestiona secrets. Per a una contrasenya, Secret Manager.
  • Posar claus en HSM per precaució. Costa bastant més per versió de clau i només aporta si hi ha un requisit normatiu concret.
  • Consell: separa qui administra les claus de qui accedeix a les dades. Sense aquesta separació, el xifratge no protegeix de ningú rellevant.
  • Consell: anomena els secrets per la seva funció, no pel seu valor. db-password-catalogo, no password-prod-2026.

Exercicis

Exercici 1 — Treure la contrasenya de la plantilla del MIG

Escriu el pla complet per eliminar la contrasenya de l'startup-script de tpl-catalogo-v3 sense tallar el servei: comandes, canvis al codi, permisos, ordre de les operacions i verificació. Inclou què fer amb la contrasenya actual, que fa vuit mesos que està exposada a les metadades i a l'historial de Git.

Exercici 2 — Decidir el model de xifratge

Per a cada conjunt de dades d'AlpinaShop, decideix entre Google-managed, CMEK o CSEK, i justifica-ho en dues línies:

  1. Els 60 GB d'imatges de producte a alpinashop-catalogo.
  2. La base de dades alpinashop-pedidos, amb noms, adreces i últims quatre dígits de targeta.
  3. Les exportacions CSV per a la Lucía a gs://alpinashop-catalogo/exportaciones/.
  4. Els discos de les instàncies del MIG, que només contenen el codi de l'aplicació.
  5. Una còpia dels contractes signats amb proveïdors, que per contracte "no pot ser accessible pel proveïdor de núvol".

Exercici 3 — Un incident de secret exposat

Un dilluns al matí, l'escaneig de secrets del repositori avisa: la clau sk_live_... de la passarel·la de pagament és en un commit de fa tres setmanes, en un fitxer de proves d'integració. El repositori és privat, amb accés per a les sis persones de l'equip tècnic i dos col·laboradors externs.

Escriu el pla de resposta ordenat per prioritat, i les mesures perquè no torni a passar.


Solucions

Solució 1

Ordre d'operacions, sense tall de servei:

# PAS 0 — La contrasenya actual esta compromesa. Es ROTA, no es reutilitza.
NUEVA=$(openssl rand -base64 32)

gcloud sql users set-password app_catalogo \
  --instance=alpinashop-pedidos --password="$NUEVA"

# PAS 1 — Crear el secret amb el valor NOU
gcloud secrets create db-password-catalogo \
  --replication-policy=user-managed --locations=europe-west1,europe-west4 \
  --labels=entorno=produccion,equipo=plataforma,aplicacion=catalogo
printf '%s' "$NUEVA" | gcloud secrets versions add db-password-catalogo --data-file=-
unset NUEVA

# PAS 2 — Permis, nomes a la identitat de l'aplicacio i nomes sobre aquest secret
gcloud secrets add-iam-policy-binding db-password-catalogo \
  --member="serviceAccount:[email protected]" \
  --role="roles/secretmanager.secretAccessor"

PAS 3 — Canvi al codi: l'aplicació passa a fer servir el mòdul secretos.py de l'apartat 4 i deixa de llegir os.environ["DB_PASSWORD"]. És important que l'arrencada falli de forma sorollosa si no pot llegir el secret, en lloc de continuar amb un valor buit.

# PAS 4 — Plantilla nova, SENSE contrasenya i amb el compte de servei correcte
gcloud compute instance-templates create tpl-catalogo-v4 \
  --service-account=sa-catalogo-web@alpinashop-prod.iam.gserviceaccount.com \
  --scopes=https://www.googleapis.com/auth/cloud-platform \
  --metadata-from-file=startup-script=startup-sin-secretos.sh \
  --tags=web-catalogo

# PAS 5 — Actualitzacio progressiva, sense tall (02-01 i 03-02)
gcloud compute instance-groups managed rolling-action start-update alpinashop-web-mig \
  --region=europe-west1 \
  --version=template=tpl-catalogo-v4 \
  --max-surge=2 --max-unavailable=0

# PAS 6 — Verificar i netejar
gcloud compute instances describe alpinashop-web-1 --zone=europe-west1-b \
  --format="value(metadata.items[startup-script])" | grep -i password || echo "Net"

gcloud compute instance-templates delete tpl-catalogo-v3 --quiet

Què fer amb la contrasenya vella. Va estar vuit mesos llegible per a qualsevol amb compute.viewer i va quedar a l'historial de Git. Es considera compromesa: rotar-la és obligatori i és el pas 0, no l'últim. Reescriure l'historial de Git és recomanable però secundari; el que anul·la el risc és que el valor ja no serveixi per a res. Addicionalment, convé revisar els registres d'accés a Cloud SQL del període per si hi hagués connexions des d'orígens inesperats.

Solució 2

  1. Imatges de producte → Google-managed, o CMEK si es busca coherència. No són dades personals ni confidencials: són fotos d'un catàleg públic. Google-managed és suficient. En el cas d'AlpinaShop es va aplicar CMEK per homogeneïtat de política i per poder demostrar control davant d'una auditoria, cosa que és un argument vàlid sempre que s'assumeixi el mode de fallada afegit.
  2. alpinashop-pedidos → CMEK, sens dubte. Conté dades personals subjectes a l'RGPD i fragments de dades de targeta. Aquí la capacitat de revocar i d'auditar l'ús de la clau és exactament el que es demana en una auditoria. Amb la salvetat operativa coneguda: exigeix crear una instància nova i migrar.
  3. Exportacions CSV → CMEK, la mateixa clau que les comandes. Són un extracte de les mateixes dades personals. Seria incoherent protegir l'origen i no la còpia; de fet, les exportacions solen ser el punt més feble de la cadena.
  4. Discos del MIG → Google-managed. Només contenen codi, que a més és a Artifact Registry i a Git. CMEK aquí només afegiria un mode de fallada —si la clau falla, les instàncies no arrenquen— sense protegir res que no sigui ja públic internament.
  5. Contractes amb la clàusula d'inaccessibilitat → EKM (EXTERNAL), o replantejar el requisit. És exactament el cas per al qual existeix External Key Manager: la clau viu fora de Google i sense ella Google no pot desxifrar. CSEK seria tècnicament possible a Cloud Storage, però la custòdia manual de la clau és massa fràgil per a un requisit contractual. L'alternativa honesta és discutir amb el proveïdor si el requisit se satisfà amb CMEK més registres d'accés, que és bastant més operable.

Solució 3

Prioritat 1 — Invalidar la credencial (minuts, no hores).

# 1) Al panell de la passarel·la de pagament: revocar sk_live_... ARA.
#    Mentre existeixi, el repositori, les seves copies i els portatils de vuit
#    persones la contenen. Reescriure l'historial NO la invalida.

# 2) Generar la clau nova i desar-la on ha de ser
printf '%s' "$CLAVE_NUEVA" | \
  gcloud secrets versions add api-key-pasarela-pago --data-file=-

# 3) Reinici progressiu perque els processos rellegeixin 'latest'
gcloud compute instance-groups managed rolling-action restart alpinashop-web-mig \
  --region=europe-west1 --max-unavailable=1

Prioritat 2 — Avaluar l'impacte. Revisar al panell de la passarel·la les operacions de les últimes tres setmanes buscant cobraments, reemborsaments o consultes des d'orígens no habituals. Si hi va haver ús indegut, hi ha obligacions de notificació que depenen del marc aplicable: això ho decideix el responsable de compliment normatiu, no l'equip tècnic.

Prioritat 3 — Netejar. Eliminar el secret de l'historial de Git (git filter-repo o equivalent), forçar l'actualització de totes les còpies de l'equip i comprovar que no ha quedat en artefactes derivats: imatges de contenidor a Artifact Registry, registres de Cloud Build, exportacions de registres.

Prioritat 4 — Que no torni a passar.

Mesura Com
Escaneig previ al commit git-secrets o gitleaks com a hook, més l'escaneig de secrets del proveïdor del repositori
Escaneig al pipeline Un pas de Cloud Build que falla si detecta patrons de credencial (06-01)
Secrets a les proves Les proves d'integració llegeixen de Secret Manager amb una credencial de proves, mai sk_live_
Claus separades per entorn La passarel·la ofereix claus de prova i de producció; en desenvolupament no hi ha d'haver una de producció
Revisió d'accessos Reavaluar si els dos col·laboradors externs necessiten accés al repositori complet
Rotació periòdica --rotation-period=90d amb avís per Pub/Sub, perquè rotar sigui rutina i no una emergència
Formació El fitxer de proves es va pujar sense mala intenció. La mesura més eficaç sol ser explicar a l'equip per què importa

Conclusió

La contrasenya d'app_catalogo ha sortit de l'startup-script. Saps per què aquell lloc era inacceptable —llegible amb compute.viewer, present a l'historial de Git, no rotable sense desplegar, duplicada per tot arreu i sense cap traçabilitat— i has construït l'alternativa completa: els secrets db-password-catalogo i api-key-pasarela-pago a Secret Manager, amb el seu model de secret contenidor i versions immutables, l'àlies latest, i els tres estats d'una versió.

Domines l'accés des de l'aplicació Flask amb la identitat sa-catalogo-web i sense ni una sola credencial al codi, amb memòria cau en memòria i la consciència del que aquesta memòria cau implica per a la rotació. Has concedit roles/secretmanager.secretAccessor a nivell de secret i mai de projecte, i saps que el registre de qui llegeix cada secret existeix però s'ha d'activar. Coneixes el procediment de rotació en cinc passos, amb la regla que evita les catàstrofes: deshabilitar sempre abans de destruir, i mai el mateix dia; i saps que Secret Manager no rota res, només avisa per Pub/Sub i l'automatització l'escrius tu. Has triat entre replicació automàtica i regional entenent que és irreversible i que la residència de les dades és una pregunta de compliment normatiu. I has vist les integracions que fan això encara més net: Cloud Run amb --set-secrets, amb fitxer muntat millor que variable d'entorn; el controlador CSI a GKE, que tanca l'advertiment de 02-05 sobre el base64 dels Secrets de Kubernetes; i availableSecrets a Cloud Build.

En xifratge, has entès el primer que cal entendre: tot està xifrat en repòs per defecte, així que la pregunta real és qui controla la clau. Coneixes la jerarquia DEK/KEK, que explica per què rotar és instantani, per què revocar inutilitza les dades a l'instant i per què CMEK és substituir la KEK i no xifrar tu res. Tens la taula de decisió entre Google-managed, CMEK i CSEK, amb criteris i no amb dogmes: CMEK quan necessitis revocar, auditar o complir; CSEK gairebé mai. Has creat el clauer alpinashop-keyring i les claus clave-catalogo i clave-pedidos amb rotació a 90 dies, sabent que clauers i claus no s'esborren mai i que la ubicació ha de ser compatible amb la del recurs. Has aplicat CMEK al bucket concedint permís a l'agent de servei —el pas que tothom oblida—, has descobert que només afecta els objectes nous, i has après la restricció que canvia els plans: CMEK a Cloud SQL només es pot establir en crear la instància. I tens clars els nivells HSM i EXTERNAL, amb el preu real de la sobirania: una dependència externa al camí crític de les teves dades.

Només falta un trajecte per protegir, i és el més visible de tots: el que va del navegador del client a alpinashop-lb-ip. Avui AlpinaShop se serveix per una adreça IP numèrica i sense certificat; ningú no compra una motxilla de 200 euros en un lloc amb un cadenat ratllat. A la lliçó següent, 03-07, Cloud DNS, certificats TLS i publicació segura de serveis, tanquem el mòdul: crearem la zona pública d'alpinashop.example, apuntarem el domini a la IP global, aprovisionarem un certificat gestionat per Google —amb els seus requisits de DNS i aquesta espera a PROVISIONING que desespera tothom—, ajustarem la política SSL i les capçaleres de seguretat de l'aplicació, i repassarem amb una llista de comprovació tot el que hem construït al mòdul 3.

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