mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
feat: add non-intrusive security warning system
- Security warning banner shows when security score is low - Displays security score (0-5) based on enabled features - Fully dismissible (1 day, 1 week, forever) - Shows details of what's enabled/disabled - Links directly to Security settings tab - Enhanced /api/security/status endpoint - Updated documentation This is Phase 1 of the security improvement plan: - Non-breaking (no user impact) - Educational (shows security posture) - Dismissible (respects user choice) - Helpful (one-click to security settings)
This commit is contained in:
@@ -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: <token>" \
|
||||
-d '{"validityMinutes": 30, "maxUses": 5}'
|
||||
|
||||
# Export/Import Testing
|
||||
curl -X POST http://localhost:7655/api/config/export \
|
||||
-H "X-API-Token: <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
|
||||
@@ -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=<generated-on-install>"
|
||||
```
|
||||
|
||||
### 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**.
|
||||
@@ -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)
|
||||
@@ -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`)
|
||||
|
||||
@@ -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 (
|
||||
<ErrorBoundary>
|
||||
<WebSocketContext.Provider value={enhancedStore}>
|
||||
<SecurityWarning />
|
||||
<div class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 p-2 font-sans">
|
||||
<div class="container w-[95%] max-w-screen-xl mx-auto">
|
||||
{/* Header */}
|
||||
|
||||
@@ -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<SecurityStatus | null>(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 (
|
||||
<Portal>
|
||||
<div class="fixed top-0 left-0 right-0 z-50 bg-yellow-50 dark:bg-yellow-900/20 border-b border-yellow-200 dark:border-yellow-800 shadow-sm">
|
||||
<div class="max-w-7xl mx-auto px-4 py-3">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="text-2xl">{getScoreEmoji(status()!.score, status()!.maxScore)}</span>
|
||||
<div>
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Security Score: <span class={getScoreColor(status()!.score, status()!.maxScore)}>
|
||||
{status()!.score}/{status()!.maxScore}
|
||||
</span>
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowDetails(!showDetails())}
|
||||
class="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
{showDetails() ? 'Hide' : 'Show'} Details
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mt-1">
|
||||
Your Pulse instance is accessible without authentication. Proxmox credentials could be exposed.
|
||||
</p>
|
||||
|
||||
<Show when={showDetails()}>
|
||||
<div class="mt-3 space-y-1">
|
||||
<div class="text-xs space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={status()!.credentialsEncrypted ? 'text-green-600' : 'text-red-600'}>
|
||||
{status()!.credentialsEncrypted ? '✅' : '❌'}
|
||||
</span>
|
||||
<span>Credentials encrypted at rest</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={status()!.exportProtected ? 'text-green-600' : 'text-red-600'}>
|
||||
{status()!.exportProtected ? '✅' : '❌'}
|
||||
</span>
|
||||
<span>Export requires authentication</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={status()!.hasAuthentication ? 'text-green-600' : 'text-red-600'}>
|
||||
{status()!.hasAuthentication ? '✅' : '❌'}
|
||||
</span>
|
||||
<span>Authentication enabled</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={status()!.hasHTTPS ? 'text-green-600' : 'text-red-600'}>
|
||||
{status()!.hasHTTPS ? '✅' : '❌'}
|
||||
</span>
|
||||
<span>HTTPS connection</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={status()!.hasAuditLogging ? 'text-green-600' : 'text-red-600'}>
|
||||
{status()!.hasAuditLogging ? '✅' : '❌'}
|
||||
</span>
|
||||
<span>Audit logging enabled</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex items-center gap-3 mt-3">
|
||||
<a
|
||||
href="/settings?tab=security"
|
||||
class="text-sm font-medium text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
Enable Security →
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/rcourtman/Pulse/blob/main/docs/SECURITY.md"
|
||||
target="_blank"
|
||||
class="text-sm text-gray-600 dark:text-gray-400 hover:underline"
|
||||
>
|
||||
Learn More
|
||||
</a>
|
||||
<div class="relative group">
|
||||
<button
|
||||
onClick={() => handleDismiss('day')}
|
||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
|
||||
>
|
||||
Dismiss ▼
|
||||
</button>
|
||||
<div class="absolute left-0 top-full mt-1 bg-white dark:bg-gray-800 rounded shadow-lg border border-gray-200 dark:border-gray-700 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity">
|
||||
<button
|
||||
onClick={() => handleDismiss('day')}
|
||||
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
For 1 day
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDismiss('week')}
|
||||
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
For 1 week
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDismiss('forever')}
|
||||
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
Forever
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user