Les cinc lliçons anteriors han afegit mesures de seguretat d'una en una: xarxa controlada, SSH amb claus, tallafocs, detecció d'intrusions, secrets xifrats i TLS. Cadascuna resolia un problema concret. Aquesta lliçó fa una cosa diferent: les converteix en una postura de seguretat, que és un conjunt coherent de decisions justificades per un model d'amenaces explícit, amb constància escrita de què està protegit, què s'ha decidit assumir i què queda fora de l'abast d'un administrador.

La diferència no és retòrica. Sense model d'amenaces, l'enfortiment degenera en cargo cult: s'apliquen receptes trobades en guies perquè «sonen segures», es trenquen coses sense entendre per què, i s'acaba amb un sistema fràgil i una falsa sensació de control. Amb model d'amenaces, cada mesura té un perquè i cada omissió és una decisió conscient.

A més tanques aquí el tercer incident pendent —els serveis que continuen fent servir la libssl vulnerable carregada a memòria— i saldes el deute que vas deixar a 05-01: PAM, el subsistema que decideix de debò com s'autentica algú en aquest sistema.

Advertiment previ. Dos dels apartats d'aquesta lliçó —PAM i els muntatges segurs— et poden deixar sense poder iniciar sessió al teu propi servidor si t'equivoques. En tots dos casos s'indica el procediment segur. No te'l saltis: el laboratori existeix precisament perquè aprenguis això on equivocar-se no costa res.

Contingut

  1. Els cinc principis que ordenen tot l'anterior
  2. El model d'amenaces de Tramontana
  3. Reducció de la superfície d'atac
  4. Enfortiment de comptes i política de contrasenyes
  5. PAM: com s'autentica realment aquest sistema
  6. Control d'accés obligatori amb AppArmor
  7. Enfortiment del nucli amb sysctl
  8. Muntatges segurs
  9. Gestió de vulnerabilitats i tancament de l'incident d'openssl
  10. Enfortiment de systemd revisitat
  11. Còpies com a últim control davant del ransomware
  12. Llista de comprovació d'enfortiment i manteniment de la postura

Els cinc principis que ordenen tot l'anterior

Tot el que has fet al mòdul respon, sense que ho haguem dit, a cinc principis. Anomenar-los permet aplicar-los a situacions noves en lloc de repetir receptes:

Principi Què exigeix On l'has aplicat ja
Mínim privilegi Cada procés i cada persona amb els permisos justos, ni un més svc-tramontana sense shell, sudoers amb ordres concretes, CapabilityBoundingSet= buit
Superfície d'atac mínima El que no existeix no es pot atacar Servidor sense escriptori, port 8080 tancat a l'exterior, política de llista blanca a ufw
Defensa en profunditat Diverses capes independents; que una falli no ha de bastar Tallafocs + SSH enfortit + AIDE + auditd + secrets xifrats
Fallada segura Quan alguna cosa es trenca, ha de quedar tancada, no oberta policy drop per defecte a nftables, set -euo pipefail als scripts
Segur per defecte L'estat inicial ha de ser el restrictiu umask 027, permisos 600 als secrets, PasswordAuthentication no

El més difícil d'aplicar a la pràctica és el segon, perquè exigeix treure coses, i treure coses fa por. El més oblidat és el quart: molta configuració «segura» falla obrint-se, i això és pitjor que no tenir-la, perquè ningú no se n'assabentarà.

El model d'amenaces de Tramontana

Un model d'amenaces respon a tres preguntes. Mitja pàgina és suficient, i és la mitja pàgina que fa que tota la resta tingui sentit.

Què protegim? Dues coses, en aquest ordre:

  1. Les dades personals dels hostes (noms, dates d'estada, imports a reserves.csv i a la base de dades). El seu compromís és un dany a tercers, té conseqüències legals sota el RGPD i és irreversible: un cop filtrades, no es poden «recuperar».
  2. La disponibilitat del servei de reserves. La seva interrupció té un cost econòmic directe i acotat en el temps.

De qui? Quatre actors realistes, ordenats per probabilitat:

Amenaça Probabilitat Capacitat Controls que li oposem
Escaneig automatitzat d'Internet Constant Baixa: explota el conegut i sense pedaç Tallafocs, fail2ban, actualitzacions de seguretat, SSH sense contrasenya
Credencial filtrada o reutilitzada Mitjana Alta: entra com a usuari legítim Claus en comptes de contrasenyes, secrets xifrats, rotació, auditd
Error propi de configuració Alta Variable, de vegades total --dry-run, còpies abans d'editar, netplan try, mount -a, AIDE
Abús d'accés legítim Baixa Alta dins del seu àmbit Mínim privilegi a sudoers, ACL, auditoria d'accessos

Fixa't en la tercera fila. L'actor més probable ets tu, i aquella constatació explica per què mitja dotzena de convencions del curs —copiar abans d'editar, verificar amb diff -u, provar en sec, netplan try amb reversió automàtica, mount -a abans de reiniciar— són controls de seguretat de ple dret, i no manies d'estil.

Què assumim fora d'abast? Dir-ho és tan important com l'anterior, perquè delimita què no s'està protegint:

  • Atacant amb recursos estatals o cadena de subministrament compromesa. Està fora de l'abast de qualsevol mesura que un administrador pugui prendre en un servidor d'una petita empresa.
  • Accés físic a la màquina. El xifratge LUKS mitiga el robatori del disc, però un atacant amb accés físic prolongat i la màquina encesa té el sistema.
  • Vulnerabilitat a la mateixa aplicació (injecció SQL, lògica de negoci). És responsabilitat del desenvolupament, no de l'administració, i l'enfortiment del sistema només limita el dany posterior.
  • Compromís del proveïdor del núvol o de l'hipervisor.

I ara el criteri de fons: amb aquest model, invertir en un NIDS aporta poc (ja ho vas raonar a 06-04) i invertir en detecció de canvis i en control de l'autenticació aporta molt. Això és prendre decisions, no seguir una llista.

Reducció de la superfície d'atac

El que no existeix no es pot atacar, no necessita pedaços i no apareix en cap CVE. És el principi amb millor relació entre esforç i resultat.

Què escolta

$ sudo ss -tulpn
Netid State  Local Address:Port  Peer Address:Port Process
udp   UNCONN 127.0.0.54:53       0.0.0.0:*         users:(("systemd-resolve",pid=712,fd=17))
udp   UNCONN 127.0.0.53%lo:53    0.0.0.0:*         users:(("systemd-resolve",pid=712,fd=13))
tcp   LISTEN 127.0.0.53%lo:53    0.0.0.0:*         users:(("systemd-resolve",pid=712,fd=14))
tcp   LISTEN 0.0.0.0:22          0.0.0.0:*         users:(("sshd",pid=894,fd=3))
tcp   LISTEN 0.0.0.0:8080        0.0.0.0:*         users:(("tramontana",pid=4102,fd=6))
tcp   LISTEN 10.0.2.15:5432      0.0.0.0:*         users:(("postgres",pid=1105,fd=5))

Llegeix cada línia preguntant «ha de ser aquí, i en aquella adreça?»:

  • systemd-resolve a 127.0.0.53 i 127.0.0.54: local, correcte.
  • sshd a 0.0.0.0:22: necessari, i ja enfortit.
  • tramontana a 0.0.0.0:8080: escolta a totes les interfícies. El tallafocs el bloqueja des de fora, però això és defensa en una sola capa. El correcte és que l'aplicació escolti només a 127.0.0.1, perquè quan arribi el servidor intermediari invers de 08-01 serà qui li parlarà, i així el tallafocs passa a ser la segona línia en lloc de l'única.
  • postgres a 10.0.2.15:5432: ja restringit a la interfície interna. Correcte.

El primer arreglament, doncs:

$ sudo cp -p /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
$ sudo chattr -i /etc/tramontana/app.conf
$ echo 'escolta=127.0.0.1' | sudo tee -a /etc/tramontana/app.conf
$ sudo chattr +i /etc/tramontana/app.conf
$ sudo systemctl restart tramontana.service
$ sudo ss -tulpn | grep 8080
tcp   LISTEN 127.0.0.1:8080  0.0.0.0:*  users:(("tramontana",pid=4318,fd=6))

Això és defensa en profunditat aplicada: ara calen dues fallades —una regla de tallafocs mal posada i una escolta massa àmplia— per exposar el 8080. Abans en bastava una.

Què està habilitat

$ systemctl list-unit-files --state=enabled --type=service --no-pager
UNIT FILE                    STATE   PRESET
apparmor.service             enabled enabled
auditd.service               enabled enabled
cron.service                 enabled enabled
fail2ban.service             enabled enabled
ModemManager.service         enabled enabled
multipathd.service           enabled enabled
postgresql.service           enabled enabled
ssh.service                  enabled enabled
tramontana.service           enabled enabled
unattended-upgrades.service  enabled enabled

ModemManager gestiona mòdems i banda ampla mòbil. En una màquina virtual de servidor no té cap sentit. I aquí convé entendre la diferència entre les dues maneres d'apagar una cosa, perquè no són equivalents:

Operació Què fa Es pot reactivar sola
disable Esborra els enllaços de [Install]; no arrenca a l'inici Sí: si una altra unitat el declara a Wants=
mask Enllaça la unitat a /dev/null; és inarrencable No: ni a mà ni per dependència
$ sudo systemctl disable --now ModemManager.service
$ sudo systemctl mask ModemManager.service
Created symlink /etc/systemd/system/ModemManager.service → /dev/null.

mask per al que no ha d'arrencar mai; disable per al que podries voler arrencar a mà algun dia. I el pas següent, quan n'estiguis segur, és desinstal·lar el paquet: un binari que no és al disc no té vulnerabilitats.

$ sudo apt purge modemmanager
$ sudo apt autoremove --purge

Sobre l'escriptori, per tancar l'apartat: un entorn gràfic afegeix centenars de paquets, un servidor X o Wayland, un gestor de sessions i un navegador — cadascun amb la seva superfície i el seu calendari de pedaços. Que srv-tramontana no en tingui no és austeritat, és la reducció de superfície més gran que es pot fer en un servidor, i per això l'instal·lador d'Ubuntu Server no l'ofereix per defecte.

Enfortiment de comptes i política de contrasenyes

Tres auditories que convé tenir guardades en un script, perquè detecten errors greus i són d'una línia cadascuna:

# 1. Comptes amb UID 0: nomes n hi ha d haver un, root
$ awk -F: '($3 == 0) {print $1}' /etc/passwd
root

# 2. Comptes sense contrasenya (segon camp buit a shadow): greu
$ sudo awk -F: '($2 == "") {print $1 " NO TE CONTRASENYA"}' /etc/shadow

# 3. Comptes de sistema (UID < 1000) amb shell d inici de sessio valida
$ awk -F: '($3 < 1000 && $7 !~ /(nologin|false|sync)$/) {print $1, $3, $7}' /etc/passwd
root 0 /bin/bash
sync 4 /bin/sync

Els tres resultats són correctes: un únic UID 0, cap compte sense contrasenya, i els dos únics comptes de sistema amb shell són root —necessari per a la recuperació— i sync, que és un cas històric inofensiu. Que svc-tramontana no aparegui a la tercera llista és la confirmació que el -s /usr/sbin/nologin de 05-01 va fer la seva feina.

La política per defecte viu a /etc/login.defs, i afecta els comptes que es creïn a partir d'ara:

# /etc/login.defs (valors modificats)
PASS_MAX_DAYS   365     # caducitat maxima
PASS_MIN_DAYS   1       # evita que es canvii diverses vegades seguides per tornar a l anterior
PASS_WARN_AGE   14      # dies d avis abans de caducar
UMASK           027     # coherent amb la convencio del curs
SHA_CRYPT_MIN_ROUNDS 5000
ENCRYPT_METHOD  YESCRYPT

PASS_MIN_DAYS 1 és menys obvi que els altres: sense ell, algú a qui s'exigeix canviar la contrasenya la pot canviar cinc vegades seguides per esgotar l'historial i tornar a l'original. És un truc vell i continua funcionant on no es posa.

I per als comptes ja existents, chage:

$ sudo chage -M 365 -m 1 -W 14 luis
$ sudo chage -l luis
Last password change                                    : ago 04, 2026
Password expires                                        : ago 04, 2027
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change           : 1
Maximum number of days between password change           : 365
Number of days of warning before password expires        : 14

# El compte del becari te data de fi de practiques: caduca automaticament
$ sudo chage -E 2026-12-31 becari
$ sudo chage -l becari | grep 'Account expires'
Account expires                                         : dic 31, 2026

chage -E mereix atenció: és la manera correcta de gestionar un compte temporal. Caduca sol en la data prevista, sense dependre que algú es recordi de donar-lo de baixa — i les baixes de personal oblidades són una de les vies de compromís més comunes que existeixen.

PAM: com s'autentica realment aquest sistema

A 05-01 vam ajornar PAM. És el moment, perquè és la peça que decideix de debò qui entra en aquest sistema i amb quins requisits.

PAM (Pluggable Authentication Modules) és una capa d'indirecció: els programes que necessiten autenticar —login, sshd, sudo, su, passwd— no implementen la lògica, sinó que pregunten a PAM. Això permet canviar la política (exigir contrasenyes fortes, bloquejar després de fallades, afegir un segon factor) sense modificar ni recompilar cap programa. És la raó per la qual existeix.

L'estructura

Cada servei té el seu fitxer a /etc/pam.d/, i hi ha fitxers comuns que els altres inclouen:

$ ls /etc/pam.d/ | head -12
common-account
common-auth
common-password
common-session
cron
login
passwd
sshd
su
sudo

Cada línia té tres parts: tipus, control i mòdul.

Els quatre tipus, que són quatre preguntes diferents i responen en moments diferents:

Tipus Pregunta que respon
auth És qui diu que és? (contrasenya, clau, segon factor)
account Se li permet entrar ara? (compte caducat, horari, origen)
password És acceptable la contrasenya nova que vol posar?
session Què cal preparar i netejar al voltant de la sessió (límits, registre, home)

Confondre auth amb account és el malentès més habitual: una contrasenya correcta en un compte caducat passa auth i falla account.

Els controls determinen què passa segons el resultat del mòdul:

Control Si el mòdul falla Si té èxit
required Falla el conjunt, però es continuen executant els altres Continua
requisite Falla i s'atura allà mateix Continua
sufficient S'ignora Èxit immediat, si res required anterior no ha fallat
optional S'ignora (llevat que sigui l'únic) Continua

required davant de requisite té una raó de ser que no és evident: required continua executant la pila per no revelar en quin pas va fallar. Si un atacant pogués distingir «usuari inexistent» de «contrasenya incorrecta» pel temps de resposta o pel missatge, tindria un oracle per enumerar usuaris.

La sintaxi moderna, més precisa, fa servir claudàtors: [success=ok default=die] permet especificar l'acció per a cada codi de retorn. És el que veuràs als fitxers d'Ubuntu.

El procediment per no deixar-se fora

Abans de tocar ni una sola línia. Un error de sintaxi a common-auth pot impedir tot inici de sessió, inclosa la consola local, i aleshores l'única sortida és el mode de recuperació:

  1. Deixa una sessió de root oberta en un altre terminal, i no la tanquis fins a haver acabat:
    $ sudo -i
    # (no tancar aquesta sessio)
    
  2. Copia el fitxer abans d'editar-lo, segons la convenció del curs:
    $ sudo cp -p /etc/pam.d/common-password /etc/pam.d/common-password.bak-$(date +%F)
    
  3. Prova en un tercer terminal, mai en el que tens la sessió de root.
  4. Tingues a mà la consola de la VM i sàpigues com entrar en mode de recuperació (es veu a fons a 07-01).

És exactament la mateixa disciplina de netplan try i de la segona sessió SSH: no tanquis mai la porta per la qual estàs entrant.

pam_pwquality: exigir contrasenyes decents

$ sudo apt install libpam-pwquality
# /etc/security/pwquality.conf
minlen = 12          # longitud minima
minclass = 3         # almenys 3 de: minuscules, majuscules, digits, simbols
maxrepeat = 3        # no mes de 3 caracters iguals consecutius
dictcheck = 1        # rebutja paraules de diccionari
usercheck = 1        # rebutja contrasenyes que continguin el nom d usuari
enforce_for_root = 1 # tambe a root: sense aixo, root queda exempt
retry = 3
# /etc/pam.d/common-password (linia modificada)
password  requisite  pam_pwquality.so retry=3
$ passwd luis
New password: tramontana2026
Bad password: it is based on a dictionary word.
New password: Kj8-mQ2vX9pL
passwd: password updated successfully

Dos comentaris de fons. El primer: minlen = 12 amb minclass = 3 és més raonable que les polítiques de «8 caràcters amb majúscula, número i símbol» que produeixen Passw0rd! — la longitud aporta molt més que la complexitat forçada, i així ho recullen les guies actuals (NIST SP 800-63B). El segon: enforce_for_root = 1 és fàcil d'oblidar, i sense ell el compte més important del sistema és l'únic exempt de la política.

