Skip to main content
Glama
JohnGilligan2

ufw-mcp

ufw-mcp: least-privilege UFW allow for the VoIP servers

Proposal. Nothing here has been applied to any server.

Planned host: ufw-mcp.example.com (matches itglue-mcp, proxmox-mcp, network-mcp). Separate server from network-mcp, because network-mcp's read-only guarantee is AST-enforced and adding a write path deletes the property that makes it safe to hand to five engineers. Same split as tactical-rmm-mcp / tactical-rmm-audit-mcp.

The house command it automates

Per Dan, and confirmed in shell history, two rules per address:

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

Done in bulk across a carrier's ranges, then verified with ufw status | grep 5060 | grep -c tcp / -c udp. The tool matches that shape: the wrapper handles ONE address (so the privileged code stays small), the MCP tool loops for bulk, and it returns the before/after counts the same way.

Related MCP server: toolfence

What the tool adds over typing the command

It refuses when the allow would silently do nothing.

UFW is first-match-wins and ufw allow APPENDS. On these servers there are ~3,600 rules with ~2,150 DENY entries occupying the top. So if the address is already denied, the allow lands below it, the command succeeds, the rule appears in ufw status, and no packet ever passes.

This is not hypothetical. Found live on 2026-08-17, read-only:

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 is also inconsistent across the two cores: working on core1, dead on core2. That customer would present as intermittent.

The wrapper detects this and exits 4 with the offending line, rather than reporting success. An override-deny argument exists to insert ABOVE the deny, but it is deliberately not the default: superseding a fraud block should be a decision, not a side effect.

Audit: who added or revoked what

Three layers, all keyed to the Entra identity from the validated JWT, never to an argument the caller supplies. That is the property that makes the trail trustworthy: it is signature-checked, not self-declared. network-mcp already does this in server.py::_actor() and the same code carries over.

  1. In the rule itself. comment "mcp by:approver@example.com <ts>". The audit lives in the artifact, so ufw status names who added every rule forever, with no log to correlate, visible to anyone on the box who has never heard of the MCP server.

  2. On the VoIP server. logger -t ufw-mcp writes to that machine's syslog, so the record exists where the change happened.

  3. On the MCP server. An append-only audit file with caller, action, target, host and result. It must live on a named volume or a Portainer redeploy erases it, which is a live trap here given push-to-deploy rebuilds the container.

Entra sign-in logs are a fourth, independent record of who authenticated.

Revoke is logged identically, and ufw-mcp-revoke only removes rules carrying the mcp by: tag, so it physically cannot delete an APIBAN or FraudWatch entry.

Least privilege on the box

The account is NOT a superuser. It is an ordinary user permitted to invoke one privileged action:

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

No -G sudo, no supplementary groups. Then 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

Never NOPASSWD: /usr/sbin/ufw *. ufw also has reset, disable and delete, and sudoers argument wildcards are routinely escapable. The security boundary is the SCRIPT; sudoers only decides which binary.

The trap that turns this into a root account: the wrapper must be root-owned and not writable by nocufw, nor sitting in a directory it can write. Install with install -o root -g root -m 0755. Verify the whole grant with sudo -l -U nocufw on both servers.

It does need a real shell, unlike the relay account, because the MCP tool runs sudo ufw-mcp-allow ... over SSH and OpenSSH executes remote commands through the user's shell. The shell is not the privilege; the sudoers line is.

apiban2ufw.sh: read 2026-08-17, and it changes one thing

Runs every 4 minutes from /etc/cron.d/ns_apiban, as root.

The good news: it does not flush or rewrite anything. It diffs the feed against ufw status | grep APIBAN | grep DENY, so it only ever considers rules carrying its own comment. It adds the delta and removes feed entries that have dropped off. Our ALLOW rules are invisible to it and cannot be clobbered. The exclusion-list concern was unfounded, and the account/sudoers design stands unchanged.

The thing that matters: it adds with

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

insert 1, not append. So APIBAN puts new denies at the TOP, every four minutes. That means "insert our allow at position 1" is not a durable position: it is above everything that exists at the moment we write it, and below anything APIBAN adds afterwards.

Consequence, and the wrapper now says this out loud:

  • Deny came from APIBAN -> inserting an allow above it works until apiban.org lists the address again, then breaks silently. The durable fix is upstream at the feed, not in UFW.

  • Deny is manual / FraudWatch -> inserting above is durable, because nothing re-adds it.

Both example-isp-NY denies are FraudWatch, not APIBAN, so those are the durable case.

One latent bug worth knowing, not ours to fix today: remove_ips_ufw deletes by rule spec (ufw delete deny from $line/32), not by comment. If an address ever appears in both the APIBAN feed and a manual deny, a feed removal could delete the manual rule instead.

Still open before building

  1. Decide whether removing a fraud block is in scope. Adding a scoped allow and deleting an FraudWatch DENY are different privileges. Recommend the tool ships with allow + revoke-own-rules only, and unblocking a fraud-blocked address stays manual or gets its own tighter group.

  2. Fix the five dead rules above by hand, independently of this project.

Revoke removed, 2026-08-18

John's call, matching Alice's concern in the design meeting that a first-level user could remove a SIP trunk or Inteliquent address. Removed at BOTH layers, because removing only the tool would leave the privilege in place:

  • no ufw_revoke_ip tool, and ufw.py has no REVOKE constant, so the executor physically cannot run that path

  • the sudoers grant on both switches is narrowed to ufw-mcp-allow and ufw-mcp-list

  • /usr/local/sbin/ufw-mcp-revoke deleted from both switches

Verified after the change: sudo -l -U nocufw lists two paths, invoking the revoke wrapper as nocufw is refused, and allow/list still work.

tests/test_no_revoke.py fails if the tool or the constant comes back, so re-enabling is a deliberate act. Doing it properly means restoring all four: constant, tool, sudoers entry, wrapper file.

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