opnsense-mcp-server 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opnsense_mcp_server-0.3.2/.dockerignore +19 -0
- opnsense_mcp_server-0.3.2/.github/workflows/ci.yml +52 -0
- opnsense_mcp_server-0.3.2/.github/workflows/publish-docker.yml +31 -0
- opnsense_mcp_server-0.3.2/.github/workflows/publish-pypi.yml +25 -0
- opnsense_mcp_server-0.3.2/.gitignore +37 -0
- opnsense_mcp_server-0.3.2/CHANGELOG.md +133 -0
- opnsense_mcp_server-0.3.2/CONTRIBUTING.md +143 -0
- opnsense_mcp_server-0.3.2/Dockerfile +13 -0
- opnsense_mcp_server-0.3.2/LICENSE +21 -0
- opnsense_mcp_server-0.3.2/Makefile +47 -0
- opnsense_mcp_server-0.3.2/PKG-INFO +463 -0
- opnsense_mcp_server-0.3.2/README.md +433 -0
- opnsense_mcp_server-0.3.2/docs/README.md +20 -0
- opnsense_mcp_server-0.3.2/docs/best-practices/ddns-cloudflare.md +146 -0
- opnsense_mcp_server-0.3.2/docs/best-practices/voip-whatsapp.md +224 -0
- opnsense_mcp_server-0.3.2/pyproject.toml +84 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/__init__.py +3 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/__main__.py +5 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/api_client.py +668 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/config.py +89 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/config_cache.py +335 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/py.typed +0 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/server.py +113 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/__init__.py +1 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/dhcp.py +189 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/diagnostics.py +158 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/dns.py +150 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/firewall.py +675 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/haproxy.py +279 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/network.py +181 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/security.py +1513 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/services.py +297 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/system.py +153 -0
- opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/vpn.py +65 -0
- opnsense_mcp_server-0.3.2/tests/__init__.py +0 -0
- opnsense_mcp_server-0.3.2/tests/conftest.py +186 -0
- opnsense_mcp_server-0.3.2/tests/integration/__init__.py +0 -0
- opnsense_mcp_server-0.3.2/tests/integration/test_ipv6_analysis.py +658 -0
- opnsense_mcp_server-0.3.2/tests/integration/test_live_readonly.py +457 -0
- opnsense_mcp_server-0.3.2/tests/test_api_client.py +546 -0
- opnsense_mcp_server-0.3.2/tests/test_config.py +169 -0
- opnsense_mcp_server-0.3.2/tests/test_config_cache.py +295 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/__init__.py +1 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_dhcp.py +242 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_diagnostics.py +329 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_dns.py +216 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_firewall.py +693 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_haproxy.py +248 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_network.py +174 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_registration.py +98 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_security.py +1538 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_services.py +416 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_system.py +280 -0
- opnsense_mcp_server-0.3.2/tests/test_tools/test_vpn.py +96 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: pip install -e ".[dev]"
|
|
19
|
+
- name: Ruff lint (includes bandit security rules)
|
|
20
|
+
run: ruff check src/ tests/
|
|
21
|
+
- name: Ruff format check
|
|
22
|
+
run: ruff format --check src/ tests/
|
|
23
|
+
- name: Type checking (mypy strict)
|
|
24
|
+
run: mypy src/
|
|
25
|
+
|
|
26
|
+
test:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.11"
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: pip install -e ".[dev]"
|
|
35
|
+
- name: Run tests
|
|
36
|
+
run: pytest -v --tb=short
|
|
37
|
+
|
|
38
|
+
security:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: actions/setup-python@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.11"
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
run: pip install -e ".[dev]"
|
|
47
|
+
- name: Dependency vulnerability audit
|
|
48
|
+
run: pip-audit
|
|
49
|
+
- name: Check for hardcoded secrets
|
|
50
|
+
run: ruff check src/ --select S105,S106,S107 --no-fix
|
|
51
|
+
- name: Check for insecure SSL usage
|
|
52
|
+
run: ruff check src/ --select S501 --no-fix
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to Docker Hub
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Extract version from tag
|
|
15
|
+
id: version
|
|
16
|
+
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
|
|
17
|
+
|
|
18
|
+
- name: Log in to Docker Hub
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
21
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
22
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
23
|
+
|
|
24
|
+
- name: Build and push
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
26
|
+
with:
|
|
27
|
+
context: .
|
|
28
|
+
push: true
|
|
29
|
+
tags: |
|
|
30
|
+
lucamarien/opnsense-mcp-server:latest
|
|
31
|
+
lucamarien/opnsense-mcp-server:${{ steps.version.outputs.version }}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: release
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.13"
|
|
20
|
+
- name: Install build tools
|
|
21
|
+
run: pip install --no-cache-dir build
|
|
22
|
+
- name: Build wheel and sdist
|
|
23
|
+
run: python -m build
|
|
24
|
+
- name: Publish to PyPI
|
|
25
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Claude Code (local dev tooling — not published)
|
|
2
|
+
.claude/
|
|
3
|
+
CLAUDE.md
|
|
4
|
+
.mcp.json
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.pyo
|
|
10
|
+
*.egg-info/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
.venv/
|
|
14
|
+
.eggs/
|
|
15
|
+
|
|
16
|
+
# Credentials (NEVER commit)
|
|
17
|
+
.env
|
|
18
|
+
*.key
|
|
19
|
+
*_apikey.txt
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
*~
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
Thumbs.db
|
|
31
|
+
|
|
32
|
+
# Testing
|
|
33
|
+
.coverage
|
|
34
|
+
htmlcov/
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.ruff_cache/
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.3.2] - 2026-03-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Diagnostics:** `opn_dns_lookup` now sends correct API payload (`dns.settings.hostname` instead of `dns.hostname`) — lookups were silently failing with validation error
|
|
12
|
+
- **VPN:** `opn_ipsec_status` now uses POST for `searchPhase1`/`searchPhase2` endpoints — GET was returning empty or failed responses
|
|
13
|
+
- **VPN:** `opn_openvpn_status` now uses POST with pagination for all three search endpoints (instances, sessions, routes)
|
|
14
|
+
- **Services:** `opn_crowdsec_status` now uses POST for decisions/alerts search — consistent with `opn_crowdsec_alerts`
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **Diagnostics:** Expanded hostname validation to reject additional shell metacharacters (`(`, `)`, `{`, `}`, `<`, `>`, `'`, `"`, `\`, space)
|
|
19
|
+
- **Diagnostics:** `opn_dns_lookup` now validates the `server` parameter against the same injection rules as `hostname`
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Firewall:** `opn_firewall_log` now supports client-side filtering via `source_ip`, `destination_ip`, `action`, `interface`, and `limit` parameters — reduces context window size when investigating specific devices
|
|
24
|
+
|
|
25
|
+
## [0.3.1] - 2026-02-25
|
|
26
|
+
|
|
27
|
+
Packaging readiness and documentation sync.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Version numbers synced across pyproject.toml, \_\_init\_\_.py, and CHANGELOG (was stuck at 0.1.0)
|
|
32
|
+
- README updated to document all 60 tools (was 57 — missing firewall categories, ICMPv6 rules, NDP table, IPv6 status)
|
|
33
|
+
- Narrowed broad `except Exception` to specific exceptions in `opn_ipv6_status`
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- CLI entry point: `opnsense-mcp` command available after `pip install`
|
|
38
|
+
- `__main__.py` for `python -m opnsense_mcp` support
|
|
39
|
+
- PEP 561 `py.typed` marker for downstream type checking
|
|
40
|
+
- Test registration updated to cover all 60 tools
|
|
41
|
+
|
|
42
|
+
## [0.3.0] - 2026-02-22
|
|
43
|
+
|
|
44
|
+
Comprehensive security audit overhaul — from 4 surface-level checks to 11 in-depth security areas with compliance framework tagging.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **Security:** `opn_security_audit` completely rewritten with 11 audit sections (was 4):
|
|
49
|
+
- **Firewall rules:** Paginated analysis (no more 500-row cap), MVC + legacy config.xml rules, broad source/destination detection, port grouping best practices (SSH isolation, mixed service detection), management port exposure on WAN, insecure protocol detection (FTP, Telnet, plaintext SMTP/POP3/IMAP/LDAP, rsh/rlogin)
|
|
50
|
+
- **NAT port forwarding:** Dangerous port exposure, unrestricted sources, UDP amplification risk, insecure protocol forwarding
|
|
51
|
+
- **DNS security:** DNSSEC validation, DNS-over-TLS forwarding, resolver identity/version hiding
|
|
52
|
+
- **System hardening:** Web GUI HTTPS enforcement, SSH root login/password auth/default port, remote syslog configuration
|
|
53
|
+
- **Services:** Expanded critical service list (7 services, was 4), CrowdSec plugin detection
|
|
54
|
+
- **Certificates:** ACME + system certificate store + CA certificates (not just ACME)
|
|
55
|
+
- **VPN security:** WireGuard config audit with stale peer detection, IPsec status, OpenVPN instances
|
|
56
|
+
- **HAProxy security:** HTTP frontend detection, HTTPS redirect checks, backend health checks, security header audit (HSTS, X-Frame-Options, X-Content-Type-Options)
|
|
57
|
+
- **Gateway health:** Down/offline detection, packet loss >5%, latency >100ms
|
|
58
|
+
- **Compliance tagging:** Every finding tagged with applicable PCI DSS v4.0, BSI IT-Grundschutz, NIST SP 800-41, and CIS Benchmark controls. Manual review items listed separately.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- 6 new read-only API endpoint registrations: WireGuard server/client search, HAProxy frontend/backend/server/action search
|
|
63
|
+
- `_fetch_all_pages` pagination helper for unbounded rule analysis
|
|
64
|
+
- Port parsing, classification, and insecure protocol detection helpers
|
|
65
|
+
- 97 security tests (was 18)
|
|
66
|
+
|
|
67
|
+
## [0.2.0] - 2026-02-22
|
|
68
|
+
|
|
69
|
+
Expands from 35 to 41 tools with NAT port forwarding, full VPN coverage, DNS write operations, and static route visibility.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **Firewall:** `opn_list_nat_rules` — list NAT port forwarding (DNAT) rules with search/pagination
|
|
74
|
+
- **Firewall:** `opn_add_nat_rule` — create NAT port forwarding rules with savepoint protection
|
|
75
|
+
- **VPN:** `opn_ipsec_status` — IPsec tunnel status (IKE Phase 1 + ESP/AH Phase 2)
|
|
76
|
+
- **VPN:** `opn_openvpn_status` — OpenVPN instances, sessions, and routes
|
|
77
|
+
- **DNS:** `opn_add_dns_override` — add Unbound host overrides (A/AAAA) with auto-reconfigure and input validation
|
|
78
|
+
- **Network:** `opn_list_static_routes` — list configured static routes with search/pagination
|
|
79
|
+
- 11 new API endpoint registrations with dual camelCase/snake_case support
|
|
80
|
+
- DNS input validation (hostname, domain, IP address regex)
|
|
81
|
+
- NAT protocol validation (TCP, UDP, TCP/UDP)
|
|
82
|
+
|
|
83
|
+
## [0.1.0] - 2026-02-21
|
|
84
|
+
|
|
85
|
+
Initial release with 35 tools across 9 domains.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
#### Phase 1: Foundation
|
|
90
|
+
|
|
91
|
+
- Core infrastructure: config loading, API client with HTTP Basic Auth, SSL verification, 30s timeout
|
|
92
|
+
- OPNsense version auto-detection (camelCase for pre-25.7, snake_case for 25.7+)
|
|
93
|
+
- Dual endpoint registry supporting both API naming conventions
|
|
94
|
+
- Endpoint blocklist: `halt`, `reboot`, `poweroff`, `firmware update/upgrade` are permanently blocked
|
|
95
|
+
- FastMCP server with STDIO transport
|
|
96
|
+
- 16 read-only tools:
|
|
97
|
+
- **System:** `opn_system_status`, `opn_list_services`, `opn_gateway_status`
|
|
98
|
+
- **Network:** `opn_interface_stats`, `opn_arp_table`
|
|
99
|
+
- **Firewall:** `opn_list_firewall_rules`, `opn_list_firewall_aliases`, `opn_firewall_log`
|
|
100
|
+
- **DNS:** `opn_list_dns_overrides`, `opn_list_dns_forwards`, `opn_dns_stats`
|
|
101
|
+
- **DHCP:** `opn_list_dhcp_leases`
|
|
102
|
+
- **VPN:** `opn_wireguard_status`
|
|
103
|
+
- **Services:** `opn_haproxy_status`, `opn_list_acme_certs`, `opn_list_cron_jobs`
|
|
104
|
+
|
|
105
|
+
#### Phase 2: Write Operations
|
|
106
|
+
|
|
107
|
+
- SavepointManager for safe firewall modifications with 60-second auto-rollback
|
|
108
|
+
- Write guard requiring `OPNSENSE_ALLOW_WRITES=true` environment variable
|
|
109
|
+
- 8 write tools:
|
|
110
|
+
- **Firewall:** `opn_confirm_changes`, `opn_toggle_firewall_rule`, `opn_add_firewall_rule`, `opn_delete_firewall_rule`, `opn_add_alias`
|
|
111
|
+
- **DNS:** `opn_reconfigure_unbound`
|
|
112
|
+
- **Services:** `opn_reconfigure_haproxy`
|
|
113
|
+
|
|
114
|
+
#### Phase 3: Advanced Features
|
|
115
|
+
|
|
116
|
+
- **Security:** `opn_security_audit` — comprehensive firewall audit (firmware, permissive rules, disabled rules, critical services, ACME certificates)
|
|
117
|
+
- **Config:** `opn_download_config` — download `config.xml` with XML-aware sensitive data stripping (passwords, keys, secrets redacted by default)
|
|
118
|
+
- **Config:** `opn_scan_config` — session-cached full configuration scan with runtime inventory
|
|
119
|
+
- **Config:** `opn_get_config_section` — retrieve individual config sections as structured JSON
|
|
120
|
+
- **Diagnostics:** `opn_ping` (async job-based with guaranteed cleanup), `opn_traceroute`, `opn_dns_lookup`, `opn_pf_states`
|
|
121
|
+
- **DHCP:** `opn_list_kea_leases` — Kea DHCP server leases
|
|
122
|
+
- **DHCP:** `opn_list_dnsmasq_leases` — dnsmasq DNS/DHCP server leases
|
|
123
|
+
- **Services:** `opn_crowdsec_status`, `opn_crowdsec_alerts` — CrowdSec security engine status and alerts
|
|
124
|
+
- ConfigCache system for session-scoped config caching with automatic invalidation on writes
|
|
125
|
+
- Hostname input validation against shell metacharacter injection
|
|
126
|
+
- `get_text()` API client method for non-JSON responses (XML config backup)
|
|
127
|
+
|
|
128
|
+
### Security
|
|
129
|
+
|
|
130
|
+
- Bandit security scanning via Ruff (S105/S106/S107 for hardcoded credentials, S501 for SSL verification)
|
|
131
|
+
- Strict mypy type checking
|
|
132
|
+
- All tests use mocked API — no real OPNsense credentials in test suite
|
|
133
|
+
- 242 tests with full coverage of security-critical paths
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Contributing to OPNsense MCP Server
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This guide covers the coding standards, security requirements, and workflow for adding tools or fixing bugs.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/lucamarien/opnsense-mcp-server
|
|
9
|
+
cd opnsense-mcp-server
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Coding Standards
|
|
14
|
+
|
|
15
|
+
- **Python 3.11+** with strict typing (`mypy --strict`)
|
|
16
|
+
- **Linting:** `ruff check src/ tests/` (includes bandit security rules)
|
|
17
|
+
- **Formatting:** `ruff format src/ tests/`
|
|
18
|
+
- **Tests:** `pytest -v` (all tests use mocked API responses, no real OPNsense needed)
|
|
19
|
+
- **No external dependencies** beyond `fastmcp` (which provides `httpx`)
|
|
20
|
+
|
|
21
|
+
Run the full CI pipeline locally before submitting:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
make validate
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This runs: lint, format check, security scan, type check, tests, and dependency audit.
|
|
28
|
+
|
|
29
|
+
## Adding a New Tool
|
|
30
|
+
|
|
31
|
+
1. Pick the right domain file in `src/opnsense_mcp/tools/`
|
|
32
|
+
2. Add your function with the `@mcp.tool()` decorator
|
|
33
|
+
3. Use Python type hints — FastMCP auto-generates JSON schema from them
|
|
34
|
+
4. Write a clear docstring (it's the AI's **only** guide to tool selection)
|
|
35
|
+
5. Return `dict`, not formatted strings
|
|
36
|
+
6. Add tests in `tests/test_tools/`
|
|
37
|
+
7. Review against the security checklist below
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
async def opn_example_tool(ctx: Context, search: str = "", limit: int = 50) -> dict[str, Any]:
|
|
42
|
+
"""One-line description of what this does.
|
|
43
|
+
|
|
44
|
+
Use this when you need to [specific scenario].
|
|
45
|
+
Returns: dict with 'rows' (list) and 'total' (int).
|
|
46
|
+
"""
|
|
47
|
+
api = get_api(ctx)
|
|
48
|
+
return await api.get("endpoint.logical_name")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Tool Naming
|
|
52
|
+
|
|
53
|
+
- All tools use the `opn_` prefix
|
|
54
|
+
- Use descriptive names: `opn_list_firewall_rules`, `opn_add_dns_override`
|
|
55
|
+
- Read tools: `opn_list_*`, `opn_*_status`, `opn_get_*`
|
|
56
|
+
- Write tools: `opn_add_*`, `opn_delete_*`, `opn_toggle_*`, `opn_reconfigure_*`
|
|
57
|
+
|
|
58
|
+
### Endpoint Registry
|
|
59
|
+
|
|
60
|
+
Never hardcode API paths in tools. Use logical endpoint names resolved by the API client:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Good — uses the endpoint registry (auto-resolves camelCase/snake_case)
|
|
64
|
+
await api.get("firewall.search_rule")
|
|
65
|
+
|
|
66
|
+
# Bad — hardcodes the endpoint path
|
|
67
|
+
await api.get("api/firewall/filter/searchRule")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Add new endpoints to `ENDPOINT_REGISTRY` in `src/opnsense_mcp/api_client.py` with both naming variants:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
"firewall.search_rule": ("firewall/filter/searchRule", "firewall/filter/search_rule"),
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Security Requirements
|
|
77
|
+
|
|
78
|
+
These are non-negotiable for all contributions:
|
|
79
|
+
|
|
80
|
+
### Never Do
|
|
81
|
+
|
|
82
|
+
- Hardcode credentials, API keys, or secrets (enforced by Ruff S105/S106/S107)
|
|
83
|
+
- Log, print, or include API keys in any output or error messages
|
|
84
|
+
- Add API endpoints without checking against the blocklist
|
|
85
|
+
- Disable SSL verification without explicit user configuration
|
|
86
|
+
- Use global mutable state for credential storage
|
|
87
|
+
- Expose internal file paths, stack traces, or sensitive config in tool responses
|
|
88
|
+
|
|
89
|
+
### Always Do
|
|
90
|
+
|
|
91
|
+
- Load credentials from environment variables only
|
|
92
|
+
- Guard write operations with the `OPNSENSE_ALLOW_WRITES` check
|
|
93
|
+
- Use the savepoint/rollback mechanism for all firewall modifications
|
|
94
|
+
- Keep transport as STDIO only — never expose HTTP/SSE endpoints
|
|
95
|
+
- Validate user-provided inputs (hostnames, IP addresses) against injection
|
|
96
|
+
|
|
97
|
+
### Blocked Endpoints
|
|
98
|
+
|
|
99
|
+
The following endpoints are **permanently blocked** at the API client level and must never be exposed through any tool:
|
|
100
|
+
|
|
101
|
+
- `core/system/halt` — shuts down the firewall instantly
|
|
102
|
+
- `core/system/reboot` — reboots instantly
|
|
103
|
+
- `core/firmware/poweroff` — powers off instantly
|
|
104
|
+
- `core/firmware/update` — triggers system update
|
|
105
|
+
- `core/firmware/upgrade` — triggers major upgrade
|
|
106
|
+
|
|
107
|
+
## Testing Requirements
|
|
108
|
+
|
|
109
|
+
- **All tests must use mocked API responses** — never connect to a real OPNsense instance
|
|
110
|
+
- Test both success and error paths
|
|
111
|
+
- Test that blocked endpoints are rejected
|
|
112
|
+
- Test that write guards work (`ALLOW_WRITES=false` must fail)
|
|
113
|
+
- Test both camelCase and snake_case endpoint variants for version compatibility
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Run all tests
|
|
117
|
+
pytest -v
|
|
118
|
+
|
|
119
|
+
# Run tests for a specific domain
|
|
120
|
+
make test-domain DOMAIN=firewall
|
|
121
|
+
|
|
122
|
+
# Run with verbose output on failures
|
|
123
|
+
pytest -v --tb=long
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Quick Decision Trees
|
|
127
|
+
|
|
128
|
+
- **GET vs POST:** Status/read operations use GET. Search with filters uses POST. Modifications/actions use POST.
|
|
129
|
+
- **Write guard?** Read operations don't need one. Firewall changes need write guard + savepoint. Other modifications (DNS, HAProxy) need write guard only.
|
|
130
|
+
- **Savepoint?** Firewall rule changes (add/delete/toggle) use savepoints with 60-second auto-revert. Service reconfigurations (Unbound, HAProxy, dnsmasq) do not.
|
|
131
|
+
|
|
132
|
+
## Pull Request Checklist
|
|
133
|
+
|
|
134
|
+
- [ ] `make validate` passes locally
|
|
135
|
+
- [ ] New tools have tests with mocked API responses
|
|
136
|
+
- [ ] Docstrings are clear and describe when to use the tool
|
|
137
|
+
- [ ] No hardcoded credentials or sensitive data
|
|
138
|
+
- [ ] Write operations are guarded and use savepoints where appropriate
|
|
139
|
+
- [ ] Total tool count stays under 65 (currently 62)
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FROM python:3.13-slim AS builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /build
|
|
4
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
5
|
+
COPY src/ src/
|
|
6
|
+
RUN pip install --no-cache-dir build && python -m build --wheel
|
|
7
|
+
|
|
8
|
+
FROM python:3.13-slim
|
|
9
|
+
|
|
10
|
+
COPY --from=builder /build/dist/*.whl /tmp/
|
|
11
|
+
RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl
|
|
12
|
+
|
|
13
|
+
ENTRYPOINT ["opnsense-mcp"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.PHONY: lint format format-check typecheck test test-domain security audit validate clean install build docker
|
|
2
|
+
|
|
3
|
+
install: ## Install project with dev dependencies
|
|
4
|
+
pip install -e ".[dev]"
|
|
5
|
+
|
|
6
|
+
lint: ## Run ruff linter (includes bandit security rules)
|
|
7
|
+
ruff check src/ tests/
|
|
8
|
+
|
|
9
|
+
format: ## Format code with ruff
|
|
10
|
+
ruff format src/ tests/
|
|
11
|
+
|
|
12
|
+
format-check: ## Check formatting without modifying files
|
|
13
|
+
ruff format --check src/ tests/
|
|
14
|
+
|
|
15
|
+
typecheck: ## Run mypy strict type checking
|
|
16
|
+
mypy src/
|
|
17
|
+
|
|
18
|
+
test: ## Run test suite
|
|
19
|
+
pytest -v --tb=short
|
|
20
|
+
|
|
21
|
+
test-domain: ## Run tests for a specific domain (usage: make test-domain DOMAIN=system)
|
|
22
|
+
pytest tests/test_tools/test_$(DOMAIN).py -v --tb=long
|
|
23
|
+
ruff check src/opnsense_mcp/tools/$(DOMAIN).py
|
|
24
|
+
mypy src/opnsense_mcp/tools/$(DOMAIN).py
|
|
25
|
+
|
|
26
|
+
security: ## Run security-specific checks
|
|
27
|
+
ruff check src/ --select S105,S106,S107 --no-fix
|
|
28
|
+
ruff check src/ --select S501 --no-fix
|
|
29
|
+
|
|
30
|
+
audit: ## Run dependency vulnerability audit
|
|
31
|
+
pip-audit
|
|
32
|
+
|
|
33
|
+
validate: lint format-check security typecheck test audit ## Run full CI pipeline locally
|
|
34
|
+
@echo "All checks passed!"
|
|
35
|
+
|
|
36
|
+
build: ## Build wheel and sdist
|
|
37
|
+
python -m build
|
|
38
|
+
|
|
39
|
+
docker: ## Build Docker image locally
|
|
40
|
+
docker build -t opnsense-mcp-server .
|
|
41
|
+
|
|
42
|
+
clean: ## Remove build artifacts
|
|
43
|
+
rm -rf build/ dist/ *.egg-info/ .pytest_cache/ .mypy_cache/ .ruff_cache/ htmlcov/
|
|
44
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
45
|
+
|
|
46
|
+
help: ## Show this help
|
|
47
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
|