Agente ReportedIP para Linux
El agente es un único binario estático que hace dos trabajos en un servidor Linux: mantiene la Community Blacklist en el cortafuegos del núcleo y notifica a los atacantes que encuentra en los registros de ese servidor. De forma opcional también bloquea él mismo esos hallazgos locales. Sustituye al script de sincronización escrito a mano que documenta este sitio, y asume el trabajo que hacía fail2ban.
linux/amd64 y linux/arm64. Cada servidor necesita una licencia, una
viene incluida en Professional y tres en Business, véase
Licencias de servidor. Las licencias se añaden y se retiran en
Agent Servers, dentro de tu
cuenta.
Qué hace
La Community Blacklist en el núcleo
Cinco listas, una por servicio expuesto (ssh, mail, web, ftp, edge), descargadas con la frecuencia que permite tu plan y cambiadas de forma atómica a un ipset o a un conjunto de nftables. Una lista que llega vacía o con una longitud inverosímil no se aplica nunca: una descarga fallida deja en su lugar el conjunto anterior en vez de abrir la puerta.
Ataques locales reconocidos y notificados
El agente sigue los registros que el servidor ya escribe, cuenta las coincidencias por dirección frente a un umbral propio de esa fuente y notifica la dirección en cuanto se alcanza el umbral. Ninguna línea de registro, ningún nombre de usuario y ninguna URL sale de la máquina.
Hallazgos locales bloqueados, si así lo quieres
Desactivado tras la instalación. Una vez activado, una dirección que el agente ha cazado él mismo queda bloqueada en un conjunto del núcleo con un plazo de caducidad, y un reincidente se bloquea más tiempo en cada ocasión.
Requisitos
Cuatro cosas, y solo la última tiene que venir de nosotros.
- Un núcleo Linux con un filtro de paquetes en el que se pueda escribir. O bien
ipsetjunto coniptables, o biennftables. En un host Debian o Ubuntu eso esapt install ipset iptablesoapt install nftables; en la familia RHEL,dnf install ipset iptablesodnf install nftables. El agente también usaip6tablescuando está presente; sin esa herramienta los bloqueos IPv6 se registran pero no se aplican. - systemd para el temporizador y el servicio de vigilancia. En un host sin systemd el agente funciona igualmente, con la sincronización desde cron y el demonio desde tu propio sistema de arranque, y la sección de más abajo explica cómo.
- Root. Escribe reglas de cortafuegos y lee registros que no son legibles por todos, y ambas cosas lo exigen. No hay un modo reducido para un usuario normal.
- Una clave API de tu cuenta. La clave se usa dos veces durante la instalación, una para descargar la versión con licencia y otra para registrar el host. Una clave por host es la recomendación; varios hosts con una misma clave están permitidos y no cuestan nada más, porque un host se identifica por su identificador de instalación y no por la clave.
Nada más. El binario está enlazado estáticamente y compilado sin cgo, así que no trae intérprete, ni
biblioteca, ni repositorio de paquetes, ni utilidad de cron propia. Para la descarga y la suma de
comprobación, install.sh necesita curl o wget además de
sha256sum, y sin la herramienta de verificación no instala nada. El agente se desarrolló
y se midió en Debian 12 (bookworm) sobre arm64 con ISPConfig, nginx, Postfix, pure-ftpd y bind.
ipset o nftables, y qué decide auto
Los dos motores hacen el mismo trabajo, y en un host recién instalado ninguno es mejor. Lo que importa es cuál usa ya el host, porque dos filtros de paquetes escribiendo la misma cadena INPUT es la forma en que desaparece una tarde.
backend | Qué ocurre |
|---|---|
auto (predeterminado) | Se prefiere ipset cuando ipset e iptables están ambos presentes; si no, se usa nft. Esa preferencia existe porque CSF y la guía de cortafuegos publicada emplean esa cadena de herramientas: un host con reglas ya existentes las conserva así. |
ipset | Conjuntos en ipset, reglas en la cadena rip-blacklist de iptables e ip6tables. Si falta una de las herramientas, el agente se detiene con un mensaje claro en vez de recurrir a otra. |
nftables | Conjuntos y reglas en la tabla propia inet reportedip. Se detiene si falta nft. |
Ninguna comprobación de versión decide esto. Solo cuenta un binario que exista realmente, porque
iptables --version en un host con el motor nft responde algo que parece correcto y no
significa nada. Tras la primera sincronización, el motor elegido queda anotado, y un cambio posterior
exige reportedip-agent sync --migrate-backend en lugar de construir en silencio un
segundo juego de reglas.
Comprobar primero el host
doctor es el comando que se ejecuta antes de instalar. No cambia absolutamente
nada: ningún archivo, ningún directorio, ninguna regla, ningún conjunto. Todo lo que informa lo ha
leído.
reportedip-agent doctor
Responde a seis preguntas, en este orden:
- system: la distribución tal y como la nombra su propio
os-release, el núcleo, la plataforma, si systemd está realmente en marcha (leído en/run/systemd/system, no deducido de que existasystemctl), la zona horaria en la que se leen los registros sin desplazamiento, y el espacio libre allí donde estará el directorio de estado. - ssh: qué unidad ejecuta sshd, y el puerto. El caso interesante es
ssh.socket: con activación por socket,sshd -Tsigue respondiendo 22 mientras el puerto real está en la unidad de socket. El puerto se toma entonces del proceso que escucha. doctor dice cuál de los dos caminos se aplicó, porque quien ve un puerto equivocado en la configuración mira aquí primero. - firewall: dónde están
ipset,iptables,ip6tablesynft, qué motor resulta de ello, y si el conjunto de bloqueos locales lleva un plazo de caducidad por entrada. Ese último punto decide si el núcleo hace caducar un bloqueo por sí solo, y eso es precisamente lo que evita que un agente caído deje un bloqueo permanente. - panel: ISPConfig, Plesk, cPanel o DirectAdmin. Solo la disposición de registros por sitio de ISPConfig es conocida por esta versión. Los demás se nombran en vez de adivinarse, porque un patrón equivocado haría que el agente leyera contadores de bytes en lugar de registros de acceso.
- sources: cada fuente configurada, resuelta a archivos reales, con cuántos hay y cuántos son legibles, el mayor de ellos con una estimación de líneas, y una nota sobre los archivos que existen y están vacíos. En el host de panel más grande medido, el patrón web se resuelve en 196 archivos, y quien instale el agente allí debería saberlo antes y no después.
- mail: si un aviso podría salir realmente de este host. De dieciocho hosts
medidos, catorce podían entregar correo, dos tenían
sendmailcon el MTA detenido, y dos no tenían ningún MTA. Sin esta sección, alguien espera un correo que nunca llega.
Leer el veredicto
El último bloque se llama verdict y se corresponde con el código de salida, así que una
comprobación de monitorización no tiene ninguna salida que analizar.
| Salida | Veredicto | Significado |
|---|---|---|
0 | está todo lo que el agente necesita | Instalar. |
1 | funciona en este host, con las limitaciones indicadas | Cada limitación aparece nombrada en la salida. Un motor de cortafuegos ausente significa que este host notifica y no bloquea, y eso es una instalación válida. Una fuente que no se resuelve en nada conviene arreglarla antes. |
2 | no puede funcionar en este host | No es un host Linux, o es un host que no puede ni bloquear ni leer un solo registro. Corrige la causa antes de instalar. |
Vuelve a ejecutar doctor después de la instalación. Con una configuración presente ya no
adivina, informa de las fuentes configuradas en vez de las detectadas, y añade la desviación de reloj
por fuente que ha medido el demonio. Una fuente cuyo registro marca cada línea a más de un minuto del
reloj del sistema tiene una ventana de recuento que nunca se llena, y ese es el único fallo que se
parece exactamente a «el detector no funciona».
Instalación
Un comando, como root. Lee la versión actual de los metadatos públicos, descarga con tu clave la
versión compilada para tu arquitectura, la verifica contra SHA256SUMS, la instala en
/usr/local/bin/reportedip-agent y después configura el host.
curl -fsSL https://reportedip.com/agent/install.sh | REPORTEDIP_KEY=<YOUR-KEY> sh
La clave va en el entorno porque se necesita dos veces, una para la descarga con licencia y otra para
registrar el host, y pegarla dos veces es la forma en que una clave acaba con un formato equivocado en
el historial del shell. El script es sh POSIX a propósito: tiene que funcionar en hosts
donde bash no es el shell predeterminado. Se detiene con un motivo nombrado y no instala nada si no se
ejecuta como root, si falta la clave, si la plataforma no es Linux, si la arquitectura no es amd64 ni
arm64, si no existe ni curl ni wget, y si falta sha256sum.
Las dos vías de descarga están separadas a propósito. Los metadatos de versión se obtienen sin la clave y los archivos con licencia con ella, de modo que la clave nunca va a un destino de redirección que solo sirve metadatos públicos. Del archivo de sumas se comprueba únicamente la línea de tu propia arquitectura, porque el archivo también incluye la otra.
Qué hace realmente el instalador
Todo lo que sigue es idempotente. El mismo comando en un host que ya tiene el agente actualiza el binario y deja la configuración exactamente como estaba.
- Lee
https://reportedip.com/agent/latestsin la clave y extrae de ahí la versión. Ese archivo es el mismo JSON que el propio agente consulta para las actualizaciones. - Descarga
reportedip-agent_linux_<arch>ySHA256SUMScon la cabeceraX-Keyen un directorio temporal que se borra en cualquier vía de salida. - Verifica la suma de comprobación y no instala nada si hay discrepancia.
- Instala el binario con modo 0755 en
/usr/local/bin/reportedip-agenty muestra la versión que acaba de dejar. - Reinicia
reportedip-agent.servicesi esa unidad está activa. Esto importa en una actualización: el demonio de vigilancia seguiría ejecutando el código antiguo hasta el siguiente reinicio de la máquina. La unidad de sincronización es de tipo oneshot y toma el binario nuevo por sí misma en su siguiente arranque. - Ejecuta la configuración del host, salvo que
REPORTEDIP_NO_SETUP=1esté definido o que/etc/reportedip-agent/config.yamlya exista. En el segundo caso lo dice y deja la configuración en paz.
La configuración del host es el paso que escribe archivos, y es lo mismo que
reportedip-agent install --key. Por orden:
- Crea
/etc/reportedip-agenty/var/lib/reportedip-agent, el segundo con modo 0700, y corrige el modo de un directorio que ya estuviera. - Detecta el puerto SSH a partir de
sshd -T, deSSH_CONNECTIONy de los procesos sshd que escuchan, y toma la unión de los tres. Si no encuentra nada, se detiene y pide--ssh-porten vez de suponer 22. - Decide cuáles de las cinco listas del feed se configuran.
sshyedgeestán siempre activas.mail,webyftpse añaden cuando una unidad correspondiente está activa o un puerto correspondiente escucha: un host sin servidor web no recibe por tanto una lista web. - Detecta las fuentes de registro y las escribe de forma explícita en la configuración. La detección ocurre una vez, en la instalación, y el resultado permanece en el archivo: un servicio detenido por mantenimiento nunca debe desactivar una fuente en silencio.
- Escribe
/etc/reportedip-agent/config.yamlcon modo 0600 y conmode: logyban.enabled: false. Una configuración existente nunca se sobrescribe, y entonces--keyse ignora con una nota que indica cambiarapi_keyen el archivo. - Escribe la lista blanca automática en el directorio de estado: la dirección desde la que venía tu sesión SSH, más cada dirección de interfaz del host. La dirección de sesión de una ejecución anterior se conserva, para que una segunda ejecución desde otra sesión no retire la dirección en la que confiaba la primera. Las direcciones de interfaz se vuelven a determinar en cada ejecución, para que una dirección eliminada no se quede colgando.
- Genera el identificador de instalación, un UUID v4 aleatorio en
/var/lib/reportedip-agent/install-id. Esa es desde ese momento la identidad de este servidor frente a la API. El nombre del host no se usa nunca ni se transmite. - Advierte de dos cosas que no puede arreglar por ti: una tabla de nftables
inet reportedipque dejó el script de shell documentado mientras el motor resuelto es ipset, y un nginx que lee cabeceras de Cloudflare sinset_real_ip_from. La fuente web contaría entonces direcciones de Cloudflare en lugar de las del visitante. - Escribe las tres unidades de systemd en
/etc/systemd/system, ejecutadaemon-reload, activareportedip-agent-sync.service, y activa e iniciareportedip-agent-sync.timeryreportedip-agent.service. - Llama a
verify-keycon la configuración recién escrita y muestra tu rol y los informes consumidos hoy frente al límite diario. Un 401 o un 403 aquí significa que la clave está mal, y ese es el único fallo que conviene corregir de inmediato.
INPUT. Todo eso lo crea el primer
reportedip-agent sync, y por eso el instalador termina diciéndote que lo ejecutes.
Hasta entonces el host está exactamente como estaba, y una instalación que decidas descartar
cuesta un rm de dos directorios.
Las variables de entorno de install.sh
| Variable | Predeterminado | Para qué sirve |
|---|---|---|
REPORTEDIP_KEY | ninguna, obligatoria | Tu clave API. Sin ella el script se detiene antes de descargar nada. |
REPORTEDIP_VERSION | la versión actual | Fija una versión, para un despliegue por fases o para reinstalar una versión concreta. Los directorios de versiones antiguas se conservan en el punto de distribución exactamente para eso. |
REPORTEDIP_PREFIX | /usr/local/bin | Adónde va el binario. Si lo cambias, las unidades de systemd tienen que seguirlo, porque nombran la ruta absoluta. |
REPORTEDIP_BASE | https://reportedip.com/agent | La base de descarga. Para un espejo interno que sirva latest, SHA256SUMS, SHA256SUMS.sig y los dos binarios. La comprobación de firma se aplica allí igual. |
REPORTEDIP_NO_SETUP | sin definir | 1 deja el binario y se detiene. No se detecta nada, no se escribe ninguna configuración y no se instala ninguna unidad. Es la variable para una imagen base, una capa de contenedor o una ejecución de gestión de configuración que posee ella misma el archivo de configuración. |
# Binary only, no host setup: for an image or for Ansible
curl -fsSL https://reportedip.com/agent/install.sh \
| REPORTEDIP_KEY=<YOUR-KEY> REPORTEDIP_NO_SETUP=1 sh
# Later, on the running host, or from your playbook:
reportedip-agent install --key "<YOUR-KEY>"
Instalación a mano
El paso de configuración es un comando propio y acepta tres opciones. Ejecútalo cuando
REPORTEDIP_NO_SETUP=1 solo haya dejado el binario, o cuando la detección haya adivinado
algo que quieras corregir.
reportedip-agent install --key "<YOUR-KEY>"
# The detection could not find the SSH port (socket activation,
# a non-standard unit, a port from an include file):
reportedip-agent install --key "<YOUR-KEY>" --ssh-port 2222
# The session address is not where you administer this host from
# (a jump host, a console, a serial connection):
reportedip-agent install --key "<YOUR-KEY>" --admin-ip 203.0.113.0/24
--admin-ip acepta una sola dirección o un rango CIDR y sustituye por completo la
suposición. Merece la pena siempre que la sesión SSH no sea representativa, porque la dirección
adivinada es lo que se interpone entre tú y tu propia regla de bloqueo el primer día. Toda la
ejecución de instalación tiene un plazo de cinco minutos, así que un nft o un
systemctl colgado termina en un error y no en un proceso que tengas que interrumpir.
Un host sin systemd
Nada del agente depende de systemd. Las unidades son una comodidad, y doctor lo dice en
vez de fallar cuando no se pueden usar. En un host así hay que preparar dos cosas a mano.
# 1. The feed sync, from cron. Every 15 minutes is safe on any
# plan: the agent skips a fetch its own plan interval does
# not allow yet, so nothing is requested too often.
*/15 * * * * root /usr/local/bin/reportedip-agent sync >/dev/null 2>&1
@reboot root sleep 120 && /usr/local/bin/reportedip-agent sync
# 2. The watch daemon, from your init system. It runs in the
# foreground, logs to stderr and stops on SIGTERM.
/usr/local/bin/reportedip-agent watch
La entrada para el arranque no es opcional. Los plazos de caducidad de ipset no sobreviven a un
reinicio, y los conjuntos tampoco: sin una sincronización tras el arranque, el host vuelve a levantarse
sin reglas y sin bloqueos restaurados. En un host con systemd ese camino lo cubre
reportedip-agent-sync.service, activado para multi-user.target, que se
ejecuta después de cada servicio de cortafuegos y sin desplazamiento aleatorio. En un host sin
journald deja log_file definido, porque allí stderr puede no llevar a ninguna parte.
La primera hora, en orden
El orden de abajo no es una sugerencia. Cada paso responde a una pregunta de la que depende el siguiente, y otro orden es la forma en que una instalación que funciona parece estropeada.
reportedip-agent doctor. Antes que todo lo demás, porque es el único comando que no cambia nada. Lo que nombre como limitación es una limitación que, si no, volverás a descubrir como síntoma dos días más tarde.reportedip-agent sync. La primera ejecución construye la cadena, crea los conjuntos y descarga las cinco listas. Tarda un momento: las listas se obtienen una tras otra con una pausa breve entre ellas.reportedip-agent status. Ahora hay algo que mirar. Comprueba que cada lista configurada tenga un número de direcciones IPv4 verosímil, que la cadena indiqueok, que la lista blanca contenga las direcciones que esperas, y queaccountmuestre tu rol y el estado de licencia de este host.- Corregir la lista blanca. El instalador incluyó la dirección desde la que venía tu sesión SSH. Eso es una suposición. Sustitúyela por el rango desde el que administras realmente, y añade de paso tu monitorización, tu host de copias de seguridad y el rango de tu oficina.
- Esperar y volver a leer
status. Un día basta, dos es mejor. Enmode: loglas reglas están construidas por completo y se aplican, solo que registran en vez de descartar. El registro del núcleo te muestra por tanto exactamente qué habría cortadodrop. - Pasar a
mode: dropy ejecutarsync, que es lo que reconstruye las reglas. - Solo después considerar
ban.enabled: truey reiniciar a continuación el servicio de vigilancia. Ese es el segundo interruptor, y se refiere a tus propios registros en lugar de la lista comunitaria.
reportedip-agent doctor
reportedip-agent sync
reportedip-agent status
reportedip-agent whitelist add 203.0.113.0/24 "management"
reportedip-agent whitelist add 198.51.100.7 "monitoring"
reportedip-agent whitelist list
mode: log, y eso es intencionado. No se
descarta nada hasta que tú lo decidas, lo que te da dos días para advertir el rango que todavía
falta en la lista blanca. Primero la lista blanca, después drop: el orden inverso
también funciona, justo hasta la única vez en que no.
Configuración
Un archivo, /etc/reportedip-agent/config.yaml, propiedad de root y con modo 0600. El
agente se niega a arrancar cuando es legible por el grupo o por otros, y lo dice junto con el comando
chmod que lo corrige, porque ese archivo contiene tu clave API. El estado vive en
/var/lib/reportedip-agent: posiciones de lectura, la cola de informes, la memoria de
deduplicación, el registro de bloqueos, el estado de salud y el estado de actualización.
El archivo que escribe el instalador ya encaja con el host. La mayoría de las instalaciones cambian
dos cosas en su primera semana, el valor de mode y si el bloqueo local está activo, y
nada más después.
window_minutes que se descartara sin ruido te dejaría creyendo en un umbral que no
tienes. Todas las claves válidas están más abajo, y una clave que no figure en esa lista no
existe. Comprueba el archivo después de cada cambio antes de confiar en él:
reportedip-agent status termina con el código 2 y nombra la clave cuando el archivo
no se puede leer.
Cada clave de config.yaml
Aquí nada es obligatorio salvo api_key. Cada otra clave tiene el valor predeterminado que
se indica, y una clave que omitas mantiene ese valor.
| Clave | Predeterminado | Efecto |
|---|---|---|
api_key | ninguno | Tu clave API. La única clave sin valor predeterminado. REPLACE_ME cuenta como ausente. |
api_url | https://reportedip.com/wp-json/reportedip/v2 | La base REST. Tiene que ser una URL https absoluta; el http simple solo se admite en el bucle local, para un proxy local. |
update_url | derivado de api_url | De dónde vienen los binarios del agente. Sin indicación son el esquema y el host de api_url con la ruta /agent. Se define para un espejo, que también tiene que ser https. |
auto_update | true | Buscar una versión más reciente durante la sincronización e instalarla. Ponlo en false cuando la versión de este host se decida en otro sitio, por ejemplo con tu propio paquete o con un despliegue canario. reportedip-agent update sigue funcionando a mano, y status sigue informando de cuánto retraso lleva el host. |
log_level | info | info o debug. Nada más. |
log_file | /var/log/reportedip-agent.log | El registro propio del agente, además de stderr, para que journald conserve todo lo que tiene hoy. Vacío desactiva el archivo. Solo la sincronización y el demonio de vigilancia escriben ahí. |
log_max_mb | 10 | Tamaño al que el agente rota su propio archivo, comprobado en cada escritura. Rango de 1 a 1024. |
log_keep | 3 | Generaciones rotadas que se conservan, rango de 0 a 20. Con los valores predeterminados el peor caso en disco son 40 MB. No hace falta ningún fragmento de logrotate, y tampoco estorba ninguno. |
backend | auto | auto, ipset o nftables. Véase más arriba. |
mode | log | log, drop u off. Se aplica a las reglas, o sea a las listas del feed y a los bloqueos locales. |
confidence | 90 | El umbral del feed, de 1 a 100. Más bajo significa una lista más larga y más casos límite. El servidor indica un mínimo para tu plan, 75 desde Professional y 90 en Contributor, y de los dos valores gana el más alto. |
limit | 50000 | Número máximo de direcciones por petición de lista, de 1 a 50000. |
lists | ssh, edge | Qué listas del feed están activas. ssh y edge están siempre activas y no se pueden desactivar. lists.ssh.ports es la única opción por lista; las demás listas tienen juegos de puertos fijos. |
ports | del proceso real que escucha | Dentro de lists.ssh: los puertos a los que se aplica la regla ssh. El instalador los escribe a partir del proceso detectado. Una lista vacía hace que la regla se aplique a todos los puertos. |
min_entries | ssh 1000, mail 500, web 500, ftp 500, edge 1000 | El mínimo de verosimilitud por lista: por debajo de ese número de direcciones la lista no se cambia. Al menos 1, porque un mínimo de cero dejaría pasar a producción una lista vacía. |
whitelist_file | /etc/reportedip-agent/whitelist.conf | Tu lista de «nunca bloquear, nunca notificar». Una dirección o un CIDR por línea, # inicia un comentario. Una línea rota se omite con una advertencia y nunca desactiva la protección. |
sources | una entrada sshd | Las fuentes de registro. Un archivo con una clave sources decide solo, así que una entrada tuya tiene que ir junto a las del instalador y no en su lugar. |
type | ninguno | Dentro de una fuente: uno de los trece tipos de fuente. Un tipo desconocido es un error que enumera los válidos. |
path | ninguno | Dentro de una fuente: un archivo de registro. Define path o glob, nunca ambos. Sin ninguno de los dos, el agente elige el canal él mismo, journald o el archivo predeterminado de la distribución. |
glob | ninguno | Dentro de una fuente: un patrón sobre varios archivos, reevaluado cada diez minutos. Como máximo cinco segmentos con comodín. |
exclude | ninguno | Dentro de una fuente: patrones que retiran archivos que un patrón ha captado. Patrones de tipo shell, no expresiones regulares. Un patrón sin barra coincide solo con el nombre del archivo; uno con barra tiene que cubrir la ruta completa. Todo patrón que no coincida con nada se advierte al arrancar. |
poll_minutes | 5 | Dentro de una fuente: solo para imunify360, que consulta un comando en vez de leer un archivo. Rango de 1 a 60. En cualquier otro tipo es un error. |
thresholds | vacío | Sustituye la tabla interna por fuente de evento. Véase la sección siguiente. |
hits, window_minutes | véase la tabla de abajo | Dentro de una entrada de thresholds: el par que significa «tantas coincidencias en tantos minutos». |
min_hits, window_minutes | 5, 10 | El par global para toda fuente sin entrada propia en la tabla interna. |
web_min_hits, web_window_minutes | 50, 120 | El mismo par para los registros de acceso web, que necesitan volumen en lugar de un código de estado. |
dedup_hours | 6 | La misma dirección se notifica como máximo una vez por ventana de esta duración. |
queue_max | 5000 | Archivos de informe en espera. Por encima se descartan los más antiguos. |
disk_min_mb | 200 | Mebibytes libres exigidos allí donde está el directorio de estado. Por debajo no se pone en cola ningún informe nuevo mientras la detección y el bloqueo continúan. 0 desactiva el límite. |
jail_categories | tabla interna | Correspondencia adicional entre un nombre de jail de fail2ban e identificadores de Threat Categories, de 1 a 58. Solo relevante mientras fail2ban siga siendo una fuente. |
ban | desactivado | El bloque de bloqueos locales, con enabled, time_minutes, max_time_minutes, escalate y memory_hours. Véase Bloqueo. |
notify | sin correo | El bloque de correo, con email, cooldown_hours y el subbloque smtp (host, port, from, user, password, starttls). |
Un archivo completo, con cada bloque que usa un host normal:
# /etc/reportedip-agent/config.yaml root:root 0600
api_key: "YOUR_API_KEY"
api_url: https://reportedip.com/wp-json/reportedip/v2
log_level: info # info | debug
log_file: /var/log/reportedip-agent.log
log_max_mb: 10
log_keep: 3
auto_update: true
backend: auto # auto | ipset | nftables
mode: log # log | drop | off
confidence: 90 # the feed threshold
limit: 50000
lists: # ssh and edge are always on
ssh:
ports: [22] # written by install from the real listener
edge: {}
mail: {}
web: {}
min_entries: # below this many addresses a list is not applied
ssh: 1000
mail: 500
web: 500
ftp: 500
edge: 1000
whitelist_file: /etc/reportedip-agent/whitelist.conf
min_hits: 5 # the global detection pair
window_minutes: 10
web_min_hits: 50 # web access logs use their own
web_window_minutes: 120
dedup_hours: 6
queue_max: 5000
disk_min_mb: 200
sources: # detected at install time
- type: sshd
- type: web
glob: /var/www/*/log/access.log
- type: web-error
path: /var/log/nginx/error.log
- type: postfix
path: /var/log/mail.log
- type: dovecot
path: /var/log/mail.log
thresholds: {} # per source overrides, see below
ban: # block local finds, not only the feed
enabled: false
time_minutes: 30
max_time_minutes: 10080
escalate: 4
memory_hours: 24
notify:
email: "" # empty means no mail
cooldown_hours: 24
Límites de detección, por fuente
Un umbral es un par: cuántas coincidencias, dentro de cuántos minutos. Es la misma idea que
maxretry y findtime de un jail de fail2ban, y los valores de abajo proceden
de la configuración de jails del host en el que se midió todo esto. Desactivar fail2ban no cambia por
tanto la sensibilidad de un servicio.
Tres niveles deciden qué par se aplica a una fuente de evento, y gana el primero que tenga respuesta:
una entrada explícita en thresholds, luego la tabla interna, luego el par global.
| Fuente de evento | Umbral | Procedencia |
|---|---|---|
sshd | 5 en 10 min | min_hits / window_minutes |
web-error | 5 en 10 min | min_hits / window_minutes |
modsec | 5 en 10 min | min_hits / window_minutes |
exim | 5 en 10 min | min_hits / window_minutes |
web | 50 en 120 min | web_min_hits / web_window_minutes |
web-app | 20 en 60 min | interno |
postfix-sasl | 3 en 60 min | interno, medido |
postfix-reject | 5 en 60 min | interno, solo rechazos 5xx |
postfix-amavis | 3 en 60 min | interno |
dovecot | 10 en 60 min | interno, medido |
ftp | 20 en 60 min | interno |
named | 20 en 30 min | interno |
panel | 5 en 60 min | interno |
fail2ban, csf, imunify360 | sin umbral | Esa fuente ya ha contado, así que una línea es un informe. |
Para cambiar una fuente y nada más, nómbrala en thresholds. La clave es la fuente de
evento de la tabla de arriba, no el tipo de fuente de sources, y un nombre que no figure
en esa lista es un error en lugar de un ajuste que nunca se aplica. Los dos números tienen que ser al
menos 1.
# A recursive resolver that sees a lot of refused queries:
# fewer minutes, more hits, and nothing else changes.
thresholds:
named:
hits: 40
window_minutes: 5
# A mail server whose customers keep mistyping passwords:
# SASL a little more forgiving, Dovecot untouched.
thresholds:
postfix-sasl:
hits: 6
window_minutes: 60
Un umbral más alto hace al agente más discreto y deja pasar a los atacantes lentos. Uno más bajo es la dirección que cuesta algo: por debajo de unas tres coincidencias por hora en una fuente de correo o web acabarás notificando a un cliente con una contraseña mal puesta en su programa de correo, y un informe es público dentro de la comunidad. El par web es alto justo por eso, porque un WordPress normal responde a una contraseña equivocada con un HTTP 200 y el detector no puede apoyarse en el código de estado.
Hay otra magnitud que no es configurable. Una dirección con historial de bloqueos necesita menos coincidencias que una desconocida, y esa es la misma ponderación que tenía el jail recidive: un bloqueo anterior cuenta una línea el doble, dos la cuentan el triple, luego cinco y nueve veces. El peso se recorta al umbral de su fuente de evento, así que un décimo bloqueo no convierte una sola línea en un informe.
Límites de bloqueo
Cuatro valores, todos en el bloque ban, todos en la unidad que nombra la clave. Solo se
aplican a direcciones que el agente ha encontrado en tus propios registros. La lista comunitaria no
tiene ningún tiempo de bloqueo, se reemplaza por completo en cada pasada.
| Clave | Predeterminado | Significado, y qué pasa en los extremos |
|---|---|---|
enabled | false | Si los hallazgos locales se bloquean en absoluto. false es un host que notifica y no bloquea. Un ban add manual funciona en ambos casos. |
time_minutes | 30 | El primer bloqueo de una dirección. Al menos 1. Valores muy cortos hacen de la escalada lo único relevante; valores muy largos significan que un falso positivo se queda mucho tiempo en el conjunto. |
max_time_minutes | 10080 (7 días) | El techo de la escalada. No debe estar por debajo de time_minutes, o el techo acortaría el primer bloqueo, y el agente rechaza un archivo así. Bajarlo más tarde acorta también los bloqueos en curso: la sincronización siguiente los restaura recortados al techo de hoy. |
escalate | 4 | El multiplicador por reincidencia dentro de la ventana de memoria. Al menos 1, y 1 significa que cada bloqueo dura time_minutes y no hay escalada alguna. |
memory_hours | 24 | Cuánto tiempo cuenta un bloqueo para la escalada siguiente. Al menos 1. Un registro se conserva además el triple de la duración de su último bloqueo cuando eso es más largo, y eso es precisamente lo que hace alcanzable el techo sin que los bloqueos cortos hagan crecer el archivo. |
# Careful: a short first ban, a low cap, a long memory.
ban:
enabled: true
time_minutes: 10
max_time_minutes: 1440 # one day
escalate: 3
memory_hours: 72
# Strict: a long first ban and a hard escalation.
ban:
enabled: true
time_minutes: 60
max_time_minutes: 43200 # thirty days
escalate: 6
memory_hours: 168 # a week
Límites de funcionamiento
Estos valores acotan lo que consume el propio agente. No son ajustes de detección, y los dos que de verdad querrás cambiar algún día son el tamaño de la cola y el límite de disco.
| Clave | Predeterminado | Unidad | Demasiado bajo | Demasiado alto |
|---|---|---|---|---|
queue_max | 5000 | archivos en espera | Se pierden informes durante una caída de la API, los más antiguos primero. | Una caída larga deja miles de archivos pequeños por enviar después. |
dedup_hours | 6 | horas | El mismo atacante se notifica una y otra vez y se come tu cuota diaria. | Una dirección que vuelva a atacar la semana que viene se notifica tarde. |
disk_min_mb | 200 | MB libres | El agente puede contribuir a llenar /var, y eso se lleva al host entero. | Los informes se detienen en un host perfectamente sano. 0 retira el límite. |
log_max_mb | 10 | MB | Rotación en cada segunda escritura y un historial demasiado corto para investigar nada. | Junto con log_keep es el peor caso en disco. |
log_keep | 3 | generaciones | 0 no conserva ningún archivo rotado. | Como máximo 20, y el coste en disco es el producto de los dos valores. |
cooldown_hours | 24 | horas | Al menos 1, y 1 significa un correo por sincronización para un estado que sigue abierto. | Un problema de esta mañana se notifica mañana. |
poll_minutes | 5 | minutos | Un comando lanzado demasiado a menudo en un host cargado. | Como máximo 60, y los hallazgos llegan más tarde en la misma medida. |
limit | 50000 | direcciones por lista | Una lista truncada: las peores direcciones están, el resto no. | 50000 es el máximo que sirve la API. |
min_entries | 1000 / 500 | direcciones | Una lista de brevedad inverosímil pasa a producción y el host está peor protegido de lo que cree. | Una lista realmente pequeña se rechaza de forma permanente, y el conjunto se queda en su generación anterior. |
Por debajo del límite de disco, el agente no pone en cola ningún informe nuevo, anota el estado y lo
envía por correo, y sigue detectando y bloqueando. Ese orden tiene un motivo: un /var
lleno es peor que un informe perdido, y proteger un servidor rompiéndolo no es protección.
Un correo cuando algo va mal
Un notify.email vacío es el valor predeterminado y significa que este host no envía nada.
Lo que se notifica es un estado y no un evento, así que un feed que falla cada hora es un mensaje y no
veinticuatro. El agente lleva ocho estados (feed, api_key, chain, disk, source, update, queue,
license), recuerda cuándo se notificó cada uno por última vez para que un reinicio no empiece de
nuevo, y envía un aviso de fin cuando uno se ha terminado.
# A host with a local MTA: postfix, exim or msmtp. The agent
# uses "sendmail -t -i" and no password lives in a file.
notify:
email: "ops@example.org"
cooldown_hours: 24
# A host without an MTA: SMTP, and starttls stays on.
notify:
email: "ops@example.org"
cooldown_hours: 12
smtp:
host: mail.example.org
port: 587
from: agent@example.org
user: agent@example.org
password: "..."
starttls: true
Con starttls: true el correo se rechaza en lugar de enviarse en claro si el servidor no
ofrece STARTTLS. Un correo lleva el nombre del estado, la versión, una frase genérica con números y el
comando con el que mirar. Nunca una línea de registro, nunca una dirección, nunca una carga útil.
Ejecuta reportedip-agent doctor después de esta configuración: la sección
mail dice si un correo podría salir realmente del host, y esa es una pregunta distinta de
si la configuración se lee bien.
Cuándo tiene efecto un cambio
El agente lee su configuración al arrancar y no vigila el archivo, y no existe ninguna señal de recarga. El comando necesario depende de la parte que hayas tocado.
| Cambiado | Qué lo hace efectivo |
|---|---|
mode, lists, min_entries, confidence, limit, backend | reportedip-agent sync. La sincronización construye la cadena y las reglas, así que un modo nuevo está activo al final de ella. |
thresholds, min_hits, window_minutes, web_min_hits, web_window_minutes, dedup_hours, ban, notify, log_* | systemctl restart reportedip-agent.service. Eso es lo que lee el demonio de vigilancia. |
Una fuente añadida a sources | systemctl restart reportedip-agent.service. |
Una fuente retirada de sources | Una parada y un arranque, no un reinicio. En un host en producción un systemctl restart no bastó y el demonio siguió leyendo el archivo retirado: systemctl stop reportedip-agent.service && systemctl start reportedip-agent.service. |
El contenido de whitelist_file | Nada, si usas reportedip-agent whitelist add: eso escribe el archivo y el conjunto del núcleo en un paso y tiene efecto de inmediato. Editar el archivo a mano requiere un sync. |
api_key, api_url, auto_update, update_url | Nada para el siguiente sync, que es de tipo oneshot y vuelve a leer el archivo. Reinicia el servicio de vigilancia para que la vía de notificación también tome la clave. |
# The safe sequence after any edit
reportedip-agent status >/dev/null && echo "config parses"
reportedip-agent sync
systemctl restart reportedip-agent.service
systemctl status reportedip-agent.service --no-pager
El feed comunitario
Esta es la parte que la página Bloqueo a nivel de red describe como un script de shell. El agente hace lo mismo, con las partes que es fácil equivocar cuando uno lo escribe por su cuenta ya resueltas: una petición condicional por lista para que una lista sin cambios no cueste nada, una comprobación de tamaño antes de que algo pase a producción, un cambio atómico para que no haya nunca una ventana con un conjunto vacío, y reglas ligadas a puertos para que un atacante web no deje a nadie fuera de SSH.
La correspondencia de categorías es la misma que en esa página. Cuáles de las cinco listas están activas depende de lo que escuche realmente, y no hay una lista unificada, porque un conjunto por servicio es lo que hace posible la regla ligada a puertos.
| Lista | Conjunto del núcleo | Puertos a los que se aplica la regla |
|---|---|---|
ssh | rip-ssh, rip-ssh-v6 | de lists.ssh.ports |
mail | rip-mail, rip-mail-v6 | 25, 465, 587, 110, 995, 143, 993 |
web | rip-web, rip-web-v6 | 80, 443 |
ftp | rip-ftp, rip-ftp-v6 | 21 |
edge | rip-edge, rip-edge-v6 | todos los puertos, a propósito |
Con qué frecuencia se descarga el feed depende de tu plan, y lo decide el servidor, no el agente. En un servidor con licencia el intervalo es de 15 minutos con una confianza mínima de 75 desde Professional, y de una hora con una confianza mínima de 90 en Contributor. Business y Enterprise se comportan como Professional. El agente pregunta en cada pasada qué valores se aplican a este host y sigue la respuesta, así que una mejora de plan tiene efecto sin que nadie edite un archivo. No existe ninguna clave de configuración para el intervalo, y una clave que te inventes para eso detiene el agente con el código 2.
Encima de eso hay dos reglas más. Una lista descargada hace menos de 15 minutos no se vuelve a
descargar, porque ese es el tiempo que el servidor la mantiene en caché. Ejecutar sync a
mano está por tanto siempre permitido y nunca es perjudicial. Y cada descarga es condicional: una
lista sin cambios responde 304 y cuesta una petición y ninguna transferencia, y por eso un intervalo
corto es económico para ambas partes.
Qué encuentra el agente en tus registros
Cada fuente tiene su propio detector y su propio umbral, porque cinco inicios de sesión SSH fallidos
en diez minutos y cincuenta peticiones web sospechosas en dos horas son la misma afirmación sobre un
atacante y no el mismo número. La rotación, copytruncate y un registro que desaparece un
rato están cubiertos, y un archivo ilegible produce una advertencia y no una por lectura.
Los trece tipos de fuente
| Tipo | Ruta típica | Qué cuenta como coincidencia |
|---|---|---|
sshd | journald, si no /var/log/auth.log o /var/log/secure | Contraseñas equivocadas, usuarios inválidos, claves rechazadas para un usuario que no existe, límites de intentos superados, y los fallos previos a la autenticación que caracterizan a los escáneres: ninguna cadena de identificación, una versión de protocolo errónea, datos inservibles en el intercambio de banner. Una clave rechazada para un usuario válido no cuenta a propósito, porque un administrador con cinco claves escribe cuatro de esas por cada inicio de sesión correcto. Un inicio de sesión correcto borra los fallos de esa dirección. |
web | /var/log/nginx/access.log o un patrón por sitio | Los POST de inicio de sesión, contados sea cual sea el código de estado, y las rutas que ningún cliente legítimo pide. Todo lo demás de un registro de acceso se ignora, porque esta es la fuente que tiene clientes de verdad detrás. |
web-error | /var/log/nginx/error.log, /var/log/apache2/error.log | Las peticiones que el propio servidor web ha rechazado, los fallos de autenticación HTTP básica, y las líneas de ModSecurity de gravedad crítica cuando caen aquí en lugar de en un registro de auditoría. Las líneas de limitación de tasa no cuentan a propósito: un límite se dispara también con un navegador real con veinte pestañas. Los mensajes de negociación TLS y de PHP dicen algo del servidor y nada del cliente. |
web-app | los mismos registros de acceso | Se ejecuta después de web sobre las mismas líneas y cubre los ataques a nivel de aplicación: enumeración de usuarios de WordPress, sondeo de plugins y del núcleo, rutas de inicio de sesión y de explotación de Drupal. Un POST de inicio de sesión se cuenta una vez, por web. |
postfix | /var/log/mail.log, /var/log/maillog | Tres fuentes de evento distintas a partir de un solo archivo: los fallos de autenticación SASL, los rechazos NOQUEUE que son realmente 5xx (un 450 es greylisting y no cuenta), y los bloqueos de amavis. Un correo entregado nunca es una coincidencia. |
dovecot | el mismo registro de correo | Fallos de inicio de sesión IMAP y POP3, uno por línea, diga lo que diga «N attempts». La dirección remota viene del campo rip=, nunca de lip=, que es la dirección propia del servidor. Un inicio de sesión abortado sin intento de autenticación no es una coincidencia: eso es un escaneo TLS, y no se probó ninguna contraseña. |
exim | /var/log/exim4/mainlog, /var/log/exim/main.log | Fallos de autenticación y remitentes rechazados. No verificado contra un host real: los patrones vienen del formato de registro documentado. |
ftp | /var/log/syslog, /var/log/messages | Fallos de autenticación de pure-ftpd, proftpd y vsftpd. Los tres registran a través de syslog, y cada uno escribe el par de forma distinta, así que se usan tres patrones. pure-ftpd está medido: 494 de 494 líneas de un host de producción tenían una única forma. |
named | el mismo syslog | Solo una consulta entrante que bind ha rechazado. La gran trampa es el sentido inverso: connection refused resolving es el resolvedor de este host que no alcanza el servidor de nombres de otra persona, y el 90 por ciento de las líneas medidas eran eso. Un detector sin esa excepción notifica los servidores de nombres ajenos como atacantes. |
panel | /var/log/ispconfig/auth.log | Inicios de sesión fallidos en el panel de ISPConfig. El archivo tiene 0 bytes en todos los hosts medidos, y eso no es un defecto: el panel simplemente no escribe nada ahí. doctor lo señala al cabo de una semana. |
modsec | /var/log/apache2/modsec_audit.log | Un evento por transacción cuyo veredicto lleva gravedad crítica. Es el único detector con estado, porque una transacción abarca varias líneas. Nada de la petición viaja en el evento: ni el identificador de regla, ni la parte coincidente, ni la URI. |
csf | /var/log/lfd.log | Lo que CSF ha bloqueado realmente, nunca lo que solo ha detectado. Sin un segundo umbral por encima: lfd ya había contado antes de que el agente viera la línea, así que un bloqueo es un informe. |
fail2ban | /var/log/fail2ban.log, si no el diario de la unidad | Solo acciones de bloqueo. Un Restore Ban nunca se notifica, porque eso es lo que un reinicio de fail2ban reproduce desde su propia base de datos y no un ataque nuevo. Una línea Found de un filtro tampoco se notifica: el jail aún no ha decidido. |
imunify360 | consulta imunify360-agent | Incidentes del CLI, asociados a categorías por el prefijo del nombre de regla. Experimental y no verificado contra una instalación con licencia; el decodificador omite lo que no puede leer en vez de convertir una respuesta inesperada en una fuente muerta. |
Añadir una fuente que el instalador no encontró
Una fuente que el instalador no encontró simplemente falta en la configuración, y añadirla es una
entrada con una ruta o un patrón. Como un archivo con un bloque sources sustituye por
completo la lista predeterminada, tu entrada va junto a las existentes y no en un segundo bloque.
sources:
- type: sshd
- type: web
path: /var/log/nginx/access.log
# A second web server on a different path
- type: web
path: /var/log/caddy/access.log
# Every site of a panel host, rescanned every ten minutes.
# At most five wildcard segments.
- type: web
glob: /var/www/clients/*/web*/log/access.log
exclude:
# This site reports to the API on its own, through Hive or a
# honeypot. Without the exclude the host sends every address
# twice and pays twice out of the daily quota.
- /var/log/ispconfig/httpd/honeypot.example.com/*
# Exim on a host the installer saw as a Postfix machine
- type: exim
path: /var/log/exim4/mainlog
Dos rutas que apuntan al mismo archivo no son un problema y no necesitan exclude: ISPConfig publica
cada registro de acceso dos veces, en /var/www y en /var/log/ispconfig, y el
agente lee un archivo así una sola vez. Antes de confiar en una entrada nueva, apunta
reportedip-agent test al archivo, reinicia después el servicio de vigilancia y mira en
status: la sección sources muestra las coincidencias por fuente, y una
fuente con files= a cero no se resuelve en nada.
Qué sale de la máquina
Un informe contiene la dirección, los identificadores de las Threat Categories y una frase generada. Eso es todo. Ninguna línea de registro, ningún nombre de usuario, ningún cuerpo de petición, ningún User-Agent y ninguna URL se transmite nunca, así que un informe no puede filtrar tus clientes, tus rutas ni tus credenciales, ni siquiera por accidente.
En la versión actual el agente no envía ningún nombre de host. Un servidor se identifica ante la API con un UUID aleatorio generado durante la instalación, y esa es la identidad que ves en tu cuenta. Le pones una etiqueta allí si quieres reconocerlo; la etiqueta se queda en la cuenta y nunca se deriva de la máquina.
Cuatro cosas no se notifican nunca y no se bloquean nunca, y eso es una frontera en el código y no un ajuste: los rangos privados y reservados, el bucle local, cada dirección de las interfaces de este servidor junto con su puerta de enlace predeterminada, y todo lo que esté en tu lista blanca. La dirección del cliente SSH tomada en la instalación está en la lista blanca automática y pertenece por tanto al último grupo.
Bloqueo
El agente escribe dos cosas muy distintas en el núcleo, y quien viene de fail2ban suele confundirlas la primera semana. Tienen orígenes distintos, duraciones distintas e interruptores distintos, así que merece la pena separarlas una vez y bien.
Dos clases de conjunto, dos duraciones
| Community Blacklist | Hallazgos locales | |
|---|---|---|
| Conjuntos | rip-ssh, rip-mail, rip-web, rip-ftp, rip-edge, y un conjunto -v6 para cada uno | rip-local y rip-local-v6 |
| De dónde vienen las direcciones | De cada informante de la comunidad, filtradas por tu confidence | Solo de los registros de este host |
| Cómo entra una entrada | El conjunto entero se reemplaza en cada pasada del feed | Una dirección a la vez, cuando se alcanza un umbral |
| Cómo desaparece una entrada | En el cambio siguiente, cuando el feed ya no la incluye | El núcleo la hace caducar cuando se agota su propio plazo |
| Plazo por entrada | ninguno | sí, es todo el diseño |
| Interruptor | mode | ban.enabled, y mode por encima |
| Necesita licencia | sí, es la parte que se paga | no |
El cambio del feed no toca nunca rip-local. Los conjuntos del feed se reemplazan
completos y no llevan plazo; los bloqueos locales llegan de uno en uno y caducan por sí solos. Esa
separación explica por qué una caída del feed no puede levantar tus bloqueos locales, y por qué un
bloqueo local no sobrevive como huérfano después de reconstruir el conjunto del feed.
Cómo empieza un bloqueo local y cómo termina
Un bloqueo empieza cuando el contador de un detector para una dirección supera el umbral de su fuente de evento. Lo que pasa entonces, por orden:
- La lista blanca se comprueba primero, antes incluso de preguntar cualquier otra cosa. Una
dirección en la lista blanca no deja ningún rastro: nunca encontrarás tu propio rango de
administración en
reportedip-agent ban listpreguntándote si estuvo bloqueado. La comprobación indica qué capa respondió: un rango reservado interno, una de las direcciones propias de este host, o tu archivo. - Se consulta al registro de bloqueos,
/var/lib/reportedip-agent/bans.json, el tiempo de bloqueo. Guarda la dirección, cuándo caduca el bloqueo, cuántos bloqueos cayeron en la ventana de memoria, cuándo empezó el último, la fuente y las categorías. El archivo se escribe antes de la entrada en el núcleo, así que un fallo justo después de escribir en el núcleo no puede perder el registro que hace caducar el bloqueo. - La dirección entra en
rip-localcon ese tiempo como plazo por entrada. El núcleo la hace caducar. No hay temporizador de limpieza, ni tarea de cron, ni cola de desbloqueo, y por eso un agente caído o eliminado no deja ningún bloqueo permanente. - Se escribe una línea en el registro con la dirección, el tiempo de bloqueo, la fuente, cuántos bloqueos tiene esa dirección en la ventana, el desfase entre la línea de registro y la entrada en el núcleo, y los dos comandos que lo deshacen.
Un bloqueo en curso no se acorta nunca. Cuando llega una coincidencia nueva para una dirección que ya
está bloqueada, no pasa nada: tampoco es una escalada, porque la escalada cuenta bloqueos y no
coincidencias, exactamente como el jail recidive al que sustituye. Una dirección que ya está bloqueada
no puede bloquearse otra vez. Esa regla también mantiene honesto el periodo de transición, cuando el
agente ve un ataque dos veces, una como línea cruda de auth.log y otra como línea de
bloqueo de fail2ban.log.
Una línea de registro reproducida tampoco produce un bloqueo. Una coincidencia cuya marca de tiempo no sea más reciente que el último bloqueo de esa dirección es una relectura tras una parada brusca y no una nueva infracción, y la vía de notificación tiene la misma protección en su archivo de deduplicación.
El estado sobrevive a más que un reinicio. Cada sincronización compara bans.json con lo
que el núcleo tiene realmente y vuelve a poner lo que falta, con el tiempo restante:
- Tras un reinicio de la máquina son todos, porque ni un plazo de ipset ni el
propio conjunto sobreviven a un reinicio. Por eso el servicio de sincronización está activado para
multi-user.targety se ejecuta después de cada servicio de cortafuegos. - En funcionamiento normal es lo que se llevó el conjunto: un
firewall-cmd --reload, uncsf -r, unipset flush. Las entradas que el núcleo todavía tiene se dejan intactas, así que el hueco es una parte de un intervalo de sincronización y no el resto del bloqueo. El registro dice de forma explícita cuándo faltaban entradas bajo bloqueos activos, porque eso significa que algo ha modificado el conjunto por debajo del agente. - Un registro caducado no vuelve nunca. El atacante de ayer no es motivo para cortar a alguien hoy.
- La lista blanca se vuelve a comprobar en cada restauración. Puede haber crecido mientras el host estaba apagado, y una dirección que pusiste ayer en la lista blanca no debe volver a bloquearse por un registro del día anterior.
La escalada, con números reales
El primer bloqueo dura time_minutes. Cada reincidencia dentro de la ventana de memoria lo
multiplica por escalate, y max_time_minutes lo limita. Con los valores
predeterminados (30 minutos, multiplicador 4, techo de una semana), una dirección que vuelve una y
otra vez sigue este camino:
| Bloqueo | Cálculo | Duración |
|---|---|---|
| 1.º | time_minutes | 30 minutos |
| 2.º | 30 × 4 | 2 horas |
| 3.º | 120 × 4 | 8 horas |
| 4.º | 480 × 4 | 32 horas |
| 5.º | 1920 × 4 | 128 horas, cinco días y ocho horas |
| 6.º y siguientes | serían 512 horas, limitado | 7 días, el valor de max_time_minutes |
El cálculo concreto. Un atacante llama a tu puerto SSH el lunes a las 09:00 y alcanza cinco fallos en diez minutos: a las 09:07 recibe por tanto un bloqueo de 30 minutos que caduca a las 09:37. Vuelve a las 11:00 y se le bloquea de nuevo, esta vez dos horas, porque el primer bloqueo sigue dentro de la memoria de 24 horas. El tercer intento a las 14:00 cuesta ocho horas y lo lleva hasta las 22:00. El cuarto, a primera hora del martes, cuesta 32 horas. A partir de ahí el registro se conserva el triple de la duración del último bloqueo en lugar de las 24 horas redondas, y eso es precisamente lo que hace alcanzable el quinto escalón: un bloqueo largo vale una memoria larga, un bloqueo de 30 minutos solo el día configurado, y el archivo de bloqueos no crece por tanto a causa de bloqueos cortos.
Dos ajustes cambian la forma de esa curva. escalate: 1 desactiva la escalada por completo
y da la misma duración a cada bloqueo. Un memory_hours corto en relación con
time_minutes significa que una dirección vuelve a ser primeriza tras un día de pausa, y
eso es a veces justo lo adecuado para un servidor de correo con clientes de verdad.
Bajar max_time_minutes se aplica también a los bloqueos que ya están en curso. La
sincronización siguiente los restaura recortados al techo de hoy, porque quien baja el techo tras un
incidente espera que los bloqueos en curso lo sigan en vez de quedarse una semana en el núcleo.
La cadena en el núcleo
Con el motor ipset, el agente posee exactamente una cadena, rip-blacklist, y un salto
hacia ella. La cadena se vacía y se reconstruye en cada sincronización, así que su contenido es
siempre lo que dice la configuración, y el salto solo se inserta en la posición 1 de
INPUT si no está ya.
# What a sync builds, in this order:
iptables -N rip-blacklist
iptables -F rip-blacklist
iptables -A rip-blacklist -m set --match-set rip-whitelist src -j RETURN
iptables -A rip-blacklist -m set --match-set rip-local src -j DROP
iptables -A rip-blacklist -p tcp --dport 22 \
-m set --match-set rip-ssh src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 25,465,587,110,995,143,993 \
-m set --match-set rip-mail src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 80,443 \
-m set --match-set rip-web src -j DROP
iptables -A rip-blacklist -m set --match-set rip-edge src -j DROP
iptables -I INPUT 1 -j rip-blacklist
La lista blanca es la primera regla y retorna, no es la última regla. Un
RETURN por delante de todo lo demás significa que una dirección de la lista blanca sale
de la cadena antes de que se consulte ningún conjunto, así que está por encima de cada bloqueo,
incluido uno que ya esté en rip-local. Eso es lo que convierte a
whitelist add en una salida real de un cierre accidental, sin sincronización y sin
reinicio. Una lista blanca colocada al final sería una lista de excepciones que llegan demasiado
tarde, y en un host en el que te has quedado fuera, «demasiado tarde» es todo el problema.
La regla de los bloqueos locales no está ligada a ningún puerto, porque un host que
ha atacado tu servidor de correo tampoco tiene nada que hacer en el puerto 22. Las cinco reglas del
feed están ligadas a puertos: una dirección de la lista web se descarta por tanto en 80 y 443 y sigue
alcanzando SSH. Es intencionado: una dirección compartida, un NAT de operador o un proxy comprometido
en la lista web no deben dejar a un administrador fuera de la máquina. Solo edge se
aplica a todos los puertos, y eso es lo que significa la categoría que hay detrás.
Con el motor nftables la forma es la misma en versión nativa: una tabla inet reportedip,
una cadena colgada de input con prioridad -10 y política accept, primero las dos reglas
return de la lista blanca, luego rip-local, luego las listas del feed con
sus puertos. Los conjuntos de la lista blanca son conjuntos de intervalos para poder contener rangos
CIDR; los conjuntos locales llevan la opción de plazo, que las dos herramientas exigen en el conjunto
antes de que un elemento pueda llevar un plazo.
En mode: log se construyen las mismas reglas, y el veredicto es una línea de registro con
tasa limitada (seis por minuto, con el prefijo rip-<lista>:) en lugar de
DROP. Todo lo demás es idéntico, y por eso el modo de registro es un ensayo de verdad y
no una simulación.
mode y ban.enabled son dos interruptores
Este es el malentendido más frecuente, así que tiene su propio párrafo. mode decide qué
hacen las reglas. ban.enabled decide si tus propios hallazgos llegan a ser
alguna vez una entrada. Quien solo pone mode: drop aplica la lista comunitaria y no
obtiene nada de sus propios hallazgos.
mode | ban.enabled | Lista comunitaria | Tus propios hallazgos |
|---|---|---|---|
log | false | Se aplica y registra, no se descarta nada | Notificados a la comunidad, no bloqueados localmente |
log | true | Se aplica y registra | Anotados en rip-local y visibles en ban list, registrados en vez de descartados |
drop | false | Se descarta | Solo notificados. Es un estado permanente perfectamente razonable. |
drop | true | Se descarta | También se descartan, con escalada. La configuración completa. |
off | cualquiera | Nada en la vía de paquetes: el salto se retira y la cadena se queda con sus reglas | Anotados en el registro, sin efecto |
off es el interruptor de emergencia y es honesto al respecto. Retira lo que cuelga la
cadena de la vía de paquetes, hasta cinco veces para atrapar saltos duplicados, y se detiene con
estruendo si un salto sobrevive, porque decir «off» y seguir descartando es peor que un error. Los
conjuntos conservan sus entradas, así que volver atrás no requiere ninguna descarga completa.
Un bloqueo manual ignora los dos interruptores a propósito:
reportedip-agent ban add funciona incluso con ban.enabled: false, porque es
un acto deliberado de un operador, igual que lo era fail2ban-client set banip. La lista
blanca sigue aplicándose, y en un host sin motor de cortafuegos el comando se niega.
Pasar de log a drop
# 1. What would have been dropped? The rules already match in
# log mode, so ask the kernel log.
journalctl -k --since "24 hours ago" | grep -c "rip-"
journalctl -k --since "24 hours ago" | grep -o "rip-[a-z]*:" | sort | uniq -c
# 2. Is anything of yours in there? Check every source address
# against your whitelist before you switch.
reportedip-agent whitelist list
# 3. Switch, and rebuild the rules.
sed -i "s/^mode: log/mode: drop/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
reportedip-agent status
# 4. Only now the second switch, if you want it.
# ban.enabled: true, then restart the daemon.
systemctl restart reportedip-agent.service
reportedip-agent ban list
Si el paso 1 devuelve un número de cuatro cifras, es normal en un host expuesto y es la lista comunitaria haciendo su trabajo. Lo que importa no es la cantidad, sino si alguna dirección de ese registro te pertenece. Dos días en modo de registro bastan para averiguarlo, y la respuesta es aquí mucho más barata que después del cambio.
Mirar qué hay realmente en el núcleo
reportedip-agent status es el resumen, y lee tanto los archivos de estado como el núcleo
en marcha para que se puedan comparar los dos. Donde se contradicen, el núcleo es la verdad, y status
lo dice: un registro sin entrada en el núcleo es un bloqueo que no tiene efecto.
# The agent's own view
reportedip-agent status
reportedip-agent ban list
# ipset backend, straight from the kernel
ipset list -t rip-ssh # header and entry count only
ipset list rip-local # entries, each with its timeout
iptables -S rip-blacklist
iptables -L INPUT -n --line-numbers | head
# nftables backend
nft list table inet reportedip
nft list set inet reportedip rip-local
En ban list, la columna kernel es lo que el núcleo todavía tiene en el
contador y record lo que espera bans.json. Un guion bajo
kernel junto a un registro activo significa que el bloqueo no tiene efecto y que la
sincronización siguiente lo restaurará. Una fila cuyo registro dice none existe solo en
el núcleo: alguien usó ipset o nft a mano, o se perdió el registro. Caducará
igualmente, pero ningún reinicio la traerá de vuelta.
Levantar un bloqueo, y desmontarlo todo
reportedip-agent whitelist add <dirección> actúa de inmediato y está por
encima de cada bloqueo, incluido uno que ya esté en el conjunto. Escribe el archivo y el conjunto
del núcleo en un paso y no necesita ni sincronización ni reinicio.
# Lift one ban and forget its history. The next detection starts
# from a first offence rather than escalating on top of a verdict
# you disagreed with.
reportedip-agent unban 203.0.113.45
# Never ban and never report this address or range, from now on.
reportedip-agent whitelist add 203.0.113.0/24 "customer office"
# Ban by hand. Works even with ban.enabled: false. A time given
# here is the time, not the first step of an escalation.
reportedip-agent ban add 203.0.113.45
reportedip-agent ban add 203.0.113.45 1440
# Stop blocking without uninstalling: no jump, chain and sets kept.
sed -i "s/^mode: .*/mode: off/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
# Remove the agent from the packet path completely (ipset backend).
iptables -D INPUT -j rip-blacklist; iptables -F rip-blacklist; iptables -X rip-blacklist
ip6tables -D INPUT -j rip-blacklist; ip6tables -F rip-blacklist; ip6tables -X rip-blacklist
for s in $(ipset list -n | grep "^rip-"); do ipset destroy "$s"; done
# nftables backend
nft delete table inet reportedip
unban y whitelist add toman ambos el bloqueo de sincronización, así que el
paso de restauración de una sincronización en curso no puede volver a poner lo que estás levantando.
Retirar una entrada del archivo de la lista blanca es la única operación que no es inmediata: tiene
efecto con la reconstrucción de la sincronización siguiente, y retirar una excepción nunca necesita
ser instantáneo.
rip-. iptables-restore descarta todo el archivo en cuanto
topa con un conjunto desconocido, así que una línea olvidada en
/etc/iptables/rules.v4 puede quitarte todo el cortafuegos en el siguiente arranque.
El agente reconstruye su cadena en cada arranque y no necesita nada guardado.
status advierte cuando encuentra rip- en rules.v4,
rules.v6, /etc/sysconfig/iptables, ip6tables,
/etc/nftables.conf o /etc/nftables.d.
Comandos
| Comando | Efecto | Salida |
|---|---|---|
install [--key K] [--admin-ip IP] [--ssh-port N] | Detecta los servicios, escribe la configuración, la lista blanca automática y el identificador de instalación, instala y activa las unidades y verifica la clave. Nunca sobrescribe una configuración existente. Solo como root. | 0, 1 si la clave se rechaza, 2 si hay un problema de configuración |
sync | Una pasada del feed: reconstruir el conjunto de la lista blanca, descargar cada lista de forma condicional, cambiar lo que ha pasado la comprobación de tamaño, reconstruir la cadena, restaurar los bloqueos locales que falten, hacer la limpieza, buscar una actualización cada seis horas. Es lo que ejecuta el temporizador, y lo que se ejecuta al arrancar. | 0, 1 degradado, también 0 si otra sincronización tiene el bloqueo |
watch | El demonio. Sigue cada fuente, cuenta, notifica y bloquea si eso está activado. Termina con SIGTERM. | 1 si el bloqueo de la cola sigue retenido tras un minuto, 2 si hay un problema de configuración |
report-queue | Envía una vez los informes en espera. Útil tras una caída de la API, o en un host donde el demonio no se ejecuta. | 0, 1 degradado |
status | Versión, motor, modo, herramientas, cantidad y último éxito por lista, lista blanca, comprobación de la cadena, posiciones de lectura y coincidencias por fuente, cola y emisor, cuenta y licencia, disco, bloqueos locales, estados abiertos. | 0 sano, 1 degradado, 2 configuración |
doctor | Lo que ofrece el host y lo que falta. No cambia nada, funciona sin configuración. | 0, 1 con limitaciones, 2 imposible |
test <archivo>... [--type T] [--lines] | Hace pasar los detectores por archivos de registro reales. No notifica nada, no bloquea nada, no escribe nada. | 0, 2 si hay un problema de archivo o de tipo |
ban list | add <ip> [minutos] | rm <ip> | Muestra los bloqueos locales frente a lo que el núcleo tiene realmente, pone uno a mano, levanta uno. | 0, 1 si falla el núcleo, 2 si un argumento es erróneo |
unban <ip> | Lo mismo que ban rm, bajo la palabra que teclea un operador cuando hay un incendio. | como arriba |
whitelist add <ip|cidr> [comentario] | list | rm <ip|cidr> [--auto] | Gestiona la lista de «nunca bloquear, nunca notificar». add escribe el archivo y el conjunto del núcleo de una vez. list muestra tu archivo y la lista blanca automática. --auto retira una entrada escrita por el instalador. | 0, 1 si el archivo cambió y el conjunto no, 2 si la dirección es errónea |
update [--check] | Comprueba el punto de distribución, sustituye este binario y después reinicia el servicio de vigilancia. --check solo informa. Es el único comando que lee la configuración de forma laxa, así que funciona también en un host cuya configuración es más reciente que su binario. | 0, 1 si la comprobación o la instalación falló |
housekeeping | Retira los restos propios del agente y muestra las cifras. La sincronización hace lo mismo en silencio. | 0, 2 si hay un problema de configuración o de estado |
version | Muestra la versión y nada más. | 0 |
help | La lista de comandos, con las rutas de configuración y de estado. | 0 |
test, en lugar de fail2ban-regex
test hace pasar la cadena de detectores por un archivo de registro que ya tienes, con los
umbrales y la lista blanca de este host, y muestra qué direcciones habrían superado un umbral y en qué
punto del archivo. No notifica nada, no bloquea nada y no escribe nada, así que es seguro en un host
de producción y es la respuesta honesta a la pregunta de si esto habría cazado el ataque de la semana
pasada.
# A configured source: the type is taken from the config
reportedip-agent test /var/log/auth.log
# Any other file: name the type yourself
reportedip-agent test --type postfix /var/log/mail.log.1
# A rotated log and its successor as one stream, in this order,
# so a window that spans the rotation counts like the daemon saw it.
# A .gz is read inflated.
reportedip-agent test /var/log/mail.log.2.gz /var/log/mail.log.1 /var/log/mail.log
# Every single hit, with line number, address and timestamp
reportedip-agent test --lines /var/log/auth.log
La línea de resumen indica las líneas leídas, cuántas coincidieron, cuántas no llevaban una marca de tiempo utilizable y cuántas se omitieron por ser demasiado largas. Después, una fila por dirección con sus coincidencias, sus reinicios, si habría sido bloqueada y cuántas veces, la hora de la primera coincidencia y el umbral aplicado. Una dirección que esté en tu lista blanca se muestra con la capa que respondió y marcada como nunca bloqueada y nunca notificada, lo que hace de este comando la vía más rápida para demostrar que una entrada de la lista blanca funciona de verdad.
Dos cosas que test no hace. No adivina ningún formato: sin --type el archivo
tiene que ser una de las rutas de fuente configuradas, y si no lo dice y enumera los tipos válidos. Y
no aplica la ponderación del historial de bloqueos, así que el demonio necesita menos coincidencias
para una dirección con bloqueos anteriores de las que sugiere esta relectura.
Funcionamiento
- Un temporizador al ritmo de tu plan, con dispersión.
reportedip-agent-sync.timerse dispara unos minutos después de su activación y después de forma repetida en el intervalo que trae tu plan, con un desplazamiento aleatorio pero fijo, para que una flota no alcance la API en el mismo segundo. Qué intervalo es eso lo dice el servidor, véase El feed comunitario. La vía de arranque está separada:reportedip-agent-sync.serviceestá activado paramulti-user.targety se ejecuta después denetwork-online.targety después de cada servicio de cortafuegos, sin desplazamiento aleatorio, porque un host no debe quedarse un cuarto de hora sin protección tras un reinicio. - Un servicio de vigilancia que se queda parado cuando debe.
reportedip-agent.servicese reinicia tras un fallo al cabo de diez segundos, como máximo cinco veces en cinco minutos, y nunca con el código de salida 2. Un host mal configurado se queda parado con un motivo legible en vez de reiniciarse sin fin. Las dos unidades se ejecutan conNoNewPrivileges,ProtectHome,PrivateTmp, una personalidad bloqueada y un conjunto restringido de familias de direcciones. - Su propio registro, su propia rotación. El agente escribe en stderr, que journald
recoge, y en su propio archivo cuando
log_fileestá definido. Rota ese archivo él mismo enlog_max_mby conservalog_keepgeneraciones. No depende de logrotate y no llena el diario. - Un correo cuando algo va mal, otro cuando está arreglado. Por estado y no por evento, con un tiempo de espera, así que un feed roto produce un mensaje y no uno por hora.
- Un mínimo de disco. Por debajo de
disk_min_mbel agente no pone en cola informes nuevos y lo dice, mientras la detección y el bloqueo continúan. housekeeping. Archivos de cola antiguos, entradas de deduplicación caducadas, posiciones de lectura antiguas de archivos que ya no existen, archivos temporales y de bloqueo sobrantes, registros de bloqueo caducados que ya no alimentan ninguna escalada, y el binario anterior en cuanto cumple un mes. Se ejecuta en cada sincronización, y a mano cuando quieres ver las cifras.- Actualización automática cada seis horas. Una versión nueva se verifica antes de instalarse contra una firma Ed25519 con la clave pública compilada en el binario, así que una descarga manipulada falla en tu máquina en vez de darse por fiable porque llegó por HTTPS. El binario anterior se conserva para una vuelta atrás y lo retira la limpieza después de treinta días.
systemctl list-timers reportedip-agent-sync.timer
systemctl status reportedip-agent.service --no-pager
journalctl -u reportedip-agent.service -n 50 --no-pager
journalctl -u reportedip-agent-sync.service -n 50 --no-pager
tail -n 100 /var/log/reportedip-agent.log
Códigos de salida
| Código | Significado | Qué hacer |
|---|---|---|
0 | Sano | Nada. |
1 | Degradado, pero volverá a intentarlo | Leer la salida. Un nuevo intento puede arreglarlo perfectamente. Un estado de error abierto en el registro de salud tiñe el código de salida incluso si la ejecución en sí salió bien, porque un código 0 junto a un error anotado no es una señal para una monitorización. |
2 | No repetible sin una persona | La configuración, los permisos de archivo, una herramienta ausente, un bloqueo retenido por otro proceso. Ejecutar doctor. systemd no reinicia el servicio de vigilancia con un 2. |
Con eso, reportedip-agent status se puede usar directamente como comprobación de
monitorización, sin script envoltorio y sin analizar la salida.
Actualizaciones y notas de versión
El agente busca una versión nueva cada seis horas, durante la sincronización. Verifica la descarga
contra una firma Ed25519 con la clave pública integrada en el binario, conserva la anterior para una
vuelta atrás y reinicia él mismo el servicio de vigilancia.
reportedip-agent update hace lo mismo a petición, y update --check solo
informa de lo que está publicado.
Volver a ejecutar el comando de instalación también actualiza un host, y es la vía a seguir cuando una versión eleva la versión mínima: el agente rechaza entonces la actualización automática y lo dice, porque necesita a una persona. Las vueltas a una versión anterior se rechazan en general, así que un host que ha descargado una versión más reciente no retrocede por sí solo.
Un caso de actualización conviene conocerlo de antemano. El analizador de configuración es estricto
con las claves desconocidas, así que un binario más antiguo no puede leer una configuración que ya
lleva un bloque más reciente. Para un cliente el orden es inocuo, porque el agente se actualiza
primero y una clave nueva solo aparece cuando alguien la añade. Si aun así acabas con un binario más
antiguo que su configuración, update es la salida: es el único comando que lee el archivo
de forma laxa, y nombra las claves que no conoce.
Lo que ha cambiado la versión actual es público y no necesita ninguna clave:
curl -s https://reportedip.com/agent/whats-new
reportedip-agent update --check
Los cambios en la propia REST API, o sea los campos de respuesta y los códigos de estado que afectan a una integración, están en cambio en el Changelog de la API.
Licencias de servidor
El agente se licencia por servidor. Los servidores forman su propia reserva y se cuentan por separado de los dominios del plugin Hive, así que instalar el agente nunca te cuesta un dominio.
| Plan | Servidores incluidos | Más servidores |
|---|---|---|
| Free | ninguno | No contratable |
| Contributor | ninguno | No contratable |
| Professional | 1 | Tantos como quieras |
| Business | 3 | Tantos como quieras |
| Enterprise | según contrato | según contrato |
Una licencia adicional cuesta 4,90 € al mes o 49 € al año por servidor, IVA incluido, y se abarata por servidor a medida que sube la cantidad: 4,90 € para los cuatro primeros, 3,90 € desde el quinto, 2,90 € desde el décimo, 1,90 € desde el vigesimoquinto y 1,40 € desde el quincuagésimo. El multiplicador de volumen de Business aumenta tu número de dominios y no tus servidores incluidos, porque los servidores son la parte que se paga aparte.
La cantidad la regulas tú mismo en Agent Servers, dentro de tu cuenta. Un host aparece ahí en cuanto su agente llama a la API por primera vez, con su identificador de instalación, su versión y cuándo se le oyó por última vez. Si la cuenta tiene una licencia libre, el host la toma de inmediato. Si no tiene ninguna, la fila lo dice y un botón añade una. No hay nada que registrar por adelantado ni nada que copiar en un archivo: el host ya tiene una clave API de tu cuenta, y eso es una prueba más fuerte que cualquier archivo de verificación.
La identidad es el identificador de instalación, nunca el nombre del host y nunca la clave. Cambiar la clave API en un host no crea una segunda licencia, y renombrar la máquina no cambia nada. Si tienes más agentes que licencias, los hosts más antiguos conservan la suya. Una licencia de servidor cancelada sigue funcionando siete días.
Qué pasa sin licencia
Solo una cosa se detiene: el feed ya no se descarga. Todo lo demás sigue, y eso es intencionado. Un servidor no debe quedarse sin protección porque se haya quedado atrás una factura, y un host sin licencia no es un host indefenso. Simplemente ya no recibe la lista comunitaria.
| Función | Sin licencia |
|---|---|
| Descarga del feed | Se detiene. Es la parte que se paga. |
| La lista que ya está en el núcleo | Se queda. Nunca se vacía y sigue bloqueando. |
| Detección local | Sigue. Las trece fuentes, todos los umbrales. |
| Bloqueo local | Sigue, escalada incluida, restauración tras un reinicio incluida. |
| Informes | Siguen, dentro del límite diario de tu plan. |
| Lista blanca, cadena, limpieza, rotación de registros | Siguen. |
| Actualización automática | Sigue. Un agente obsoleto en un host sin pagar es nuestro riesgo, no nuestra palanca. |
status | Muestra el estado de licencia, el motivo, la edad de la lista y qué hacer al respecto. |
El servicio no se detiene, los conjuntos no se vacían, los bloqueos locales no se levantan, y no hay publicidad en el registro. Una cuenta Free puede por tanto usar el agente como simple informante con protección local completa, y eso es un uso legítimo.
Sustituir fail2ban
El agente no se coloca al lado de fail2ban, lo releva. fail2ban sigue disponible como una fuente entre muchas, así que un host que todavía lo ejecuta sigue viendo sus bloqueos notificados. Pero un host sin fail2ban no pierde absolutamente nada: para cada tipo de ataque que antes llegaba por un jail, ahora hay un detector que lee el registro original.
La comparación se midió y no se afirmó. A lo largo de once días en un host de producción, el agente encontró 187 de las 189 direcciones que fail2ban había bloqueado en la misma máquina. Las dos que faltaban eran direcciones de prueba introducidas a mano. Además de eso notificó 78 direcciones más que atacaban realmente y se habían quedado por debajo de los umbrales de fail2ban, y en FTP acertó exactamente, tres de tres.
Dos diferencias prácticas. La escalada para los reincidentes viene del historial de bloqueos propio
del agente y no de un jail que lee el registro de otro jail, y
reportedip-agent test <archivo> ocupa el lugar de fail2ban-regex.
Comprobar primero si algo ya posee estos nombres
Esto costó una tarde en una flota de producción, así que va antes que todo lo demás. Un script de
cortafuegos escrito a mano, del tipo que este sitio documentaba antes, puede usar
exactamente los nombres que usa el agente. Encontrados en el campo: los conjuntos
rip-whitelist, rip-ssh, rip-mail, rip-web,
rip-ftp y rip-edge, más una cadena rip-blacklist colgada con
-A INPUT -j rip-blacklist, reconstruida cada hora por una tarea de cron, y casi carácter
por carácter la cadena que el agente construye él mismo.
En un host así el orden habitual es erróneo en las dos direcciones. La primera sincronización del
agente reconstruye la cadena y sustituye las reglas DROP existentes por reglas
LOG, lo que deja al host sin protección hasta que la tarea de cron antigua vuelva a
ejecutarse, es decir hasta una hora más tarde. Y cuando esa tarea se ejecuta, reconstruye la cadena a
su manera y retira de paso la regla rip-local del agente. Dos programas, una cadena, y
cada uno deshaciendo el trabajo del otro cada hora.
Comprueba por tanto antes de la primera sincronización, y si los nombres chocan, no te quedes en
mode: log en este host. Instala el agente, que no toca ninguna regla, asume la lista
blanca antigua, desactiva la tarea de cron antigua, y solo después lanza la primera sincronización
con mode: drop ya puesto.
# 1. Does something already own these names?
ipset list -n | grep "^rip-"
iptables -S INPUT | grep rip-
iptables -S rip-blacklist 2>/dev/null | head
crontab -l | grep -iE "ipset|blacklist|reportedip"
ls -l /etc/cron.d/ /etc/cron.hourly/ 2>/dev/null
# 2. If yes, take the old whitelist over FIRST. The feed can be
# downloaded again; that list cannot.
ipset list rip-whitelist | sed -n "/^Members/,\$p" | tail -n +2 > /tmp/rip-wl
ipset list rip-whitelist-v6 | sed -n "/^Members/,\$p" | tail -n +2 >> /tmp/rip-wl
wc -l /tmp/rip-wl
while read -r a; do
[ -n "$a" ] && reportedip-agent whitelist add "$a" "imported from the old script"
done < /tmp/rip-wl
reportedip-agent whitelist list | wc -l
# 3. Disable the old job, then hand the chain over in one step.
crontab -l | grep -v blacklist | crontab - # or: rm /etc/cron.d/<job>
sed -i "s/^mode: log/mode: drop/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
reportedip-agent status
reportedip-agent whitelist list.
Ejecutar los dos un tiempo
Ejecutar los dos es seguro y es la forma sensata de migrar. El agente nunca escribe en
/etc/fail2ban, y fail2ban no sabe nada de los conjuntos rip-, así que los
dos usan cadenas separadas y estados separados. Dos reglas que descartan la misma dirección cuestan
una comparación de paquete.
Lo único que no conviene hacer es notificar el mismo evento dos veces. Si conservas la acción de fail2ban que envía los bloqueos por HTTP y configuras fail2ban como fuente del agente, cada bloqueo sale del host dos veces y se paga dos veces de tu cuota diaria. Elige el agente o la acción.
# What did fail2ban ban, and what does the agent make of the
# same logs? Same file, same window, two verdicts.
fail2ban-client status sshd
reportedip-agent test /var/log/auth.log
# Per jail, then the agent over the file behind it
for j in $(fail2ban-client status | sed -n "s/.*Jail list:\t*//p" | tr -d " " | tr "," " "); do
echo "== $j"; fail2ban-client status "$j" | grep -E "Total banned|Currently banned"
done
reportedip-agent status | sed -n "/^sources:/,/^queue:/p"
Desactivar fail2ban de forma definitiva
Hazlo cuando hayas comparado los dos durante unos días y la lista de fuentes del agente cubra cada jail que tenías.
# 1. Stop it and keep it stopped.
systemctl stop fail2ban
systemctl disable fail2ban
# 2. Remove the fail2ban source from the agent config, then stop
# and start the daemon. A restart is not enough for a removed
# source: the daemon keeps tailing the old file.
$EDITOR /etc/reportedip-agent/config.yaml
systemctl stop reportedip-agent.service
systemctl start reportedip-agent.service
# 3. Check what fail2ban left in the kernel. Stopping it does not
# always clean up: a jail that once used an iptables action
# leaves old-style f2b- chains behind.
nft list tables | grep f2b || echo "no f2b table"
iptables -S | grep "f2b-" || echo "no f2b chain"
ipset list -n | grep f2b || echo "no f2b set"
# 4. Remove the leftovers from the RUNNING ruleset.
iptables -D INPUT -j f2b-sshd
iptables -F f2b-sshd && iptables -X f2b-sshd
# 5. And from the SAVED one, or they come back at the next boot.
grep -n "f2b" /etc/iptables/rules.v4 /etc/iptables/rules.v6
netfilter-persistent save
# 6. Confirm the agent is happy without it.
reportedip-agent status | grep -i fail2ban
reportedip-agent status; echo "exit $?"
f2b- solo del juego de reglas en marcha, netfilter-persistent las
restaura en cada arranque desde /etc/iptables/rules.v4 y desde
/etc/iptables/rules.v6. Vuelven vacías, así que no descartan nada y parecen
inofensivas, y dentro de un año seguirán ahí desconcertando a quien lea el juego de reglas
después. Revisa los dos archivos: en un host los restos estaban solo en el archivo v6, y quien
busca únicamente en rules.v4 pasa de largo. Primero limpiar el juego de reglas en
marcha, luego guardarlo, y comprobarlo después de un reinicio.
Una vuelta atrás es un comando y tarda segundos, porque el agente nunca tocó
/etc/fail2ban: systemctl enable --now fail2ban devuelve cada jail y su
propia tabla, y el agente no se ve afectado. Medido en un host en producción, los once jails habían
vuelto en ocho segundos.
Consumo de recursos
Medido en un host Debian 12 arm64 con siete archivos de registro seguidos por el agente: 0,085 % de un núcleo y 16,6 MB de memoria residente. No hay intérprete que arrancar, ni base de datos, ni directorio de caché que calentar, y eso es la mayor parte de la explicación de esas cifras tan pequeñas.
En disco, los archivos propios del agente están acotados por la configuración y no por la esperanza:
log_max_mb por log_keep más el archivo actual para el registro,
queue_max archivos pequeños para la cola, y un registro de bloqueos que solo guarda lo
que una escalada todavía necesita. El directorio de estado de un host con carga se queda bastante por
debajo de cien megabytes, y por eso el límite de disco predeterminado de 200 MB no se dispara nunca en
una máquina sana.
Qué no es el agente
No es un antivirus, ni un cortafuegos de aplicaciones web, ni un sustituto de Imunify360. No lee tus archivos, no examina cuerpos de peticiones y no pone nada en cuarentena. Bloquea y notifica direcciones, y lo hace sobre una superficie pequeña y auditable. Para protección a nivel de una sola petición en un sitio WordPress, usa en su lugar el plugin Hive. Los dos se ejecutan en la misma máquina sin estorbarse.
Resolución de problemas
Un síntoma, una causa probable, un comando. reportedip-agent doctor y
reportedip-agent status responden entre los dos a la mayoría de estos casos, y son las
dos cosas que el soporte pide primero.
| Síntoma | Causa | Comando |
|---|---|---|
| No arranca y nombra su archivo de configuración | El archivo es legible por el grupo o por otros, y contiene tu clave API. | chmod 0600 /etc/reportedip-agent/config.yaml && chown root:root /etc/reportedip-agent/config.yaml |
| Código 2 con «field ... not found in type» | Una clave desconocida en la configuración. El analizador es estricto a propósito. | Retirar o corregir la clave que se nombra. La lista de claves válidas está más arriba. |
| Cada comando termina con código 2 tras una vuelta atrás | El binario es más antiguo que la configuración y no puede leer un bloque más reciente. | reportedip-agent update, el único comando que lee el archivo de forma laxa y nombra las claves desconocidas. |
| Los conjuntos existen pero están vacíos | O el feed se rechazó, o la lista falló en la comprobación de tamaño. | reportedip-agent status dice cuál de las dos, y la línea de la lista lleva el último error. |
status indica unlicensed | La cuenta no tiene ninguna licencia libre para este host. | Añadir una en Agent Servers. La lista del núcleo sigue trabajando mientras tanto. |
| Nunca se notifica nada | La fuente que habría reaccionado no se detectó nunca, o no se alcanza su umbral. | reportedip-agent test /ruta/al/registro, luego reportedip-agent status y leer la sección sources. |
| Nunca se bloquea nada, aunque salen informes | ban.enabled sigue en false, o mode sigue en log. Dos interruptores. | reportedip-agent ban list dice cuál de los dos en sus últimas líneas. |
| Ningún estado de fuente en absoluto | El demonio de vigilancia no se ha ejecutado nunca. | systemctl enable --now reportedip-agent.service |
Una fuente muestra files=0 | La ruta o el patrón no se resuelve en nada en este host. | reportedip-agent doctor nombra la fuente y el patrón que intentó. |
| Una fuente de registro aparece como ilegible | El archivo no es legible ni para root. Suele ser un panel de control que pone el modo 000 al rotar. | ls -l sobre la ruta, y corregir la rotación que lo ha producido. |
| Una fuente tiene coincidencias pero nunca notifica | Sus marcas de tiempo están a más de un minuto del reloj del sistema, así que la ventana de recuento nunca se llena. | reportedip-agent doctor, las líneas de desviación en sources. Corregir la zona horaria de ese registro. |
| Tu propia dirección ha sido bloqueada | No estaba en la lista blanca. | reportedip-agent whitelist add <dirección>. Efecto inmediato, porque la regla de la lista blanca va delante de la de bloqueo. |
Un bloqueo está en bans.json pero no en el núcleo | Un reinicio sin sincronización, o una recarga del cortafuegos que se llevó el conjunto. | reportedip-agent sync lo restaura con el tiempo restante. |
Una entrada del núcleo con el registro none | Alguien bloqueó a mano con ipset o nft, o se perdió el registro. Caduca, pero ningún reinicio la trae de vuelta. | reportedip-agent ban list |
| Un conjunto existe con el tipo equivocado o sin plazo | Un resto del script de shell documentado o de otra herramienta. | ipset destroy <conjunto> y después reportedip-agent sync, que lo vuelve a crear correctamente. |
Las dos cadenas de herramientas tienen objetos rip- | El motor se cambió sin migración. | reportedip-agent sync --migrate-backend, y status muestra los comandos exactos de eliminación para el otro lado. |
| El cortafuegos ha desaparecido tras un reinicio | Un juego de reglas guardado hace referencia a un conjunto rip-, e iptables-restore descarta todo el archivo ante un conjunto desconocido. | grep -n rip- /etc/iptables/rules.v4 /etc/iptables/rules.v6 y retirar esas líneas. El agente no necesita nada guardado. |
| Una fuente retirada se sigue leyendo | Un systemctl restart no basta para una fuente retirada. | systemctl stop reportedip-agent.service && systemctl start reportedip-agent.service |
| Los informes se detienen, la detección sigue | Se alcanzó el mínimo de disco. | Liberar espacio, luego reportedip-agent housekeeping. |
| HTTP 429 en los informes | El límite diario de informes de tu plan. | Los límites están en Autenticación. |
| La cola no deja de crecer | El emisor está en pausa tras un fallo, con una espera progresiva. | reportedip-agent status muestra la pausa y el motivo; reportedip-agent report-queue envía una pasada a mano. |
| Cada coincidencia web es el mismo puñado de direcciones | nginx ve un proxy o Cloudflare y no al visitante. | Configurar set_real_ip_from. reportedip-agent install advierte precisamente de este caso. |
| «more addresses than the counter tracks at once» | Un barrido más amplio que el contador por fuente. No es un defecto. | Nada. Un barrido tan amplio es un caso para la lista comunitaria y no para bloqueos locales. |
status dice que el host va atrasado de versión | La actualización automática no llega, o auto_update está desactivada. | reportedip-agent update --check, luego reportedip-agent update. |
Última actualización: · Mantenido por el equipo de ReportedIP