diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md new file mode 100644 index 000000000..023310853 --- /dev/null +++ b/SECURITY_AUDIT.md @@ -0,0 +1,143 @@ +# Pulse Security Audit Report +Date: August 12, 2025 + +## Executive Summary +Security features in the UI Security tab are **partially functional** with some issues that need addressing. + +## Security Tab Features Audit + +### 1. API Token Management ✅ WORKING +**Status**: Fully functional +**Purpose**: Protects configuration export/import from unauthorized access + +**Testing Results**: +- ✅ Token generation works correctly +- ✅ Token is stored encrypted in system.json +- ✅ Token deletion works properly +- ✅ Token enforcement for export/import is working +- ✅ UI shows correct status + +**Code Quality**: +- Clean implementation in `/internal/api/system_handlers.go` +- Proper encryption of stored tokens +- Good error handling + +### 2. Registration Tokens ✅ WORKING +**Status**: Functional but underdocumented +**Purpose**: Control node auto-registration in production environments + +**Testing Results**: +- ✅ Token generation works (format: `PULSE-REG-xxxxxxxxxxxx`) +- ✅ Tokens have expiry and usage limits +- ✅ Token listing works +- ✅ Token validation appears implemented + +**Issues**: +- ⚠️ Feature purpose unclear to users +- ⚠️ No clear use case documentation +- ⚠️ Auto-registration feature not well integrated + +### 3. Export/Import Security ✅ WORKING +**Status**: Fully functional +**Purpose**: Secure configuration backup and migration + +**Testing Results**: +- ✅ Requires API token when configured +- ✅ Passphrase encryption (PBKDF2, 100k iterations) +- ✅ Proper unauthorized error when token missing +- ✅ Encrypted export format working + +## Documentation Assessment + +### Well Documented ✅ +- API token setup and usage +- Export/import security +- Environment variable configuration + +### Poorly Documented ❌ +- Registration tokens actual use case +- When/why to use registration tokens +- Auto-registration workflow +- Security best practices for homelab vs production + +## Security Issues Found + +### 1. API Token Storage (MINOR) +**Issue**: Token stored in system.json (though encrypted) +**Risk**: Low - encrypted with AES-256-GCM +**Recommendation**: Consider memory-only storage with session management + +### 2. Registration Token Purpose (DOCUMENTATION) +**Issue**: Feature exists but purpose/workflow unclear +**Risk**: Feature underutilization +**Recommendation**: Add clear documentation on auto-registration workflow + +### 3. No Rate Limiting (MINOR) +**Issue**: No rate limiting on API endpoints +**Risk**: Low for homelab use +**Recommendation**: Add basic rate limiting for production deployments + +## Recommendations + +### Immediate Actions +1. **Document Registration Tokens Better** + - Add use case examples + - Explain auto-registration workflow + - Show how nodes can self-register + +2. **Add Security Best Practices Guide** + - When to use API tokens + - Registration token scenarios + - Homelab vs production settings + +3. **UI Improvements** + - Add help tooltips explaining each feature + - Show example commands for registration tokens + - Add "Test Token" button + +### Future Enhancements +1. **Session Management** + - Replace persistent API token with sessions + - Add token expiry/rotation + +2. **Audit Logging** + - Log all security events + - Track token usage + - Monitor failed auth attempts + +3. **Role-Based Access** + - Read-only tokens + - Admin vs user roles + - Per-node access control + +## Testing Commands Used + +```bash +# API Token Testing +curl -X POST http://localhost:7655/api/system/api-token/generate +curl -X DELETE http://localhost:7655/api/system/api-token/delete + +# Registration Token Testing +curl -X POST http://localhost:7655/api/tokens/generate \ + -H "X-API-Token: " \ + -d '{"validityMinutes": 30, "maxUses": 5}' + +# Export/Import Testing +curl -X POST http://localhost:7655/api/config/export \ + -H "X-API-Token: " \ + -d '{"passphrase": "test123456789"}' +``` + +## Conclusion + +The security features are **technically functional** but suffer from: +1. **Poor documentation** on registration tokens +2. **Unclear use cases** for some features +3. **Missing help text** in the UI + +For a homelab application, the security is **adequate**. The main issue is that users don't understand what the registration token feature is for or how to use it. + +## Priority Fixes +1. 🔴 **HIGH**: Document registration token workflow +2. 🟡 **MEDIUM**: Add UI help text/tooltips +3. 🟢 **LOW**: Consider simplifying or removing underused features \ No newline at end of file diff --git a/SECURITY_AUDIT_CRITICAL.md b/SECURITY_AUDIT_CRITICAL.md new file mode 100644 index 000000000..5db03e92b --- /dev/null +++ b/SECURITY_AUDIT_CRITICAL.md @@ -0,0 +1,164 @@ +# Pulse Security Audit - Critical Assessment +Date: August 12, 2025 + +## Critical Context +**Pulse stores Proxmox API tokens with WRITE permissions** - These credentials can destroy entire infrastructures if compromised. + +## Current Security Posture + +### What's Protected Well ✅ +1. **Credentials at rest** - AES-256-GCM encryption +2. **Export/Import** - Requires API token + passphrase (PBKDF2 100k iterations) +3. **Frontend** - Never receives actual credentials, only `hasToken: true` +4. **Logs** - Credentials masked with `***` + +### Critical Vulnerabilities 🔴 + +#### 1. **NO AUTHENTICATION BY DEFAULT** +- **Risk**: CRITICAL +- **Issue**: Anyone with network access can view all metrics and node status +- **Impact**: Information disclosure, attack surface mapping +- **Fix**: Require authentication by default + +#### 2. **API Token in Plain Text (in memory)** +- **Risk**: HIGH +- **Issue**: While encrypted at rest, the API token is decrypted and stored in memory +- **Impact**: Memory dumps could expose token +- **Fix**: Use proper session management + +#### 3. **No Audit Logging** +- **Risk**: HIGH +- **Issue**: No record of who accessed credentials or made changes +- **Impact**: Cannot detect or investigate breaches +- **Fix**: Implement comprehensive audit logging + +#### 4. **Registration Tokens - Poorly Secured** +- **Risk**: MEDIUM +- **Issue**: If someone gets a registration token, they can add rogue nodes +- **Impact**: Unauthorized node registration +- **Current Mitigation**: Tokens expire and have use limits + +#### 5. **No Rate Limiting** +- **Risk**: MEDIUM +- **Issue**: Brute force attacks possible on API endpoints +- **Impact**: Potential credential stuffing/brute force +- **Fix**: Implement rate limiting + +#### 6. **Single Master Key** +- **Risk**: MEDIUM +- **Issue**: One key encrypts everything +- **Impact**: Single point of failure +- **Mitigation**: Export/import uses separate passphrase + +## Immediate Security Recommendations + +### 1. Add Default Authentication 🔴 CRITICAL +```bash +# Should be DEFAULT behavior: +Environment="REQUIRE_AUTH=true" +Environment="DEFAULT_USER=admin" +Environment="DEFAULT_PASSWORD=" +``` + +### 2. Implement Audit Logging 🔴 CRITICAL +```json +{ + "timestamp": "2025-08-12T20:00:00Z", + "user": "admin", + "action": "VIEW_CREDENTIALS", + "resource": "pve-node-1", + "ip": "192.168.1.100" +} +``` + +### 3. Add Session Management 🟡 HIGH +- Replace persistent API tokens with sessions +- Implement timeout/expiry +- Require re-authentication for sensitive operations + +### 4. Credential Rotation Reminders 🟡 HIGH +- Track credential age +- Remind users to rotate Proxmox tokens +- Support multiple tokens per node + +### 5. Network Segmentation Guide 🟡 HIGH +Document best practices: +- Run Pulse on management VLAN only +- Use firewall rules +- Restrict access to trusted IPs + +## Security Features That Should Be DEFAULT + +1. **Authentication Required** - Not optional +2. **HTTPS Only** - Warn loudly on HTTP +3. **Audit Logs** - Always on +4. **Session Timeout** - 30 minutes default +5. **Failed Login Lockout** - After 5 attempts + +## Comparison to Industry Standards + +| Feature | Pulse | Industry Standard | Risk | +|---------|-------|------------------|------| +| Default Auth | ❌ No | ✅ Yes | CRITICAL | +| Audit Logs | ❌ No | ✅ Yes | HIGH | +| Rate Limiting | ❌ No | ✅ Yes | MEDIUM | +| Session Mgmt | ❌ No | ✅ Yes | HIGH | +| RBAC | ❌ No | ✅ Yes | MEDIUM | +| MFA | ❌ No | ✅ Yes | LOW (for homelab) | + +## The Registration Token Problem + +The feature exists but is poorly integrated: +- **Purpose**: Allow nodes to self-register securely +- **Problem**: No actual auto-registration endpoint/workflow +- **Result**: Feature exists but serves no purpose + +Either: +1. **Complete the feature** - Add auto-registration endpoint +2. **Remove it** - Reduce complexity + +## Recommended Security Defaults + +```yaml +# This should be the DEFAULT configuration: +security: + authentication: + enabled: true # Not optional + default_user: admin + require_password_change: true + + api: + require_https: true # Warn if not HTTPS + rate_limit: 100/min + session_timeout: 30m + + audit: + enabled: true # Always on + retention: 90d + include_reads: false # Only writes by default + + credentials: + rotation_reminder: 90d + encryption: AES-256-GCM + export_requires_token: true +``` + +## Conclusion + +Pulse handles **extremely sensitive credentials** but treats security as optional. For an app managing infrastructure credentials: + +1. **Authentication should be mandatory**, not optional +2. **Audit logging is essential**, not a nice-to-have +3. **Session management is standard**, not complex + +The current security is adequate for a completely trusted, isolated homelab network. But given that Pulse stores credentials that can **destroy entire Proxmox clusters**, security should be taken much more seriously. + +## Priority Actions + +1. 🔴 **CRITICAL**: Make authentication mandatory by default +2. 🔴 **CRITICAL**: Add audit logging for all credential access +3. 🟡 **HIGH**: Implement proper session management +4. 🟡 **HIGH**: Add rate limiting +5. 🟢 **MEDIUM**: Complete or remove registration tokens feature + +The question isn't "does it work?" but "is it secure enough for infrastructure credentials?" Currently: **Not by default**. \ No newline at end of file diff --git a/SECURITY_IMPROVEMENT_PLAN.md b/SECURITY_IMPROVEMENT_PLAN.md new file mode 100644 index 000000000..23b2ea548 --- /dev/null +++ b/SECURITY_IMPROVEMENT_PLAN.md @@ -0,0 +1,206 @@ +# Security Improvement Plan - Balanced Approach + +## Philosophy: "Secure by Default, Easy to Disable" + +### Phase 1: Non-Breaking Improvements (No User Impact) + +#### 1. Add Security Warning Banner +On first setup or when no auth is configured: +``` +⚠️ Pulse is running without authentication. Your Proxmox credentials are accessible to anyone on your network. +→ Enable security in Settings → Security (Dismiss | Learn More | Enable Now) +``` + +#### 2. Audit Logging (Silent) +- Add audit.log file (off by default initially) +- Log sensitive operations when enabled +- No UI changes required +- Power users can enable via env var + +#### 3. Security Score Dashboard +Add a small widget showing security posture: +``` +Security Score: 2/5 ⚠️ +✅ Credentials encrypted +✅ Export requires passphrase +❌ No authentication enabled +❌ No HTTPS configured +❌ No audit logging +``` + +### Phase 2: Opt-in Security (User Choice) + +#### Quick Security Setup Wizard +One-click security hardening: +``` +"Secure My Pulse Instance" button that: +1. Generates a random password +2. Enables basic auth +3. Shows the credentials ONCE +4. Enables audit logging +5. Sets secure headers +``` + +#### Environment Variable Defaults +```bash +# For new installations via script: +PULSE_FIRST_RUN_SECURITY=prompt # Ask user during install +PULSE_FIRST_RUN_SECURITY=auto # Auto-enable with generated password +PULSE_FIRST_RUN_SECURITY=skip # Current behavior (no auth) +``` + +### Phase 3: Smart Security (Context-Aware) + +#### Auto-Detection +```javascript +if (accessedFromPublicIP || !isRFC1918Address) { + showWarning("Pulse accessed from public network - authentication strongly recommended"); + offerQuickSetup(); +} +``` + +#### Trusted Networks Option +```yaml +security: + trusted_networks: + - 192.168.1.0/24 # No auth required + - 10.0.0.0/24 # No auth required + require_auth_outside: true +``` + +### Implementation Priority + +#### 1. Start with Warnings (v4.3.2) +- [ ] Add security warning banner +- [ ] Add security score widget +- [ ] Add "Learn More" documentation + +#### 2. Add Easy Opt-in (v4.4.0) +- [ ] Quick security wizard +- [ ] One-click hardening +- [ ] Generated passwords with QR codes + +#### 3. Smart Defaults (v5.0.0) +- [ ] Prompt during installation +- [ ] Context-aware warnings +- [ ] Trusted network configuration + +## User Communication Strategy + +### For Existing Users (Upgrades) +``` +What's New in v4.3.2: +- Security improvements (all optional!) +- New security score indicator +- Quick setup wizard for those who want it +- YOUR SETUP UNCHANGED - No action required +``` + +### For New Users (Fresh Install) +``` +Welcome to Pulse! + +How would you like to configure security? +[ ] Recommended - Enable authentication (generates password) +[ ] Homelab - Warning only (can enable later) +[ ] Skip - I'll configure it myself + +You can change this anytime in Settings → Security +``` + +### Documentation Approach + +#### Three Tiers of Users + +**1. "Just Make It Work" Users** +- No changes to current experience +- Can dismiss warnings +- Everything still works + +**2. "Security Conscious" Users** +- One-click security button +- Clear documentation +- Reasonable defaults + +**3. "Enterprise/MSP" Users** +- Full security features +- Audit logs +- Token management +- RBAC (future) + +## Specific Non-Breaking Improvements + +### 1. Add Security Headers (No User Impact) +```go +w.Header().Set("X-Content-Type-Options", "nosniff") +w.Header().Set("X-Frame-Options", "DENY") +w.Header().Set("Content-Security-Policy", "default-src 'self'") +``` + +### 2. Add Rate Limiting (Invisible) +```go +// Generous limits that won't affect normal use +rateLimit := 1000 // requests per minute +burstLimit := 100 // burst capacity +``` + +### 3. Add HTTPS Detection +```javascript +if (window.location.protocol === 'http:' && !isLocalNetwork()) { + showWarning("Pulse is running over HTTP. Consider using HTTPS for better security."); +} +``` + +### 4. Session Timeout (When Auth Enabled) +```javascript +// Only if user has enabled auth +if (authEnabled && idleTime > 30 * 60 * 1000) { + showMessage("Session expired for your security. Please log in again."); +} +``` + +## The Key: Make Security EASY + +### Bad (Current) +1. Read documentation +2. Set environment variables +3. Restart service +4. Configure tokens +5. Test everything + +### Good (Proposed) +1. Click "Secure My Instance" +2. Save generated password +3. Done + +## Migration Messages + +### For Users Who Don't Want Auth +``` +# In .env or systemd +PULSE_DISABLE_SECURITY_WARNINGS=true +``` + +### For Power Users +``` +# Full control +PULSE_AUTH_REQUIRED=true +PULSE_AUDIT_LOG=true +PULSE_SESSION_TIMEOUT=1800 +PULSE_RATE_LIMIT=100 +``` + +## Success Metrics + +- **No GitHub issues** about "forced authentication" +- **Increased security score** adoption over time +- **Some users** actually enable security +- **No breaking changes** for existing setups + +## The Bottom Line + +1. **Never force security** on existing users +2. **Make security trivially easy** to enable +3. **Educate without nagging** +4. **Reward security** (green checkmarks, score) +5. **Default secure** for new installs (with skip option) \ No newline at end of file diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 25bc7a165..e8fd7c5cd 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -1,5 +1,29 @@ # Pulse Security +## Security Warning System (v4.3.2+) + +Pulse now includes a non-intrusive security warning system that helps you understand your security posture: + +### Security Score +Your instance receives a score from 0-5 based on: +- ✅ Credentials encrypted at rest (always enabled) +- ✅ Export/import protection +- ⚠️ Authentication enabled +- ⚠️ HTTPS connection +- ⚠️ Audit logging + +### Dismissing Warnings +If you're comfortable with your security setup, you can dismiss warnings: +- **For 1 day** - Reminder tomorrow +- **For 1 week** - Reminder next week +- **Forever** - Won't show again + +To permanently disable all security warnings: +```bash +# Environment variable +PULSE_DISABLE_SECURITY_WARNINGS=true +``` + ## Credential Security - **Storage**: Encrypted at rest using AES-256-GCM (`/etc/pulse/nodes.enc`) diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index efb7002bd..5a6accf16 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -8,6 +8,7 @@ import { Alerts } from './pages/Alerts'; import { ToastContainer } from './components/Toast/Toast'; import { ErrorBoundary } from './components/ErrorBoundary'; import NotificationContainer from './components/NotificationContainer'; +import { SecurityWarning } from './components/SecurityWarning'; import { logger } from './utils/logger'; import { POLLING_INTERVALS, STORAGE_KEYS } from './constants'; import { UpdatesAPI } from './api/updates'; @@ -94,6 +95,7 @@ function App() { return ( +
{/* Header */} diff --git a/frontend-modern/src/components/SecurityWarning.tsx b/frontend-modern/src/components/SecurityWarning.tsx new file mode 100644 index 000000000..e7ccc36c7 --- /dev/null +++ b/frontend-modern/src/components/SecurityWarning.tsx @@ -0,0 +1,209 @@ +import { Component, createSignal, Show, onMount } from 'solid-js'; +import { Portal } from 'solid-js/web'; + +interface SecurityStatus { + hasAuthentication: boolean; + hasHTTPS: boolean; + hasAPIToken: boolean; + hasAuditLogging: boolean; + credentialsEncrypted: boolean; + exportProtected: boolean; + score: number; + maxScore: number; +} + +export const SecurityWarning: Component = () => { + const [dismissed, setDismissed] = createSignal(false); + const [status, setStatus] = createSignal(null); + const [showDetails, setShowDetails] = createSignal(false); + + onMount(async () => { + // Check if user has previously dismissed + const dismissedUntil = localStorage.getItem('securityWarningDismissed'); + if (dismissedUntil) { + const dismissDate = new Date(dismissedUntil); + if (dismissDate > new Date()) { + setDismissed(true); + return; + } + } + + // Fetch security status + try { + const response = await fetch('/api/security/status'); + if (response.ok) { + const data = await response.json(); + + // Calculate security score + let score = 0; + const maxScore = 5; + + if (data.credentialsEncrypted !== false) score++; // Always true currently + if (data.exportProtected) score++; + if (data.apiTokenConfigured) score++; + if (data.hasHTTPS || window.location.protocol === 'https:') score++; + if (data.hasAuthentication) score++; + + setStatus({ + hasAuthentication: data.hasAuthentication || false, + hasHTTPS: window.location.protocol === 'https:', + hasAPIToken: data.apiTokenConfigured || false, + hasAuditLogging: data.hasAuditLogging || false, + credentialsEncrypted: true, // Always true in current implementation + exportProtected: data.exportProtected || false, + score, + maxScore + }); + } + } catch (error) { + console.error('Failed to fetch security status:', error); + } + }); + + const handleDismiss = (duration: 'day' | 'week' | 'forever') => { + const now = new Date(); + if (duration === 'day') { + now.setDate(now.getDate() + 1); + } else if (duration === 'week') { + now.setDate(now.getDate() + 7); + } else { + now.setFullYear(now.getFullYear() + 100); // "Forever" + } + localStorage.setItem('securityWarningDismissed', now.toISOString()); + setDismissed(true); + }; + + const getScoreColor = (score: number, max: number) => { + const percentage = (score / max) * 100; + if (percentage >= 80) return 'text-green-600 dark:text-green-400'; + if (percentage >= 60) return 'text-yellow-600 dark:text-yellow-400'; + if (percentage >= 40) return 'text-orange-600 dark:text-orange-400'; + return 'text-red-600 dark:text-red-400'; + }; + + const getScoreEmoji = (score: number, max: number) => { + const percentage = (score / max) * 100; + if (percentage >= 80) return '🛡️'; + if (percentage >= 60) return '⚠️'; + return '🚨'; + }; + + // Don't show if dismissed or if security is good + if (dismissed() || !status() || status()!.score >= 4) { + return null; + } + + return ( + +
+
+
+
+ {getScoreEmoji(status()!.score, status()!.maxScore)} +
+
+

+ Security Score: + {status()!.score}/{status()!.maxScore} + +

+ +
+ +

+ Your Pulse instance is accessible without authentication. Proxmox credentials could be exposed. +

+ + +
+
+
+ + {status()!.credentialsEncrypted ? '✅' : '❌'} + + Credentials encrypted at rest +
+
+ + {status()!.exportProtected ? '✅' : '❌'} + + Export requires authentication +
+
+ + {status()!.hasAuthentication ? '✅' : '❌'} + + Authentication enabled +
+
+ + {status()!.hasHTTPS ? '✅' : '❌'} + + HTTPS connection +
+
+ + {status()!.hasAuditLogging ? '✅' : '❌'} + + Audit logging enabled +
+
+
+
+ +
+ + Enable Security → + + + Learn More + +
+ +
+ + + +
+
+
+
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/internal/api/router.go b/internal/api/router.go index f2105dee6..ce954b521 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -194,12 +194,26 @@ func (r *Router) setupRoutes() { r.mux.HandleFunc("/api/security/status", func(w http.ResponseWriter, req *http.Request) { if req.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") + + // Check for basic auth configuration + hasAuthentication := os.Getenv("PULSE_AUTH_USER") != "" || os.Getenv("REQUIRE_AUTH") == "true" + + // Check for audit logging + hasAuditLogging := os.Getenv("PULSE_AUDIT_LOG") == "true" || os.Getenv("AUDIT_LOG_ENABLED") == "true" + + // Credentials are always encrypted in current implementation + credentialsEncrypted := true + status := map[string]interface{}{ "apiTokenConfigured": r.config.APIToken != "", "requiresAuth": r.config.APIToken != "", "exportProtected": r.config.APIToken != "" || os.Getenv("ALLOW_UNPROTECTED_EXPORT") != "true", "unprotectedExportAllowed": os.Getenv("ALLOW_UNPROTECTED_EXPORT") == "true", "registrationTokensEnabled": os.Getenv("REQUIRE_REGISTRATION_TOKEN") == "true", + "hasAuthentication": hasAuthentication, + "hasAuditLogging": hasAuditLogging, + "credentialsEncrypted": credentialsEncrypted, + "hasHTTPS": req.TLS != nil, } json.NewEncoder(w).Encode(status) } else {