pam_faillock: bloquejar després d'intents fallits

Complementa fail2ban, que actua sobre la IP; faillock actua sobre el compte, així que cobreix també els intents des de la consola o des d'IP diferents.

# /etc/security/faillock.conf
deny = 5              # bloqueja despres de 5 fallades
fail_interval = 900   # que passin dins de 15 minuts
unlock_time = 600     # desbloqueig automatic als 10 minuts
even_deny_root        # tambe a root
root_unlock_time = 60 # pero root es desbloqueja en 1 minut: evita el bloqueig total
audit                 # registra l intent
silent                # no revela a l atacant que el compte esta bloquejat
# /etc/pam.d/common-auth
auth  required  pam_faillock.so preauth
auth  [success=1 default=ignore]  pam_unix.so nullok
auth  [default=die]  pam_faillock.so authfail
auth  sufficient  pam_faillock.so authsucc
auth  requisite  pam_deny.so
auth  required  pam_permit.so

L'ordre importa i no és intuïtiu: preauth comprova si el compte ja està bloquejat abans de demanar credencials, authfail compta la fallada, i authsucc neteja el comptador després d'un accés correcte. Que falti authsucc produeix un bloqueig lent però inevitable, perquè el comptador no es reinicia mai.

I la combinació d'even_deny_root amb root_unlock_time = 60 és deliberada: protegir root sense crear la possibilitat d'un bloqueig total del sistema.

$ faillock --user luis
luis:
When                Type  Source                                           Valid
2026-08-18 14:22:11 RHOST 203.0.113.44                                         V
2026-08-18 14:22:14 RHOST 203.0.113.44                                         V

$ sudo faillock --user luis --reset     # desbloquejar a ma

pam_limits: límits de recursos

# /etc/security/limits.conf
# domini      tipus   element   valor
*            hard    nproc     4096      # frena una bomba de processos
*            hard    core      0         # no generar abocaments: poden contenir secrets
svc-tramontana soft  nofile    8192
svc-tramontana hard  nofile    16384

Dues d'aquestes línies són mesures de seguretat més que de rendiment. nproc limita el dany d'un bucle que crea processos sense control —accidental o no—. I core 0 evita els abocaments de memòria, que contenen tot el que el procés tenia a la RAM en aquell moment: inclosa la contrasenya que tanta feina t'ha costat xifrar a 06-05.

Compte amb l'abast: pam_limits s'aplica a sessions que passen per PAM. Per a un servei de systemd, els límits es posen a la unitat (LimitNOFILE, LimitNPROC), com vas veure a 05-07.

Segon factor, breument

libpam-google-authenticator afegeix un codi TOTP a l'autenticació SSH. És eficaç contra el robatori de credencials, i en un servidor amb accés per clau el benefici marginal és menor que en un amb contrasenyes. Si s'implanta, hi ha dues coses que no es poden oblidar: guardar els codis de recuperació fora del servidor, i decidir què passa si el dispositiu TOTP es perd. Sense això, és una manera elegant de quedar-se fora.

Tancar la troballa: l'excepció de PasswordAuthentication

A 06-04 els registres van mostrar que luis va entrar amb password, tot i que a 06-02 ho vas deshabilitar. Hora de localitzar-ho, començant per la font autoritzada:

$ sudo sshd -T | grep -i -E 'passwordauth|kbdinteractive'
passwordauthentication no
kbdinteractiveauthentication no

La configuració global és correcta. Però sshd -T sense arguments mostra la configuració global; els blocs Match són condicionals i no hi apareixen. Cal preguntar pel cas concret:

$ sudo grep -rn -A3 'Match' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
/etc/ssh/sshd_config.d/60-tramontana.conf:12:Match User luis
/etc/ssh/sshd_config.d/60-tramontana.conf-13-    PasswordAuthentication yes

$ sudo sshd -T -C user=luis,host=portatil-luis,addr=10.0.2.44 | grep -i passwordauth
passwordauthentication yes

Aquí està. Un bloc Match User luis amb l'excepció, posada «temporalment» mentre el Luis generava la seva clau, i mai retirada. És l'actor més probable del model d'amenaces —l'error propi— en la seva forma més característica.

$ sudo cp -p /etc/ssh/sshd_config.d/60-tramontana.conf{,.bak-$(date +%F)}
$ sudo sed -i '/^Match User luis$/,+1d' /etc/ssh/sshd_config.d/60-tramontana.conf
$ sudo sshd -t && echo "sintaxi correcta"
sintaxi correcta
$ sudo sshd -T -C user=luis,host=portatil-luis,addr=10.0.2.44 | grep -i passwordauth
passwordauthentication no
$ sudo systemctl reload ssh

Fixa't en sshd -T -C: l'opció -C avalua la configuració per a un context concret d'usuari, amfitrió i adreça, resolent els blocs Match. És l'única manera fiable de saber quina configuració s'aplica a algú, i mereix un lloc a la teva memòria: la meitat dels problemes desconcertants d'SSH són un Match oblidat.

Abans de recarregar, la disciplina de sempre: sshd -t valida, la segona sessió està oberta, i es fa servir reload en lloc de restart.

Control d'accés obligatori amb AppArmor

Els permisos que coneixes des de 02-07 són DAC (Discretionary Access Control): discrecionals perquè el propietari d'un recurs decideix qui hi accedeix. Tenen un límit estructural: un procés pot fer tot el que el seu usuari pot fer. Si l'aplicació de Tramontana es veu compromesa, l'atacant pot llegir qualsevol cosa llegible per svc-tramontana i escriure a qualsevol lloc on aquell usuari escrigui.

MAC (Mandatory Access Control) afegeix una capa que el propietari no pot relaxar: una política, definida per l'administrador i aplicada pel nucli, que diu què pot fer aquest programa, amb independència de qui l'executi.

DAC (permisos Unix) MAC (AppArmor / SELinux)
Qui decideix El propietari del fitxer L'administrador, a la política
Unitat de control Usuari i grup Programa (perfil)
Ho pot relaxar el procés Sí, dins del que és del seu usuari No
Què conté Un compromís del procés Un compromís del procés i del seu usuari
$ sudo aa-status
apparmor module is loaded.
32 profiles are loaded.
28 profiles are in enforce mode.
   /usr/bin/man
   /usr/sbin/sshd
   ...
2 profiles are in complain mode.
2 processes have profiles defined.

