From 6c0beb5a96fe551268f043b7e9f4168f4c496326 Mon Sep 17 00:00:00 2001 From: Howard Enos Date: Sun, 21 Jun 2026 11:28:23 -0700 Subject: [PATCH] sync: auto-sync from HOWARD-HOME at 2026-06-21 11:27:38 Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-06-21 11:27:38 --- errorlog.md | 6 + ...6-21-howard-unifi-pfsense-control-verbs.md | 164 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 session-logs/2026-06/2026-06-21-howard-unifi-pfsense-control-verbs.md diff --git a/errorlog.md b/errorlog.md index 6fdc315a..feb1032f 100644 --- a/errorlog.md +++ b/errorlog.md @@ -17,6 +17,12 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure · +2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy] + +2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint] + +2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints] + 2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createCustomGroup]: The required parameter is missing : groupName [ctx: cmd=raw] 2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createCustomGroup]: One or more parameters are not expected: name [ctx: cmd=make-group] diff --git a/session-logs/2026-06/2026-06-21-howard-unifi-pfsense-control-verbs.md b/session-logs/2026-06/2026-06-21-howard-unifi-pfsense-control-verbs.md new file mode 100644 index 00000000..dd6745e2 --- /dev/null +++ b/session-logs/2026-06/2026-06-21-howard-unifi-pfsense-control-verbs.md @@ -0,0 +1,164 @@ +# Session — unifi-wifi pfSense SSH gateway-control verbs (ROADMAP §E) + +## User +- **User:** Howard Enos (howard) +- **Machine:** Howard-Home +- **Role:** tech + +## Session Summary + +Picked up the unifi-wifi skill build-out, specifically the open WIP in ROADMAP §E — the pfSense +gateway "compatibility layer". The skill already had a working read-only SSH backend +(`pfsense-ssh.sh`: `audit`/`dhcp`/`run`/`shell`) and a superseded REST backend +(`pfsense-backend.sh`) that held the full control-verb contract. Per Mike's 2026-06-16 decision +(SSH, not the REST API package), the remaining work was to implement gated CONTROL verbs on the +SSH backend mirroring the `gw-control` contract, then rewire `gw-audit.sh`/`gw-control.sh` dispatch +to the SSH backend. + +Before writing code, ran read-only discovery against the live Cascades pfSense (Plus 25.07-RELEASE, +reachable at 192.168.0.1 over the Cascades site VPN) to pin the exact config schema rather than +guess. Discovery established: `pfSsh.php` does NOT eval piped ad-hoc code (only its built-in +`playback` scripts), so the reliable primitive is `php` with a `require_once("config.inc")` +bootstrap, which loads `$config` + `write_config()` + `filter_configure()`. Filter rules are keyed +on `tracker` (the `id` field is empty on 25.07); enabled/disabled is the PRESENCE of a `disabled` +key. `easyrule` supports `block/unblock/showblock ` and auto-manages its alias ++ WAN rule. Cascades has 20 filter rules and 0 NAT port-forwards. + +Built a new versioned PHP helper `scripts/pfsense-gwc.php` (argv-driven, no operator data +interpolated into PHP source) for list/toggle/set/delete against `$config`, with a config backup + +`write_config()` + `filter_configure()` on every write. Extended `pfsense-ssh.sh` with 12 verbs +(`pf-list`, `fw-list`, `showblock`, `pf-disable/enable/delete`, `pf-set-ports`, `pf-set-src`, +`fw-disable/enable`, `block-ips`, `unblock`) — DRY-RUN by default, `--apply` to commit. The helper +ships to the box via `base64 | openssl base64 -A -d` and runs with argv. Rewired dispatch in both +`gw-control.sh` and `gw-audit.sh` to prefer the SSH backend (keyed on +`clients//pfsense-firewall`), running the dispatch BEFORE UOS site resolution so a +pfSense-only client slug works; the REST backend is now a dormant fallback. + +Validated live on Cascades: reads (`fw-list` all 20 rules with correct schema, `pf-list`, +`showblock`), tracker- and descr-based matching, the full `block-ips`/`unblock` cycle on a TEST-NET +documentation IP (192.0.2.123), and the `pfsense-gwc.php` write path via a `fw-disable`/`fw-enable` +round-trip on an inert rule (config backup + reload confirmed, rule returned to original state). +Updated ROADMAP §E status to done, recorded the pfSense PHP gotchas, and updated SKILL.md's verb +reference + dispatch description. + +## Key Decisions + +- **`php` + `config.inc` bootstrap, not config.xml text parsing.** Robust against version drift and + gives pfSense's own `write_config()`/`filter_configure()` for safe commit + reload. Chosen after + confirming `pfSsh.php` only runs its built-in `playback` scripts, not piped code. +- **Versioned PHP helper file (`pfsense-gwc.php`) instead of a bash heredoc.** Keeps the control + logic readable/lintable; shipped to the box per-call via base64 over the existing SSH session. +- **argv-driven helper.** All operator data passed as positional args, never interpolated into PHP + source — no shell/PHP injection surface. +- **Match filter rules by `tracker` or exact `descr`, not `id`.** The `id` field is empty on pf25.07; + `tracker` is the stable unique key. +- **Dispatch runs before UOS site resolution.** A pfSense-only site is keyed by client slug (not a + UOS site name), so resolving the UOS site first would hard-exit before dispatch could fire. +- **SSH backend preferred, REST kept dormant.** Honors Mike's 2026-06-16 decision; REST + (`pfsense-backend.sh` + `clients//pfsense-api`) stays in-tree only as a fallback. +- **`block-ips` via `easyrule`, toggles via the PHP path.** easyrule is the cleanest, canonical + pfSense block mechanism (auto alias + rule); rule toggles need direct `$config` edits. + +## Problems Encountered + +- **pfSsh.php piped code did nothing.** Expected `echo '...; exec' | pfSsh.php` to eval; it only + prints its banner + `playback` command list. Switched to `php` with a temp-file script. +- **php_rc=255 with no error output.** pfSense runs `display_errors=Off`, so fatals are silent. + Fixed by running php with `2>&1` AND `ini_set("display_errors","1")` in the helper. +- **`Cannot redeclare backup_config()` fatal.** pfSense's `config.lib.inc` already defines it (and + many generic names). Prefixed all helper functions with `gwc_`. Also dropped the extra + `require_once` of util/functions/filter — `config.inc` already pulls them; re-requiring caused + the redeclare fatal. +- **Empty output on first `fw-list` runs.** Root-caused via step markers in the remote script (the + three issues above), not by guessing. +- **`No such file or directory` on a verb test.** Working dir had persisted from an earlier `cd` + into the scripts dir; relative script path broke. Re-ran with `cd /c/claudetools` + path. + +## Configuration Changes + +Created: +- `.claude/skills/unifi-wifi/scripts/pfsense-gwc.php` — argv-driven pfSense config control helper. + +Modified: +- `.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh` — added 12 control verbs, `--apply`/`--if` + parsing, `sq()` shell-quote + `run_gwc()` ship-and-run helper, fixed `run` to use preserved args. +- `.claude/skills/unifi-wifi/scripts/gw-control.sh` — SSH-first dispatch (pfsense-firewall cred), + moved above UOS site resolution; REST kept as fallback. +- `.claude/skills/unifi-wifi/scripts/gw-audit.sh` — same SSH-first dispatch for `audit`. +- `.claude/skills/unifi-wifi/references/ROADMAP.md` — §E status → done; verb list; pfSense PHP + gotchas added to cross-platform notes. +- `.claude/skills/unifi-wifi/SKILL.md` — pfSense section rewritten (read/write verb tables, + dispatch); gw-control usage block corrected (routes to pfsense-ssh.sh, match by tracker/descr). + +## Credentials & Secrets + +No new credentials created or discovered. Used existing vault entries (read-only): +- `clients/cascades-tucson/pfsense-firewall` — host 192.168.0.1, user `admin`, password stored. + Used by `pfsense-ssh.sh` (system OpenSSH via askpass). +- `clients/cascades-tucson/pfsense-openvpn-howard` — the Cascades site VPN profile (referenced for + reachability; not directly used by the scripts). + +## Infrastructure & Servers + +- **Cascades pfSense** — 192.168.0.1 (LAN), pfSense Plus **25.07-RELEASE**, admin SSH drops straight + to a shell. Reachable over the Cascades site VPN (16ms). 20 filter rules, 0 NAT port-forwards. +- PHP include_path on the box: `.:/etc/inc:/usr/local/pfSense/include:...` — `require_once("config.inc")` + resolves from anywhere. +- Test residue left on Cascades: an inert `'Blocked via EasyRule'` WAN block rule (tracker + 1782065739) + empty `EasyRuleBlockHostsWAN` alias, created by the `block-ips` validation. Blocks + nothing (empty alias); returned to enabled state. Pending operator decision to remove or keep. + +## Commands & Outputs + +``` +# Discovery (read-only) — schema + tooling +pfsense-ssh.sh cascades-tucson run 'cat /etc/version' # 25.07-RELEASE +# php bootstrap works; pfSsh.php only runs playback scripts (not piped code) +php /tmp/q.php -> NAT_RULES=0 FILTER_RULES=20 +easyrule -> block|unblock|showblock ; pass [port] + +# New read verbs +pfsense-ssh.sh cascades-tucson fw-list # 20 rules, keyed on tracker +pfsense-ssh.sh cascades-tucson pf-list # (no NAT port-forwards configured) +pfsense-ssh.sh cascades-tucson showblock # No entries are blocked on interface: wan + +# Live write validations +pfsense-ssh.sh cascades-tucson block-ips 192.0.2.123 --apply # Block added successfully +pfsense-ssh.sh cascades-tucson showblock # 192.0.2.123/32 +pfsense-ssh.sh cascades-tucson unblock 192.0.2.123 --apply # Entry unblocked successfully +pfsense-ssh.sh cascades-tucson fw-disable 1782065739 --apply # backup + write_config + filter reload -> [off] +pfsense-ssh.sh cascades-tucson fw-enable 1782065739 --apply # -> [on] (restored) + +# Dispatch validation +gw-control.sh cascades-tucson fw-list # -> dispatches to pfsense-ssh.sh +gw-control.sh cascades-tucson fw-disable 1772841904 # dry-run via dispatch + +# Syntax check +bash -n pfsense-ssh.sh gw-control.sh gw-audit.sh # all [OK] +``` + +Key gotcha encoded in scripts: pfSense `display_errors=Off` (run php with `2>&1` + `ini_set`), +`backup_config()` collision (prefix `gwc_*`), `config.inc`-only bootstrap (no re-require). + +## Pending / Incomplete Tasks + +- **`pf-*` (NAT port-forward) verbs are built but NOT live-verified.** Cascades has 0 port-forwards, + so they were coded against the documented pfSense NAT schema (`destination.port`, `target`, + `local-port`, `associated-rule-id`). Need a pfSense box with an actual port-forward + a vaulted + `clients//pfsense-firewall` cred to confirm field names before trusting `--apply`. Marked + live-verify-pending in ROADMAP §E. +- **Cascades easyrule test residue** — decide whether to remove the inert + `'Blocked via EasyRule'` rule (tracker 1782065739) + empty `EasyRuleBlockHostsWAN` alias. +- **Optional `pf-add`/create verbs** — not needed today (we only close/scope existing exposure); + noted in ROADMAP as future. +- Other §B/§C/§D ROADMAP items remain (per-client AP creds, gateway-hosted VPN server, read-only + vault cred) — untouched this session. + +## Reference Information + +- Session-log path: `session-logs/2026-06/2026-06-21-howard-unifi-pfsense-control-verbs.md` +- Skill: `.claude/skills/unifi-wifi/` — ROADMAP §E is the design/verb-map + pfSense PHP gotchas. +- Verb contract source (UniFi side): `scripts/gw-control.sh` header. +- Prior sync commits this session: `5ede4fe` (earlier auto-sync), `96a5dd6` (the pfSense build). +- pfSense filter-rule schema: keyed on `tracker`; `type` = pass/block/reject; `disabled` key + presence = off; `source`/`destination` are objects (`{any:""}` or `{network,port}`).