diff --git a/docs/cutover/RUNBOOK-cutover-wordpress.md b/docs/cutover/RUNBOOK-cutover-wordpress.md index c675244..5262db0 100644 --- a/docs/cutover/RUNBOOK-cutover-wordpress.md +++ b/docs/cutover/RUNBOOK-cutover-wordpress.md @@ -8,13 +8,49 @@ Todos los tiempos de abajo están **medidos en el ensayo del 31-jul**, no estima ## Antes de empezar (verde obligatorio) - [ ] **Freeze editorial** avisado a Inma. El cutover no puede caer el día de publicación de la carta. + *(La carta 738 se compone el martes: el lunes está libre.)* - [ ] **CDMON sigue vivo** (§1). Es el rollback: si no está, no se ejecuta. -- [ ] **Modo SSL de la zona en Cloudflare = Full (strict)**. Si está en *Flexible*, Cloudflare habla - HTTP con el origen mientras Traefik fuerza HTTPS → **bucle de redirección y sitio caído**. - Nuestro token no puede leer este ajuste: lo confirma Inma. +- [x] ~~**Modo SSL de la zona**~~ — **CONFIRMADO POR INMA (2-ago): está en `Full`, no `Flexible`.** + El riesgo de bucle de redirección queda descartado. (*Full (strict)* además valida el + certificado del origen; es mejor y se puede subir después, no bloquea el cutover.) +- [ ] 🔴 **`wp-nuevo.feadulta.com` declarado en Coolify — ver paso 0. SIN ESTO SE ROMPEN LOS BOTS.** - [ ] **Inventario del wildcard.** La zona tiene `*.feadulta.com` → apex, proxied. Al mover el A del - apex, **todo subdominio no declarado se va a Hetzner** y Traefik devolverá su 404. Comprobado - pendiente: `info.feadulta.com`. + apex, **todo subdominio no declarado se va a Hetzner** y Traefik devolverá su 404. + `info.feadulta.com`: Rafa lo da por residual, se acepta que dé 404. + +--- + +## 0. 🔴 `wp-nuevo.feadulta.com` — hacer ANTES del cutover + +**Los bots de la carta (`teologasbot`, `escritoresbot`, `recordatoriobot`) publican por +`wp-nuevo.feadulta.com`, no por `www`**, porque el WAF de Cloudflare los bloquea por `www` (regla +"bloquear bots 1"). Aviso de Mixbot, 2-ago. + +Ese hostname **no tiene registro propio**: lo cubre el wildcard. Al mover el A del apex viaja a +Hetzner, y **Traefik solo tiene router para `nuevo.feadulta.com`** → devolvería su 404 y los bots +dejarían de publicar. La carta 738 se compone el **martes**. + +⚠️ **Hay que hacerlo en el PANEL de Coolify.** Por API no vale: `PATCH /services/{uuid}` con `fqdn` +da `422 "This field is not allowed"`, y actualizar `SERVICE_FQDN_WORDPRESS` por API cambia la +variable pero **no regenera los routers de Traefik** (comprobado el 2-ago). + +Servicio `feadulta-wp` → campo de dominios: + +``` +antes del cutover: https://nuevo.feadulta.com,https://wp-nuevo.feadulta.com +después del cutover: https://www.feadulta.com,https://feadulta.com,https://wp-nuevo.feadulta.com +``` + +**Plan B si el martes wp-nuevo falla:** los bots cambian una línea de su `.env` y publican por +**`nuevo.feadulta.com`** — es grey cloud, así que **no pasa por Cloudflare y ninguna regla del WAF +le aplica**, ya tiene certificado y ya está enrutado. Mixbot puede hacerlo al momento. + +Verificación tras el cutover (no vale hacerla desde la máquina de Rafa: **Cloudflare devuelve 403 a +esa IP para cualquier hostname de feadulta**, así que un 403 desde ahí no significa nada): + +```bash +curl -sI https://wp-nuevo.feadulta.com/wp-json/ | head -3 # desde el Hetzner o desde los bots +``` ## Tiempos medidos (ensayo del 31-jul) @@ -113,20 +149,30 @@ payload de SEO spam que dejó el atacante (div oculto con enlaces a un casino, c de una letra). Está inerte —quien lo leía era `wp-highlits`, en cuarentena— pero **viaja dentro del dump y cada import lo reintroduce**. -## 6b. Plugins de seguridad — son 8, no 7 +## 6b. Plugins de seguridad — Wordfence entra, LLAR sale -A los 7 plugins de producción se añade **Wordfence** (8.2.2, instalado limpio desde wordpress.org el -31-jul). Motivo: CDMON tenía **ModSecurity** delante a nivel de hosting —fue lo que detectó las -subidas de `wp-cl.php` y `health-check.php` en el #183—, y **en Hetzner con Traefik no hay WAF -ninguno**. Sin Wordfence, el servidor nuevo estrenaría con una capa defensiva menos que la que tenía -el sitio comprometido. +**Siguen siendo 7 plugins activos**, pero no los mismos: entra **Wordfence** (8.2.2, limpio desde +wordpress.org) y sale **`limit-login-attempts-reloaded`** (decisión de Rafa, 2-ago). + +Motivo de que entre Wordfence: CDMON tenía **ModSecurity** delante a nivel de hosting —fue lo que +detectó las subidas de `wp-cl.php` y `health-check.php` en el #183— y **en Hetzner con Traefik no hay +WAF ninguno**. Sin él, el servidor nuevo estrenaría con una capa defensiva menos que la que tenía el +sitio comprometido. + +Motivo de que salga LLAR: Wordfence hace lo mismo (límite de intentos, bloqueos) **y además** +firewall de aplicación, escáner de malware y 2FA. Los dos a la vez son dos contadores compitiendo y +bloqueos difíciles de diagnosticar. + +**El dump trae LLAR en `active_plugins`, así que cada import lo revive** → lo retira +`post-import-seguridad.sh` (paso 3c), junto con sus 12 opciones huérfanas. El mismo script comprueba +que Wordfence sigue activo y lo reinstala si faltara. ⚠️ Wordfence queda con **protección básica** (a nivel de plugin). El *extended protection* exige configurar `auto_prepend_file` desde su asistente; se hace **después** del cutover y con calma, no en la ventana. -⚠️ Wordfence y `limit-login-attempts-reloaded` solapan en el bloqueo de login. No es un conflicto, -pero conviene decidir cuál manda para no acabar con dos contadores distintos. +⚠️ `fea-cloudflare-realip.php` es obligatorio con Wordfence por el mismo motivo que lo era con LLAR: +sin él ve todas las visitas como si vinieran de las IPs de Cloudflare. Está en el manifiesto. ## 7. Quitar el WP_HOME/WP_SITEURL del staging @@ -176,6 +222,15 @@ intacto y sirviendo — comprobado con `curl --resolve` contra `134.0.10.170`. **Esto solo funciona mientras CDMON siga vivo.** Es la razón por la que el §1 no es opcional. +## 🔴 El correo — no es del cutover, pero es lo que más puede doler + +El cutover **no toca el correo**: el MX apunta a `mail.feadulta.com` → `134.0.13.123`, una IP +distinta de la del web. Mover el A del apex no lo afecta. + +**Pero la caducidad del 07/08 sí.** Los 17 buzones viven en CDMON, y `ediciones@` y `contenido@` son +de donde los bots sacan el material de la carta: si cae el correo, se para la carta. Hay que +resolverlo con CDMON **esta semana**, vaya como vaya el cutover. + ## Después (no el mismo día) - **Backups en Hetzner: hoy NO HAY NINGUNO.** La tabla `scheduled_database_backups` de Coolify está diff --git a/scripts/cutover/post-import-seguridad.sh b/scripts/cutover/post-import-seguridad.sh index 88e7288..61987f6 100644 --- a/scripts/cutover/post-import-seguridad.sh +++ b/scripts/cutover/post-import-seguridad.sh @@ -92,6 +92,42 @@ else REMOTE fi +echo "" +echo "=== 3c. Retirar limit-login-attempts-reloaded ===" +# Decision de Rafa (2-ago): el bloqueo de login lo lleva Wordfence, que hace lo mismo +# y ademas firewall de aplicacion, escaner de malware y 2FA. Tener los dos = dos +# contadores compitiendo. El dump trae LLAR en active_plugins, asi que cada import +# lo revive: por eso se retira aqui y no una sola vez a mano. +if [ $DRY -eq 1 ]; then + echo " [dry-run] desactivaria y borraria limit-login-attempts-reloaded" + echo " [dry-run] limpiaria las opciones limit_login_*" +else + wp plugin deactivate limit-login-attempts-reloaded >/dev/null 2>&1 || true + wp plugin delete limit-login-attempts-reloaded >/dev/null 2>&1 || true + echo " limit-login-attempts-reloaded: retirado" + ssh -o BatchMode=yes "$SRV" "bash -s" </dev/null + n=\$(docker exec mysql-$U mysql -uroot -p"\$RP" -N -e "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE 'limit_login%';" wordpress 2>/dev/null) + echo " opciones limit_login_* restantes: \$n" +REMOTE +fi + +echo "" +echo "=== 3d. Wordfence presente? ===" +if [ $DRY -eq 1 ]; then + echo " [dry-run] comprobaria Wordfence" +else + if wp plugin is-installed wordfence 2>/dev/null; then + wp plugin activate wordfence >/dev/null 2>&1 || true + echo " wordfence: instalado y activo" + else + echo " wordfence: NO instalado -> instalando desde wordpress.org" + wp plugin install wordfence --activate >/dev/null 2>&1 && echo " wordfence: instalado" || echo " wordfence: FALLO la instalacion, revisar a mano" + fi +fi + echo "" echo "=== 4. Verificacion ===" echo " administradores que quedan:"