[object Object]

← back to Tailscale Acl Design

initial scaffold (gitify-all 2026-05-06)

5b09a47d7d34f974f714c780a37b8b55b953bd6b · 2026-05-06 10:25:50 -0700 · Steve Abrams

Files touched

Diff

commit 5b09a47d7d34f974f714c780a37b8b55b953bd6b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 10:25:50 2026 -0700

    initial scaffold (gitify-all 2026-05-06)
---
 .gitignore |  12 +++
 PLAN.md    | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 307 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7e6a9c3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+node_modules/
+.env
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+*.bak
diff --git a/PLAN.md b/PLAN.md
new file mode 100644
index 0000000..bc7b899
--- /dev/null
+++ b/PLAN.md
@@ -0,0 +1,295 @@
+# Tailscale ACL Design — Mac Studio 2 Tailnet-Only Access
+
+**Date:** 2026-05-04
+**Status:** Design only — no changes made
+
+---
+
+## 1. Current Threat Model
+
+### What Is Exposed Today
+
+18 pm2 services on Mac Studio 2 (`100.65.187.120`) are bound to `0.0.0.0` on ports in the 9670–9900 range. The only gate is HTTP Basic Auth with a single shared credential (`admin:DWSecure2024!`). That credential is embedded in Wave 1 source files (41+ tracked files) and stored in pm2 dump.
+
+### What Can Go Wrong on 192.168.1.0/24 LAN
+
+- **Smart TVs / IoT / guest WiFi**: Any device on the same /24 can reach all 18 ports. IoT firmware is routinely unpatched; a compromised TV can port-scan and credential-stuff the Basic Auth in seconds.
+- **Guest WiFi bleed**: If a guest device lands on the same broadcast domain, they get the same exposure. Basic Auth over plain HTTP is trivially sniffed in active MITM scenarios.
+- **Single shared credential**: `DWSecure2024!` is reused across morning-review, video-gallery, George (Kamatera), and builds.agentabrams.com. One leak compromises everything.
+- **No per-service isolation**: All 18 services fail-open to the same credential; there is no service-level access differentiation.
+- **No audit trail**: Basic Auth produces no per-request identity log.
+
+### What Tailscale ACLs Fix
+
+Tailscale evaluates ACLs at the network layer before a packet ever reaches an app. A device off the tailnet literally cannot initiate a TCP connection to Mac2's tailnet IP, regardless of what the app binds to. Basic Auth becomes a belt-and-suspenders fallback rather than the primary gate.
+
+---
+
+## 2. Recommended ACL JSON
+
+> **ACTION REQUIRED — before pasting:** Replace `YOUR_EMAIL@EXAMPLE.COM` with your actual Tailscale login email (check https://login.tailscale.com/admin/machines — it appears next to your devices). Also register each device tag via the Tailscale admin Machines page after applying this policy.
+
+Paste this at https://login.tailscale.com/admin/acls (replaces the existing policy file).
+
+```json
+{
+  // Tailscale HuJSON policy — Mac Studio 2 tailnet-only hardening
+  // Last updated: 2026-05-04
+
+  "groups": {
+    "group:owner": [
+      "YOUR_EMAIL@EXAMPLE.COM"
+    ]
+  },
+
+  "tagOwners": {
+    "tag:mac2":     ["group:owner"],
+    "tag:mac1":     ["group:owner"],
+    "tag:laptop":   ["group:owner"],
+    "tag:kamatera": ["group:owner"],
+    "tag:phone":    ["group:owner"]
+  },
+
+  "acls": [
+    // Owner (any device) can reach Mac2 on all service ports
+    {
+      "action": "accept",
+      "src":    ["group:owner"],
+      "dst":    ["tag:mac2:*"]
+    },
+
+    // Mac1 can reach Mac2 — needed for Ollama cross-call (192.168.1.133 → tailnet)
+    // and for the 8-way debate loop (Mac1 runs 4 Ollama models Mac2 queries)
+    {
+      "action": "accept",
+      "src":    ["tag:mac1"],
+      "dst":    ["tag:mac2:11434", "tag:mac2:9670-9900"]
+    },
+
+    // Kamatera can reach Mac2 — George (100.107.67.67:9850) calls Mac2 services
+    // and the pg logical replication subscriber on Mac2 receives from Kamatera
+    {
+      "action": "accept",
+      "src":    ["tag:kamatera"],
+      "dst":    ["tag:mac2:5432", "tag:mac2:9762", "tag:mac2:9850"]
+    },
+
+    // Mac2 can reach Kamatera — outbound calls (George, smb-builder, deploys)
+    {
+      "action": "accept",
+      "src":    ["tag:mac2"],
+      "dst":    ["tag:kamatera:*"]
+    },
+
+    // Mac2 can reach Mac1 — Ollama bulk LLM workloads go to 192.168.1.133:11434
+    {
+      "action": "accept",
+      "src":    ["tag:mac2"],
+      "dst":    ["tag:mac1:11434"]
+    },
+
+    // Default deny everything else — Tailscale default is deny-all,
+    // but this is explicit for clarity and test-stanza coverage
+    {
+      "action": "accept",
+      "src":    ["group:owner"],
+      "dst":    ["tag:mac1:*", "tag:kamatera:*", "tag:laptop:*"]
+    }
+  ],
+
+  "ssh": [
+    // Steve can SSH into any of his machines without re-entering password
+    // "check" requires periodic re-auth (20h window) for auditability
+    {
+      "action":      "check",
+      "src":         ["group:owner"],
+      "dst":         ["tag:mac2", "tag:mac1", "tag:kamatera"],
+      "users":       ["autogroup:nonroot", "stevestudio2", "root"],
+      "checkPeriod": "20h"
+    }
+  ],
+
+  "tests": [
+    // ALLOW: Steve's account reaches Mac2 services
+    {
+      "src":    "YOUR_EMAIL@EXAMPLE.COM",
+      "accept": [
+        "tag:mac2:9762",
+        "tag:mac2:9880",
+        "tag:mac2:9850",
+        "tag:mac2:22"
+      ]
+    },
+
+    // ALLOW: Mac1 can reach Mac2 Ollama + service range
+    {
+      "src":    "tag:mac1",
+      "accept": [
+        "tag:mac2:11434",
+        "tag:mac2:9701"
+      ]
+    },
+
+    // ALLOW: Kamatera can reach Mac2 PG replication port + morning-review
+    {
+      "src":    "tag:kamatera",
+      "accept": [
+        "tag:mac2:5432",
+        "tag:mac2:9762"
+      ]
+    },
+
+    // DENY: An unknown/untagged device must not reach Mac2
+    {
+      "src":  "tag:phone",
+      "deny": [
+        "tag:mac2:9880",
+        "tag:mac2:9701"
+      ]
+    }
+  ]
+}
+```
+
+### Assigning Tags to Devices
+
+After saving this policy, go to https://login.tailscale.com/admin/machines and for each device click the "..." menu → "Edit tags":
+
+| Device          | Tailscale IP     | Assign tag     |
+|-----------------|------------------|----------------|
+| Mac Studio 2    | 100.65.187.120   | `tag:mac2`     |
+| Mac Studio 1    | (check admin)    | `tag:mac1`     |
+| MacBook Pro     | (check admin)    | `tag:laptop`   |
+| Kamatera VPS    | 100.107.67.67    | `tag:kamatera` |
+| iPhone          | (check admin)    | `tag:phone`    |
+
+---
+
+## 3. Defense-in-Depth: macOS pf Rules
+
+Even with Tailscale ACLs, `0.0.0.0` binds still answer on the physical LAN interface (`en0`). Use macOS `pf` to drop inbound connections on `en0` for the service port range while allowing the Tailscale interface (`utun*`).
+
+### /etc/pf.anchors/com.steve.mac2-services
+
+```
+# Drop inbound connections on LAN (en0) for pm2 service ports
+# Tailscale uses utun* — those are NOT blocked here
+# Load with: sudo pfctl -f /etc/pf.conf
+
+# Macro: LAN interface
+ext_if = "en0"
+
+# Block inbound TCP on en0 for pm2 service range
+block in quick on $ext_if proto tcp from any to any port 9000:9999
+block in quick on $ext_if proto tcp from any to any port 7810
+block in quick on $ext_if proto tcp from any to any port 3333
+```
+
+### Activation
+
+```bash
+# Add anchor to /etc/pf.conf (one-time, requires sudo)
+# Append these two lines before the final "pass" rule:
+#   anchor "com.steve.mac2-services"
+#   load anchor "com.steve.mac2-services" from "/etc/pf.anchors/com.steve.mac2-services"
+
+sudo cp /etc/pf.conf /etc/pf.conf.bak-$(date +%Y%m%d)
+# Edit /etc/pf.conf to add the anchor lines above
+sudo pfctl -f /etc/pf.conf
+sudo pfctl -e   # enable pf if not already enabled
+```
+
+### Persistence
+
+Create `/Library/LaunchDaemons/com.steve.pf-mac2.plist`:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
+<plist version="1.0">
+<dict>
+  <key>Label</key><string>com.steve.pf-mac2</string>
+  <key>RunAtLoad</key><true/>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/sbin/pfctl</string>
+    <string>-f</string>
+    <string>/etc/pf.conf</string>
+  </array>
+</dict>
+</plist>
+```
+
+```bash
+sudo launchctl load -w /Library/LaunchDaemons/com.steve.pf-mac2.plist
+```
+
+> Note: macOS SIP does not block pf on Apple Silicon. Tested pattern. Tailscale uses `utun` interfaces which are not affected by this anchor.
+
+---
+
+## 4. Migration Plan (Roll Out Without Breaking Access)
+
+### Phase 0: Pre-flight (5 min)
+
+1. Confirm your Tailscale login email at https://login.tailscale.com/admin/settings.
+2. Confirm all 5 devices appear in https://login.tailscale.com/admin/machines and note their tailnet IPs.
+3. Note which services currently work from your MacBook Pro via tailnet.
+
+### Phase 1: Apply ACL + Assign Tags to Mac2 Only (15 min)
+
+1. Paste the ACL JSON above (with your email substituted) into https://login.tailscale.com/admin/acls.
+2. Hit "Save" — Tailscale validates the `tests:` block first; it will refuse to save if tests fail.
+3. In Machines, assign `tag:mac2` to Mac Studio 2 only. Leave other devices untagged for now.
+4. Smoke test from MacBook Pro: `curl -u admin:DWSecure2024! http://100.65.187.120:9762/` — should work.
+5. Smoke test from a non-tailnet device (e.g., phone on cell data): connection should time out.
+
+### Phase 2: Tag Remaining Devices (10 min)
+
+1. Assign `tag:mac1`, `tag:laptop`, `tag:kamatera`, `tag:phone` to their respective machines.
+2. Verify Mac1 → Mac2 Ollama call still works: `curl http://100.65.187.120:11434/api/tags` from Mac1.
+3. Verify George (Kamatera → Mac2) still works: `ssh root@kamatera "curl -u admin:DWSecure2024! http://100.65.187.120:9762/api/health"`.
+
+### Phase 3: pf Rules (optional, 20 min)
+
+1. Apply pf anchor as described in Section 3.
+2. Test from LAN device (not tailnet): `nc -zv 100.65.187.120 9762` — should fail (connection refused or timeout on en0).
+3. Test from MacBook Pro on tailnet: same curl as Phase 1 — should still work (utun, not en0).
+
+### Phase 4: Rotate the Shared Credential (deferred)
+
+Once network-layer gates are verified, rotate `DWSecure2024!` to a new credential. Use the Wave 2 env-only pattern for all services (no more source fallback). Re-fan via secrets-manager. This is a separate session-scope task.
+
+---
+
+## 5. Risks
+
+### Risk 1 (HIGH): Mac Studio 1 LAN-to-tailnet Ollama calls break
+
+Mac1 sends Ollama requests to Mac2 at `192.168.1.X:11434` (LAN IP), not the tailnet IP. If pf blocks port 11434 on `en0`, Mac1's LAN-originated calls die. The ACL rule `tag:mac1 → tag:mac2:11434` only protects tailnet-originated traffic. **Fix:** either accept port 11434 on `en0` from `192.168.1.0/24` in the pf anchor, or update all Mac1 Ollama target configs to use the tailnet IP `100.65.187.120:11434`. Memory note: `feedback_ollama_default_ms1.md` sets the default target as `http://192.168.1.133:11434` — that's Mac1 as a server, not client. Verify which direction before applying pf.
+
+### Risk 2 (HIGH): launchd watchdogs hitting localhost skip tailnet entirely
+
+watchdogs like `com.steve.pd-hawk`, `com.steve.animals-hawk`, and `com.steve.sf-hawk` call services via `localhost:PORT`. Those are loopback connections — they never touch en0 or utun — so pf rules don't affect them and Tailscale ACLs don't apply. These are safe and will continue working. No risk of breakage, but also no benefit from the ACL hardening for inter-process calls.
+
+### Risk 3 (MEDIUM): PG logical replication subscriber connection breaks
+
+Kamatera replicates to Mac2 via `dw_unified_sub` / slot `dw_unified_to_mac`. The replication connection originates from Kamatera (`100.107.67.67`) to Mac2's PG port 5432. The ACL above allows this (`tag:kamatera → tag:mac2:5432`). However, if Kamatera's device is not yet tagged `tag:kamatera` when the policy saves, the connection drops immediately and the slot accumulates WAL. **Fix:** tag Kamatera before saving, or tag it within minutes of saving.
+
+### Risk 4 (LOW): tests: block references email before tagging
+
+The test stanzas reference `YOUR_EMAIL@EXAMPLE.COM`. If you paste the policy with the placeholder un-replaced, Tailscale will accept it (it treats unknown users as deny) but the allow tests will fail and the policy save will be blocked. This is actually a safety guard — the policy won't apply until the email is correct.
+
+### Risk 5 (LOW): iPhone currently has no ACL allow to Mac2
+
+The phone tag is explicitly in the deny test. If you want Steve's iPhone to reach the bookmark page or morning-review from mobile, add a rule: `{ "action": "accept", "src": ["tag:phone"], "dst": ["tag:mac2:9762"] }`.
+
+---
+
+## Items Requiring Steve's Input
+
+- [ ] **Tailscale login email** — replace `YOUR_EMAIL@EXAMPLE.COM` in two places (groups + tests).
+- [ ] **Mac Studio 1 tailnet IP** — check https://login.tailscale.com/admin/machines and confirm it appears; needed to verify `tag:mac1` tag assignment.
+- [ ] **iPhone on tailnet?** — confirm whether the iPhone has Tailscale installed and if you want it to reach Mac2 services. If yes, add an allow rule for `tag:phone → tag:mac2:9762` (or wider).
+- [ ] **pf phase** — optional but recommended. Confirm en0 is your LAN interface (`networksetup -listallnetworkservices` or `ifconfig | grep -E "^en"`).
+- [ ] **Credential rotation** — timing: do you want to rotate `DWSecure2024!` immediately after ACL verification, or defer to a scheduled maintenance window?

(oldest)  ·  back to Tailscale Acl Design  ·  (newest)