docs: migrate all gururmm session logs to claudetools session-logs/

This commit is contained in:
2026-05-15 06:13:52 -07:00
parent d688279c04
commit 9bbc08a1ec
8 changed files with 2989 additions and 970 deletions

View File

@@ -1,361 +1,399 @@
# Session Log: 2026-04-21
# Session Log 2026-04-21
## User
- **User:** Mike Swanson (mike)
- **Machine:** DESKTOP-0O8A1RL
- **Role:** admin
---
## Session Summary
This session completed the M365 multi-tenant onboarding initiative. The goal was to onboard all 41 CIPP-managed partner tenants to the ComputerGuru app suite (Security Investigator, Exchange Operator, User Manager, Tenant Admin, Defender Add-on) with minimal customer interaction — customers click one URL (Tenant Admin consent), then the `onboard-tenant.sh` script handles all remaining programmatic consent and role assignments automatically.
Continuation from previous conversation (context compacted). This session covered three areas:
### Accomplishments
1. **BirthBiologic vault save** — fixed a broken vault stub and saved GuruRMM site credentials for the new BirthBiologic client
2. **MSI build fix** — diagnosed and fixed "MSI build on Pluto failed" error caused by a missing WiX extension flag in `install.rs`
3. **DESIGN.md created** — comprehensive per-component design guide for GuruRMM covering architectural decisions, rules, and constraints that were previously only in session logs and verbal decisions
1. **Tenant Admin manifest fix (from previous session)**: Added `AppRoleAssignment.ReadWrite.All` (GUID: `06b708a9-e830-4db3-a914-8e69da51d44f`) to Tenant Admin app. This was required for the script to programmatically grant appRoleAssignments to other SPs in customer tenants. Fixed via Management app PATCH.
---
2. **Re-onboarded martylryan.com and grabblaw.com**: These two were consented before the manifest fix. Both needed Tenant Admin re-consent (done by Mike), then script re-run. Both now fully onboarded with all apps and directory roles.
- martylryan.com: All 4 apps + Exchange Admin + User Admin + Auth Admin assigned
- grabblaw.com: 3 apps (no MDE) + Exchange Admin + User Admin + Auth Admin assigned; Defender skipped (no MDE license)
## Key Work
3. **Cascades Tucson GoDaddy admin account** (from previous session):
- Found disabled account `admin@NETORGFT4257522.onmicrosoft.com`
- Renamed UPN to `admin@cascadestucson.com` (domain was verified default)
- Enabled account, reset password to `Gptf*ttb123!@#-cs`
- Vaulted at `D:/vault/clients/cascades-tucson/m365-admin.sops.yaml`
### 1. BirthBiologic Vault Entry — Fixed and Saved
4. **Batch tenant sweep**: Ran `onboard-tenant.sh` against all 40 pending tenants. 17 were already fully consented and onboarded successfully. 23 still need initial Tenant Admin consent.
**Problem:** A broken unencrypted stub existed at `D:/vault/clients/birthbiologic/gururmm-site-main.sops.yaml`. `vault.sh add` failed ("file already exists"), `vault.sh create` doesn't exist, and `sops --encrypt` failed with "no matching creation rules found" when the input file wasn't named `.sops.yaml`.
5. **tenant-consent.html**: Updated to show only remaining pending tenants. 19 tenants now marked done (including martylryan + grabblaw post re-consent). 22 still pending.
**Root cause:** The SOPS `.sops.yaml` creation rule uses `path_regex: '.*\.sops\.yaml$'` — it only matches files already named `.sops.yaml`. Attempting to encrypt a `.plain.yaml` file doesn't match the rule.
### Files Modified This Session
**Fix:**
1. Deleted the broken stub
2. Wrote plaintext to `gururmm-site-main.plain.yaml`
3. Encrypted with explicit AGE key + `--encrypted-regex` flags: `sops --encrypt --age age1qz7ct84m50u06h97artqddkj3c8se2yu4nxu59clq8rhj945jc0s5excpr --encrypted-regex '^(credentials|...)$' input.plain.yaml > output.sops.yaml`
4. Deleted plaintext
5. Verified: `vault.sh get-field clients/birthbiologic/gururmm-site-main.sops.yaml credentials.api_key` returned correct value
**BirthBiologic GuruRMM credentials (also in vault):**
```
client_id: da526b38-e832-4159-ab13-a3d94e9897a2
site_id: 3b20ef97-c764-4ef8-9154-79c3d5b486f8
site_code: BRIGHT-PEAK-5980
api_key: grmm_1ZB1qV9Q61b9Noq8BIaZGwLNjZMfF49i
installer_url (landing): https://rmm.azcomputerguru.com/install/BRIGHT-PEAK-5980
msi_url (direct): https://rmm.azcomputerguru.com/sites/3b20ef97-c764-4ef8-9154-79c3d5b486f8/installer
```
Vault file: `D:/vault/clients/birthbiologic/gururmm-site-main.sops.yaml`
---
### 2. MSI Build Fix — "MSI build on Pluto failed"
**Symptom:** Clicking "Download MSI" in the GuruRMM dashboard for any site returned "MSI build on Pluto failed" in red.
**Diagnosis:** Server log showed:
```
stdout=C:\gururmm\installer\gururmm-agent.wxs(226) : error WIX0094:
The identifier 'Binary:Wix4UtilCA_X64' could not be found.
```
**Root cause:** The `build_site_msi_on_pluto` function in `server/src/api/install.rs` was calling `wix build` without `-ext WixToolset.Util.wixext`. The `InstallReportCA` custom action uses `BinaryRef="Wix4UtilCA_X64"` which lives in the Util extension. The base-MSI build in `build-agents.sh` had the flag; the on-demand per-site build did not.
**Fix:** Added `-ext WixToolset.Util.wixext` to the WiX command in `build_site_msi_on_pluto`:
```
"cd C:\\gururmm\\installer && wix.exe build gururmm-agent.wxs \
-arch x64 -d Version={version} -d SITEKEY={site_id} \
-o {remote_out} -ext WixToolset.Util.wixext"
```
Applied directly on Jupiter via `sed -i`, rebuilt server (`cargo build --release` in `server/`), restarted `gururmm-server`. Then committed and pushed the fix to Gitea.
**Fix commit:** `6106087` — "fix: add WixToolset.Util.wixext to site MSI build command"
**Note:** This was a discrepancy between `build-agents.sh` (had the flag) and `install.rs` (didn't). Added to DESIGN.md as a documented rule.
---
### 3. DESIGN.md — GuruRMM Design Guide Created
Created `docs/DESIGN.md` in the GuruRMM repo. This is a new document capturing per-component design decisions and hard constraints that were previously scattered across session logs and verbal decisions.
**Committed:** `6b76dd7` — "docs: add DESIGN.md — per-component architectural decisions and rules"
**Sections:**
- Project-Wide Rules (no TOML/config for endpoints, registry as source of truth)
- Agent (auto-install, per-agent enrollment keys, legacy OS support, .old cleanup, downgrade guard)
- Installer/MSI (WiX v4 only, Pluto-only, required extension, Wait="no" rationale, install-report CA as debug logger, no UI extension)
- Build Pipeline (webhook-only builds, parallelism, signing, toolchain self-bootstrapping, build lock)
- Server (PostgreSQL not MariaDB, INET sqlx pattern, ConnectInfo extractor, stop-before-replace, migration recording)
- Dashboard (useMemo pitfall, sidebar colors, modal key reset, theme support)
- Tray Application (separate crate, user session, policy-controlled, named pipe IPC)
- Protocol / Wire Format (WebSocket message types, heartbeat)
---
## Files Created / Modified
| File | Change |
|---|---|
| `.claude/skills/remediation-tool/scripts/onboard-tenant.sh` | Major rewrite: programmatic consent for all 4 non-admin apps after Tenant Admin consent |
| `.claude/skills/remediation-tool/references/tenants.md` | NEW: full 41-tenant list with display names, domains, tenant IDs, onboarding status, consent URLs |
| `.claude/skills/remediation-tool/references/tenant-consent.html` | NEW + updated: dark-theme HTML page with clickable consent links; 19 tenants marked done |
| `.claude/skills/remediation-tool/references/gotchas.md` | Updated: Grabblaw and martylryan marked fully onboarded with dates |
| `D:/vault/clients/cascades-tucson/m365-admin.sops.yaml` | NEW: SOPS-encrypted admin credentials for Cascades Tucson |
|------|--------|
| `D:/vault/clients/birthbiologic/gururmm-site-main.sops.yaml` | Created (encrypted vault entry for BirthBiologic RMM site) |
| `/home/guru/gururmm/server/src/api/install.rs` | Added `-ext WixToolset.Util.wixext` to Pluto WiX build command |
| `docs/DESIGN.md` (in gururmm repo) | Created — comprehensive design guide |
---
## Commits (gururmm repo)
| SHA | Message |
|-----|---------|
| `6106087` | fix: add WixToolset.Util.wixext to site MSI build command |
| `6b76dd7` | docs: add DESIGN.md — per-component architectural decisions and rules |
---
## Update: 19:25 UTC — MSI Still Failing, Root Cause Found and Fixed
### Problem
After the earlier `install.rs` fix and server rebuild, MSI generation was still failing with the same `WIX0094` error.
### Root Cause
Two compounding issues:
**1. Wrong binary deployed.** The `gururmm-server` service runs from `/opt/gururmm/gururmm-server`, not `/usr/local/bin/gururmm-server`. The rebuild at 17:53 placed the new binary in `/home/guru/gururmm/server/target/release/gururmm-server` but it was never copied to `/opt/gururmm/`. The old binary (from 2026-04-20 18:32) kept running.
```
ExecStart=/opt/gururmm/gururmm-server ← service path
/usr/local/bin/gururmm-server ← wrong path (stale, Apr 20)
/home/guru/gururmm/server/target/release/gururmm-server ← new binary (never deployed)
```
**2. Migration 013 not registered.** Once the correct binary was deployed and the service restarted, it crashed immediately on startup:
```
Error: while executing migration 13: error returned from database:
relation "install_reports" already exists
```
Migration 013 (`install_reports` table) had been applied to the DB in a prior session but never recorded in `_sqlx_migrations`. sqlx tried to re-run it, hit the conflict, and crashed.
### Fix
1. Deployed the correct binary:
```bash
sudo systemctl stop gururmm-server
sudo cp /home/guru/gururmm/server/target/release/gururmm-server /opt/gururmm/gururmm-server
```
2. Registered migration 013 in `_sqlx_migrations`:
```sql
INSERT INTO _sqlx_migrations (version, description, installed_on, success, checksum, execution_time)
VALUES (
13,
'install reports',
NOW(),
true,
decode('76d53ea1c51f9ce70c01f5b8b545d17f63eab5b2c447e880cdb1f25807ed30c626df818aadea6db9d024cdf2e72d3062', 'hex'),
0
);
```
Checksum was computed via `hashlib.sha384` of the migration file contents.
3. Restarted service — came up clean, agents reconnected.
### Lesson
**Always deploy to `/opt/gururmm/gururmm-server`** — that is the path in the systemd `ExecStart`. `/usr/local/bin/gururmm-server` is a stale copy from early setup and is not used. This should be added to CONTEXT.md / DESIGN.md anti-patterns.
---
## Pending / Next Tasks
From previous session (still pending):
- [ ] Test MSI installer on BirthBiologic server — install via `https://rmm.azcomputerguru.com/install/BRIGHT-PEAK-5980` or MSI from dashboard
- [ ] Consent `tenant-admin` and `user-manager` apps in BirthBiologic tenant (only `investigator` consented so far)
- [ ] BirthBiologic Datto → SharePoint migration script (PowerShell, tenant-admin Graph API, app-only auth, reads Datto Workplace local file server, uploads to SharePoint via Sites.ReadWrite.All)
- [ ] mvaninc CA policy — create policy requiring MFA for all sign-ins (Mike to do in portal, not scriptable)
- [ ] Legacy build deployment — still needs first trigger via webhook push to produce legacy binaries
---
## Infrastructure
| Component | Location | Notes |
|-----------|----------|-------|
| GuruRMM server | guru@172.16.3.30 | `gururmm-server` service |
| Pluto build VM | Administrator@172.16.3.36 | Windows MSVC + WiX |
| Downloads dir | /var/www/gururmm/downloads/ | binaries, MSIs |
| Build log | /var/log/gururmm-build.log | |
| Vault | D:/vault/ | SOPS AGE-encrypted |
---
## Credentials
### Cascades Tucson M365 Admin
- **Username:** admin@cascadestucson.com
- **Password:** Gptf*ttb123!@#-cs
- **Vault:** `D:/vault/clients/cascades-tucson/m365-admin.sops.yaml`
- **Notes:** Renamed from admin@NETORGFT4257522.onmicrosoft.com (original GoDaddy provisioned account)
- **PostgreSQL (gururmm):** `gururmm` / `43617ebf7eb242e814ca9988cc4df5ad` @ 172.16.3.30:5432/gururmm
- **Build server SSH:** guru@172.16.3.30
- **Pluto SSH:** Administrator@172.16.3.36
- **Webhook secret:** `gururmm-build-secret`
- **Gitea internal API:** http://172.16.3.20:3000
- **BirthBiologic RMM site:** api_key `grmm_1ZB1qV9Q61b9Noq8BIaZGwLNjZMfF49i` (also in vault)
---
## onboard-tenant.sh Architecture
## Update: 21:30 UTC — Cleanup EXE, Debug Agent, BB-SERVER MSI Troubleshooting
### Flow
1. Resolve domain → tenant GUID (openid-configuration)
2. Acquire Tenant Admin token (client_credentials) to verify consent
3. Locate resource SPs in tenant: Microsoft Graph, Exchange Online, Defender ATP
4. For each app (Security Investigator, Exchange Operator, User Manager, Defender Add-on):
- Create SP if missing (`POST /servicePrincipals`) — sleep 5 after creation for replication
- Grant all appRoleAssignments idempotently
5. Assign directory roles (Exchange Admin to Sec Inv SP; User Admin + Auth Admin to User Mgr SP)
6. Print status table
### Context
### Key GUIDs
**Permission resource app IDs:**
- Microsoft Graph: `00000003-0000-0000-c000-000000000000`
- Exchange Online: `00000002-0000-0ff1-ce00-000000000000`
- Defender ATP: `fc780465-2017-40d4-a0c5-307022471b92`
**App IDs:**
- Security Investigator: `bfbc12a4-f0dd-4e12-b06d-997e7271e10c`
- Exchange Operator: `b43e7342-5b4b-492f-890f-bb5a4f7f40e9`
- User Manager: `64fac46b-8b44-41ad-93ee-7da03927576c`
- Tenant Admin: `709e6eed-0711-4875-9c44-2d3518c47063`
- Defender Add-on: `dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b`
**Tenant Admin manifest permissions required:**
- `AppRoleAssignment.ReadWrite.All`: `06b708a9-e830-4db3-a914-8e69da51d44f`
- `Application.ReadWrite.All`: `1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9`
- `Directory.ReadWrite.All`: `19dbc75e-c2e2-444c-a770-ec69d8559fc7`
### Bugs Fixed During Development
1. **stdout/stderr pollution in `create_sp_if_missing`**: Human-readable status lines were going to stdout, corrupting `sp_oid=$(create_sp_if_missing ...)`. Fix: all status echoes changed to `>&2`.
2. **Graph replication delay**: Newly created SPs need ~5s before appRoleAssignments can be granted. Fix: `sleep 5` after successful SP creation.
3. **jq null iterator**: `[.value[] | select(...)]` threw on fresh SPs with null appRoleAssignments. Fix: `[.value[]? | select(...)]`.
Continuing from the previous compacted conversation. All work in this update is in the GuruRMM project (gururmm repo on Jupiter, local copy at D:\claudetools\projects\msp-tools\guru-rmm).
---
## Onboarding Status (as of 2026-04-21)
### 1. Cleanup EXE Deployment
### Done (19 tenants)
andysmobilefuel.com, tedards.net, cascadestucson.com, cclac.net, cobaltfinearts.com, dataforth.com, glaztech.com, heieck.org, jemaenterprises.com, mvan.onmicrosoft.com, bestmassageintucson.com, rednourlaw.com, reliantpump.services, ridgetopgroup.com, safesitellc.com, sonorangreenllc.com, valleywideplastering.com, martylryan.com, grabblaw.com
Resumed deploying `gururmm-cleanup.exe` to Jupiter. Method used: base64-encode the EXE on Pluto via RMM agent command, capture the output, decode locally, SCP to Jupiter.
### Pending — Needs Tenant Admin Consent (22 tenants)
Brian Kahn (briankahn.onmicrosoft.com), cuadro.design, Curtis Plumbing (cparizona.onmicrosoft.com), cwconcretellc.com, Feline Ltd (felineltd.onmicrosoft.com), ICE INC (iceinc.us.com), Instrumental Music (instrumentalmusic.onmicrosoft.com), JR Kennedy (jrkco.com), Khalsa Montessori (khalsamontessorischools.onmicrosoft.com), Kittle Design (kittlearizona.com), LeeAnn Parkinson (lamaddux.com), Patient Care Advocates (pcatucson.com), Putt Land Surveying (puttsurveying.com), Rincon Vista Vet (rinconvistavet.onmicrosoft.com), Russo Law (rrs-law.com), SANDTEKO (SANDTEKOMACHINERY.com), Shave Kevin (az2son.com), Starr Pass Realty (starrpass.com), The Dumpster Guys (dumpsterguys.onmicrosoft.com), The Prairie Schooner (theprairieschooner.onmicrosoft.com), Tucson Golden Corral (tucsongoldencorral.onmicrosoft.com), Tucson Mountain Motors (tucsonmountainmotors.com), Von's Carstar (vonscarstar.com)
**Pluto agent ID:** `5316f56f-a1b3-4ac5-97ac-71ddf6a74d2e`
### Not in CIPP (needs investigation)
- Len's Auto Brokerage (tenant: 5ba99b55-...) — Mike accidentally opened Brian Kahn consent URL logged in as admin@lensautobrokerage.onmicrosoft.com; Len's may not be in CIPP partner list
---
## Pending / Next Steps
1. **22 tenants need initial Tenant Admin consent** — use `tenant-consent.html` to send links or open directly; after each consent, run `onboard-tenant.sh <domain>`
2. **Len's Auto Brokerage** — check if in CIPP, add if not, then onboard
3. **Brian Kahn** — needs Brian Kahn's own Global Admin to click consent URL (not admin@lensautobrokerage.onmicrosoft.com)
4. **Tenant-consent.html UUID tenants** — three entries show GUIDs not domains (f5f86b40, dfee2224, and cparizona/felineltd/etc use onmicrosoft.com domains) — verify display names in tenants.md match
---
## Reference
- **Consent HTML:** `D:/claudetools/.claude/skills/remediation-tool/references/tenant-consent.html`
- **Tenant list:** `D:/claudetools/.claude/skills/remediation-tool/references/tenants.md`
- **Onboarding script:** `D:/claudetools/.claude/skills/remediation-tool/scripts/onboard-tenant.sh`
- **Gotchas:** `D:/claudetools/.claude/skills/remediation-tool/references/gotchas.md`
- **Cascades vault:** `D:/vault/clients/cascades-tucson/m365-admin.sops.yaml`
---
## Update: 07:26 — Cloudflare Tunnel Decommission + pfSense Audit
### Summary
Decommissioned the Cloudflare tunnel (cloudflared Docker container on Jupiter), migrated all 9 tunneled services to direct Cloudflare proxy, and conducted a comprehensive pfSense audit removing ~40 stale config objects (NAT rules, filter rules, outbound NAT, IPsec, and aliases).
---
### Background: Why the Tunnel Was Created
A Cox routing issue caused Cloudflare-proxied services to route inefficiently (Cox → Cloudflare PoP → back to Cox WAN). The cloudflared tunnel was created as a workaround — it establishes an outbound connection from Jupiter to Cloudflare PoPs, so all proxied traffic flows through the tunnel rather than requiring port forwards.
---
### Cloudflared Container — DNS Fix
**Problem:** cloudflared container had no DNS servers configured (`[]`), causing it to use Docker's default resolver which couldn't reach `region1.v2.argotunnel.com`. This produced a `Failed to refresh DNS local resolver` timeout every 5 minutes, causing intermittent slowness.
**Fix:** Recreated container with explicit DNS:
```
--dns=1.1.1.1 --dns=1.0.0.1
```
Container startup confirmed clean after DNS fix.
**Tunnel ID:** `78d3e58f-1979-4f0e-a28b-98d6b3c3d867`
**Config location on Jupiter:** `/mnt/cache/appdata/cloudflared/config.yml`
---
### Cloudflare DNS Migration
**Key discovery:** pfSense has NO NAT rule for port 443 on primary Cox WAN IP (98.181.90.163). All port 443 rules are bound to specific 72.194.62.x IPs. Direct proxy to 98.181.90.163 gave 522 errors because of this.
**Solution:** Use 72.194.62.10 (which has an existing `443 → NPM:18443` NAT rule) as the target for NPM-backed services.
**Services migrated from tunnel CNAME → direct Cloudflare proxy A records:**
| Hostname | Old Target | New Target | Backend |
|---|---|---|---|
| git.azcomputerguru.com | tunnel CNAME | 72.194.62.10 | NPM → Jupiter:18443 |
| rmm.azcomputerguru.com | tunnel CNAME | 72.194.62.10 | NPM → Jupiter:18443 |
| rmm-api.azcomputerguru.com | tunnel CNAME | 72.194.62.10 | NPM → Jupiter:18443 |
| plexrequest.azcomputerguru.com | tunnel CNAME | 72.194.62.10 | NPM → Jupiter:18443 |
| sync.azcomputerguru.com | tunnel CNAME | 72.194.62.10 | NPM → Jupiter:18443 |
| azcomputerguru.com | tunnel CNAME | 72.194.62.5 | IX Web Hosting:443 |
| analytics.azcomputerguru.com | tunnel CNAME | 72.194.62.5 | IX Web Hosting:443 |
| community.azcomputerguru.com | tunnel CNAME | 72.194.62.5 | IX Web Hosting:443 |
| radio.azcomputerguru.com | tunnel CNAME | 72.194.62.5 | IX Web Hosting:443 |
All 9 services tested and confirmed working. Container then stopped and removed.
**Public IP layout (relevant):**
- `72.194.62.5` → IX Web Hosting server (172.16.3.10) via NAT
- `72.194.62.10` → NPM on Jupiter (172.16.3.20:18443) via NAT
- `98.181.90.163/31` — Primary Cox WAN, NO port 443 NAT rule
---
### pfSense SSH Access Fix
pfSense SSH was failing non-interactively with "Too many authentication failures" (SSH client tried multiple keys, hit MaxAuthTries before reaching id_ed25519).
**Fix:** Added `id_ed25519` public key to pfSense admin user via web GUI (port 4433). Had to include `webguicss=pfSense.css` and `dashboardcolumns=2` fields in the form POST to avoid theme validation errors.
**SSH command:** `ssh -o StrictHostKeyChecking=no -i C:/Users/guru/.ssh/id_ed25519 -p 2248 admin@172.16.0.1`
**Vault updated:** `D:/vault/infrastructure/pfsense-firewall.sops.yaml` — added `web_port`, `ssh_key`, `ssh_cmd` fields.
---
### pfSense Audit — Rules Removed
All removals were done by uploading PHP scripts via SCP, executing on pfSense, then reloading filter with `pfSsh.php playback svc restart filter`.
Config backup pattern: `/cf/conf/config.xml.bak-<description>-<timestamp>`
**Round 1 — TSM Network (dead server):**
- NAT: TSM Network HTTP forward (72.194.62.x → TSM)
- NAT: TSM Network HTTPS forward
- NAT: LDAP to DC16
- FILTER: Associated pass rules
**Round 2 — Neptune, IPsec, Gitea SSH, orphans:**
- NAT: Neptune Exchange HTTP/HTTPS forwards
- NAT: 172.16.3.25 wildcard forward
- NAT: 172.16.3.25 HTTP/HTTPS forwards
- NAT: Gitea SSH forward (72.194.62.x:22 → Jupiter) — superseded by Cloudflare proxy
- FILTER: All associated pass rules
- FILTER: Orphaned LDAP filter rule
- FILTER: Neptune pass rules
- IPSEC: Phase 1 + Phase 2 for 184.182.208.116 (Mike's house — no longer needed)
**Round 3 — Seafile:**
- NAT: 72.194.62.9 Seafile/Sync forward — Seafile desktop client uses sync.azcomputerguru.com (now via NPM on .10), not a dedicated IP; .9 rule was orphaned
- FILTER: Associated pass rule
**Round 4 — Neptune outbound NAT:**
- OUTBOUND NAT: NEPTUNE_Internal → 72.194.62.7 masquerade rule
**Round 5 — Neptune Exchange filter (missed in Round 2):**
- FILTER: Rule with destination NEPTUNE_Internal:Exchange_Ports (was a filter rule, not NAT — earlier script only checked NAT)
**Total rules removed: ~22 NAT/filter/IPsec rules**
---
### pfSense Audit — Aliases Removed (22)
```
All_Ports, EX1_Internal, Emby_Ports, Exchange_Ports, Exchange_VIP,
MailProtector_LDAP, NEPTUNE_Internal, Nextcloud_Local, NPM_Ports,
OwnCloud_Ports, RNAT_Webhost, RustDesk_Server, RustDesk_Server_Internal,
SpamIssue, Syslog, UNMS, Unifi_SSL, Unraid_Jupiter, Unraid_Sync,
VIP_NO_AUTODISCOVER, VPN_Ports, Webhost_Internal
**JWT generation (Pluto admin user):**
```python
import json, base64, hmac, hashlib, time
secret_bytes = 'ZNzGxghru2XUdBVlaf2G2L1YUBVcl5xH0lr/Gpf/QmE='.encode('utf-8')
# User sub: 490e2d0f-067d-4130-98fd-83f06ed0b932 (admin@azcomputerguru.com)
```
**Remaining aliases (all active/valid):**
`Cloudflare`, `FiberGW`, `HTTP_HTTPS`, `ICE_Users`, `NPM_Server`, `Unifi_Server`, `Unifi_TCP`, `Unifi_UDP`, `Webhost_TCP`, `Webhost_UDP`, `Tailscale`, `TFTP Server`, `WireGuard`
**SCP to Pluto failed** (SYSTEM account has no SSH private key at `C:\Windows\System32\config\systemprofile\.ssh\`). Fell back to base64-through-agent approach.
---
### pfSense Items Investigated — Left Alone
| Item | Decision |
|---|---|
| Golden Corral (72.194.62.6 → 172.16.1.6, HTTP_HTTPS) | Leave as-is — live client, working, no RDP exposed (80/443 only) |
| 72.194.62.7 VIP ("MAIL/NEPTUNE") | Unused IP — no rules reference it; could remove VIP or reassign |
| `Cloudflare` alias | Unused — could apply to restrict WAN access to CF IPs only |
| Broad `pass tcp/udp any→any` WAN rule | Noted, not yet addressed |
| 72.194.62.4 → NPM:18443 ("Emby on Fiber") | Verified pointing to NPM, labeled correctly |
| OwnCloud VM (172.16.3.22) | NAT rule still valid — cloud.acghosting.com lives there |
---
### Infrastructure Reference
| Asset | Detail |
|---|---|
| pfSense | 172.16.0.1, SSH port 2248, HTTPS port 4433, admin user |
| pfSense config | `/cf/conf/config.xml` |
| Jupiter (Unraid) | 172.16.3.20 |
| NPM (Nginx Proxy Manager) | Jupiter:18443 (HTTPS), Jupiter:1880 (HTTP) |
| cloudflared | Stopped/removed — tunnel decommissioned |
| Primary Cox WAN | 98.181.90.163/31 — no port 443 NAT |
| Additional public IPs | 72.194.62.210, 70.175.28.5157 |
---
### Pending / Next Steps (Infrastructure)
1. **72.194.62.7 VIP** — decide: remove (Neptune gone) or repurpose
2. **Cloudflare alias** — consider applying to WAN rules to restrict to CF IPs only (security hardening)
3. **Broad WAN pass rule** — review and tighten if possible
4. **22 M365 tenants** — still need initial Tenant Admin consent (unchanged from earlier session)
---
## Note for Howard
**Vault + SOPS age key setup required on ACG-Tech03L before remediation-tool will work.**
### 1. Clone the vault repo
Run in Git Bash (real terminal, not Claude Code shell):
**Base64 command sent to Pluto:**
```powershell
[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:/gururmm/agent/target/debug-agent/release/gururmm-agent.exe'))
```
File size: 3.8 MB (3,948,544 bytes). B64 length: 5,264,728 chars.
**Decode locally and SCP to Jupiter:**
```bash
git clone http://azcomputerguru@172.16.3.20:3000/azcomputerguru/vault.git D:/vault
py -c "import base64; ..." # decode to D:/tmp/gururmm-agent-debug.exe
scp D:/tmp/gururmm-agent-debug.exe guru@172.16.3.30:/tmp/gururmm-agent-debug.exe
ssh guru@172.16.3.30 'sudo cp /tmp/gururmm-agent-debug.exe /var/www/gururmm/downloads/gururmm-agent-debug.exe'
```
Password: `Gptf*77ttb123!@#-git`
**Result:** `/var/www/gururmm/downloads/gururmm-agent-debug.exe` deployed (3.8 MB).
`http://172.16.3.30:3001/install/debug/download` → HTTP 200 (3,948,544 bytes). ✓
### 2. Install the SOPS age key
**Note:** Cloudflare challenges `https://rmm.azcomputerguru.com/install/debug/download` for non-browser requests — this is expected/normal. Browser downloads work fine.
Create this file: `C:\Users\howard\.config\sops\age\keys.txt`
Content (copy exactly):
```
# created: 2026-03-30T13:53:19-07:00
# public key: age1qz7ct84m50u06h97artqddkj3c8se2yu4nxu59clq8rhj945jc0s5excpr
AGE-SECRET-KEY-1DE3V6V0ZLLZ45A7GA77M79CTN4LZQMTRCURP8VRGNLV6T2FSZEEQXUW2EU
```
### 3. Add vault_path to identity.json
Edit `.claude/identity.json` in your ClaudeTools folder, add:
```json
"vault_path": "D:/vault"
```
### 4. Test
```bash
bash C:/claudetools/.claude/skills/remediation-tool/scripts/get-token.sh grabblaw.com investigator
```
Expected: JWT token starting with `eyJ...`
**Note on cleanup.exe:** Not yet built. The `gururmm-cleanup.exe` will be produced automatically by `build-agents.sh` on the next triggered build. The server route `/install/cleanup/download/exe` returns 503 until that first build completes.
---
## Note for Mike (Mac)
### 2. Pluto's SSH Public Key (for future reference)
**Vault + SOPS age key setup required on Mikes-MacBook-Air before remediation-tool will work.**
Pluto SYSTEM account does NOT have `id_ed25519`. The pubkey retrieved earlier (`system@PLUTO`) was incorrect or from a different context.
### 1. Clone the vault repo
**Pluto's SYSTEM .ssh dir** contains only `known_hosts` (94 bytes).
Run in a real terminal (not Claude Code shell):
**Jupiter's authorized_keys** was updated to add Pluto pubkey:
```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFWaMV0U3WZG3kuts7mqVaF9SN0TsKqPAC37GdVGbq0Y system@PLUTO
```
(Added to `/home/guru/.ssh/authorized_keys` — may be irrelevant since SYSTEM has no private key.)
---
### 3. Debug Agent Feature — build-agents.sh and Server Routes
**Already committed in prior session:**
- `build-agents.sh`: added `--features debug-agent --target-dir target\debug-agent` to Pluto SSH build command + SCP + deploy block
- `agent/Cargo.toml`: added `debug-agent = []` feature
- `agent/src/service.rs`: cfg-gated `SERVICE_NAME`, `SERVICE_DISPLAY_NAME`, `INSTALL_DIR`, `CONFIG_DIR` constants
- `agent/src/registry.rs`: `REGISTRY_KEY` = `SOFTWARE\GuruRMM-Debug` when feature enabled
- `agent/src/device_id.rs`: stores device ID in `C:\ProgramData\GuruRMM-Debug\.device-id`
- `agent/src/updater/mod.rs`: `detect_binary_path()` and `detect_config_dir()` use debug paths
- `server/src/main.rs` on Jupiter: routes for `/install/debug/download` and cleanup endpoints
- `server/src/api/install.rs` on Jupiter: `download_debug_exe()` handler
---
### 4. GuruRMM Debug Site Created
Created a new site for the debug agent to enroll into:
| Field | Value |
|-------|-------|
| Site ID | `d6b8233a-6cc1-4a44-888d-01ee49123fba` |
| Site name | GuruRMM Debug |
| Site code | `BOLD-HARBOR-1855` |
| API key | `grmm_mm2DnrF6kt9Ml8AyJCuHJJHnBTyXHX_4` |
| Client | AZ Computer Guru (`417420f4-c3f4-482a-acd4-d6f63c8cddde`) |
**Issue identified:** The debug agent currently prompts for a site code on first run because:
1. No config file exists
2. No site code embedded in the binary
**Fix needed (not yet done):** Hardcode the debug site API key into the `debug-agent` feature using a `cfg`-gated constant. Or embed it at build time. This would allow the debug EXE to auto-install silently without prompting.
**Current workaround:** User entered `BRIGHT-PEAK-5980` (BirthBiologic) when prompted.
---
### 5. BB-SERVER Connected
Debug agent installed on BB-SERVER (BirthBiologic's server) and is now online in the RMM.
| Field | Value |
|-------|-------|
| Agent ID | `6c02baa7-0f1c-4990-b466-c9ab9eaefd3b` |
| Hostname | BB-SERVER |
| OS | Windows Server 2016 (build 14393) |
| Agent version | 0.6.2 |
| Site | BirthBiologic Main Office (`3b20ef97-c764-4ef8-9154-79c3d5b486f8`) |
| Status | online |
---
### 6. MSI Installer Troubleshooting via BB-SERVER
Using BB-SERVER's debug agent to test the MSI installer and capture verbose logs.
**Problem 1 — Cloudflare blocks non-browser downloads:**
- `Invoke-WebRequest` without a browser UA gets Cloudflare's JS challenge page instead of the MSI
- Fix: pass `-UserAgent 'Mozilla/5.0 ...'` to Invoke-WebRequest
**Problem 2 — msiexec doesn't accept forward slashes:**
- Error 2203 "Cannot open database file" with C:/grmm.msi
- Fix: use `C:\\grmm.msi` (JSON-escaped backslash)
**Working command format:**
```
Invoke-WebRequest -Uri '...' -OutFile C:\\grmm.msi -UserAgent $ua -UseBasicParsing;
msiexec /i C:\\grmm.msi /quiet /l*v C:\\grmm.log;
Get-Content C:\\grmm.log -Tail 100
```
**Command in flight** (cmd ID `fa68659e-3395-48a2-adee-9624dfd40cd7`) — still running as of session save. Check with:
```bash
git clone http://azcomputerguru@172.16.3.20:3000/azcomputerguru/vault.git ~/vault
curl -s "http://172.16.3.30:3001/api/commands/fa68659e-3395-48a2-adee-9624dfd40cd7" \
-H "Authorization: Bearer <JWT>"
```
Password: `Gptf*77ttb123!@#-git`
---
### 2. Install the SOPS age key
### 7. RMM API — Correct Endpoints
```bash
mkdir -p ~/.config/sops/age
cat > ~/.config/sops/age/keys.txt << 'AGEEOF'
# created: 2026-03-30T13:53:19-07:00
# public key: age1qz7ct84m50u06h97artqddkj3c8se2yu4nxu59clq8rhj945jc0s5excpr
AGE-SECRET-KEY-1DE3V6V0ZLLZ45A7GA77M79CTN4LZQMTRCURP8VRGNLV6T2FSZEEQXUW2EU
AGEEOF
chmod 600 ~/.config/sops/age/keys.txt
| Operation | Endpoint |
|-----------|----------|
| Send command | `POST http://172.16.3.30:3001/api/agents/:id/command` |
| Get command status | `GET http://172.16.3.30:3001/api/commands/:id` |
| List agents | `GET http://172.16.3.30:3001/api/agents` |
| Get site install info | `GET http://172.16.3.30:3001/api/sites/:id/install-info` |
| Download site MSI (auth) | `GET http://172.16.3.30:3001/api/sites/:id/installer` |
| Download site MSI (public) | `GET https://rmm.azcomputerguru.com/install/BRIGHT-PEAK-5980/download/msi` |
**JWT generation for API calls:**
- Secret (raw bytes): `ZNzGxghru2XUdBVlaf2G2L1YUBVcl5xH0lr/Gpf/QmE=`
- Admin user sub: `490e2d0f-067d-4130-98fd-83f06ed0b932` (admin@azcomputerguru.com)
- Claims: `sub`, `role: "admin"`, `orgs: []`, `exp: now+3600`, `iat: now`
- Algorithm: HS256, key = secret string encoded as UTF-8 bytes (NOT base64-decoded)
**Known user IDs:**
```
490e2d0f-067d-4130-98fd-83f06ed0b932 admin@azcomputerguru.com (admin)
4d754f36-0763-4f35-9aa2-0b98bbcdb309 claude-api@azcomputerguru.com (admin)
294c1242-68ac-42e7-85b0-564c8b155dba howard@azcomputerguru.com (admin)
```
### 3. Add vault_path to identity.json
---
Edit `/Users/azcomputerguru/ClaudeTools/.claude/identity.json`, add:
```json
"vault_path": "/Users/azcomputerguru/vault"
```
### 8. JSON Escaping Issue with Agent Commands
### 4. Test
The RMM server's serde_json is strict about JSON escape sequences. Commands containing `\"` embedded double-quotes cause "invalid escape" errors when passed via `--data-binary @file` from curl if there are edge cases.
```bash
bash ~/ClaudeTools/.claude/skills/remediation-tool/scripts/get-token.sh grabblaw.com investigator
```
**Working approach:** Use shell single-quote wrapping with `'"'"'` technique for embedded single-quoted PowerShell strings in the curl -d argument. Avoids file escaping entirely.
Expected: JWT token starting with `eyJ...`
**Key rules:**
- Never use `\g`, `\L`, `\D`, etc. — only valid JSON escapes: `\\`, `\"`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`
- Forward slashes are fine in JSON strings
- Backslashes in PowerShell paths need `\\` in JSON (gives `\` in the actual string)
---
### Pending Tasks
| Task | Status | Notes |
|------|--------|-------|
| Cleanup EXE on Pluto | Pending | Needs first build trigger. Route ready, will 503 until built. |
| Debug agent auto-install | Not done | Needs hardcoded debug site key in `debug-agent` feature |
| MSI 2762 test on BB-SERVER | In progress | Command running, awaiting result |
| BirthBiologic — MSI verified working | Pending | Testing now |
| BirthBiologic — consent tenant-admin/user-manager | Pending | |
| BirthBiologic — Datto→SharePoint migration script | Pending | |
| mvaninc CA policy (MFA) | Pending | Mike to do manually in portal |
| Remote uninstall feature | Pending | New WS message + server DELETE endpoint + dashboard button |
---
### Infrastructure Additions This Update
| Item | Value |
|------|-------|
| Debug site | BOLD-HARBOR-1855, api_key `grmm_mm2DnrF6kt9Ml8AyJCuHJJHnBTyXHX_4` |
| BB-SERVER agent | ID `6c02baa7-...`, online, BirthBiologic Main Office |
| Debug EXE | `/var/www/gururmm/downloads/gururmm-agent-debug.exe` (3.8 MB) |