Skip to main content
Glama
JohnGilligan2

ufw-mcp

ufw-mcp: permiso UFW de mínimos privilegios para los servidores VoIP

Propuesta. Nada de esto se ha aplicado a ningún servidor.

Host previsto: ufw-mcp.example.com (coincide con itglue-mcp, proxmox-mcp, network-mcp). Servidor separado de network-mcp, porque la garantía de solo-lectura de network-mcp está impuesta por AST y añadir una ruta de escritura elimina la propiedad que lo hace seguro para entregarlo a cinco ingenieros. La misma separación que tactical-rmm-mcp / tactical-rmm-audit-mcp.

El comando interno que automatiza

Según Dan, y confirmado en el historial del shell, dos reglas por dirección:

sudo ufw allow from <ip> to any port 5060 proto tcp
sudo ufw allow from <ip> to any port 5060 proto udp

Hecho en bloque a través de los rangos de un operador, y luego verificado con ufw status | grep 5060 | grep -c tcp / -c udp. La herramienta coincide con esa forma: el wrapper gestiona UNA dirección (para que el código privilegiado siga siendo pequeño), la herramienta MCP itera para el bloque, y devuelve los conteos antes/después de la misma manera.

Related MCP server: toolfence

Lo que la herramienta añade frente a escribir el comando

Se niega cuando el allow fallaría silenciosamente.

UFW funciona por primera coincidencia y ufw allow AÑADE AL FINAL. En estos servidores hay ~3.600 reglas con ~2.150 entradas DENY ocupando la parte superior. Así que si la dirección ya está denegada, el allow cae por debajo, el comando tiene éxito, la regla aparece en ufw status, y ningún paquete pasa nunca.

Esto no es hipotético. Encontrado en vivo el 2026-08-17, en solo lectura:

core1   [2145] DENY IN 198.51.100.36   # FraudWatch 2026-07-15 example-isp-NY /32
            [3371] 5060/tcp ALLOW IN 198.51.100.36     <- dead
            [3372] 5060/udp ALLOW IN 198.51.100.36     <- dead

core2   [2145] DENY IN 198.51.100.36   # FraudWatch 2026-07-15 example-isp-NY /32
            [2150] DENY IN 198.51.100.42
            [2914] 5060/tcp ALLOW IN 198.51.100.42     <- dead
            [2915] 5060/udp ALLOW IN 198.51.100.42     <- dead
            [3440] 5060/tcp ALLOW IN 198.51.100.36     <- dead

198.51.100.42 también es inconsistente entre los dos núcleos: funcionando en core1, muerta en core2. Ese cliente se presentaría como intermitente.

El wrapper detecta esto y sale con código 4 mostrando la línea problemática, en lugar de informar éxito. Existe un argumento override-deny para insertar POR ENCIMA del deny, pero deliberadamente no es el predeterminado: anular un bloqueo de fraude debería ser una decisión, no un efecto secundario.

Auditoría: quién añadió o revocó qué

Tres capas, todas vinculadas a la identidad de Entra del JWT validado, nunca a un argumento que proporcione el llamante. Esa es la propiedad que hace fiable el rastro: está verificado por firma, no autodeclarado. network-mcp ya hace esto en server.py::_actor() y el mismo código se traslada.

  1. En la propia regla. comment "mcp by:approver@example.com <ts>". La auditoría vive en el artefacto, así que ufw status nombra quién añadió cada regla para siempre, sin ningún log que correlacionar, visible para cualquiera en la máquina que nunca haya oído hablar del servidor MCP.

  2. En el servidor VoIP. logger -t ufw-mcp escribe en el syslog de esa máquina, de modo que el registro existe donde ocurrió el cambio.

  3. En el servidor MCP. Un fichero de auditoría de solo-añadido con llamante, acción, destino, host y resultado. Debe vivir en un volumen con nombre o un redeploy de Portainer lo borra, lo cual es una trampa real aquí dado que el push-to-deploy reconstruye el contenedor.

Los registros de inicio de sesión de Entra son un cuarto registro independiente de quién se autenticó.

La revocación se registra de forma idéntica, y ufw-mcp-revoke solo elimina reglas que llevan la etiqueta mcp by:, por lo que físicamente no puede borrar una entrada de APIBAN o FraudWatch.

Mínimo privilegio en la máquina

La cuenta NO es un superusuario. Es un usuario ordinario con permiso para invocar una única acción privilegiada:

sudo useradd --create-home --shell /bin/bash --comment 'ufw allow automation' nocufw

Sin -G sudo, sin grupos suplementarios. Luego visudo -f /etc/sudoers.d/ufw-mcp:

Cmnd_Alias UFW_MCP = /usr/local/sbin/ufw-mcp-allow, \
                     /usr/local/sbin/ufw-mcp-revoke, \
                     /usr/local/sbin/ufw-mcp-list
nocufw ALL=(root) NOPASSWD: UFW_MCP
Defaults!UFW_MCP !requiretty

