v5: gate legacy sensor-proxy and prune dev docs
This commit is contained in:
parent
862310c4c6
commit
337987cb56
25 changed files with 149 additions and 1905 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
16
SECURITY.md
16
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ docker run -d \
|
|||
<details>
|
||||
<summary><strong>Advanced: OIDC / SSO</strong></summary>
|
||||
|
||||
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.
|
||||
</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` |
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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?*
|
||||
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <VMID> | 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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://<pulse-ip>: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://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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<SettingsProps> = (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);
|
||||
|
|
|
|||
|
|
@ -443,7 +443,11 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
|||
socketHostState = r.monitor.SocketProxyHostDiagnostics()
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
28
internal/api/sensor_proxy_gate.go
Normal file
28
internal/api/sensor_proxy_gate.go
Normal file
|
|
@ -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.",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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('/')
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue