diff --git a/.gitignore b/.gitignore index 81f66e9..c63b063 100644 --- a/.gitignore +++ b/.gitignore @@ -139,6 +139,7 @@ mock.env.backup # Sensitive files - DO NOT COMMIT secrets.env *secret*.env +docs/PULSE_PRO_IMPLEMENTATION.md # Browser/session artifacts **/cookies.txt @@ -149,6 +150,10 @@ secrets.env # Development documentation (local only) CLAUDE_DEV_SETUP.md AGENT_METRICS_*.md +DEV-QUICK-START.md +docs/DOCS_AUDIT_*.md +docs/SECURITY_AUDIT_*.md +docs/development/ # Temporary scripts tmp_*.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edb78e8..a35147a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ cd .. npm run mock:on # Optional: enable mock data ``` -See `docs/development/MOCK_MODE.md` for tips on synthetic fixtures. +Mock mode is supported for development, but the internal developer notes are not shipped in this repository. --- diff --git a/DEV-QUICK-START.md b/DEV-QUICK-START.md deleted file mode 100644 index 947eed4..0000000 --- a/DEV-QUICK-START.md +++ /dev/null @@ -1,99 +0,0 @@ -# Development Quick Start - -## Prerequisites - -- Go **1.24.9** or newer -- Node.js 20+ -- pnpm 9+ (for frontend work) - -> **Tip**: Read [`ARCHITECTURE.md`](ARCHITECTURE.md) to understand the system design before diving in. - -## Hot-Reload Development Mode - -Start the development environment with hot-reload: - -```bash -./scripts/hot-dev.sh -``` - -This starts: -- Backend API on port 7656 -- Frontend on port 7655 with hot-reload -- Both backend and frontend automatically reload on code changes - -Access the app at: http://localhost:7655 or http://192.168.0.123:7655 - -## Toggle Between Mock and Production Data - -Switch modes seamlessly without manually restarting services: - -```bash -# Enable mock mode (test with fake data) -npm run mock:on - -# Disable mock mode (use real Proxmox nodes) -npm run mock:off - -# Check current mode -npm run mock:status - -# Edit mock configuration -npm run mock:edit -``` - -Or use the script directly: - -```bash -./scripts/toggle-mock.sh on # Enable mock mode -./scripts/toggle-mock.sh off # Disable mock mode (use production data) -./scripts/toggle-mock.sh status # Show current status -``` - -The toggle script automatically: -- Updates `mock.env` configuration -- Restarts the backend with new settings -- Keeps the frontend running (no restart needed) -- Syncs production config when switching to production mode -- Switches `PULSE_DATA_DIR` between `/opt/pulse/tmp/mock-data` (mock) and `/etc/pulse` (production) so test data never touches real credentials - -## Mock Mode Configuration - -Edit `mock.env` to customize mock data: - -```bash -PULSE_MOCK_MODE=false # Enable/disable mock mode -PULSE_MOCK_NODES=7 # Number of mock nodes -PULSE_MOCK_VMS_PER_NODE=5 # Average VMs per node -PULSE_MOCK_LXCS_PER_NODE=8 # Average containers per node -PULSE_MOCK_RANDOM_METRICS=true # Enable metric fluctuations -PULSE_MOCK_STOPPED_PERCENT=20 # Percentage of stopped guests -``` - -Prefer `mock.env.local` for personal tweaks (`cp mock.env mock.env.local`). The toggle script honours `.local` first, keeping the shared defaults untouched. - -## Development Workflow - -1. Start hot-dev: `./scripts/hot-dev.sh` -2. Switch to mock mode for testing: `npm run mock:on` -3. Develop and test your changes -4. Switch to production mode to verify: `npm run mock:off` -5. Code changes auto-reload, no manual restarts needed! - -## Troubleshooting - -If the backend doesn't pick up changes: -```bash -npm run mock:off # Force restart with production data -npm run mock:on # Force restart with mock data -``` - -Check backend logs: -```bash -tail -f /tmp/pulse-backend.log -``` - -Check if services are running: -```bash -lsof -i :7656 # Backend -lsof -i :7655 # Frontend -``` diff --git a/SECURITY.md b/SECURITY.md index 2e804df..20f3522 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -52,7 +52,7 @@ Preferred option (no SSH keys, no proxy wiring): Deprecated option (existing installs only): -- `pulse-sensor-proxy` is deprecated in Pulse v5 and is not recommended for new deployments. +- `pulse-sensor-proxy` is deprecated in Pulse v5 and is not recommended for new deployments. In v5, legacy sensor-proxy endpoints are disabled by default unless `PULSE_ENABLE_SENSOR_PROXY=true` is set on the Pulse server. - Existing installs continue to work during the migration window, but plan to move to `pulse-agent --enable-proxmox`. - Canonical temperature docs: `docs/TEMPERATURE_MONITORING.md` @@ -376,7 +376,7 @@ docker run -e API_TOKENS=ansible-token,docker-agent-token rcourtman/pulse:latest **Security Note**: Tokens defined via environment variables are hashed with SHA3-256 before being stored on disk. Plain values never persist beyond startup. -#### Token Management (Settings → Security → API tokens) +#### Token Management (Settings → API Tokens) - Issue dedicated tokens for automation/agents without sharing a global credential - View prefixes/suffixes and last-used timestamps for auditing - Revoke tokens individually without downtime @@ -441,7 +441,7 @@ docker run \ - Debug logs may contain sensitive data—enable only when needed - JSON format recommended for security monitoring and SIEM - Adjust retention based on compliance requirements - - Changes take effect on restart +- Changes take effect on restart ## CORS (Cross-Origin Resource Sharing) @@ -460,14 +460,14 @@ sudo systemctl edit pulse [Service] Environment="ALLOWED_ORIGINS=https://app.example.com" -# Multiple origins (comma-separated) -ALLOWED_ORIGINS="https://app.example.com,https://dashboard.example.com" - # Development mode (allows localhost) PULSE_DEV=true ``` -**Security Note**: Never use `ALLOWED_ORIGINS=*` in production as it allows any website to access your API. +Notes: + +- `ALLOWED_ORIGINS` currently supports a single origin or `*` (it is written directly to `Access-Control-Allow-Origin`). +- Never use `ALLOWED_ORIGINS=*` in production as it allows any website to access your API. ## Monitoring and Observability @@ -566,7 +566,7 @@ curl -X POST http://localhost:7655/api/security/reset-lockout \ **Export blocked?** You're on a public network – login with password, set an API token (`API_TOKENS`), or set `ALLOW_UNPROTECTED_EXPORT=true` **Rate limited?** Wait 1 minute and try again **Can't login?** Check `PULSE_AUTH_USER` and `PULSE_AUTH_PASS` environment variables -**API access denied?** Verify the token you supplied matches one of the values created in *Settings → Security → API tokens* (use the original token, not the hash) +**API access denied?** Verify the token you supplied matches one of the values created in *Settings → API Tokens* (use the original token, not the hash) **CORS errors?** Configure `ALLOWED_ORIGINS` for your domain **Forgot password?** Start fresh – delete your Pulse data and restart diff --git a/docker-compose.yml b/docker-compose.yml index 18d584d..00ca491 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,9 @@ services: - "${PULSE_PORT:-7655}:7655" volumes: - pulse-data:/data - # Secure temperature monitoring via host-side proxy (requires setup - see docs) - # Uncomment after installing pulse-sensor-proxy on host with --standalone flag - # Mount is read-only (:ro) for security - proxy uses SO_PEERCRED for access control + # Temperature monitoring: + # - Recommended (v5): install pulse-agent on each Proxmox host with --enable-proxmox. + # - Deprecated: pulse-sensor-proxy (host-side proxy). If you already use it, mount the socket read-only. # - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:ro environment: - TZ=${TZ:-UTC} diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 69a8278..a86ab6d 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -53,7 +53,7 @@ docker run -d \
Advanced: OIDC / SSO -Configure Single Sign-On in **Settings → Security → OIDC**, or use environment variables to lock the configuration. +Configure Single Sign-On in **Settings → Security → Single Sign-On**, or use environment variables to lock the configuration. See [OIDC Documentation](OIDC.md) and [Proxy Auth](PROXY_AUTH.md) for details.
@@ -92,6 +92,7 @@ Environment variables take precedence over `system.json`. | `PULSE_PUBLIC_URL` | Public URL for notifications/OIDC | `""` | | `ALLOWED_ORIGINS` | CORS allowed domains | `""` (Same origin) | | `DISCOVERY_ENABLED` | Auto-discover nodes | `false` | +| `PULSE_ENABLE_SENSOR_PROXY` | Enable legacy `pulse-sensor-proxy` endpoints (deprecated, unsupported) | `false` | | `PULSE_AUTH_HIDE_LOCAL_LOGIN` | Hide username/password form | `false` | | `DEMO_MODE` | Enable read-only demo mode | `false` | diff --git a/docs/DOCS_AUDIT_V5.md b/docs/DOCS_AUDIT_V5.md deleted file mode 100644 index eccd7c9..0000000 --- a/docs/DOCS_AUDIT_V5.md +++ /dev/null @@ -1,317 +0,0 @@ -# Pulse v5 Documentation Audit (pre-stable) - -This is a working audit of Pulse documentation as of `VERSION=5.0.0-rc.4`, focused on release readiness for a v5 stable cut. - -## Status (updated 2025-12-18) - -Most of the issues identified in this audit have been addressed in-repo: - -- Updated install recommendation and bootstrap-token guidance across entrypoints (`README.md`, `docs/INSTALL.md`, `docs/FAQ.md`, `docs/TROUBLESHOOTING.md`, `docs/DOCKER.md`) -- Rewritten AI and API docs to match the current v5 implementation (`docs/AI.md`, `docs/API.md`) -- Rewritten metrics history docs to match SQLite store + tiered retention (`docs/METRICS_HISTORY.md`) -- Fixed adaptive polling defaults and rollout paths (`docs/monitoring/ADAPTIVE_POLLING.md`, `docs/operations/ADAPTIVE_POLLING_ROLLOUT.md`) -- Reduced temperature monitoring contradictions by making the agent the recommended path and scoping sensor-proxy as a legacy/alternative (`docs/TEMPERATURE_MONITORING.md`, `docs/security/TEMPERATURE_MONITORING.md`, `SECURITY.md`, sensor-proxy docs) -- Updated Helm/Kubernetes docs to prefer OCI distribution and flag the legacy agent block (`docs/KUBERNETES.md`, `deploy/helm/pulse/README.md`, `deploy/helm/pulse/values.yaml`) -- Added missing “operator clarity” docs (`docs/DEPLOYMENT_MODELS.md`, `docs/UPGRADE_v5.md`) -- Link validation run: no broken relative `.md` links found at time of update - -## Goals - -- Identify docs that are **stale**, **contradictory**, or **redundant** -- Identify **missing docs** needed for a v5 stable release -- Produce an actionable “what to change, where” checklist - -## Highest-Priority Fixes (release-blockers) - -### 1) Temperature monitoring guidance is contradictory - -There are multiple competing “truths” about how temperature monitoring works in v5: - -- `SECURITY.md` describes container deployments as requiring `pulse-sensor-proxy` and explicitly blocks SSH-based temps in containers. -- Multiple docs under `docs/security/` and `cmd/pulse-sensor-proxy/README.md` claim `pulse-sensor-proxy` is deprecated in favor of the unified agent. -- `docs/TEMPERATURE_MONITORING.md` is an extensive sensor-proxy-first guide and reads as “current”, but conflicts with the “deprecated” banner elsewhere. -- The backend still has extensive support and UX flows for sensor proxy install/register (`/api/install/install-sensor-proxy.sh`, temperature proxy diagnostics, container SSH blocking guidance). - -Action: -- Decide the **canonical** v5 story: - - **Option A (agent-first)**: “Install `pulse-agent --enable-proxmox` on each Proxmox host for temperatures and management. `pulse-sensor-proxy` is legacy or edge-case only.” - - **Option B (proxy-first for containers)**: “If Pulse runs in Docker/LXC, temperatures require `pulse-sensor-proxy` (socket/HTTPS). The agent is optional for other features.” -- Update all docs to align with the chosen story, and ensure `SECURITY.md` reflects it unambiguously. - -Status: -- Docs updated to be agent-first, with `pulse-sensor-proxy` treated as a legacy/alternative option. -- Remaining work is primarily product positioning and long-term deprecation decisions, not broken documentation. - -Files involved: -- `SECURITY.md` -- `docs/TEMPERATURE_MONITORING.md` -- `docs/security/TEMPERATURE_MONITORING.md` -- `docs/security/SENSOR_PROXY_HARDENING.md` -- `docs/security/SENSOR_PROXY_NETWORK.md` -- `docs/security/SENSOR_PROXY_APPARMOR.md` -- `docs/operations/SENSOR_PROXY_CONFIG.md` -- `docs/operations/SENSOR_PROXY_LOGS.md` -- `cmd/pulse-sensor-proxy/README.md` - -### 2) AI docs do not match the actual v5 API and configuration model - -`docs/AI.md` and the AI section in `docs/API.md` appear written for an older/alternate API surface: - -- `docs/AI.md` documents `PULSE_AI_PROVIDER` and `PULSE_AI_API_KEY` env vars, but the current implementation persists encrypted AI config in `ai.enc` and supports multi-provider credentials (Anthropic/OpenAI/DeepSeek/Gemini/Ollama) plus Anthropic OAuth. -- `docs/API.md` references endpoints like `POST /api/ai/chat` and `PUT /api/settings/ai` that do not match the router (current endpoints include `/api/ai/execute`, `/api/ai/models`, `/api/settings/ai/update`, OAuth endpoints, patrol stream, cost summary). - -Action: -- Rewrite AI docs to match current behavior: - - Providers actually supported - - How keys/tokens are stored (encrypted) and what the UI exposes - - Anthropic OAuth flow and security implications - - Patrol and command execution (“autonomous mode”) safety controls - - Correct API endpoints and auth requirements - -Files involved: -- `docs/AI.md` -- `docs/API.md` -- `internal/config/ai.go` (source of truth for config fields) -- `internal/api/router.go` (source of truth for endpoints) - -Status: -- `docs/AI.md` rewritten to match multi-provider + encrypted config. -- `docs/API.md` AI endpoints updated to match router. - -### 3) Installation “recommended path” is inconsistent across docs - -- `README.md` recommends “Proxmox LXC (Recommended)” via GitHub `install.sh`. -- `docs/INSTALL.md` and `docs/FAQ.md` currently present Docker as the easiest/recommended path. - -Action: -- Pick one recommendation hierarchy and make it consistent: - - If Proxmox LXC is the primary path, it should be the top section in `docs/INSTALL.md` and the FAQ answer should reflect it. - -Files involved: -- `README.md` -- `docs/INSTALL.md` -- `docs/FAQ.md` - -Status: -- Install docs now consistently present Proxmox VE LXC installer as the recommended path and include bootstrap-token retrieval. - -### 4) Kubernetes/Helm docs and chart docs are out of date for v5 - -- `docs/KUBERNETES.md` references a chart repo URL and “Docker Agent sidecar”. -- `deploy/helm/pulse/README.md` describes “optional Docker monitoring agent” and defaults to `ghcr.io/rcourtman/pulse-docker-agent`. - -Action: -- Update Helm docs to match the v5 agent direction: - - If `pulse-docker-agent` is deprecated, the chart should not reference it as primary. - - Align chart distribution instructions (Helm repo vs OCI). - -Files involved: -- `docs/KUBERNETES.md` -- `deploy/helm/pulse/README.md` -- `deploy/helm/pulse/values.yaml` -- `deploy/helm/pulse/templates/*` - -Status: -- `docs/KUBERNETES.md` updated to prefer OCI chart installs and flag the legacy agent block. -- `deploy/helm/pulse/README.md` and `deploy/helm/pulse/values.yaml` now label the agent workload as legacy. - -## Redundant / Duplicated Docs (needs consolidation) - -### Auto-update docs: two competing sources - -- `docs/AUTO_UPDATE.md` describes “Settings → System Updates” and includes docker image instructions that differ from other docs. -- `docs/operations/AUTO_UPDATE.md` documents systemd timers and edits `/var/lib/pulse/system.json` which appears stale for current config defaults (`/etc/pulse/system.json`). - -Action: -- Choose one canonical page (likely `docs/AUTO_UPDATE.md`) and: - - Move operational/timer details into it (or link to a clearly “advanced ops” page) - - Fix stale paths and service names - - Remove or clearly label the non-canonical duplicate - -Files involved: -- `docs/AUTO_UPDATE.md` -- `docs/operations/AUTO_UPDATE.md` - -Status: -- Both documents updated to current UI naming and paths; optional future work is to consolidate into a single canonical page. - -### Temperature monitoring docs: two sources with different “truth” - -- `docs/TEMPERATURE_MONITORING.md` (sensor proxy focused, extensive) -- `docs/security/TEMPERATURE_MONITORING.md` (agent recommended, proxy “legacy”) - -Action: -- Collapse into one canonical document with a clear decision tree, then: - - Keep the other as a short redirect page, or delete it. - -Files involved: -- `docs/TEMPERATURE_MONITORING.md` -- `docs/security/TEMPERATURE_MONITORING.md` - -Status: -- `docs/TEMPERATURE_MONITORING.md` is now the canonical deep-dive, and `docs/security/TEMPERATURE_MONITORING.md` is a security/overview page. - -### Adaptive polling docs disagree with defaults and file paths - -- `docs/monitoring/ADAPTIVE_POLLING.md` claims adaptive polling is enabled by default and says env default is `true`. -- Code defaults `AdaptivePollingEnabled=false` and `docs/operations/ADAPTIVE_POLLING_ROLLOUT.md` references `/var/lib/pulse/system.json`. - -Action: -- Make one canonical doc, fix defaults and paths, and ensure UI path matches current navigation. - -Files involved: -- `docs/monitoring/ADAPTIVE_POLLING.md` -- `docs/operations/ADAPTIVE_POLLING_ROLLOUT.md` - -Status: -- Defaults and paths updated to match current behavior. - -## Stale / Incorrect Content (targeted findings) - -### `docs/API.md` - -Issues: -- AI endpoints mismatch current router paths (examples: `POST /api/ai/chat` vs current `/api/ai/execute`; settings update path differs). -- “complete REST API documentation” claim is optimistic. It’s a curated subset plus a “check router.go” note. - -Action: -- Update AI section to match `internal/api/router.go`. -- Consider splitting into: - - “Stable/public API” (guaranteed) - - “Internal/subject to change” (documented but not stable) - -### `docs/METRICS_HISTORY.md` - -Issues: -- Documents `PULSE_METRICS_*_RETENTION_DAYS` env vars that do not appear to exist in the server config. -- Claims metrics are stored under `/etc/pulse/data/metrics/`, but the metrics store is SQLite (`metrics.db`) under the configured data directory. - -Action: -- Rewrite this doc to match the tiered retention model and actual storage format/location. - -### `docs/FAQ.md` - -Issues: -- Recommends Docker as easiest install, conflicts with repo README. -- Password reset guidance does not mention the bootstrap token requirement that can appear after removing `.env`. -- Mentions `METRICS_RETENTION_DAYS` which does not appear to be a current server config knob (v5 uses tiered retention settings). - -Action: -- Align install recommendation with v5 positioning. -- Update auth reset steps to include bootstrap token retrieval where applicable. -- Replace metrics retention knob guidance with current retention model and UI location. - -### `docs/TROUBLESHOOTING.md` and `docs/DOCKER.md` - -Issues: -- “Forgot password” flow implies you can just rerun the setup wizard after deleting `.env`, but first-time setup can require the bootstrap token. - -Action: -- Update password reset steps and link to the bootstrap token section in `docs/INSTALL.md`. - -### `docs/RELEASE_NOTES.md` - -Issues: -- Entire document is v4.x release notes. - -Action: -- Replace with v5 release notes (or move to `docs/releases/` and add v5.0.0 as the top section). -- For the v5 stable cut, include: breaking changes, migration notes, and versioned “what changed since v4”. - -### `cmd/pulse-sensor-proxy/README.md` - -Issues: -- Mentions downloading via `/download/pulse-sensor-proxy` but the server router does not expose this endpoint. -- “Deprecated” banner conflicts with current server behavior and security guidance. - -Action: -- Either bring it in line with the chosen v5 temperature story, or clearly scope it as legacy. - -### Broken local link - -- `docs/TEMPERATURE_MONITORING.md` contains an absolute link to `/opt/pulse/cmd/pulse-sensor-proxy/README.md` which does not work in GitHub. - -Action: -- Replace with a repo-relative link (or link to the canonical temperature doc). - -### Widespread “runtime path” drift (`/opt/pulse/...`) - -Several user-facing docs mix: -- repository paths (`/opt/pulse/...`) used in this dev workspace, and -- runtime paths used in real installs (`/etc/pulse`, `/data`, `/var/log/pulse`, systemd units). - -This creates confusion and broken copy-paste commands. - -Examples to review: -- `docs/ZFS_MONITORING.md` references `/opt/pulse/.env` and `/opt/pulse/pulse.log`. -- `docs/operations/*` references `/var/lib/pulse/system.json` rather than `/etc/pulse/system.json`. - -Action: -- Adopt a consistent convention across docs: - - **Runtime**: `/etc/pulse` (systemd/LXC), `/data` (Docker/Helm) - - **Repo/dev**: `/opt/pulse` only in development docs - - **Logs**: `journalctl -u pulse` (systemd) and `docker logs` (Docker), plus `/var/log/pulse/*` only if actually used in production images. - -## Missing Docs for a v5 Stable Release (recommended additions) - -### v5 upgrade guide (v4 → v5) - -Add a single canonical page covering: -- “What changes in v5” in operator terms -- Any breaking changes and required actions -- Post-upgrade verification checklist (health endpoint, scheduler health, agents connected, temps, notifications) -- Rollback guidance for each deployment model (Docker, systemd/LXC, Helm) - -Suggested path: -- `docs/UPGRADE_v5.md` (or `docs/MIGRATION_v5.md`) - -### “Deployment model matrix” - -Many docs implicitly assume a deployment type. Add a short matrix page that answers: -- What works on Docker vs Proxmox LXC vs systemd vs Helm -- How updates work per model -- Where config lives per model -- What “recommended” means (and why) - -Suggested path: -- `docs/DEPLOYMENT_MODELS.md` - -### AI safety and permissions - -If v5 ships AI “execute/run-command” features: -- Document default safety posture -- What autonomous mode does -- Required scopes/roles -- Audit logging expectations -- Clear warning section for production - -Suggested path: -- Expand `docs/AI.md` with a “Safety” section, or add `docs/AI_SAFETY.md`. - -## Quick “Status” Inventory (what to touch for v5) - -This is a fast triage list to help plan the doc refresh. Treat anything marked “Review” as “verify against current behavior”. - -- Rewrite: `docs/AI.md` -- Rewrite: `docs/METRICS_HISTORY.md` -- Rewrite: `docs/RELEASE_NOTES.md` (or replace with v5 release notes) -- Update + align: `docs/INSTALL.md`, `docs/FAQ.md`, `docs/TROUBLESHOOTING.md` -- Update: `docs/API.md` (especially AI endpoints) -- Decide canonical + consolidate: - - `docs/TEMPERATURE_MONITORING.md` vs `docs/security/TEMPERATURE_MONITORING.md` - - `docs/AUTO_UPDATE.md` vs `docs/operations/AUTO_UPDATE.md` - - `docs/monitoring/ADAPTIVE_POLLING.md` vs `docs/operations/ADAPTIVE_POLLING_ROLLOUT.md` -- Review (Helm): `docs/KUBERNETES.md`, `deploy/helm/pulse/README.md` -- Review (paths): `docs/ZFS_MONITORING.md` (and any other doc that uses `/opt/pulse/...` in user instructions) - -## Suggested “Doc Refresh” Execution Order - -1. Decide v5 canonical stories (agent vs proxy for temps, AI capabilities, Helm strategy). -2. Update the primary entrypoints: - - `README.md` - - `docs/README.md` - - `docs/INSTALL.md` -3. Fix contradictions and remove duplicates (temperature, auto-update, adaptive polling). -4. Update `docs/API.md` to reflect current endpoints (especially AI). -5. Add v5 upgrade guide and deployment matrix. -6. Sweep FAQ + troubleshooting for the new canonical flows. diff --git a/docs/PULSE_PRO_IMPLEMENTATION.md b/docs/PULSE_PRO_IMPLEMENTATION.md deleted file mode 100644 index d8df644..0000000 --- a/docs/PULSE_PRO_IMPLEMENTATION.md +++ /dev/null @@ -1,401 +0,0 @@ -# Pulse Pro Implementation Plan - -**Goal**: Gate AI features behind a Pro license to create a sustainable income stream. - -**Timeline**: ~1-2 weeks of focused work - ---- - -## Phase 1: License System Architecture - -### 1.1 License Format (Simple JWT) - -```go -// internal/license/license.go -type LicenseData struct { - LicenseID string `json:"lid"` // Unique license ID - Email string `json:"email"` // Customer email - Tier string `json:"tier"` // "pro", "msp", "enterprise" - IssuedAt time.Time `json:"iat"` - ExpiresAt time.Time `json:"exp"` // Empty = lifetime - MaxNodes int `json:"max_nodes"` // 0 = unlimited - Features []string `json:"features"` // ["ai_chat", "ai_patrol", "ai_alerts"] -} -``` - -**Why JWT?** -- Self-contained (no license server needed for validation) -- Signed with your private key, verified with public key embedded in binary -- Can be verified offline (important for air-gapped homelabs) -- Standard format, easy to generate from any payment processor webhook - -### 1.2 License Validation Flow - -``` -┌────────────────────────────────────────────────────────────────┐ -│ User purchases license on LemonSqueezy/Gumroad │ -│ ↓ │ -│ Webhook hits your simple license API (can be CloudFlare Worker)│ -│ ↓ │ -│ Generate JWT signed with private key │ -│ ↓ │ -│ Email license key to customer │ -│ ↓ │ -│ User pastes key in Pulse Settings → Pro tab │ -│ ↓ │ -│ Pulse validates signature with embedded public key │ -│ ↓ │ -│ Store encrypted license in config dir (license.enc) │ -└────────────────────────────────────────────────────────────────┘ -``` - ---- - -## Phase 2: Backend Implementation - -### 2.1 New Files to Create - -``` -internal/license/ -├── license.go # License struct, validation, JWT parsing -├── license_test.go # Tests -└── features.go # Feature flags (what Pro includes) - -internal/config/ -└── license.go # License persistence (store/load from disk) -``` - -### 2.2 License Service - -```go -// internal/license/license.go -package license - -import ( - "crypto/ed25519" - "encoding/base64" - "errors" - "time" - - "github.com/golang-jwt/jwt/v5" -) - -// Embedded public key (compiled into binary) -// Generate keypair: go run ./cmd/license-keygen -var publicKeyBase64 = "YOUR_PUBLIC_KEY_HERE" - -type Service struct { - license *LicenseData - loaded bool -} - -func NewService() *Service { - return &Service{} -} - -func (s *Service) LoadFromKey(licenseKey string) error { - // Parse and validate JWT - // Store in s.license -} - -func (s *Service) IsValid() bool { - if s.license == nil { - return false - } - if !s.license.ExpiresAt.IsZero() && time.Now().After(s.license.ExpiresAt) { - return false - } - return true -} - -func (s *Service) HasFeature(feature string) bool { - if !s.IsValid() { - return false - } - for _, f := range s.license.Features { - if f == feature || f == "all" { - return true - } - } - return false -} - -// Feature constants -const ( - FeatureAIChat = "ai_chat" - FeatureAIPatrol = "ai_patrol" - FeatureAIAlerts = "ai_alerts" - FeatureOIDC = "oidc" // SSO/OIDC authentication - FeatureKubernetes = "kubernetes" // K8s cluster monitoring - FeatureMultiUser = "multi_user" // Multiple user accounts - FeatureAPIAccess = "api_access" // Full API access for integrations - FeatureWhiteLabel = "white_label" // Custom branding (MSP tier) - FeatureAll = "all" -) -``` - -### 2.3 Integration Points - -Modify these files to check license: - -| File | What to Gate | -|------|--------------| -| `internal/api/ai_handlers.go` | Chat endpoints, patrol endpoints | -| `internal/ai/patrol.go` | Patrol service start | -| `internal/ai/service.go` | AI chat service | -| `internal/ai/alert_triggered.go` | Alert analysis | -| `internal/api/oidc_handlers.go` | OIDC/SSO configuration | -| `internal/api/kubernetes_handlers.go` | K8s cluster endpoints | -| `internal/monitoring/kubernetes/` | K8s monitoring service | - -**Example gating in ai_handlers.go:** - -```go -func (h *AISettingsHandler) HandleChat(w http.ResponseWriter, r *http.Request) { - // Check Pro license - if !h.licenseService.HasFeature(license.FeatureAIChat) { - utils.WriteJSONError(w, http.StatusPaymentRequired, - "AI Chat requires Pulse Pro. Visit https://pulserelay.pro to upgrade.") - return - } - // ... existing logic -} -``` - ---- - -## Phase 3: Frontend Implementation - -### 3.1 New Settings Tab: "Pro License" - -Location: `frontend-modern/src/routes/settings/+page.svelte` (or equivalent) - -``` -┌──────────────────────────────────────────────────────────────┐ -│ ⚡ Pulse Pro │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ License Key │ │ -│ │ [________________________________________________] │ │ -│ │ [Activate License] │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ │ -│ ✅ License Status: Active (Pro) │ -│ 📧 Licensed to: user@example.com │ -│ 📅 Expires: Never (Lifetime) │ -│ │ -│ ───────────────────────────────────────────────────────── │ -│ │ -│ Included Features: │ -│ ✅ AI Chat Assistant │ -│ ✅ AI Patrol (Background Health Checks) │ -│ ✅ AI Alert Analysis │ -│ ✅ Priority Support │ -│ │ -│ ───────────────────────────────────────────────────────── │ -│ │ -│ Don't have a license? │ -│ [Get Pulse Pro →] https://pulserelay.pro │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -### 3.2 Graceful Degradation for Unlicensed Users - -When AI features are accessed without a license: - -- **AI Settings Tab**: Show features but disabled with "Upgrade to Pro" message -- **Chat Button**: Show but with "Pro" badge, clicking prompts upgrade -- **Patrol Findings**: Hide or show "Enable with Pro" placeholder - -**Don't be hostile.** The free version should still feel complete. Pro is an enhancement, not a hostage situation. - ---- - -## Phase 4: Payment & License Generation - -### 4.1 Payment Processor: LemonSqueezy - -**Why LemonSqueezy over alternatives?** -- Handles global VAT/sales tax automatically -- Generates invoices (enterprises need this) -- Good webhook support for automation -- Reasonable fees (~5% + 50¢) -- Supports both subscription and one-time payments - -### 4.2 Pricing Structure (Suggested) - -| Tier | Price | Features | Target | -|------|-------|----------|--------| -| **Pro Monthly** | $12/month | AI features, OIDC/SSO, K8s monitoring | Individuals | -| **Pro Annual** | $99/year | Same as monthly, 2 months free | Power users | -| **Pro Lifetime** | $249 one-time | All Pro features, forever | Homelabbers who hate subscriptions | -| **MSP** | $49/month | All Pro + unlimited instances, white-label, multi-tenant | MSPs | -| **Enterprise** | Custom | All features + support SLA, on-prem license server | Large orgs | - -### 4.3 Feature Matrix - -| Feature | Free | Pro | MSP | Enterprise | -|---------|------|-----|-----|------------| -| Proxmox VE/PBS/PMG monitoring | ✅ | ✅ | ✅ | ✅ | -| Docker/Podman monitoring | ✅ | ✅ | ✅ | ✅ | -| Alerts (Discord, Slack, etc.) | ✅ | ✅ | ✅ | ✅ | -| Metrics history | ✅ | ✅ | ✅ | ✅ | -| Backup explorer | ✅ | ✅ | ✅ | ✅ | -| **AI Chat** | ❌ | ✅ | ✅ | ✅ | -| **AI Patrol** | ❌ | ✅ | ✅ | ✅ | -| **AI Alert Analysis** | ❌ | ✅ | ✅ | ✅ | -| **OIDC/SSO** | ❌ | ✅ | ✅ | ✅ | -| **Kubernetes monitoring** | ❌ | ✅ | ✅ | ✅ | -| Unlimited instances | ❌ | ❌ | ✅ | ✅ | -| White-label branding | ❌ | ❌ | ✅ | ✅ | -| Multi-tenant mode | ❌ | ❌ | ✅ | ✅ | -| Priority support | ❌ | Email | Email | Dedicated | -| SLA | ❌ | ❌ | ❌ | ✅ | - -### 4.3 License Generation Service - -A simple Cloudflare Worker or Vercel Edge Function: - -```javascript -// Simplified license generator (LemonSqueezy webhook handler) -addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) -}) - -async function handleRequest(request) { - const webhook = await request.json() - - if (webhook.meta.event_name === 'order_created') { - const license = generateLicense({ - email: webhook.data.attributes.user_email, - tier: 'pro', - expiresAt: null, // lifetime for now - }) - - // Send license via email - await sendLicenseEmail(webhook.data.attributes.user_email, license) - } - - return new Response('OK') -} - -function generateLicense(data) { - // Sign JWT with private key - // Return base64-encoded license key -} -``` - ---- - -## Phase 5: Launch Communication - -### 5.1 Changelog Entry - -```markdown -## v5.0.0 - The AI Update - -### 🚀 Major Changes - -**Introducing Pulse Pro** - -Pulse 5.0 includes powerful AI features that require a Pro license: -- **AI Chat**: Natural language interface to your infrastructure -- **AI Patrol**: Background health monitoring and insights -- **AI Alert Analysis**: Smart analysis when alerts fire - -Core monitoring features remain **completely free and open source**. - -Pro licenses support ongoing development and enable me to work on Pulse full-time. - -[Get Pulse Pro →](https://pulserelay.pro) - ---- - -*Pulse has grown from a weekend project to something used by thousands. -To keep improving it, I need to make it sustainable. Thank you for your support!* - -— Richard -``` - -### 5.2 Preemptive FAQ - -Add to README or docs: - -**Q: Why are AI features paid?** -A: AI features require significant development effort and ongoing maintenance. Pro licenses let me work on Pulse sustainably while keeping core monitoring free. - -**Q: Will monitoring features become paid?** -A: No. Proxmox/Docker/K8s monitoring, alerts, history, and all current free features will remain free forever. - -**Q: What if I'm already using AI in the RC?** -A: Thank you for testing! RC users were beta testers helping shape these features. The final release requires a Pro license. - -**Q: I can't afford Pro.** -A: Email me (richard@pulserelay.pro). I offer discounts for students, hobbyists in financial hardship, and open source contributors. - -**Q: Can I self-host without Pro?** -A: Absolutely. Pulse works great without AI features. Pro is optional. - ---- - -## Implementation Order - -1. **Week 1: Backend** - - [ ] Create `internal/license/` package - - [ ] Implement JWT validation with embedded public key - - [ ] Add license persistence (encrypted storage) - - [ ] Gate AI endpoints with license checks - - [ ] Add `/api/license` endpoints (check, activate) - -2. **Week 2: Frontend + Payment** - - [ ] Add Pro License settings tab - - [ ] Update AI settings to show Pro-gated state - - [ ] Set up LemonSqueezy product - - [ ] Create license generation webhook - - [ ] Set up pulserelay.pro landing page (can be simple) - - [ ] Write announcement blog post - -3. **Launch** - - [ ] Release v5.0.0 stable - - [ ] Post to Reddit (/r/homelab, /r/Proxmox, /r/selfhosted) - - [ ] Post to GitHub Discussions - - [ ] Email mailing list (if you have one) - ---- - -## Security Considerations - -1. **Private key**: Never commit to repo. Store in password manager + secure backup. -2. **License validation**: Always verify signature, never trust claims without verification. -3. **Obfuscation**: Consider light obfuscation of license check code (not for security, but to discourage trivial patching). -4. **Grace period**: If validation fails, maybe grant 7-day grace period before disabling (better UX). - ---- - -## What NOT to Do - -- ❌ Phone-home license validation (breaks air-gapped installs) -- ❌ Aggressive license enforcement (pisses off users) -- ❌ Remove free features to "encourage" upgrades -- ❌ Make the free version feel crippled -- ❌ Hide that it's paid (be upfront in README) - ---- - -## Success Metrics (First 90 Days) - -| Metric | Target | -|--------|--------| -| Pro licenses sold | 50-100 | -| Monthly revenue | $500-$1000 | -| Churn rate | <5% | -| Negative community reactions | <10 vocal complaints | -| GitHub stars lost | <50 | - -If you hit these numbers, you've validated the model. Then you can expand to MSP tier, add features, etc. - ---- - -*This plan can be adjusted based on your preferences. Want me to help implement any specific part?* diff --git a/docs/README.md b/docs/README.md index 1bbe4cf..0eb287f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,16 +42,14 @@ Welcome to the Pulse documentation portal. Here you'll find everything you need - **[Unified Agent](UNIFIED_AGENT.md)** – Single binary for Host and Docker monitoring. - **[VM Disk Monitoring](VM_DISK_MONITORING.md)** – Enabling QEMU Guest Agent for disk stats. -- **[Temperature Monitoring](TEMPERATURE_MONITORING.md)** – Setting up hardware sensors. +- **[Temperature Monitoring](TEMPERATURE_MONITORING.md)** – Agent-based temperature monitoring (`pulse-agent --enable-proxmox`). Sensor proxy is deprecated in v5. - **[Webhooks](WEBHOOKS.md)** – Custom notification payloads. ## 💻 Development - **[API Reference](API.md)** – Complete REST API documentation. -- **[Dev Quick Start](../DEV-QUICK-START.md)** – Hot-reload workflow for contributors. - **[Architecture](../ARCHITECTURE.md)** – System design and component interaction. - **[Contributing](../CONTRIBUTING.md)** – How to contribute to Pulse. -- **[Mock Mode](development/MOCK_MODE.md)** – Developing without real infrastructure. --- diff --git a/docs/SECURITY_AUDIT_2025-11-07.md b/docs/SECURITY_AUDIT_2025-11-07.md deleted file mode 100644 index 664e8f3..0000000 --- a/docs/SECURITY_AUDIT_2025-11-07.md +++ /dev/null @@ -1,631 +0,0 @@ -# Security Audit Report - Pulse Temperature Proxy -## Date: 2025-11-07 -## Auditors: Claude (Sonnet 4.5) + Codex - ---- - -## Executive Summary - -This document presents the findings and remediations from a comprehensive security audit of the pulse-sensor-proxy architecture. The audit identified **9 security issues** ranging from critical to low severity, all of which have been **successfully remediated**. - -### Overall Assessment - -**Before Audit:** B+ (Good, with important gaps) -**After Remediation:** A (Excellent security posture) - -**Risk Reduction:** All critical vulnerabilities eliminated. System now resilient to container compromise scenarios. - ---- - -## Audit Methodology - -1. **Architecture Review** - Analysis of trust boundaries and security design -2. **Code Review** - Line-by-line examination of security-critical code paths -3. **Threat Modeling** - Evaluation of attack vectors and exploitation scenarios -4. **Collaborative Analysis** - Claude + Codex independent review and challenge -5. **Implementation** - Codex-led implementation of all fixes -6. **Testing** - Comprehensive test coverage for all security features - ---- - -## Findings and Remediations - -### CRITICAL SEVERITY - -#### 1. Socket Directory Tampering ✅ FIXED - -**Finding:** -Socket directory mounted read-write into containers, allowing compromised containers to: -- Unlink socket and create man-in-the-middle proxies -- Fill `/run/pulse-sensor-proxy/` to exhaust tmpfs -- Race proxy service on restart to hijack socket path - -**CVSS Score:** 8.1 (High) -**Attack Complexity:** Low -**Privileges Required:** Low (container access) -**Impact:** Complete compromise of proxy communication - -**Remediation:** -- Changed all socket mounts to read-only (`:ro`) -- Updated documentation to reflect secure configuration -- Added validation to installer - -**Files Modified:** -- `docker-compose.yml` -- `docs/TEMPERATURE_MONITORING.md` - -**Status:** ✅ Deployed - ---- - -#### 2. Unrestricted SSRF via get_temperature ✅ FIXED - -**Finding:** -Proxy would SSH to ANY hostname/IP passing format validation, enabling: -- Internal network reconnaissance via SSH handshakes -- Port scanning using proxy as relay -- Resource exhaustion via slow-loris SSH attacks -- Complete bypass of network security controls - -**CVSS Score:** 8.6 (High) -**Attack Complexity:** Low -**Privileges Required:** Low (container access) -**Impact:** Full internal network access from host context - -**Remediation:** -- Implemented multi-layer node validation system -- Configurable `allowed_nodes` list (hostnames, IPs, CIDR ranges) -- Automatic cluster membership validation on Proxmox hosts -- 5-minute cache of cluster membership -- New metric: `pulse_proxy_node_validation_failures_total` - -**Configuration Example:** -```yaml -allowed_nodes: - - "pve1" - - "192.168.1.0/24" -strict_node_validation: true -``` - -**Files Created:** -- `cmd/pulse-sensor-proxy/validation.go` -- `cmd/pulse-sensor-proxy/validation_test.go` - -**Files Modified:** -- `cmd/pulse-sensor-proxy/config.go` -- `cmd/pulse-sensor-proxy/main.go` -- `cmd/pulse-sensor-proxy/metrics.go` - -**Tests:** 4 new tests, all passing -**Status:** ✅ Deployed - ---- - -#### 3. Missing Read Deadline (Connection Exhaustion) ✅ FIXED - -**Finding:** -No read deadline allowed attackers to hold connection slots indefinitely: -- Connect but never send data -- 4 UIDs could consume all 8 global slots -- Trivial DoS with minimal resources - -**CVSS Score:** 7.5 (High) -**Attack Complexity:** Low -**Privileges Required:** Low (container access) -**Impact:** Complete service denial - -**Remediation:** -- Added configurable `read_timeout` (default 5s) and `write_timeout` (default 10s) -- Read deadline set before request parsing, cleared before handler -- Write deadline set before response transmission -- Automatic penalty on timeout -- New metrics: `pulse_proxy_read_timeouts_total`, `pulse_proxy_write_timeouts_total` - -**Configuration:** -```yaml -read_timeout: 5s -write_timeout: 10s -``` - -**Files Modified:** -- `cmd/pulse-sensor-proxy/config.go` -- `cmd/pulse-sensor-proxy/main.go` -- `cmd/pulse-sensor-proxy/metrics.go` - -**Status:** ✅ Deployed - ---- - -#### 4. Multi-UID Rate Limit Bypass ✅ FIXED - -**Finding:** -Rate limiting per-UID easily bypassed by creating multiple users in container: -- Each user mapped to unique host UID (100000-165535 range) -- Each UID got separate rate limit quota -- Attackers could drive proxy to 100% CPU - -**CVSS Score:** 7.5 (High) -**Attack Complexity:** Low -**Privileges Required:** Low (container access) -**Impact:** Service degradation/denial - -**Remediation:** -- Automatic detection of ID-mapped UID ranges from `/etc/subuid` and `/etc/subgid` -- Rate limits applied per-range for container UIDs -- Rate limits applied per-UID for host UIDs (backwards compatible) -- Metrics show `peer="range:100000-165535"` or `peer="uid:0"` - -**Technical Implementation:** -- `identifyPeer()` checks if BOTH UID AND GID are in mapped ranges -- If in range: all UIDs share rate limits -- If NOT in range: legacy per-UID limiting - -**Files Modified:** -- `cmd/pulse-sensor-proxy/throttle.go` -- `cmd/pulse-sensor-proxy/main.go` -- `cmd/pulse-sensor-proxy/auth.go` -- `cmd/pulse-sensor-proxy/metrics.go` - -**Files Created:** -- `cmd/pulse-sensor-proxy/throttle_test.go` - -**Tests:** 1 new test, passing -**Status:** ✅ Deployed - ---- - -### MEDIUM SEVERITY - -#### 5. Incomplete GID Authorization ✅ FIXED - -**Finding:** -`allowed_peer_gids` populated from config but never checked during authorization: -- Created false sense of security -- GID-based policies silently ignored -- Administrators unaware policies not enforced - -**CVSS Score:** 5.3 (Medium) -**Attack Complexity:** Low -**Impact:** Authorization bypass - -**Remediation:** -- Implemented GID checking in `authorizePeer()` -- Peer authorized if UID **OR** GID matches -- Debug logging shows which rule granted access -- Updated documentation - -**Files Modified:** -- `cmd/pulse-sensor-proxy/auth.go` - -**Files Created:** -- `cmd/pulse-sensor-proxy/auth_test.go` - -**Tests:** 2 new tests, all passing -**Status:** ✅ Deployed - ---- - -#### 6. Unbounded SSH Output ✅ FIXED - -**Finding:** -No limit on SSH command output size: -- Malicious remote node could stream gigabytes -- Memory exhaustion -- CPU spike on parsing - -**CVSS Score:** 6.5 (Medium) -**Attack Complexity:** Low -**Impact:** Resource exhaustion - -**Remediation:** -- Added `max_ssh_output_bytes` config (default: 1MB) -- Stream with `io.LimitReader` to cap output size -- Error if limit exceeded -- New metric: `pulse_proxy_ssh_output_oversized_total{node}` -- WARN logging for oversized outputs - -**Configuration:** -```yaml -max_ssh_output_bytes: 1048576 # 1MB default -``` - -**Files Modified:** -- `cmd/pulse-sensor-proxy/config.go` -- `cmd/pulse-sensor-proxy/ssh.go` -- `cmd/pulse-sensor-proxy/metrics.go` - -**Files Created:** -- `cmd/pulse-sensor-proxy/ssh_test.go` - -**Tests:** 1 new test, passing -**Status:** ✅ Deployed - ---- - -#### 7. Weak Host Key Validation (TOFU) ✅ FIXED - -**Finding:** -Trust-On-First-Use (TOFU) via `ssh-keyscan`: -- Trusts whatever key remote offers on first contact -- No administrator approval for new fingerprints -- Vulnerable to MITM if container influences routing - -**CVSS Score:** 6.5 (Medium) -**Attack Complexity:** Medium -**Impact:** MITM attacks possible - -**Remediation:** -- Implemented Proxmox host key seeding from `/etc/pve/priv/known_hosts` -- Falls back to ssh-keyscan only if Proxmox unavailable (with WARN) -- Added `require_proxmox_hostkeys` config option -- Fingerprint change detection with ERROR logging -- New metric: `pulse_proxy_hostkey_changes_total{node}` - -**Configuration:** -```yaml -require_proxmox_hostkeys: false # true = strict mode -``` - -**Files Modified:** -- `internal/ssh/knownhosts/manager.go` -- `cmd/pulse-sensor-proxy/ssh.go` -- `cmd/pulse-sensor-proxy/config.go` -- `cmd/pulse-sensor-proxy/metrics.go` - -**Files Created:** -- `internal/ssh/knownhosts/manager_test.go` - -**Tests:** 7 new tests, all passing -**Status:** ✅ Deployed - ---- - -#### 8. Insufficient Capability Separation ✅ FIXED - -**Finding:** -Any UID in `allowed_peer_uids` could call privileged methods: -- No separation between read-only and admin capabilities -- If another service's UID in allowlist, inherits full control - -**CVSS Score:** 6.5 (Medium) -**Attack Complexity:** Low -**Impact:** Privilege escalation - -**Remediation:** -- Implemented capability-based authorization system -- Three capability levels: `read`, `write`, `admin` -- Per-UID capability assignment -- Privileged methods require `admin` capability -- Backwards compatible with legacy config - -**Configuration:** -```yaml -allowed_peers: - - uid: 0 - capabilities: [read, write, admin] # Root gets all - - uid: 1000 - capabilities: [read] # Docker: read-only - - uid: 1001 - capabilities: [read, write] # Can call temps but not key distribution -``` - -**Files Created:** -- `cmd/pulse-sensor-proxy/capabilities.go` - -**Files Modified:** -- `cmd/pulse-sensor-proxy/config.go` -- `cmd/pulse-sensor-proxy/auth.go` -- `cmd/pulse-sensor-proxy/main.go` - -**Tests:** 1 new test, passing -**Status:** ✅ Deployed - ---- - -### LOW SEVERITY - -#### 9. Missing Systemd Hardening ✅ FIXED - -**Finding:** -Additional systemd hardening options available but not enabled: -- `MemoryDenyWriteExecute` (prevents RWX memory) -- `RestrictRealtime` (denies realtime scheduling) -- `ProtectHostname` (hostname protection) -- `ProtectKernelLogs` (kernel log protection) -- `SystemCallArchitectures` (native only) - -**CVSS Score:** 3.1 (Low) -**Attack Complexity:** High -**Impact:** Defense in depth - -**Remediation:** -- Added all missing hardening directives -- Verified compatibility with Go runtime -- Updated systemd unit file - -**Files Modified:** -- `scripts/pulse-sensor-proxy.service` - -**Status:** ✅ Deployed - ---- - -## New Security Features - -### Enhanced Metrics - -All new security features include Prometheus metrics: - -| Metric | Purpose | -|--------|---------| -| `pulse_proxy_node_validation_failures_total{node, reason}` | SSRF attempt detection | -| `pulse_proxy_read_timeouts_total` | Connection DoS detection | -| `pulse_proxy_write_timeouts_total` | Write timeout tracking | -| `pulse_proxy_limiter_rejections_total{peer, reason}` | Rate limit monitoring | -| `pulse_proxy_limiter_penalties_total{peer, reason}` | Penalty tracking | -| `pulse_proxy_global_concurrency_inflight` | Concurrency monitoring | -| `pulse_proxy_ssh_output_oversized_total{node}` | Output size violations | -| `pulse_proxy_hostkey_changes_total{node}` | Fingerprint changes | - -### Improved Logging - -- Node validation failures: WARN with "potential SSRF attempt" -- Read timeouts: WARN with "slow client or attack" -- Fingerprint changes: ERROR level -- All events include correlation IDs -- Peer labels show "range:X-Y" for containers - -### Configuration Flexibility - -All features configurable via: -- YAML config file (`/etc/pulse-sensor-proxy/config.yaml`) -- Environment variables (e.g., `PULSE_SENSOR_PROXY_READ_TIMEOUT`) -- Command-line flags - ---- - -## Testing Summary - -### Test Coverage - -**Total New Tests:** 17 -**All Tests Passing:** ✅ Yes - -**Test Breakdown:** -- Node validation: 4 tests -- Authorization: 3 tests -- Rate limiting: 1 test -- SSH output limits: 1 test -- Host key management: 7 tests -- Capability system: 1 test - -### Build Verification - -```bash -✅ All tests pass: go test ./cmd/pulse-sensor-proxy ./internal/ssh/knownhosts -✅ Binary builds: ./pulse-sensor-proxy-hardened -✅ Configuration validated -✅ Systemd unit verified -``` - ---- - -## Deployment Guide - -### Breaking Changes - -1. **Socket mounts MUST be changed to `:ro`** (security fix) - ```yaml - # OLD: - - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:rw - - # NEW: - - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:ro - ``` - -2. **Containers with multiple users now share rate limits** (security fix, prevents bypass) - -### Migration Steps - -1. **Update Configuration** - - Create `/etc/pulse-sensor-proxy/config.yaml`: - ```yaml - # Node allowlist (prevents SSRF) - allowed_nodes: - - "10.0.0.0/24" # Your cluster network - strict_node_validation: true - - # Timeouts (prevents DoS) - read_timeout: 5s - write_timeout: 10s - - # SSH output limits - max_ssh_output_bytes: 1048576 # 1MB - - # Host key management - require_proxmox_hostkeys: false # Set true for strict mode - - # Capability-based authorization - allowed_peers: - - uid: 0 - capabilities: [read, write, admin] - - uid: 1000 - capabilities: [read] # Docker containers: read-only - ``` - -2. **Update Socket Mounts** - - Docker: - ```bash - # Edit docker-compose.yml - sed -i 's/:rw$/:ro/g' docker-compose.yml - docker compose down && docker compose up -d - ``` - - LXC: - ```bash - # Mounts created by install script are already correct - # Verify: pct config | grep mp - ``` - -3. **Restart Proxy** - - ```bash - systemctl restart pulse-sensor-proxy - ``` - -4. **Update Monitoring** - - Add Prometheus alerts: - ```yaml - groups: - - name: pulse-sensor-proxy-security - rules: - # SSRF attempts - - alert: PulseSensorSSRFAttempt - expr: rate(pulse_proxy_node_validation_failures_total[5m]) > 0 - labels: - severity: warning - annotations: - summary: "SSRF attempt blocked on {{ $labels.instance }}" - - # Read timeout attacks - - alert: PulseSensorReadTimeouts - expr: rate(pulse_proxy_read_timeouts_total[5m]) > 1 - labels: - severity: warning - annotations: - summary: "High read timeout rate on {{ $labels.instance }}" - - # Fingerprint changes - - alert: PulseSensorHostKeyChange - expr: increase(pulse_proxy_hostkey_changes_total[1h]) > 0 - labels: - severity: critical - annotations: - summary: "SSH host key changed for {{ $labels.node }}" - ``` - -### Backwards Compatibility - -**Preserved:** -- Empty `allowed_nodes` + Proxmox host = auto-validate cluster -- Empty `allowed_nodes` + non-Proxmox = allow all (legacy) -- Host UID rate limiting unchanged -- Legacy `allowed_peer_uids` format still works (grants all capabilities) - -**Changed (intentionally):** -- Socket mounts now `:ro` (security fix) -- Container UIDs now share rate limits (security fix) - ---- - -## Security Posture Comparison - -| Attack Vector | Before | After | Improvement | -|---------------|--------|-------|-------------| -| **SSRF** | ❌ Trivially exploitable | ✅ Eliminated | Node validation | -| **Connection DoS** | ❌ 4 UIDs = full starvation | ✅ Eliminated | Read deadlines | -| **Multi-UID Bypass** | ❌ 100+ UIDs available | ✅ Eliminated | Range-based limiting | -| **Socket Tampering** | ❌ Container can MITM | ✅ Eliminated | Read-only mount | -| **GID Policy Bypass** | ❌ Silently ignored | ✅ Enforced | GID checking | -| **Memory Exhaustion** | ❌ Unbounded SSH output | ✅ Mitigated | Output limits | -| **MITM Attacks** | ⚠️ TOFU vulnerable | ✅ Improved | Proxmox key seeding | -| **Privilege Escalation** | ⚠️ UID = full admin | ✅ Controlled | Capability system | -| **Process Exploitation** | ⚠️ Basic hardening | ✅ Hardened | Systemd directives | - ---- - -## Risk Assessment - -### Before Audit - -**Critical Risks:** -- Container compromise → Full internal network SSRF -- Container compromise → Trivial service DoS -- Container compromise → Rate limit bypass -- Container compromise → Proxy MITM - -**Overall Risk Level:** HIGH - -### After Remediation - -**Residual Risks:** -- Proxy binary compromise → SSH key access (unavoidable given architecture) -- Zero-day in Go runtime or dependencies -- Social engineering / operator error - -**Overall Risk Level:** LOW - -**Risk Reduction:** 85%+ reduction in exploitable attack surface - ---- - -## Recommendations for Production - -### Immediate Actions - -1. ✅ Deploy all security fixes (all completed) -2. ✅ Update socket mounts to read-only -3. ✅ Configure node allowlists -4. ✅ Enable monitoring/alerting - -### Ongoing Security - -1. **Regular audits** - Annual security reviews -2. **Dependency updates** - Monitor Go/SSH library security advisories -3. **Log monitoring** - Watch for validation failures and timeouts -4. **Key rotation** - Use existing rotation script quarterly -5. **Incident response** - Document and practice response procedures - -### Future Enhancements - -1. **Strict host key mode** - Require administrator approval for new fingerprints -2. **TLS for metrics** - Encrypt metrics endpoint -3. **Advanced rate limiting** - Adaptive throttling based on behavior -4. **Extended audit logging** - Structured audit logs with retention - ---- - -## Conclusion - -The pulse-sensor-proxy architecture underwent comprehensive security hardening, addressing all identified vulnerabilities. The system now demonstrates: - -- **Defense in Depth:** Multiple layers of security controls -- **Least Privilege:** Capability-based authorization -- **Attack Resilience:** DoS-resistant design -- **SSRF Prevention:** Complete node validation -- **Container Isolation:** Read-only mounts, range-based limiting -- **Monitoring:** Comprehensive security telemetry - -**Final Security Grade:** A (Excellent) - -The proxy is now production-ready for security-sensitive deployments. - ---- - -## References - -- **Security Changelog:** `docs/SECURITY_CHANGELOG.md` -- **Hardening Guide:** `docs/PULSE_SENSOR_PROXY_HARDENING.md` -- **Security Architecture:** `docs/TEMPERATURE_MONITORING_SECURITY.md` -- **Configuration Guide:** `docs/CONFIGURATION.md` - ---- - -## Audit Team - -**Lead Auditor:** Claude (Anthropic Sonnet 4.5) -**Implementation:** OpenAI Codex -**Methodology:** Collaborative adversarial analysis - -**Audit Duration:** 2025-11-07 (single day comprehensive audit) -**Lines of Code Reviewed:** ~5,000 -**Security Issues Found:** 9 -**Issues Remediated:** 9 (100%) - ---- - -**For security concerns or questions:** -https://github.com/rcourtman/Pulse/issues diff --git a/docs/SECURITY_AUDIT_2025-12-18.md b/docs/SECURITY_AUDIT_2025-12-18.md deleted file mode 100644 index 685d56b..0000000 --- a/docs/SECURITY_AUDIT_2025-12-18.md +++ /dev/null @@ -1,375 +0,0 @@ -# Security Audit Report - Pulse Application -## Date: 2025-12-18 -## Auditor: Claude (Gemini) - ---- - -## Executive Summary - -This document presents the findings from a comprehensive security audit of the Pulse monitoring application. The audit examined authentication, authorization, cryptography, input validation, SSRF prevention, command execution, and general security practices. - -### Overall Assessment - -**Security Posture: A- (Excellent with minor recommendations)** - -The codebase demonstrates a mature security posture with: -- ✅ Strong cryptographic practices (bcrypt, SHA3-256, AES-256-GCM) -- ✅ Comprehensive SSRF protection for webhooks -- ✅ CSRF protection for session-based authentication -- ✅ Rate limiting and account lockout -- ✅ Command execution policy with blocklist/allowlist -- ✅ Proper input sanitization and validation -- ✅ Security headers implementation -- ✅ Audit logging - -A prior security audit on 2025-11-07 addressed 9 critical to low severity issues in the sensor-proxy component, all of which were successfully remediated. - ---- - -## Audit Scope - -### Components Reviewed -1. **Authentication System** (`internal/api/auth.go`, `internal/auth/`) -2. **Session Management** (`internal/api/security.go`, session stores) -3. **Cryptography** (`internal/crypto/crypto.go`) -4. **API Token Management** (`internal/api/security_tokens.go`) -5. **OIDC Integration** (`internal/api/security_oidc.go`) -6. **Webhook/Notification Security** (`internal/notifications/`) -7. **Command Execution** (`internal/agentexec/policy.go`) -8. **Database Operations** (`internal/metrics/store.go`) -9. **Configuration & Secrets** (`internal/config/`) - ---- - -## Strengths Identified - -### 1. Authentication & Password Security ✅ -- **bcrypt hashing** with cost factor 12 for passwords -- **SHA3-256** for API token hashing -- **Constant-time comparison** for token validation (prevents timing attacks) -- **12-character minimum** password length requirement -- **Automatic hashing** of plain-text passwords on startup - -### 2. Session Security ✅ -- **HttpOnly cookies** for session tokens -- **Secure flag** set based on HTTPS detection -- **SameSite policy** properly configured (Lax/None based on proxy detection) -- **24-hour session expiry** with sliding window extension -- **Session invalidation** on password change - -### 3. Rate Limiting & Account Lockout ✅ -- **10 attempts/minute** for auth endpoints -- **5 failed attempts** triggers 15-minute lockout -- **Per-username AND per-IP** tracking -- **Lockout bypass prevention** (both must be clear) - -### 4. CSRF Protection ✅ -- **CSRF tokens** generated per session -- **Separate CSRF cookie** (not HttpOnly, readable by JS) -- **Header/form validation** for state-changing requests -- **Safe methods** (GET, HEAD, OPTIONS) exempted -- **API token auth** correctly bypasses CSRF (not vulnerable) - -### 5. SSRF Prevention ✅ -- **Webhook URL validation** with DNS resolution check -- **Private IP blocking** (RFC1918, link-local, loopback) -- **Cloud metadata endpoint blocking** (169.254.169.254, etc.) -- **Configurable allowlist** for internal webhooks -- **DNS rebinding protection** via IP resolution verification - -### 6. Encryption at Rest ✅ -- **AES-256-GCM** for credential encryption -- **Unique nonce** generation per encryption operation -- **Key file protections** with existence validation before encryption -- **Orphaned data prevention** (refuses to encrypt if key deleted) - -### 7. Security Headers ✅ -- Content-Security-Policy -- X-Frame-Options (DENY by default) -- X-Content-Type-Options: nosniff -- X-XSS-Protection -- Referrer-Policy -- Permissions-Policy - -### 8. Command Execution Policy ✅ -- **Blocklist** for dangerous commands (rm -rf, mkfs, dd, etc.) -- **Auto-approve list** for read-only inspection commands -- **Require approval** for service control, package management -- **Sudo normalization** for consistent policy application - -### 9. SQL Injection Prevention ✅ -- **Parameterized queries** used throughout metrics store -- **Prepared statements** for batch operations -- No string concatenation in SQL queries - -### 10. XSS Prevention ✅ -- **DOMPurify** for markdown rendering -- **HTML entity encoding** in tooltips -- **Allowed tag/attribute lists** for sanitized content -- **LLM output sanitization** (AI chat) - ---- - -## Findings & Recommendations - -### HIGH SEVERITY: None Identified - -### MEDIUM SEVERITY - -#### M1. Admin Bypass Debug Mode 🟡 -**Location:** `internal/api/auth.go:675-691` - -**Finding:** -The `adminBypassEnabled()` function allows bypassing authentication when both `ALLOW_ADMIN_BYPASS=1` and `PULSE_DEV=true` are set. While properly gated for development only: - -```go -if os.Getenv("ALLOW_ADMIN_BYPASS") != "1" { - return -} -if os.Getenv("PULSE_DEV") == "true" || strings.EqualFold(os.Getenv("NODE_ENV"), "development") { - log.Warn().Msg("Admin authentication bypass ENABLED (development mode)") - adminBypassState.enabled = true -} -``` - -**Risk:** Accidental production deployment with these env vars could expose full admin access. - -**Recommendation:** -1. Add prominent warning log at startup if either var is set -2. Consider disallowing in Docker `PULSE_DOCKER=true` mode -3. Document this explicitly as a development-only feature - ---- - -#### M2. Recovery Token Exposure Window 🟡 -**Location:** Session and recovery token stores - -**Finding:** -Recovery tokens for password reset appear to be stored in JSON files. While tokens are hashed: -- File permissions should be verified as 0600 -- Token expiration should be enforced server-side (appears to be implemented) - -**Recommendation:** -1. Verify file permissions are set correctly (0600) during token store initialization -2. Add cleanup routine for expired tokens - ---- - -### LOW SEVERITY - -#### L1. Cookie Security in HTTP Proxies 🔵 -**Location:** `internal/api/auth.go:75-107` - -**Finding:** -When behind an HTTP (non-HTTPS) proxy, cookies fall back to `SameSite=Lax` with `Secure=false`. This is functionally necessary but reduces security. - -**Recommendation:** -1. Log a warning when cookies are set without Secure flag -2. Add documentation recommending HTTPS termination at proxy - ---- - -#### L2. Session Token Entropy 🔵 -**Location:** `internal/api/auth.go:109-118` - -**Finding:** -Session tokens are 32 bytes (256 bits) of entropy via `crypto/rand`, which is excellent. However, the error handling falls back to empty string: - -```go -if _, err := cryptorand.Read(b); err != nil { - log.Error().Err(err).Msg("Failed to generate secure session token") - return "" // Fallback - should never happen -} -``` - -**Recommendation:** -Consider returning an error or panicking rather than returning empty string, as an empty session token could have undefined behavior. - ---- - -#### L3. OIDC State Parameter Validation 🔵 -**Location:** `internal/api/security_oidc.go` - -**Finding:** -OIDC configuration is properly validated and state parameters should be verified during the OAuth flow. This should be confirmed in the callback handler. - -**Recommendation:** -1. Verify state parameter is generated with sufficient entropy -2. Ensure state parameter has short expiration (5-10 minutes) - ---- - -#### L4. Apprise CLI Command Execution 🔵 -**Location:** `internal/notifications/notifications.go` - -**Finding:** -The Apprise CLI path and targets are passed to `exec.CommandContext`. While the CLI path is configurable: - -```go -args := []string{"-t", title, "-b", body} -args = append(args, cfg.Targets...) -execFn := n.appriseExec -``` - -**Risk:** If an attacker can control `cfg.Targets`, they might inject malicious arguments. - -**Recommendation:** -1. Validate that targets match expected Apprise URL format -2. Consider sanitizing or escaping special characters in targets - ---- - -### INFORMATIONAL - -#### I1. Dependencies ℹ️ -The `go.mod` shows modern, well-maintained dependencies: -- Go 1.24.0 (latest stable) -- `golang.org/x/crypto v0.45.0` (current) -- `github.com/coreos/go-oidc/v3 v3.17.0` (current) - -**Recommendation:** -Run `govulncheck` periodically to scan for known vulnerabilities. - ---- - -#### I2. GitGuardian Integration ℹ️ -The `.gitguardian.yaml` is properly configured to: -- Ignore documentation and example files -- Block placeholder patterns -- Scan actual code and configuration - ---- - -#### I3. Existing Security Audit ℹ️ -The previous audit (2025-11-07) addressed critical vulnerabilities in the sensor-proxy: -- Socket directory tampering (CRITICAL) ✅ Fixed -- SSRF via get_temperature (CRITICAL) ✅ Fixed -- Connection exhaustion DoS (CRITICAL) ✅ Fixed -- Multi-UID rate limit bypass (CRITICAL) ✅ Fixed -- Incomplete GID authorization (MEDIUM) ✅ Fixed -- Unbounded SSH output (MEDIUM) ✅ Fixed -- Weak host key validation (MEDIUM) ✅ Fixed -- Insufficient capability separation (MEDIUM) ✅ Fixed -- Missing systemd hardening (LOW) ✅ Fixed - ---- - -## Security Architecture Summary - -### Data Flow Security -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Client (Browser/API) │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ HTTPS + TLS │ Session Cookie (HttpOnly, Secure, SameSite) ││ -│ │ │ CSRF Token (Cookie + Header validation) ││ -│ │ │ API Token (Header: X-API-Token) ││ -│ └─────────────────────────────────────────────────────────────┘│ -└─────────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Pulse Server │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Rate Limiter │ │ Auth Middleware │ │ CSRF Handler │ │ -│ │ 10 auth/min │ │ Session/Token │ │ State-changing │ │ -│ │ 500 api/min │ │ Validation │ │ operations │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Account Lockout │ │ Command Policy │ │ SSRF Prevention │ │ -│ │ 5 attempts/15m │ │ Block/Allow/ │ │ Private IP │ │ -│ │ │ │ Require Approval│ │ blocklist │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ Encryption at Rest (AES-256-GCM) ││ -│ │ - Node credentials: /etc/pulse/nodes.enc ││ -│ │ - Email settings: /etc/pulse/email.enc ││ -│ │ - Webhooks: /etc/pulse/webhooks.enc ││ -│ │ - OIDC config: /etc/pulse/oidc.enc ││ -│ └─────────────────────────────────────────────────────────────┘│ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Password/Token Hashing -| Credential Type | Algorithm | Parameters | -|-----------------|-----------|------------| -| User Passwords | bcrypt | Cost factor 12 | -| API Tokens | SHA3-256 | - | -| Encryption Key | AES-256-GCM | 32-byte random key | -| Session Tokens | Random | 32 bytes (256-bit) | - ---- - -## Compliance Checklist - -| Requirement | Status | Notes | -|-------------|--------|-------| -| Password hashing | ✅ | bcrypt, cost 12 | -| Session management | ✅ | Secure cookies, 24h expiry | -| CSRF protection | ✅ | Token-based | -| Rate limiting | ✅ | Auth + API endpoints | -| Encryption at rest | ✅ | AES-256-GCM | -| HTTPS support | ✅ | TLS configurable | -| Security headers | ✅ | CSP, X-Frame-Options, etc. | -| Audit logging | ✅ | Auth events logged | -| Input validation | ✅ | SQL params, webhook URLs | -| Command execution control | ✅ | Policy-based | - ---- - -## Recommendations Summary - -### Priority 1 (Consider Addressing) -- [ ] M1: Add additional safeguards for dev mode bypass -- [ ] M2: Verify recovery token file permissions - -### Priority 2 (Optional Improvements) -- [ ] L1: Add warning logs for non-secure cookies -- [ ] L2: Improve session token generation error handling -- [ ] L3: Document OIDC state parameter security -- [ ] L4: Add Apprise target validation - -### Priority 3 (Ongoing) -- [ ] I1: Run `govulncheck` regularly -- [ ] Keep dependencies updated -- [ ] Review GitGuardian alerts - ---- - -## Conclusion - -The Pulse application demonstrates a **strong security posture** with comprehensive protections against common web application vulnerabilities. The codebase shows evidence of security-conscious development practices: - -1. **Defense in depth** with multiple layers of authentication and authorization -2. **Secure defaults** requiring explicit configuration to reduce security -3. **Modern cryptography** using industry-standard algorithms -4. **Comprehensive validation** of user inputs and external URLs -5. **Audit trails** for security-relevant events - -The identified findings are primarily of low to medium severity and represent opportunities for hardening rather than critical vulnerabilities. - -**Final Security Grade: A-** - ---- - -## References - -- **Previous Audit:** `docs/SECURITY_AUDIT_2025-11-07.md` -- **Security Policy:** `SECURITY.md` -- **Security Changelog:** `docs/SECURITY_CHANGELOG.md` - ---- - -## Audit Team - -**Auditor:** Claude (Gemini 2.5) -**Methodology:** Static code analysis and architecture review -**Audit Duration:** 2025-12-18 (single session) -**Files Reviewed:** ~50 source files across 10 packages - ---- - -**For security concerns or questions:** -https://github.com/rcourtman/Pulse/issues diff --git a/docs/SECURITY_CHANGELOG.md b/docs/SECURITY_CHANGELOG.md index ce694ee..f37b151 100644 --- a/docs/SECURITY_CHANGELOG.md +++ b/docs/SECURITY_CHANGELOG.md @@ -374,8 +374,6 @@ go build ./cmd/pulse-sensor-proxy ### References -- **Audit Report:** `docs/SECURITY_AUDIT_2025-11-07.md` -- **Audit Report:** `docs/SECURITY_AUDIT_2025-12-18.md` - **Temperature Monitoring Overview:** `docs/security/TEMPERATURE_MONITORING.md` - **Sensor Proxy Hardening:** `docs/security/SENSOR_PROXY_HARDENING.md` diff --git a/docs/TEMPERATURE_MONITORING.md b/docs/TEMPERATURE_MONITORING.md index 190bd00..9edeec2 100644 --- a/docs/TEMPERATURE_MONITORING.md +++ b/docs/TEMPERATURE_MONITORING.md @@ -2,7 +2,7 @@ Monitor real-time CPU and NVMe temperatures for your Proxmox nodes. -> **Deprecation notice (v5):** `pulse-sensor-proxy` is deprecated and not recommended for new deployments. Temperature monitoring should be done via the unified agent (`pulse-agent --enable-proxmox`). Existing proxy installs can continue during the migration window, but plan to migrate to the agent. +> **Deprecation notice (v5):** `pulse-sensor-proxy` is deprecated and not recommended for new deployments. Temperature monitoring should be done via the unified agent (`pulse-agent --enable-proxmox`). Existing proxy installs can continue during the migration window, but plan to migrate to the agent. In v5, legacy sensor-proxy endpoints are disabled by default unless `PULSE_ENABLE_SENSOR_PROXY=true` is set on the Pulse server. ## Recommended: Pulse Agent @@ -15,6 +15,23 @@ curl -fsSL http://:7655/install.sh | \ If you use the agent method, the rest of this document (sensor proxy) is optional. See `docs/security/TEMPERATURE_MONITORING.md` for the security model overview. +## Migration: pulse-sensor-proxy → pulse-agent + +If you already deployed `pulse-sensor-proxy`, migrate to the agent to avoid proxy maintenance and remove SSH-from-container complexity: + +1. Install `lm-sensors` on each Proxmox host (if not already): `apt install lm-sensors && sensors-detect` +2. Install the agent on each Proxmox host: + ```bash + curl -fsSL http://:7655/install.sh | \ + bash -s -- --url http://:7655 --token --enable-proxmox + ``` +3. Confirm temperatures are updating in the dashboard. +4. Disable the proxy service on hosts where it was installed: + ```bash + sudo systemctl disable --now pulse-sensor-proxy + ``` +5. If your Pulse container had a proxy socket mount, remove the mount and remove `PULSE_SENSOR_PROXY_SOCKET` from the Pulse `.env` (for example `/data/.env` in Docker) before restarting Pulse. + ## 🚀 Quick Start ### 1. Install the agent on Proxmox hosts diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index b9f90ca..22eed8d 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -78,7 +78,11 @@ sudo pulse bootstrap-token ### Correlate Logs with Requests Every API response has an `X-Request-ID` header. Use it to find the exact log entry: ```bash -grep "request_id=abc123" /var/log/pulse/pulse.log +# systemd / Proxmox LXC +journalctl -u pulse --no-pager | grep "request_id=abc123" + +# Docker +docker logs pulse 2>&1 | grep "request_id=abc123" ``` ### Check Permissions (Proxmox) diff --git a/docs/UNIFIED_AGENT.md b/docs/UNIFIED_AGENT.md index 09f9c74..3c1da64 100644 --- a/docs/UNIFIED_AGENT.md +++ b/docs/UNIFIED_AGENT.md @@ -2,6 +2,8 @@ The unified agent (`pulse-agent`) combines host, Docker, and Kubernetes monitoring into a single binary. It replaces the separate `pulse-host-agent` and `pulse-docker-agent` for simpler deployment and management. +> Note: In v5, temperature monitoring should be done via `pulse-agent --enable-proxmox`. `pulse-sensor-proxy` is deprecated and retained only for existing installs during the migration window. + ## Quick Start Generate an installation command in the UI: diff --git a/docs/development/MOCK_MODE.md b/docs/development/MOCK_MODE.md deleted file mode 100644 index 4d4e2ab..0000000 --- a/docs/development/MOCK_MODE.md +++ /dev/null @@ -1,39 +0,0 @@ -# 🧪 Mock Mode Development - -Develop Pulse without real infrastructure using the mock data pipeline. - -## 🚀 Quick Start - -```bash -# Start dev stack -./scripts/hot-dev.sh - -# Toggle mock mode -npm run mock:on # Enable -npm run mock:off # Disable -npm run mock:status # Check status -``` - -## ⚙️ Configuration -Edit `mock.env` (or `mock.env.local` for overrides): - -| Variable | Default | Description | -| :--- | :--- | :--- | -| `PULSE_MOCK_MODE` | `false` | Enable mock mode. | -| `PULSE_MOCK_NODES` | `7` | Number of synthetic nodes. | -| `PULSE_MOCK_VMS_PER_NODE` | `5` | VMs per node. | -| `PULSE_MOCK_LXCS_PER_NODE` | `8` | Containers per node. | -| `PULSE_MOCK_RANDOM_METRICS` | `true` | Jitter metrics. | -| `PULSE_MOCK_STOPPED_PERCENT` | `20` | % of offline guests. | -| `PULSE_MOCK_TRENDS_SEED_DURATION` | `1h` | Pre-seed backend chart history (improves demo “Trends” immediately). | -| `PULSE_MOCK_TRENDS_SAMPLE_INTERVAL` | `30s` | Backend chart sampling interval while in mock mode. | - -## ℹ️ How it Works -* **Data**: Swaps `PULSE_DATA_DIR` to `/opt/pulse/tmp/mock-data`. -* **Restart**: Backend restarts automatically; Frontend hot-reloads. -* **Reset**: To regenerate data, delete `/opt/pulse/tmp/mock-data` and toggle mock mode on. - -## ⚠️ Limitations -* **Happy Path**: Focuses on standard flows; use real infrastructure for complex edge cases. -* **Webhooks**: Synthetic payloads only. -* **Encryption**: Uses local crypto stack (not a sandbox for auth). diff --git a/docs/security/TEMPERATURE_MONITORING.md b/docs/security/TEMPERATURE_MONITORING.md index e5e239c..ea83644 100644 --- a/docs/security/TEMPERATURE_MONITORING.md +++ b/docs/security/TEMPERATURE_MONITORING.md @@ -5,6 +5,8 @@ This page describes the recommended v5 approach for temperature monitoring and t For the full sensor-proxy setup guide (socket mounts, HTTP mode, troubleshooting), see: `docs/TEMPERATURE_MONITORING.md`. +> **Deprecation notice (v5):** `pulse-sensor-proxy` is deprecated and not recommended for new deployments. Use `pulse-agent --enable-proxmox` for temperature monitoring. The sensor-proxy section below is retained for existing installations during the migration window. In v5, legacy sensor-proxy endpoints are disabled by default unless `PULSE_ENABLE_SENSOR_PROXY=true` is set on the Pulse server. + ## Recommended: Pulse Agent The simplest and most feature-rich method is installing the Pulse agent on your Proxmox nodes: diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 859f84c..588b0ed 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -15,7 +15,6 @@ import { showSuccess, showError, showWarning } from '@/utils/toast'; import { logger } from '@/utils/logger'; import { apiFetch, - apiFetchJSON, clearApiToken as clearApiClientToken, getApiToken as getApiClientToken, setApiToken as setApiClientToken, @@ -980,9 +979,16 @@ const Settings: Component = (props) => { const refreshHostProxyStatus = async (notify = false) => { try { - const status = (await apiFetchJSON( - '/api/temperature-proxy/host-status', - )) as HostProxyStatusResponse; + const response = await apiFetch('/api/temperature-proxy/host-status'); + if (response.status === 410) { + // pulse-sensor-proxy is deprecated and disabled by default in v5 + setHostProxyStatus(null); + return; + } + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + const status = (await response.json()) as HostProxyStatusResponse; setHostProxyStatus(status); if (notify) { showSuccess('Host proxy status refreshed', undefined, 2000); diff --git a/internal/api/diagnostics.go b/internal/api/diagnostics.go index 94b59c5..c15ff72 100644 --- a/internal/api/diagnostics.go +++ b/internal/api/diagnostics.go @@ -443,7 +443,11 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo { socketHostState = r.monitor.SocketProxyHostDiagnostics() } - diag.TemperatureProxy = buildTemperatureProxyDiagnostic(r.config, proxySync, socketHostState) + if r.config != nil && !r.config.EnableSensorProxy { + diag.TemperatureProxy = nil + } else { + diag.TemperatureProxy = buildTemperatureProxyDiagnostic(r.config, proxySync, socketHostState) + } diag.APITokens = buildAPITokenDiagnostic(r.config, r.monitor) // Test each configured node diff --git a/internal/api/router.go b/internal/api/router.go index a8a756f..eca4ad2 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -203,11 +203,11 @@ func (r *Router) setupRoutes() { r.mux.HandleFunc("/api/agents/host/report", RequireAuth(r.config, RequireScope(config.ScopeHostReport, r.hostAgentHandlers.HandleReport))) r.mux.HandleFunc("/api/agents/host/lookup", RequireAuth(r.config, RequireScope(config.ScopeHostReport, r.hostAgentHandlers.HandleLookup))) r.mux.HandleFunc("/api/agents/host/", RequireAdmin(r.config, RequireScope(config.ScopeHostManage, r.hostAgentHandlers.HandleDeleteHost))) - r.mux.HandleFunc("/api/temperature-proxy/register", r.temperatureProxyHandlers.HandleRegister) - r.mux.HandleFunc("/api/temperature-proxy/authorized-nodes", r.temperatureProxyHandlers.HandleAuthorizedNodes) - r.mux.HandleFunc("/api/temperature-proxy/unregister", RequireAdmin(r.config, r.temperatureProxyHandlers.HandleUnregister)) - r.mux.HandleFunc("/api/temperature-proxy/install-command", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleTemperatureProxyInstallCommand))) - r.mux.HandleFunc("/api/temperature-proxy/host-status", RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handleHostProxyStatus))) + r.mux.HandleFunc("/api/temperature-proxy/register", r.requireSensorProxyEnabled(r.temperatureProxyHandlers.HandleRegister)) + r.mux.HandleFunc("/api/temperature-proxy/authorized-nodes", r.requireSensorProxyEnabled(r.temperatureProxyHandlers.HandleAuthorizedNodes)) + r.mux.HandleFunc("/api/temperature-proxy/unregister", r.requireSensorProxyEnabled(RequireAdmin(r.config, r.temperatureProxyHandlers.HandleUnregister))) + r.mux.HandleFunc("/api/temperature-proxy/install-command", r.requireSensorProxyEnabled(RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleTemperatureProxyInstallCommand)))) + r.mux.HandleFunc("/api/temperature-proxy/host-status", r.requireSensorProxyEnabled(RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handleHostProxyStatus)))) r.mux.HandleFunc("/api/agents/docker/commands/", RequireAuth(r.config, RequireScope(config.ScopeDockerReport, r.dockerAgentHandlers.HandleCommandAck))) r.mux.HandleFunc("/api/agents/docker/hosts/", RequireAdmin(r.config, RequireScope(config.ScopeDockerManage, r.dockerAgentHandlers.HandleDockerHostActions))) r.mux.HandleFunc("/api/agents/kubernetes/clusters/", RequireAdmin(r.config, RequireScope(config.ScopeKubernetesManage, r.kubernetesAgentHandlers.HandleClusterActions))) @@ -218,12 +218,12 @@ func (r *Router) setupRoutes() { r.mux.HandleFunc("/api/metrics-store/stats", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleMetricsStoreStats))) r.mux.HandleFunc("/api/metrics-store/history", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleMetricsHistory))) r.mux.HandleFunc("/api/diagnostics", RequireAuth(r.config, r.handleDiagnostics)) - r.mux.HandleFunc("/api/diagnostics/temperature-proxy/register-nodes", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleDiagnosticsRegisterProxyNodes))) + r.mux.HandleFunc("/api/diagnostics/temperature-proxy/register-nodes", r.requireSensorProxyEnabled(RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleDiagnosticsRegisterProxyNodes)))) r.mux.HandleFunc("/api/diagnostics/docker/prepare-token", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleDiagnosticsDockerPrepareToken))) - r.mux.HandleFunc("/api/install/pulse-sensor-proxy", r.handleDownloadPulseSensorProxy) - r.mux.HandleFunc("/api/install/install-sensor-proxy.sh", r.handleDownloadInstallerScript) - r.mux.HandleFunc("/api/install/migrate-sensor-proxy-control-plane.sh", r.handleDownloadMigrationScript) - r.mux.HandleFunc("/api/install/migrate-temperature-proxy.sh", r.handleDownloadTemperatureProxyMigrationScript) + r.mux.HandleFunc("/api/install/pulse-sensor-proxy", r.requireSensorProxyEnabled(r.handleDownloadPulseSensorProxy)) + r.mux.HandleFunc("/api/install/install-sensor-proxy.sh", r.requireSensorProxyEnabled(r.handleDownloadInstallerScript)) + r.mux.HandleFunc("/api/install/migrate-sensor-proxy-control-plane.sh", r.requireSensorProxyEnabled(r.handleDownloadMigrationScript)) + r.mux.HandleFunc("/api/install/migrate-temperature-proxy.sh", r.requireSensorProxyEnabled(r.handleDownloadTemperatureProxyMigrationScript)) r.mux.HandleFunc("/api/install/install-docker.sh", r.handleDownloadDockerInstallerScript) r.mux.HandleFunc("/api/install/install.sh", r.handleDownloadUnifiedInstallScript) r.mux.HandleFunc("/api/install/install.ps1", r.handleDownloadUnifiedInstallScriptPS) @@ -288,6 +288,30 @@ func (r *Router) setupRoutes() { } })) + // Docker host metadata routes (for managing Docker host custom URLs, e.g., Portainer links) + r.mux.HandleFunc("/api/docker/hosts/metadata", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, dockerMetadataHandler.HandleGetHostMetadata))) + r.mux.HandleFunc("/api/docker/hosts/metadata/", RequireAuth(r.config, func(w http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + if !ensureScope(w, req, config.ScopeMonitoringRead) { + return + } + dockerMetadataHandler.HandleGetHostMetadata(w, req) + case http.MethodPut, http.MethodPost: + if !ensureScope(w, req, config.ScopeMonitoringWrite) { + return + } + dockerMetadataHandler.HandleUpdateHostMetadata(w, req) + case http.MethodDelete: + if !ensureScope(w, req, config.ScopeMonitoringWrite) { + return + } + dockerMetadataHandler.HandleDeleteHostMetadata(w, req) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } + })) + // Host metadata routes r.mux.HandleFunc("/api/hosts/metadata", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, hostMetadataHandler.HandleGetMetadata))) r.mux.HandleFunc("/api/hosts/metadata/", RequireAuth(r.config, func(w http.ResponseWriter, req *http.Request) { @@ -1042,7 +1066,7 @@ func (r *Router) setupRoutes() { r.mux.HandleFunc("/api/system/settings/update", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.systemSettingsHandler.HandleUpdateSystemSettings))) r.mux.HandleFunc("/api/system/ssh-config", r.handleSSHConfig) r.mux.HandleFunc("/api/system/verify-temperature-ssh", r.handleVerifyTemperatureSSH) - r.mux.HandleFunc("/api/system/proxy-public-key", r.handleProxyPublicKey) + r.mux.HandleFunc("/api/system/proxy-public-key", r.requireSensorProxyEnabled(r.handleProxyPublicKey)) // Old API token endpoints removed - now using /api/security/regenerate-token // Agent execution server for AI tool use diff --git a/internal/api/sensor_proxy_gate.go b/internal/api/sensor_proxy_gate.go new file mode 100644 index 0000000..1761a02 --- /dev/null +++ b/internal/api/sensor_proxy_gate.go @@ -0,0 +1,28 @@ +package api + +import "net/http" + +func (r *Router) isSensorProxyEnabled() bool { + return r != nil && r.config != nil && r.config.EnableSensorProxy +} + +func (r *Router) requireSensorProxyEnabled(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if r.isSensorProxyEnabled() { + next(w, req) + return + } + + w.Header().Set("Warning", `299 - "pulse-sensor-proxy is deprecated and disabled by default in v5"`) + writeErrorResponse( + w, + http.StatusGone, + "sensor_proxy_disabled", + "pulse-sensor-proxy is deprecated and disabled by default in v5", + map[string]string{ + "migration": "Use pulse-agent --enable-proxmox for temperature monitoring.", + "enable_env": "Set PULSE_ENABLE_SENSOR_PROXY=true (unsupported legacy) and restart Pulse to re-enable these endpoints.", + }, + ) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 8ccff30..90208bd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -99,6 +99,7 @@ type Config struct { BackupPollingInterval time.Duration `envconfig:"BACKUP_POLLING_INTERVAL"` EnableBackupPolling bool `envconfig:"ENABLE_BACKUP_POLLING" default:"true"` TemperatureMonitoringEnabled bool `json:"temperatureMonitoringEnabled"` + EnableSensorProxy bool `envconfig:"PULSE_ENABLE_SENSOR_PROXY" default:"false" json:"-"` // Legacy pulse-sensor-proxy support (deprecated, opt-in) WebhookBatchDelay time.Duration `envconfig:"WEBHOOK_BATCH_DELAY" default:"10s"` AdaptivePollingEnabled bool `envconfig:"ADAPTIVE_POLLING_ENABLED" default:"false"` AdaptivePollingBaseInterval time.Duration `envconfig:"ADAPTIVE_POLLING_BASE_INTERVAL" default:"10s"` @@ -118,7 +119,6 @@ type Config struct { MetricsRetentionHourlyDays int `json:"metricsRetentionHourlyDays"` // Hourly averages, default: 7 days MetricsRetentionDailyDays int `json:"metricsRetentionDailyDays"` // Daily averages, default: 90 days - // Logging settings LogLevel string `envconfig:"LOG_LEVEL" default:"info"` LogFormat string `envconfig:"LOG_FORMAT" default:"auto"` // "json", "console", or "auto" @@ -580,6 +580,7 @@ func Load() (*Config, error) { DiscoveryEnabled: false, DiscoverySubnet: "auto", TemperatureMonitoringEnabled: true, + EnableSensorProxy: false, EnvOverrides: make(map[string]bool), OIDC: NewOIDCConfig(), // Metrics retention defaults (tiered) @@ -819,6 +820,22 @@ func Load() (*Config, error) { } } + if enabledStr := utils.GetenvTrim("PULSE_ENABLE_SENSOR_PROXY"); enabledStr != "" { + if enabled, err := strconv.ParseBool(enabledStr); err == nil { + cfg.EnableSensorProxy = enabled + cfg.EnvOverrides["PULSE_ENABLE_SENSOR_PROXY"] = true + if enabled { + log.Warn().Msg("Legacy pulse-sensor-proxy support enabled via PULSE_ENABLE_SENSOR_PROXY (deprecated, unsupported)") + } else { + log.Info().Msg("Legacy pulse-sensor-proxy support disabled via PULSE_ENABLE_SENSOR_PROXY") + } + } else { + log.Warn(). + Str("value", enabledStr). + Msg("Invalid PULSE_ENABLE_SENSOR_PROXY value, ignoring") + } + } + if hideLocalLoginStr := utils.GetenvTrim("PULSE_AUTH_HIDE_LOCAL_LOGIN"); hideLocalLoginStr != "" { if hide, err := strconv.ParseBool(hideLocalLoginStr); err == nil { cfg.HideLocalLogin = hide diff --git a/scripts/generate-release-notes.sh b/scripts/generate-release-notes.sh index 95c2581..03e5630 100755 --- a/scripts/generate-release-notes.sh +++ b/scripts/generate-release-notes.sh @@ -92,7 +92,7 @@ docker pull rcourtman/pulse:v${VERSION} docker stop pulse && docker rm pulse docker run -d --name pulse \\ --restart unless-stopped \\ - -p 7655:7655 -p 7656:7656 \\ + -p 7655:7655 \\ -v /opt/pulse/data:/data \\ rcourtman/pulse:v${VERSION} \`\`\` @@ -322,7 +322,7 @@ docker pull rcourtman/pulse:v${VERSION} docker stop pulse && docker rm pulse docker run -d --name pulse \ --restart unless-stopped \ - -p 7655:7655 -p 7656:7656 \ + -p 7655:7655 \ -v /opt/pulse/data:/data \ rcourtman/pulse:v${VERSION} ``` diff --git a/scripts/install-host-agent.ps1 b/scripts/install-host-agent.ps1 index 80423be..25282c7 100644 --- a/scripts/install-host-agent.ps1 +++ b/scripts/install-host-agent.ps1 @@ -15,9 +15,9 @@ # └─────────────────────────────────────────────────────────────────────────────┘ # # Usage: -# iwr -useb http://pulse-server:7656/install-host-agent.ps1 | iex +# iwr -useb http://pulse-server:7655/install-host-agent.ps1 | iex # OR with parameters: -# $url = "http://pulse-server:7656"; $token = "your-token"; iwr -useb "$url/install-host-agent.ps1" | iex +# $url = "http://pulse-server:7655"; $token = "your-token"; iwr -useb "$url/install-host-agent.ps1" | iex # # Parameters can be passed via environment variables or script parameters @@ -248,7 +248,7 @@ if (-not $isAdmin) { # Interactive prompts if parameters not provided if (-not $PulseUrl) { - $PulseUrl = Read-Host "Enter Pulse server URL (e.g., http://pulse.example.com:7656)" + $PulseUrl = Read-Host "Enter Pulse server URL (e.g., http://pulse.example.com:7655)" } $PulseUrl = $PulseUrl.TrimEnd('/') diff --git a/scripts/uninstall-host-agent.ps1 b/scripts/uninstall-host-agent.ps1 index caecf29..c355dc4 100644 --- a/scripts/uninstall-host-agent.ps1 +++ b/scripts/uninstall-host-agent.ps1 @@ -1,7 +1,7 @@ # Pulse Host Agent Uninstallation Script for Windows # # Usage: -# iwr -useb http://pulse-server:7656/uninstall-host-agent.ps1 | iex +# iwr -useb http://pulse-server:7655/uninstall-host-agent.ps1 | iex # param(