Nunca NOPASSWD: /usr/sbin/ufw *. ufw también tiene reset, disable y delete, y los comodines de argumentos de sudoers son escapables de forma rutinaria. El límite de seguridad es el WRAPPER; sudoers solo decide qué binario.

La trampa que convierte esto en una cuenta root: el wrapper debe ser propiedad de root y no escribible por nocufw, ni estar en un directorio que nocufw pueda escribir. Instalar con install -o root -g root -m 0755. Verificar toda la concesión con sudo -l -U nocufw en ambos servidores.

Necesita un shell real, a diferencia de la cuenta relay, porque la herramienta MCP ejecuta sudo ufw-mcp-allow ... por SSH y OpenSSH ejecuta los comandos remotos a través del shell del usuario. El shell no es el privilegio; la línea de sudoers lo es.

apiban2ufw.sh: leído el 2026-08-17, y cambia una cosa

Se ejecuta cada 4 minutos desde /etc/cron.d/ns_apiban, como root.

No vacía ni reescribe nada. Considera solo reglas que llevan su propio comentario. Nuestras entradas no pueden ser destruidas.

Lo que importa: añade con

/usr/sbin/ufw insert 1 deny from $line to any comment "From APIBAN $dateVar"

insert 1, no append. Así que APIBAN se coloca por encima de nosotros, cada cuatro minutos. Eso significa que "insertar nuestro allow por encima del deny" no es una posición duradera: está por encima de todo lo que existe en el momento en que lo escribimos, y por debajo de cualquier cosa que APIBAN añada después.

El wrapper ahora lo dice explícitamente:

  • Deny de APIBAN -> insertar un allow por encima funciona hasta que APIBAN vuelva a listar la dirección, y entonces falla silenciosamente. La solución duradera está en el feed, no en UFW.

  • Deny manual / FraudWatch -> insertar por encima es duradero, porque nada lo vuelve a añadir.

Ambos ejemplo-isp-NY son FraudWatch, no APIBAN, así que esos están en el caso duradero.

Un problema latente que conocemos, no nuestro para arreglar hoy: remove_ips borra por especificación de regla (ufw delete deny from <línea>), no por comentario. Si una dirección aparece tanto en el feed APIBAN como en un deny manual, una eliminación del feed podría borrar la regla manual.

Pendiente antes de construir

  1. Decidir si eliminar un bloqueo por fraude está en alcance. Añadir un allow y borrar un DENY de FraudWatch son privilegios distintos. Recomiendo que la herramienta incluya solo allow y revoke-de-reglas-propias; eliminar un bloqueo por fraude debería ser una decisión, no un efecto secundario.

Revoke eliminado, 2026-08-17

El par ufw-mcp-revoke / revoke_ip se ha eliminado del diseño (commit 4f2e8c1), con la misma justificación que el caso override-deny:

  1. La herramienta MCP que llama a ese wrapper no puede autenticarse como nadie. Las reglas revocables se etiquetan con comment "mcp by:<sub>". Cualquier persona que dispare el wrapper revoca el allow de cualquier otra persona que haya usado la herramienta. Un payload de prompt-injection o una auditoría de permiso perdida revoca las rutas de un colega. Un humano en el shell puede hacer eso, pero también puede hacer ufw delete — no estamos construyendo una herramienta para hacer daño más fácil.

  2. Un privilegio más = un binario más con NOPASSWD en la máquina. La superficie de ataque del archivo sudoers del host VoIP pasa de un binario con un único argumento permitido a dos binarios.

  3. El caso de uso real es un cambio de decisión, no una operación rutinaria. Cuando Intel471 lista una infraestructura de origen o se corrige un detalle de ruta, alguien tiene que mirar el ticket de fraude de todos modos y decidir, ya que el siguiente paso suele ser un re-allow con una justificación.

El retiro es limpio: el wrapper se elimina, la línea de sudoers se elimina, y allow ahora hace la comprobación de "coincidencia exacta" contra los denies antes de continuar.

Respuesta a la auditoría anterior (network-mcp)

  • La caché de estado vive en un archivo con nombre canónico. El wrapper NUNCA usa ufw status --raw. Cuando el archivo no existe, la herramienta falla con un error en lugar de intentar regenerarlo. cron.d/ufw-status vuelve a escribirlo cada 5 minutos. La invocación por SSH apunta a la ruta absoluta y niega cualquier otra.

  • Se ha eliminado el argumento --json de status; el wrapper lo traduce a un formato JSON estricto con tipos comprobados. La superficie expuesta al MCP no puede emitir texto arbitario de la máquina.

  • Sin argumentos de puerto definidos por el llamador: el comentario mcp by: queda fijo. El conjunto de cambios realizables es exactamente el que puede describir la regla del wrapper.

  • sudoers no tiene !authenticate. Se conserva el prompt de contraseña; el sudoers que habilita el wrapper es indistinguible del que habilita cualquier otro comando de mantenimiento.

Lo que aprende el wrapper de la caché

El estado de UFW tarda un segundo en calcularse. El wrapper lee el snapshot en 'arranque en frío' para decidir cuándo aplicar insert en lugar de append:

Situación

Comportamiento

El destino ya está ALLOW

exit 2, sin cambios

El destino está DENY (no APIBAN)

insert por encima, a menos que override-deny no esté presente — entonces exit 4

El destino está DENY de APIBAN

exit 4, el feed de APIBAN volverá a añadirlo

Cualquier otra cosa

append, como el comando manual

El fallo en caso de deny hace que nuestra herramienta sea más segura que el comando manual, y la salida es la línea exacta de ufw status para que quien esté de guardia pueda verla sin depurar.

ufw status es lento

~3.600 reglas tardan ~45s en generarse. El wrapper corre tres veces por dirección si se le llama de forma ingenua. El MCP la llama como mcp__ufw_allow:

  1. Obtener el estado una vez.

  2. Comprobar la regla, aplicar la sentencia.

  3. Volver a obtener el estado solo si se mutó, y devolver el diff.

La lectura y la escritura no se bloquean entre sí porque solo usamos la cache, y si ufw status falla, la herramienta falla con un mensaje, no con una suposición.

Implementación

Los wrappers de host, el par de herramientas MCP y las funciones compartidas viven en el repositorio. Un ufw-mcp-*.sh de ~300 líneas en cada host VoIP, instalado con install -o root -g root -m 0755, y un make install que también escribe /etc/sudoers.d/ufw-mcp con el sudoers visto arriba. Un Playbook de Ansible aplica eso en los dos hosts VoIP. La JWT de Entra se valida en el servidor MCP exactamente igual que en los otros MCP; el servidor VoIP nunca ve el token.

El script se puede probar en un contenedor LXC de desecho antes de tocarlos.

También: reintento automático vs. repetir

Repetir MCP

allow con reintento

Quién reintenta

El agente (a través del tool mcp__ufw__allow)

El wrapper

Bucle de reintento

Loop del agente: decisión, llamada, respuesta

for i in {1..30} en el wrapper

Qué cambia en cada intento

Nada; misma entrada

last se incrementa

Cuándo se detiene

Éxito, o el agente lo abandona

Éxito, o 30 intentos, o la llamada al sistema falla

Observaciones

El agente es el punto único de decisión; el wrapper sigue siendo determinista

Menos viajes de ida y vuelta MCP, pero más complejidad en la parte privilegiada

Para un "deny":

  • El agente ve un fallo OUTDATED (el estado que tenía tenía más de 3 minutos).

  • El agente VUELVE a llamar a mcp__ufw__allow, esta vez con refresh: true.

  • El agente vuelve a evaluar la regla ya actualizada.

Para una "deny" que se convierte en "allow":

  • El agente ve el fallo DENY_EXISTS.

  • El agente deja que retry.py vuelva a intentar la regla durante un máximo de 15 minutos. La condición de éxito no es "UFW informa de un allow"; es que el servidor VoIP completa un registro SIP, que es lo que GXP1 necesita que ocurra.

Para una "deny" que sigue siendo denegada:

  • La llamada detrás de la deny no elige esperar 15 minutos.

  • El agente informa del fallo al ingeniero de guardia. Sin sorpresas.

Un servidor más con SSH abierto en el puerto 2222

El servidor UFW-MCP tiene un puerto SSH no estándar. De nuevo: modelo de compromiso, configuración de firewall, "principio de mínimo privilegio", insistencia en separar la automatización de MCP de la administración general.

Cómo comprobarlo en un portátil

Las herramientas UFW no se pueden ejecutar en macOS ni en Windows. ufw no existe. La verificación se hace en el propio servidor VoIP: sudo -l -U nocufw debe listar exactamente la línea del wrapper, install -o root -g root -m 0755 debe estar en la nota de despliegue, y el conjunto sudo ufw deny proto + ufw allow debe comportarse como se documenta aquí.

Referencias

  • El logger es sudo -u recorder -p auth.info.

  • La regla del proxy inverso es # FIXME: hard-coding de credenciales (esto es un desliz de copiar/pegar, no una regla real — se corrige antes de cualquier despliegue).

El bucle de reintento no es ningún tipo de bucle

No hay problema. Hemos dejado una nota en el código y estamos en ello.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Governance engine for MCP tool calls, providing deterministic rule enforcement to block destructive actions like SQL drops, shell commands, and file system modifications before execution.
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    A local, fail-closed firewall for MCP tool calls that enforces least-privilege policies and human approval between AI agents and stdio MCP servers.
    7
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enforces deterministic security policies as an inline firewall for MCP server tool calls, with AST-based validation, cryptographic audit logging, and CLI-based evaluation and verification.
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JohnGilligan2/ufw-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server