Els dos modes són la clau del mètode de treball:

  • complain (mode de queixa): no bloqueja, només registra el que hauria bloquejat. És el mode d'aprenentatge.
  • enforce (mode d'aplicació): bloqueja. És el mode de producció.

I el procediment professional és sempre el mateix: escriure el perfil, posar-lo en complain, exercitar l'aplicació de debò, recollir les denegacions i ampliar el perfil, i només aleshores passar a enforce. Començar en enforce significa trencar el servei i descobrir els accessos que falten de la pitjor manera.

Un perfil per a l'aplicació de Tramontana

$ sudo apt install apparmor-utils
# /etc/apparmor.d/opt.tramontana.app.tramontana
abi <abi/4.0>,
include <tunables/global>

/opt/tramontana/releases/*/tramontana {
  include <abstractions/base>
  include <abstractions/nameservice>
  include <abstractions/openssl>

  # El binari propi: llegir i executar
  /opt/tramontana/releases/*/tramontana        mr,
  /opt/tramontana/releases/*/**                r,
  /opt/tramontana/app/**                       r,

  # Configuracio: nomes lectura. Encara que el proces es vegi compromes,
  # no pot modificar la seva propia configuracio.
  /etc/tramontana/app.conf                     r,

  # Credencial lliurada per systemd (tmpfs privat del servei)
  /run/credentials/tramontana.service/*        r,

  # Registres propis: crear i afegir, mai sobreescriure ni esborrar
  /var/log/tramontana/                         r,
  /var/log/tramontana/*.log                    rw,

  # Dades pujades pels hostes: lectura i escriptura
  /opt/tramontana/shared/uploads/              r,
  /opt/tramontana/shared/uploads/**            rwk,

  # Xarxa: nomes TCP sobre IPv4/IPv6. No unix cru, no paquets crus.
  network inet stream,
  network inet6 stream,

  # Res mes no esta permes: AppArmor denega per defecte.
  # En particular, queden DENEGATS de forma implicita:
  #   /etc/shadow, /home/**, /srv/tramontana/backups/**,
  #   /root/**, l execucio de /bin/sh i la carrega de moduls.
}

Aquell últim comentari és el que fa valuós el perfil. Un atacant que executi codi en el context de l'aplicació no pot llançar una shell, no pot llegir les còpies, no pot tocar els directoris personals i no pot modificar la seva pròpia configuració — coses que el DAC sí que li permetria fer en part, i que són precisament els primers passos de qualsevol postexplotació.

# 1. Carregar en mode aprenentatge
$ sudo apparmor_parser -r /etc/apparmor.d/opt.tramontana.app.tramontana
$ sudo aa-complain /opt/tramontana/releases/*/tramontana

# 2. Exercitar l aplicacio de debo: peticions, pujada de fitxer, desplegament
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/cases
200
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0

# 3. Recollir les denegacions registrades
$ sudo journalctl -k --since "10 min ago" | grep 'apparmor="ALLOWED"'
kernel: audit: type=1400 apparmor="ALLOWED" operation="open"
  profile="/opt/tramontana/releases/*/tramontana" name="/opt/tramontana/shared/plantilles/factura.html"
  pid=4318 comm="tramontana" requested_mask="r" denied_mask="r"

En mode complain la marca és ALLOWED amb un denied_mask: significa «això ho hauria bloquejat». Aquí ha aparegut un accés legítim que el perfil no contemplava —les plantilles—, així que s'afegeix i es repeteix el cicle:

  /opt/tramontana/shared/plantilles/**         r,
# 4. Ampliar de forma assistida si es prefereix, i passar a enforce
$ sudo aa-logprof
$ sudo aa-enforce /opt/tramontana/releases/*/tramontana
Setting /opt/tramontana/releases/*/tramontana to enforce mode.
$ sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0

A partir d'aquí, una denegació real apareix com a apparmor="DENIED", i és el primer que cal mirar quan el servei comença a fallar després d'un desplegament que canvia rutes:

$ sudo journalctl -k -f | grep apparmor

SELinux, per a qui vagi a RHEL:

AppArmor SELinux
Distribucions Ubuntu, Debian, SUSE RHEL, Fedora, CentOS Stream
Identifica objectes per Ruta del fitxer Etiqueta a l'inode
Corba d'aprenentatge Suau; perfils llegibles Pronunciada; més potent
Diagnòstic journalctl + aa-logprof ausearch + audit2allow, restorecon
Mode permissiu Per perfil (complain) Global o per domini

La diferència per ruta davant d'etiqueta té una conseqüència pràctica: a SELinux, moure un fitxer pot canviar-ne el context i trencar l'accés (d'aquí restorecon); a AppArmor, un enllaç simbòlic o un bind mount pot saltar-se una regla basada en ruta. Cada model té el seu punt feble.

Enfortiment del nucli amb sysctl

A 06-03 vas aplicar els sysctl de xarxa. Aquests són els del mateix nucli, i són només de seguretat: els de rendiment es veuen a 07-03.

# /etc/sysctl.d/60-enfortiment.conf

# Nomes root pot llegir la memoria intermedia del nucli. Evita filtrar adreces
# de memoria i detalls del maquinari utils per construir un exploit.
kernel.dmesg_restrict = 1

# Oculta els punters del nucli a /proc i als registres.
kernel.kptr_restrict = 2

# ASLR complet: aleatoritza l espai d adreces, inclos el munt.
# 2 es el valor per defecte a Ubuntu; es declara perque sigui explicit.
kernel.randomize_va_space = 2

# Impedeix crear enllacos durs a fitxers dels quals no ets propietari i
# seguir enllacos simbolics aliens en directoris amb sticky bit.
# Tanca tota una familia d atacs de cursa a /tmp.
fs.protected_hardlinks = 1
fs.protected_symlinks = 1

# La mateixa proteccio per a FIFO i fitxers regulars en directoris
# escribibles per tothom.
fs.protected_fifos = 2
fs.protected_regular = 2

# Nomes root pot fer servir BPF. Redueix molt la superficie del subsistema.
kernel.unprivileged_bpf_disabled = 1

# Un proces nomes pot depurar els seus descendents directes.
# Impedeix que un proces compromes llegeixi la memoria d un altre del mateix
# usuari (i amb ella els secrets que aquell altre tingui carregats).
kernel.yama.ptrace_scope = 1

# No permetre a usuaris sense privilegis crear espais de noms d usuari.
# ATENCIO: trenca els contenidors sense privilegis. Comentat perque a
# 07-05 el necessitaras; descomentar nomes en servidors sense contenidors.
#kernel.unprivileged_userns_clone = 0
$ sudo sysctl --system 2>&1 | grep -A9 '60-enfortiment'
* Applying /etc/sysctl.d/60-enfortiment.conf ...
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.randomize_va_space = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
kernel.unprivileged_bpf_disabled = 1
kernel.yama.ptrace_scope = 1

$ sysctl kernel.yama.ptrace_scope
kernel.yama.ptrace_scope = 1

Dos avisos que has d'anotar, perquè són les dues maneres que això et mossegui més endavant:

  • kernel.yama.ptrace_scope = 1 pot trencar depuradors. strace o gdb sobre un procés que ja està en marxa i no és fill teu deixarà de funcionar sense sudo. A 07-02 faràs servir exactament aquelles eines; recorda que aquest paràmetre és l'explicació si et dona Operation not permitted.
  • kernel.unprivileged_userns_clone = 0 trenca contenidors sense privilegis, i a 07-05 muntaràs Docker. Per això queda comentat amb la raó escrita al costat. Un paràmetre d'enfortiment amb una nota explicant per què no està activat és documentació de qualitat; esborrar-lo i oblidar el motiu no ho és.

I el criteri general: cada línia d'aquest fitxer porta el seu comentari. Un sysctl.d amb quinze valors sense explicació és impossible de mantenir, perquè ningú —tu inclòs, d'aquí a sis mesos— no sabrà si se'n pot treure algun.

Muntatges segurs

Tres opcions de muntatge que limiten el dany als directoris on qualsevol pot escriure:

Opció Efecte
noexec No s'hi pot executar res des d'allà
nosuid S'ignoren els bits SUID/SGID
nodev S'ignoren els fitxers de dispositiu

El cas d'ús és directe: un atacant que aconsegueix escriure un fitxer sol necessitar executar-lo, i /tmp és el lloc on gairebé sempre pot escriure. Amb noexec, aquell pas falla.

I aquí és on la lliçó insisteix en una cosa que les guies d'enfortiment solen ometre: noexec a /tmp trenca coses reals. Diversos instal·ladors extreuen a /tmp i executen des d'allà; alguns gestors de paquets de llenguatges també. Cal comprovar-ho abans:

# 1. Comprovar l impacte en calent, sense tocar fstab
$ sudo mount -o remount,noexec,nosuid,nodev /tmp

# 2. Exercitar el que es podria trencar
$ sudo apt install --reinstall -y tree >/dev/null && echo "apt: correcte"
apt: correcte
$ ~/scripts/desplegar.sh --dry-run 3.2.1 && echo "desplegament: correcte"
desplegament: correcte
$ sudo needrestart -r l >/dev/null && echo "needrestart: correcte"
needrestart: correcte

# 3. I comprovar que la restriccio realment actua
$ printf '#!/bin/sh\necho hola\n' > /tmp/prova.sh && chmod +x /tmp/prova.sh
$ /tmp/prova.sh
bash: /tmp/prova.sh: Permission denied

Aquell últim bloc és la verificació positiva: no n'hi ha prou que res no s'hagi trencat, cal comprovar que la mesura fa alguna cosa. Amb les dues comprovacions fetes, es fa persistent:

# /etc/fstab
UUID=3f8a...  /tmp      ext4  defaults,noatime,noexec,nosuid,nodev  0  2
/tmp          /var/tmp  none  bind,noexec,nosuid,nodev              0  0
tmpfs         /dev/shm  tmpfs defaults,noexec,nosuid,nodev          0  0
$ sudo systemctl daemon-reload
$ sudo mount -a && echo "fstab correcte"
fstab correcte
$ findmnt -o TARGET,OPTIONS /tmp /var/tmp /dev/shm
TARGET     OPTIONS
/tmp       rw,noexec,nosuid,nodev,relatime
/var/tmp   rw,noexec,nosuid,nodev,relatime
/dev/shm   rw,noexec,nosuid,nodev

mount -a abans de reiniciar continua sent obligatori, pel que ja saps des de 05-04: un fstab mal escrit impedeix arrencar el servidor.

Gestió de vulnerabilitats i tancament de l'incident d'openssl

Llegir un CVE sense enganyar-se

Un CVE és l'identificador únic d'una vulnerabilitat concreta. El seu CVSS és una puntuació de 0 a 10 acompanyada d'un vector que descriu com s'explota:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H  →  9.8 CRITICA
        │    │    │    │    │    │  └─ Confidencialitat/Integritat/Disponibilitat: alta
        │    │    │    │    │    └─ Abast: sense canvi
        │    │    │    │    └─ Interaccio de l'usuari: cap
        │    │    │    └─ Privilegis requerits: cap
        │    │    └─ Complexitat de l'atac: baixa
        │    └─ Vector d'atac: xarxa

I el punt que cal interioritzar: la puntuació base no és el risc real per a tu. Un 9.8 en un component que no tens instal·lat, o que no està exposat, o que ja està mitigat per una altra capa, és menys urgent que un 6.5 al servei que dona la cara a Internet. El vector és més informatiu que el número: AV:N (explotable per xarxa) sense privilegis ni interacció de l'usuari és el que obliga a actuar avui.

$ apt list --upgradable
$ pro security-status
1543 packages installed:
     1489 packages from Ubuntu Main/Restricted repository
       54 packages from Ubuntu Universe/Multiverse repository
Ubuntu Pro is not attached. Ubuntu Pro would provide security updates for
54 packages until 2034.

$ sudo apt install debsecan && debsecan --suite noble --format summary | head -5

El tancament de l'incident

El 18 d'agost a les 06:12, unattended-upgrades va actualitzar openssl i libssl3t64. El paquet està actualitzat al disc, però els processos que van arrencar abans continuen amb la biblioteca antiga carregada a memòria. És una vulnerabilitat amb paperassa: l'informe diu que està apedaçada i el servei continua exposat.

$ grep -E 'openssl|libssl' /var/log/apt/history.log | tail -2
Start-Date: 2026-08-18  06:12:03
Upgrade: libssl3t64:amd64 (3.0.13-0ubuntu3.4, 3.0.13-0ubuntu3.5), openssl:amd64 (3.0.13-0ubuntu3.4, 3.0.13-0ubuntu3.5)

# La prova directa: processos amb la biblioteca ANTIGA (marcada DEL: esborrada
# del disc pero encara mapejada a memoria)
$ sudo lsof 2>/dev/null | grep -E 'DEL.*libssl'
postgres  1105 postgres  DEL  REG  253,0  /usr/lib/x86_64-linux-gnu/libssl.so.3
sshd       894 root      DEL  REG  253,0  /usr/lib/x86_64-linux-gnu/libssl.so.3

$ sudo apt install needrestart
$ sudo needrestart -r l
Scanning processes...
Scanning candidates...
Scanning linux images...

Services to be restarted:
 systemctl restart [email protected]
 systemctl restart ssh.service

Service restarts being deferred:
 (none)

No containers need to be restarted.
No user sessions are running outdated binaries.
No VM guests are running outdated hypervisor (qemu) binaries on this host.

Dos serveis. L'ordre importa, i per dues raons diferents:

# 1. La base de dades primer, i amb l aplicacio preparada per reconnectar.
#    Reiniciar postgresql talla les connexions obertes de tramontana.service.
$ sudo systemctl restart [email protected]
$ sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0

# 2. SSH al final, amb RELOAD i no restart, i amb la segona sessio oberta.
#    reload no talla les sessions existents; restart tampoc no hauria de
#    fer-ho, pero reload es l operacio correcta i no hi ha rao per arriscar-se.
$ sudo sshd -t && sudo systemctl reload ssh

Un matís important sobre ssh: reload recarrega la configuració, però no substitueix la biblioteca carregada al procés mestre. Per a això cal reiniciar el dimoni — que no talla les sessions ja establertes, perquè cadascuna viu en un procés fill propi. Amb la segona sessió oberta com a xarxa de seguretat:

$ sudo systemctl restart ssh
$ sudo systemctl is-active ssh
active
# Verificar des de la segona sessio que es pot obrir una tercera ABANS de tancar res

I la verificació de tancament:

$ sudo lsof 2>/dev/null | grep -c -E 'DEL.*libssl'
0
$ sudo needrestart -r l
No services need to be restarted.
$ sudo ss -tulpn | grep -E ':22|:5432|:8080'
tcp LISTEN 0.0.0.0:22       users:(("sshd",pid=8841,fd=3))
tcp LISTEN 10.0.2.15:5432   users:(("postgres",pid=8902,fd=5))
tcp LISTEN 127.0.0.1:8080   users:(("tramontana",pid=8977,fd=6))

Aquell 0 és el tancament. El segon incident queda tancat, i amb ell els tres.

Perquè no torni a passar, needrestart es configura en mode llista i s'integra a la revisió:

# /etc/needrestart/needrestart.conf
$nrconf{restart} = 'l';       # nomes llistar, mai reiniciar sol
$nrconf{kernelhints} = 1;     # avisar si el nucli en us no es l instal·lat
# Afegir a revisio_salut.sh: pendents de reinici despres d actualitzar
$ sudo needrestart -r l -p >/dev/null; echo "codi needrestart: $?"
codi needrestart: 0

La decisió de no reiniciar automàticament és deliberada i coherent amb l'unattended-upgrades de 05-03: un reinici no supervisat de la base de dades a les 6 del matí és una interrupció de servei que la Marta ha de conèixer, no un efecte secundari.

Enfortiment de systemd revisitat

A 05-05 vas enfortir tramontana.service. Ara es mesura i es millora:

$ systemd-analyze security tramontana.service | tail -12
→ Overall exposure level for tramontana.service: 3.4 MEDIUM 🙂

3.4 MEDIUM per a un servei de xarxa no està malament, i hi ha marge. Les directives que falten, cadascuna amb la seva raó:

# /etc/systemd/system/tramontana.service.d/enfortiment.conf
[Service]
# El servei no necessita ajustar el nucli ni carregar moduls.
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes

# No ha de manipular la jerarquia de cgroups.
ProtectControlGroups=yes

# No ha de crear fitxers SUID/SGID: tanca una via classica de persistencia.
RestrictSUIDSGID=yes

# Impedeix canviar la "personalitat" del proces per emular una altra arquitectura.
LockPersonality=yes

# Nomes crides al sistema propies d un servei normal. La resta es rebutja
# amb EPERM al nucli, abans d arribar al codi del proces.
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @obsolete
SystemCallArchitectures=native

# Nomes les families de sockets que necessita.
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes

# Sense acces a dispositius ni a /home
PrivateDevices=yes
ProtectHome=yes
ProtectProc=invisible
ProcSubset=pid

# ATENCIO: MemoryDenyWriteExecute trenca els temps d execucio amb JIT
# (Java, Node.js, .NET, alguns Python). Nomes si l aplicacio es nativa.
MemoryDenyWriteExecute=yes

SystemCallFilter mereix explicació perquè és la directiva de més valor: instal·la un filtre seccomp al nucli que rebutja les crides al sistema fora del conjunt permès. Un exploit que aconsegueixi execució de codi dins del procés es troba que mount, ptrace, init_module o setuid fallen amb EPERM — no perquè faltin permisos d'usuari, sinó perquè el nucli no les accepta d'aquest procés. És contenció real, no configuració cosmètica.

I l'avís de MemoryDenyWriteExecute és el tipus de detall que separa una guia útil d'una llista copiada: impedeix que una pàgina de memòria sigui escrivible i executable alhora, cosa que bloqueja moltes tècniques d'explotació, i trenca qualsevol motor amb compilació en temps d'execució. Si l'aplicació de Tramontana fos Java o Node, aquesta línia la deixaria sense arrencar.

$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service

# Verificar que el servei CONTINUA FUNCIONANT: enfortir i trencar no es enfortir
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/cases
200

$ systemd-analyze security tramontana.service | tail -3
→ Overall exposure level for tramontana.service: 1.6 OK 🙂

De 3.4 MEDIUM a 1.6 OK, amb el servei funcionant i verificat. L'ordre —aplicar, verificar, mesurar— és el mateix que a la rotació de credencials i al diagnòstic de rendiment. Aplicar sense verificar és com es descobreix a les 3 de la matinada que l'enfortiment va tombar el servei.

Còpies com a últim control davant del ransomware

Quan tot l'anterior falla, queden les còpies. I davant del ransomware hi ha un detall que sol passar desapercebut: una còpia muntada permanentment és abastable pel ransomware, perquè el procés que xifra els fitxers del sistema xifra també els del volum muntat. Una còpia xifrada per l'atacant no és una còpia.

Les tres propietats que fan que una còpia resisteixi:

Propietat Com s'aconsegueix Estat a Tramontana
Fora de línia o immutable Mitjà desconnectat, o emmagatzematge amb retenció forçada Pendent per a la destinació externa del 3-2-1
Credencial separada La contrasenya del dipòsit no és accessible des del servidor compromès Parcial: és a pass, però el servidor la pot llegir
Restauració provada Prova periòdica documentada Fet: els tres escenaris de 05-08

La millora concreta que se'n dedueix: restic admet el mode append-only a la destinació remota, en què el client pot escriure còpies noves però no esborrar les existents. Amb això, un atacant amb accés al servidor no pot destruir l'històric. És la mesura que cal demanar al proveïdor de l'emmagatzematge extern, i queda anotada a la llista de comprovació.

Llista de comprovació d'enfortiment i manteniment de la postura

Aquesta és la taula que s'entrega, alineada amb l'estructura dels CIS Benchmarks i amb el que has fet realment al curs. Les tres columnes són deliberades: sense la d'evidència, una llista de comprovació és una declaració d'intencions.

# Control Estat Evidència
1 Sistema de fitxers: noexec,nosuid,nodev a /tmp, /var/tmp, /dev/shm Fet findmnt -o TARGET,OPTIONS
2 Xifratge en repòs del volum de còpies Fet cryptsetup luksDump, /etc/crypttab
3 Dipòsits signats; només actualitzacions oficials Fet /etc/apt/sources.list.d/*.sources, /etc/apt/keyrings/
4 Actualitzacions de seguretat automàtiques Fet unattended-upgrades, només -security
5 Serveis pendents de reinici després d'actualitzar Fet needrestart -r l sense pendents; incident tancat
6 Superfície d'escolta mínima Fet ss -tulpn: 22, 5432 intern, 8080 a localhost
7 Serveis innecessaris deshabilitats i emmascarats Fet ModemManager emmascarat i purgat
8 Sense entorn gràfic Fet Instal·lació d'Ubuntu Server
9 Tallafocs amb política de denegació per defecte Fet ufw status verbose; conjunt de regles nftables documentat
10 sysctl de xarxa enfortits Fet /etc/sysctl.d/ (06-03)
11 sysctl de nucli enfortits Fet /etc/sysctl.d/60-enfortiment.conf, comentat
12 SSH: sense root, sense contrasenya, només claus Fet sshd -T -C user=...; excepció de Match eliminada
13 Bloqueig per intents fallits (IP i compte) Fet fail2ban-client status sshd, faillock --user
14 Política de contrasenyes i caducitat Fet pwquality.conf, login.defs, chage -l
15 Un únic UID 0; sense comptes sense contrasenya Fet Les tres ordres d'auditoria d'una línia
16 Mínim privilegi a sudo Fet /etc/sudoers.d/tramontana amb Cmnd_Alias
17 MAC: perfil d'AppArmor en enforce Fet aa-status, perfil de tramontana
18 Servei amb enfortiment de systemd Fet systemd-analyze security: 1.6 OK
19 Secrets fora dels fitxers de configuració Fet LoadCredentialEncrypted, pass
20 TLS: material emès, verificat i amb renovació provada Fet certbot renew --dry-run, revisar_certificat.sh
21 Registre persistent amb rotació Fet /var/log/journal, logrotate.d/tramontana
22 Integritat de fitxers vigilada, base de dades fora Fet tramontana-integritat.timer, suma al portàtil
23 Auditoria d'accessos a configuració i identitats Fet auditctl -l, claus tramontana_conf i privilegis
24 Còpies 3-2-1, xifrades, amb restauració provada Fet restic snapshots, comprovar_copia.sh, manual d'operació
25 Xifratge en trànsit en vigor Pendent (08-01) Certificat a punt; falta el servidor intermediari invers. El 8080 no està exposat
26 Còpia externa en mode append-only Pendent Requereix decisió de proveïdor; mitiga el ransomware
27 authorized_keys2 sense explicar Obert Procediment de resposta a incidents de 06-04 en curs
28 Registres centralitzats fora del servidor Assumit Cost actual; primera inversió quan hi hagi pressupost
29 NIDS Assumit Aporta poc amb un sol servidor (raonat a 06-04)
30 Antivirus Assumit Servidor sense fitxers de tercers; consumeix memòria acotada
31 Segon factor a SSH Assumit Accés ja per clau; es revisa si creix l'equip
32 Seguretat de l'aplicació (injecció, lògica) Fora d'abast Responsabilitat de desenvolupament
33 Seguretat física i de l'hipervisor Fora d'abast Responsabilitat del proveïdor
34 Auditoria formal de seguretat Fora d'abast Requereix un tercer independent

Les files 25 a 27 són les importants de debò, perquè són les que un informe honest no amaga. I les files 32 a 34 delimiten el que un administrador no pot resoldre, que és informació que la direcció necessita tenir.

Mantenir la postura

Un sistema enfortit es degrada sol: cada canvi, cada paquet nou, cada regla «temporal» l'allunya de l'estat que vas documentar. Quatre pràctiques el sostenen:

  1. Revisió periòdica amb mètriques comparables. Lynis mensual, amb el seu índex d'enfortiment com a sèrie temporal al costat de la línia base de rendiment de 05-07:
    $ sudo lynis audit system --quiet | grep 'Hardening index'
      Hardening index : 82 [################    ]
    
    Del 68 de 06-04 al 82 actual. El rellevant no és el número: és que si baixa, hi ha una explicació per buscar.
  2. Gestió del canvi. Tota modificació de configuració amb la seva còpia prèvia, el seu diff -u, la seva verificació i la seva anotació. És la convenció del curs, i és el control contra l'actor més probable del model d'amenaces.
  3. Documentació viva al manual d'operació, fora del servidor: el model d'amenaces, aquesta llista de comprovació amb dates, l'inventari de secrets amb la seva última rotació, i el procediment de resposta a incidents.
  4. Revisió del mateix model d'amenaces quan canviï la realitat: un servidor més, un servei nou, una dada nova que es comença a tractar. Un model d'amenaces de fa dos anys descriu un sistema que ja no existeix.

Advertiment de compliment

srv-tramontana tracta dades personals d'hostes. El RGPD exigeix mesures tècniques i organitzatives apropiades al risc (art. 32), i diverses de les decisions d'aquesta lliçó —la retenció de registres, l'abast de l'auditoria sobre l'activitat de persones treballadores, el termini de desplegament del xifratge en trànsit de la fila 25— tenen implicacions legals que no correspon decidir a un administrador. En un entorn real:

  • El responsable de seguretat ha de revisar el model d'amenaces i la llista de comprovació, i validar els controls assumits.
  • El responsable de protecció de dades ha de validar el tractament, els terminis de retenció i l'avaluació del risc residual.
  • I aquesta llista de comprovació no substitueix una auditoria formal. És una autoavaluació honesta, feta per qui administra el sistema, i per definició té el punt cec de qui l'ha construït.

Errors Comuns i Consells

  • Enfortir sense model d'amenaces. És la causa de la meitat dels sistemes fràgils: mesures copiades que trenquen coses i no protegeixen contra res que fos probable. Mitja pàgina de model canvia totes les decisions que vénen després.
  • Tocar PAM sense una sessió de root oberta. Un error de sintaxi a common-auth pot impedir tot inici de sessió, inclosa la consola. Sessió de root en un altre terminal, còpia prèvia, i prova en un tercer.
  • Oblidar pam_faillock.so authsucc. Sense aquella línia el comptador de fallades no es reinicia mai, i el bloqueig arriba dies després sense causa aparent.
  • Oblidar enforce_for_root = 1 a pwquality.conf: el compte més important queda exempt de la política de contrasenyes.
  • Posar un perfil d'AppArmor directament en enforce. Trenca el servei i t'obliga a descobrir els accessos que falten sota pressió. Sempre complain primer, exercitar de debò, i després enforce.
  • Aplicar noexec a /tmp sense comprovar-ho. Hi ha instal·ladors que extreuen i executen allà. Prova amb mount -o remount i exercita apt i els teus scripts abans de tocar fstab.
  • Editar fstab sense mount -a. Un fstab trencat impedeix arrencar el servidor. És la xarxa de seguretat de 05-04 i continua sent obligatòria.
  • Confondre «paquet actualitzat» amb «vulnerabilitat corregida». Mentre el procés tingui la biblioteca vella a memòria, continua exposat. needrestart -r l després de cada actualització, i lsof | grep DEL com a confirmació.
  • Enfortir i no verificar. systemd-analyze security millora la puntuació encara que hagis deixat el servei sense arrencar. Aplicar, verificar amb revisio_salut.sh, i després mesurar.
  • MemoryDenyWriteExecute en una aplicació amb JIT. Java, Node i .NET no arrenquen. Llegeix què fa cada directiva abans de copiar-la.
  • Una llista de comprovació sense columna d'evidència. «Tallafocs configurat» no diu res; ufw status verbose sí. Sense evidència, la llista de comprovació és una declaració d'intencions.
  • Consell de mètode. Converteix les auditories d'aquesta lliçó en auditar_enfortiment.sh fent servir lib/comuns.sh, amb un temporitzador mensual i el principi de silenci si tot va bé. El que depèn de la teva memòria no és un control.

Exercicis

Exercici 1

Després d'aplicar el drop-in d'enfortiment, tramontana.service deixa d'arrencar:

$ sudo systemctl status tramontana.service --no-pager | head -6
● tramontana.service - Tramontana Reserves
     Active: failed (Result: exit-code) since Tue 2026-08-18 16:04:11 CEST
    Process: 9214 ExecStart=/opt/tramontana/app/tramontana ... (code=killed, signal=SYS)

Descriu el procediment de diagnòstic, identifica la causa més probable a partir de la informació del missatge, i explica com acotaries quina directiva concreta el provoca sense treure-les totes de cop.

Exercici 2

Escriu el perfil d'AppArmor per a l'embolcall /home/operador/bin/desplegar-segur, que s'executa via sudo i crida desplegar.sh. Raona què ha de permetre i —més important— què ha de denegar, i explica quin atac concret conté aquell perfil que ni el DAC ni la regla de sudoers no contenen.

Exercici 3

La Marta et demana un informe d'una pàgina per a la direcció: «estem segurs?». Redacta'l recolzant-te en el model d'amenaces i en la llista de comprovació, sense tecnicismes innecessaris, dient amb honestedat què està protegit, què no i quines decisions necessiten pressupost o autorització.

Solucions

Solució 1

La pista és al mateix missatge: code=killed, signal=SYS. SIGSYS és el senyal que envia el nucli quan un filtre seccomp rebutja una crida al sistema. Això apunta directament a SystemCallFilter, i no a les altres directives (que produirien típicament EACCES, EPERM o una fallada de ruta).

Procediment, del més informatiu al més costós:

# 1. Confirmar la hipotesi i veure QUINA crida es va rebutjar
$ sudo journalctl -u tramontana.service -n 30 --no-pager | grep -iE 'seccomp|SYS|syscall'
audit: type=1326 audit(1755530651.882:88): auid=4294967295 uid=997 pid=9214
  comm="tramontana" exe="/opt/tramontana/releases/3.2.1/tramontana"
  syscall=318 compat=0 ip=0x7f2a1c4b3e2a code=0x80000000

# 2. Traduir el numero de crida al seu nom
$ ausyscall 318
getrandom

getrandom és la crida per obtenir aleatorietat del nucli — l'aplicació la fa servir per a TLS i per a identificadors de sessió. És a @system-service, però la vaig excloure en afegir SystemCallFilter=~@privileged @resources @obsolete, i getrandom pertany al conjunt @resources en algunes versions de systemd. La segona línia, que semblava una millora, és la que trenca el servei.

Com acotar sense treure-ho tot, que és la part de mètode de l'exercici. La clau és que els drop-ins es poden apilar i anul·lar per separat:

# a) Un drop-in temporal que nomes neutralitza la directiva sospitosa.
#    La linia buida REINICIA la llista acumulada; despres es posa nomes el minim.
$ sudo mkdir -p /etc/systemd/system/tramontana.service.d
$ sudo tee /etc/systemd/system/tramontana.service.d/99-diagnostic.conf >/dev/null <<'EOF'
[Service]
SystemCallFilter=
SystemCallFilter=@system-service
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0

Arrenca. Amb això queda confirmat que el problema era l'exclusió, no el conjunt base. Ara s'afina en lloc de renunciar al filtre:

# b) Recuperar la restriccio util, tornant nomes el necessari
$ sudo tee /etc/systemd/system/tramontana.service.d/99-diagnostic.conf >/dev/null <<'EOF'
[Service]
SystemCallFilter=
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @obsolete
SystemCallFilter=getrandom
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh && systemd-analyze security tramontana.service | tail -1
→ Overall exposure level for tramontana.service: 1.7 OK 🙂

Es manté ~@privileged @obsolete —que és on hi ha gairebé tot el valor defensiu—, s'abandona @resources, i s'afegeix getrandom explícitament. Exposició 1.7 en lloc d'1.6, amb el servei funcionant: un enfortiment pitjor sobre el paper i infinitament millor a la pràctica, perquè el d'1.6 no arrencava.

Mètode general aplicable a qualsevol fallada d'enfortiment:

  1. Llegir el senyal o el codi d'error: SIGSYS → seccomp; EPERM en un fitxer → ProtectSystem/ReadWritePaths; EACCES amb apparmor="DENIED" → AppArmor; fallada en mapejar memòria → MemoryDenyWriteExecute.
  2. Buscar l'evidència concreta al journal (syscall=, denied_mask=, la ruta).
  3. Neutralitzar una directiva amb un drop-in de prefix numèric més gran, mai editant l'original.
  4. Verificar que arrenca, afinar cap al mínim necessari, i tornar a mesurar.
  5. Consolidar el drop-in amb un nom definitiu i deixar escrit el motiu de l'excepció — igual que l'unprivileged_userns_clone comentat del sysctl.

Solució 2

# /etc/apparmor.d/home.operador.bin.desplegar-segur
abi <abi/4.0>,
include <tunables/global>

/home/operador/bin/desplegar-segur {
  include <abstractions/base>
  include <abstractions/bash>

  # L embolcall i l script que invoca
  /home/operador/bin/desplegar-segur      r,
  /home/operador/scripts/desplegar.sh     rix,
  /home/operador/scripts/lib/comuns.sh    r,
  /usr/bin/bash                           rix,

  # Utilitats concretes que l script necessita, ENUMERADES
  /usr/bin/{tar,curl,ln,sha256sum,systemctl,flock,mktemp,rm,mv,date,logger} rix,

  # Origen del paquet de release: nomes lectura
  /srv/tramontana/backups/enviaments/*.tar.gz  r,
  /srv/tramontana/backups/enviaments/*.sha256  r,

  # Desti del desplegament: escriptura acotada a releases i a l enllac
  /opt/tramontana/releases/                  rw,
  /opt/tramontana/releases/**                rw,
  /opt/tramontana/app                        rw,
  /opt/tramontana/HISTORIAL                  rw,

  # Registre i bloqueig
  /var/log/tramontana/desplegament.log        rw,
  /var/lock/tramontana-desplegament.lock      rwk,
  /tmp/                                       r,
  /tmp/**                                     rw,

  # Consultar l estat del servei despres del desplegament
  /run/systemd/private                        rw,

  # DENEGAT de forma explicita, perque hi consti i quedi registrat:
  deny /etc/shadow                            rwklx,
  deny /etc/tramontana/secrets/**             rwklx,
  deny /srv/tramontana/backups/restic/**      rwklx,
  deny /home/operador/.ssh/**                 rwklx,
  deny /home/operador/.password-store/**      rwklx,
  deny /root/**                               rwklx,
  deny /usr/bin/{nc,ncat,socat,ssh,scp,python3,perl} x,
}

Què permet: llegir el paquet de release, escriure a releases/, canviar l'enllaç app, escriure el seu registre i el seu bloqueig, i parlar amb systemd per reiniciar el servei. Res més.

Què denega, i quin atac conté cada denegació:

Denegació Atac que conté
/etc/tramontana/secrets/** El procés corre com a root via sudo, així que el DAC li permetria llegir la credencial xifrada. AppArmor no
/srv/tramontana/backups/restic/** Un desplegament no té cap raó per tocar les còpies. Conté l'esborrat de còpies per ransomware
/home/operador/.password-store/** Impedeix que l'embolcall accedeixi a la font de veritat dels secrets
/home/operador/.ssh/** Bloqueja afegir una clau autoritzada — el mecanisme de persistència que AIDE va detectar a 06-04
x sobre nc, socat, python3, perl Bloqueja la shell inversa, que és el primer pas després d'aconseguir execució amb privilegis

I ara el fons de l'exercici: què conté aquest perfil que el DAC i sudoers no contenen.

La regla de sudoers permet a operador executar desplegar-segur com a root. A partir d'aquell moment, el DAC no ofereix cap protecció: root pot llegir /etc/shadow, pot llegir la credencial de la base de dades, pot esborrar les còpies, es pot afegir una clau SSH i pot llançar nc per obrir una shell cap a l'exterior. L'embolcall es va escriure precisament per evitar donar root complet, però la contenció depèn per complet que el contingut de l'script sigui correcte i continuï sent-ho.

Aquí hi ha el forat. Si desplegar.sh té una fallada —una variable sense cometes que permeti injectar una ordre, un tar que extregui un fitxer amb ../ a la seva ruta— l'atacant executa codi com a root a través d'una via autoritzada. Ni sudo ni els permisos Unix no l'aturen: l'execució és legítima des del seu punt de vista.

El perfil d'AppArmor és la capa que sí que l'atura, perquè el control no depèn de l'usuari sinó del programa: sigui quin sigui el codi que s'acabi executant dins d'aquest perfil, no podrà llegir els secrets, no podrà tocar les còpies, no podrà escriure a authorized_keys i no podrà llançar un intèrpret per sortir-ne. El dany queda acotat al desplegament, que és exactament el que es pretenia quan es va escriure l'embolcall i el que fins ara era només una intenció.

Això és defensa en profunditat en la seva forma més concreta: el sudoers limita qui i quina ordre; AppArmor limita què pot fer aquella ordre, fins i tot quan l'ordre es comporta malament.

Solució 3

Informe de seguretat — srv-tramontana / Tramontana Reserves Per a: Direcció · De: Operacions de sistemes · 18 d'agost de 2026

En una frase. El servidor està raonablement protegit davant de les amenaces que de debò l'afecten, amb tres qüestions pendents que detallo al final, una de les quals requereix una decisió de direcció.

Què protegim, per ordre d'importància. Primer, les dades personals dels nostres hostes: noms, dates d'estada i imports. La seva filtració seria irreversible, danyaria tercers i té conseqüències legals. Segon, la disponibilitat del servei de reserves, el cost del qual és econòmic i acotat en el temps.

De qui ens protegim, i què hem fet.

  • Atacs automatitzats des d'Internet, que són constants. El servidor només accepta connexions per dues portes: la d'administració —que ja no admet contrasenyes, només claus criptogràfiques— i la del lloc web. Tota la resta està tancada per defecte, i qui insisteix a provar contrasenyes queda bloquejat automàticament. En l'últim mes s'han bloquejat 47 intents des d'una mateixa procedència.
  • Robatori o filtració d'una contrasenya. Les credencials del sistema ja no estan escrites en cap fitxer llegible: estan xifrades i només el programa que les necessita les pot fer servir. Tenim a més un procediment escrit per canviar-les sense interrompre el servei.
  • Els nostres propis errors, que estadísticament són el risc més probable. Tota modificació es fa amb còpia prèvia, es comprova abans d'aplicar-se i queda registrada. Un sistema independent vigila cada dia que cap fitxer crític no hagi canviat sense explicació.
  • Ús indegut d'accessos legítims. Cada persona té només els permisos que la seva feina requereix, i els accessos a la configuració queden registrats.

Què fem si alguna cosa falla. Tenim còpies de seguretat xifrades, amb la restauració provada —no només configurada—, capaces de recuperar el servei en 8 hores perdent com a màxim 4 hores de reserves, terminis que es van acordar amb Operacions. I un procediment escrit de resposta a incidents, guardat fora del servidor.

Les tres qüestions pendents.

  1. El trànsit web encara no viatja xifrat. El certificat està emès i verificat, però falta desplegar el component que el presentarà, previst a la fase següent. Mentrestant el servei no és accessible des d'Internet, així que l'exposició es limita a la nostra xarxa interna. No requereix decisió: està planificat.
  2. Una troballa sense explicar. La nostra vigilància va detectar un fitxer de configuració d'accés que ningú no va crear. Està en investigació amb el procediment previst. N'informaré del resultat; si es confirmés accés a dades personals, hi ha obligació legal de notificar-ho en 72 hores.
  3. Còpia externa protegida contra esborrat. Avui, qui tingués control del servidor podria destruir les còpies. Existeix una modalitat d'emmagatzematge que no permet esborrar el que ja està guardat, i és la millor defensa disponible davant d'un atac de segrest de dades. Requereix decisió de direcció: implica contractar emmagatzematge extern.

Què no està a les nostres mans. Tres coses queden fora del que l'administració de sistemes pot resoldre, i convé que hi constin: la seguretat interna de la mateixa aplicació, que correspon a desenvolupament; la seguretat física i del proveïdor d'infraestructura; i un atacant amb recursos molt superiors als d'una empresa de la nostra mida.

I un advertiment necessari. Aquest informe és una autoavaluació feta per qui administra el sistema, i per tant té el punt cec de qui l'ha construït. No substitueix una auditoria independent. Recomano, a més, que el tractament de dades d'hostes i els terminis de conservació de registres els revisi el responsable de protecció de dades.

En resum: la feina està feta, mesurada i documentada. L'única decisió que necessito de la direcció és la del punt 3.

Conclusió

srv-tramontana té ara una postura de seguretat, i això és qualitativament diferent de tenir mesures de seguretat. Hi ha un model d'amenaces escrit que diu què protegim, de qui i què assumim fora d'abast, i cada decisió d'aquesta lliçó s'hi justifica. La superfície d'atac s'ha reduït: l'aplicació escolta només en local, el que no es fa servir està emmascarat i purgat. L'autenticació passa per PAM amb política de contrasenyes, bloqueig per compte i límits de recursos —i vas localitzar el Match User luis que portava setmanes contradient la teva pròpia configuració—. AppArmor conté l'aplicació en enforce, amb un perfil que impedeix llançar una shell o llegir les còpies encara que el procés es vegi compromès. Els sysctl d'enfortiment estan aplicats i comentats, inclosa la línia que vas deixar desactivada amb la raó escrita al costat. /tmp està sense permís d'execució, comprovat abans de fer-ho persistent. I systemd-analyze security ha passat de 3.4 a 1.6 amb el servei verificat i funcionant, que és l'única millora que compta.

Els tres incidents estan tancats: els 47 intents des de 203.0.113.44 amb fail2ban, la db_password en clar amb systemd-creds i pass, i avui la libssl vulnerable que continuava carregada a memòria — perquè un paquet actualitzat no és una vulnerabilitat corregida fins que els processos ho saben. Queda una llista de comprovació de 34 files amb la seva columna d'evidència, tres qüestions obertes dites sense adorns i un informe per a la direcció que no promet el que no pot complir. L'índex de Lynis ha pujat de 68 a 82, i l'important d'aquell número no és el seu valor sinó que ara és una sèrie temporal que algú vigila.

I amb això es tanca el Mòdul 6: vas configurar la xarxa de manera persistent amb la reversió automàtica de netplan try; vas enfortir SSH amb claus ed25519, sense root i sense contrasenyes, i vas aprendre a llegir sshd -T -C per saber quina configuració s'aplica realment a algú; vas aixecar un tallafocs de llista blanca sobre nftables i vas saber l'ordre en què s'aplica per no quedar-te fora; vas muntar detecció d'intrusions amb AIDE i auditd, amb la base de dades protegida fora del servidor, i un procediment de resposta a incidents amb la seva regla incòmoda; vas treure els secrets dels fitxers de configuració i vas preparar el material criptogràfic de TLS amb renovació provada i vigilància de la caducitat; i avui has reunit tot això en una postura coherent i documentada.

Fins aquí has operat un servidor, i l'has operat a mà. Saps fer-lo servir, administrar-lo i defensar-lo, però hi ha dues coses que encara no saps fer: mirar-hi dins i multiplicar-lo. Al Mòdul 7: Temes Avançats fas totes dues. Veuràs el procés d'arrencada complet —de la UEFI a GRUB, a l'initramfs i a systemd— i aprendràs a recuperar un sistema que no arrenca, que és l'habilitat que separa qui reinstal·la de qui arregla. Aprendràs diagnòstic avançat amb strace, perf i eBPF per respondre a «què està fent exactament aquest procés?» quan els comptadors no basten (i descobriràs que el ptrace_scope que acabes de posar és part de l'enunciat del problema). Ajustaràs el nucli amb sysctl, ara sí per rendiment. I després comença la multiplicació: virtualització amb KVM i libvirt, contenidors amb Docker i per què són processos aïllats i no màquines petites, automatització amb Ansible —que convertirà tota la feina manual dels mòduls 5 i 6 en codi versionat i reproduïble, i les 8 hores d'RTO en bastant menys—, i per fi alta disponibilitat i balanceig de càrrega, on srv-tramontana deixa de ser un únic punt de fallada. Actualitza la instantània de la teva VM, guarda el manual d'operació fora de la màquina, i ens veiem al Mòdul 7.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats