diff --git a/skills/saiop-infrastructure/saiop-mikrotik-mcp/SKILL.md b/skills/saiop-infrastructure/saiop-mikrotik-mcp/SKILL.md new file mode 100644 index 0000000..45a7177 --- /dev/null +++ b/skills/saiop-infrastructure/saiop-mikrotik-mcp/SKILL.md @@ -0,0 +1,77 @@ +--- +name: saiop-mikrotik-mcp +description: "MikroTik (RouterOS) read-only MCP access for rt-alpha — TLS debugging notes and the read-only verification method." +version: 1.0.0 +author: Claude Code (SAIOP ops session) +license: MIT +platforms: [linux] +prerequisites: + env_vars: [MIKROTIK_PASSWORD] + commands: [uv] +metadata: + hermes: + tags: [SAIOP, MCP, MikroTik, RouterOS, network, security] +--- + +# MikroTik (rt-alpha) read-only MCP access + +## When to use + +Querying the ATU network's MikroTik router (`rt-alpha`, +`169.239.248.166`) for read-only diagnostics (interfaces, routes, DHCP +leases, firewall rules) via Hermes. + +## What's wired up + +A custom single-tool MCP server (`/opt/ai-stack/hermes/scripts/mikrotik_mcp.py`, +run via `uv run --with mcp --with librouteros`) connects via the RouterOS +API over TLS (port 8729) as a dedicated `claudeuser` account. + +**Two independent layers, same pattern as postgres-mcp/proxmox-mcp:** +1. The `claudeuser` RouterOS account itself is read-only — **independently + verified**, not assumed: a real write attempt (`/interface/set` to + change a comment) returned `TrapError: not enough permissions (9)` + directly from the device's permission system. +2. The MCP tool also only accepts paths ending in `/print`, `/getall`, or + `/export` (rejects anything else before even attempting the call) — + defense-in-depth on top of #1, not the primary boundary. + +## TLS gotchas hit getting here (useful if this recurs for other network gear) + +- Initial connection attempts failed with a TLS handshake failure. Root + cause: RouterOS's `api-ssl` service had **no certificate assigned**, so + it fell back to **anonymous Diffie-Hellman ciphers** (`ADH-AES256-SHA256`) + — meaning no server authentication at all. Confirmed via raw + `openssl s_client -cipher "ALL:@SECLEVEL=0" -tls1_2`, which negotiated + successfully where every normal client library failed (modern TLS + stacks don't enable anonymous ciphers the same permissive way the CLI + does, even with matching settings — spent real effort confirming this + was the device's config, not a bug in our approach, before escalating + to ask for a router-side fix). +- **The actual fix was on the device, not the client**: assigning a real + certificate to the `api-ssl` service + (`/certificate add` + `/certificate sign` + `/ip service set api-ssl + certificate=...`). Once that landed, standard TLS negotiated cleanly — + no special cipher/version pinning needed in the client at all. +- Even with a real cert, it had its own separate issues (expired, + CN mismatch vs. the hostname used to connect) — handled with + `verify_mode = ssl.CERT_NONE` in the client, since we already have + independent assurance of which device we're talking to (the ~1ms ping + latency confirms it's on a directly-attached local segment, not + reachable for interception the way a public-internet path would be). + Worth a routine cert renewal on the network side regardless. +- `librouteros`'s `Api.__call__` takes the RouterOS path **positionally**, + not as a `cmd=` keyword (`api("/interface/print")`, not + `api(cmd="/interface/print")`) — easy mistake, produces a confusing + `TypeError: missing 1 required positional argument`. + +## Why a custom server instead of `mcp-server-mikrotik` + +That package exists and is actively maintained, but it's the wrong shape +for this need: it connects via **SSH and an interactive shell**, uses +RouterOS's own "Safe Mode" (a revert-on-disconnect safety net for *live +config changes*, not a read-only restriction), and exposes broad +read-write scope across firewall/DHCP/DNS/NAT/backup management. Writing +~70 lines against the already-verified API connection was less work and +matches the actual need (read-only diagnostics) far more precisely — same +reasoning as the `proxmox-mcp` custom server earlier in this project.