# Pulse Security ## Mandatory Authentication **Starting with v4.5.0, authentication setup is prompted for all new Pulse installations.** This protects your Proxmox API credentials from unauthorized access. > **Service name note:** Systemd deployments use `pulse.service`. If you're upgrading from an older install that still registers `pulse-backend.service`, substitute that name in the commands below. ### First-Run Security Setup When you first access Pulse, you'll be guided through a mandatory security setup: - Create your admin username and password - Automatic API token generation for automation - Settings are applied immediately without restart - **Your existing nodes and settings are preserved** ## Smart Security Context ### Public Access Detection Pulse automatically detects when it's being accessed from public networks: - **Private Networks**: Local/RFC1918 addresses (192.168.x.x, 10.x.x.x, etc.) - **Public Networks**: Any non-private IP address - **Stronger Warnings**: Red alerts when accessed from public IPs without authentication ### Trusted Networks Configuration (Deprecated) **Note: Authentication is now mandatory regardless of network location.** Legacy configuration (no longer applicable): ```bash # Environment variable (comma-separated CIDR blocks) PULSE_TRUSTED_NETWORKS=192.168.1.0/24,10.0.0.0/24 # Or in systemd sudo systemctl edit pulse [Service] Environment="PULSE_TRUSTED_NETWORKS=192.168.1.0/24,10.0.0.0/24" ``` When configured: - Access from trusted networks: No auth required - Access from outside: Authentication enforced - Useful for: Mixed home/remote access scenarios ## Security Warning System 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 ## Credential Security ### Encrypted at Rest (AES-256-GCM) - **Node Credentials**: Passwords and API tokens (`/etc/pulse/nodes.enc`) - **Email Settings**: SMTP passwords (`/etc/pulse/email.enc`) - **Webhook Data**: URLs and auth headers (`/etc/pulse/webhooks.enc`) - v4.1.9+ - **Encryption Key**: Auto-generated (`/etc/pulse/.encryption.key`) ### Security Features - **Logs**: Token values masked with `***` in all outputs - **API**: Frontend receives only `hasToken: true`, never actual values - **Export**: Requires a valid API token (via `X-API-Token` header or `token` query parameter) to extract credentials - **Migration**: Use passphrase-protected export/import (see [Migration Guide](MIGRATION.md)) - **Auto-Migration**: Unencrypted configs automatically migrate to encrypted format ## Export/Import Protection By default, configuration export/import is blocked for security. You have two options: ### Option 1: Set API Tokens (Recommended) ```bash # Using systemd (secure) sudo systemctl edit pulse # Add: [Service] Environment="API_TOKENS=ansible-token,docker-agent-token" Environment="API_TOKEN=legacy-token" # Optional fallback # Then restart: sudo systemctl restart pulse # Docker docker run -e API_TOKENS=ansible-token,docker-agent-token rcourtman/pulse:latest ``` ### Option 2: Allow Unprotected Export (Homelab) ```bash # Using systemd sudo systemctl edit pulse # Add: [Service] Environment="ALLOW_UNPROTECTED_EXPORT=true" # Docker docker run -e ALLOW_UNPROTECTED_EXPORT=true rcourtman/pulse:latest ``` **Note:** For production deployments, consider using Docker secrets or systemd environment variables instead of .env files for sensitive data. ## Security Features ### Core Protection - **Encryption**: All credentials encrypted at rest (AES-256-GCM) - **Export Protection**: Exports always encrypted with passphrase - **Minimum Passphrase**: 12 characters required for exports - **Security Tab**: Check status in Settings → Security ### Enterprise Security (When Authentication Enabled) - **Password Security**: - Bcrypt hashing with cost factor 12 (60-character hash) - Passwords NEVER stored in plain text - Automatic hashing on security setup - **CRITICAL**: Bcrypt hashes MUST be exactly 60 characters - **API Token Security**: - 64-character hex tokens (32 bytes of entropy) - SHA3-256 hashed before storage (64-char hash) - Raw token shown only once during generation - Tokens NEVER stored in plain text - Live reloading when .env file changes - API-only mode supported (no password auth required) - **CSRF Protection**: All state-changing operations require CSRF tokens - **Rate Limiting**: - Authentication endpoints: 10 attempts/minute per IP - General API: 500 requests/minute per IP - Real-time endpoints exempt for functionality - **Account Lockout Protection**: - Locks after 5 failed login attempts - 15-minute automatic lockout duration - Clear feedback showing remaining attempts - Time remaining displayed when locked - Manual reset available via API for administrators - **Session Management**: - Secure HttpOnly cookies - 24-hour session expiry - Session invalidation on password change - **Security Headers**: - Content-Security-Policy with strict directives - X-Frame-Options: DENY (prevents clickjacking) - X-Content-Type-Options: nosniff - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy restricting sensitive APIs - **Audit Logging**: All authentication events logged with IP addresses ### What's Encrypted in Exports - Node credentials (passwords, API tokens) - PBS credentials - Email settings passwords - Webhook URLs and authentication headers (v4.1.9+) ### What's NOT Encrypted - Node hostnames and IPs - Threshold settings - General configuration - Alert rules and schedules ## Authentication Pulse supports multiple authentication methods that can be used independently or together: ### Password Authentication #### Quick Security Setup (Recommended) The easiest way to enable authentication is through the web UI: 1. Go to Settings → Security 2. Click "Enable Security Now" 3. Enter username and password 4. Save the generated API token (shown only once!) 5. Security is enabled immediately (no restart needed) This automatically: - Generates a secure random password - Hashes it with bcrypt (cost factor 12) - Creates secure API token (SHA3-256 hashed, raw token shown once) - For systemd: Configures systemd with hashed credentials - For Docker: Saves to `/data/.env` with hashed credentials (properly quoted to prevent shell expansion) - Restarts service/container with authentication enabled #### Manual Setup (Advanced) ```bash # Using systemd (password will be hashed automatically) sudo systemctl edit pulse # Add: [Service] Environment="PULSE_AUTH_USER=admin" Environment="PULSE_AUTH_PASS=$2a$12$..." # Use bcrypt hash, not plain text! # Docker (credentials persist in volume via .env file) # IMPORTANT: Always quote bcrypt hashes to prevent shell expansion! docker run -e PULSE_AUTH_USER=admin -e PULSE_AUTH_PASS='$2a$12$...' rcourtman/pulse:latest # Or use Quick Security Setup and restart container ``` **Important**: Always use hashed passwords in configuration. Use the Quick Security Setup or generate bcrypt hashes manually. #### Features - Web UI login required when authentication enabled - Change/remove password from Settings → Security - Passwords ALWAYS hashed with bcrypt (cost 12) - Session-based authentication with secure HttpOnly cookies - 24-hour session expiry - CSRF protection for all state-changing operations - Session invalidation on password change ### API Token Authentication For programmatic access and automation. API tokens are SHA3-256 hashed for security. #### Token Setup via Quick Security The Quick Security Setup automatically: - Generates a cryptographically secure token - Hashes it with SHA3-256 - Stores only the 64-character hash - Adds the token to the managed token list #### Manual Token Setup ```bash # Using systemd (plain text values are auto-hashed on startup) sudo systemctl edit pulse # Add: [Service] Environment="API_TOKENS=ansible-token,docker-agent-token" # Docker docker run -e API_TOKENS=ansible-token,docker-agent-token rcourtman/pulse:latest # To provide pre-hashed tokens instead, list the SHA3-256 hashes # Environment="API_TOKENS=83c8...,b1de..." ``` **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) - 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 - Regenerate tokens when rotating credentials (new value displayed once) - All tokens stored as SHA3-256 hashes #### Usage ```bash # Include the ORIGINAL token (not hash) in X-API-Token header curl -H "X-API-Token: your-original-token" http://localhost:7655/api/health # Or in query parameter for export/import curl "http://localhost:7655/api/export?token=your-original-token" ``` ### Auto-Registration Security #### Default Mode - All access requires authentication - Nodes can auto-register with the API token - Setup scripts work without additional configuration #### Secure Mode - Require API token for all operations - Protects auto-registration endpoint - Enable by setting at least one API token via `API_TOKENS` (or legacy `API_TOKEN`) environment variable ## CORS (Cross-Origin Resource Sharing) By default, Pulse only allows same-origin requests (no CORS headers). This is the most secure configuration. ### Configuring CORS for External Access If you need to access Pulse API from a different domain: ```bash # Docker docker run -e ALLOWED_ORIGINS="https://app.example.com" rcourtman/pulse:latest # systemd 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. ## Security Best Practices ### Credential Storage - ✅ **DO**: Use Quick Security Setup for automatic hashing - ✅ **DO**: Store only bcrypt hashes for passwords - ✅ **DO**: Store only SHA3-256 hashes for API tokens - ❌ **DON'T**: Store plain text passwords in config files - ❌ **DON'T**: Store plain text API tokens in config files - ❌ **DON'T**: Log credentials or include them in backups ### Authentication Setup - ✅ **DO**: Use strong, unique passwords (16+ characters) - ✅ **DO**: Rotate API tokens periodically - ✅ **DO**: Use HTTPS in production environments - ❌ **DON'T**: Share API tokens between users/services - ❌ **DON'T**: Embed credentials in client-side code ### Verification Run the security verification script to ensure no plain text credentials: ```bash /opt/pulse/testing-tools/security-verification.sh ``` This checks: - No hardcoded credentials in code - No credentials exposed in logs - All passwords/tokens properly hashed - Secure file permissions - No credential leaks in API responses ## Account Lockout and Recovery ### Lockout Behavior - After **5 failed login attempts**, the account is locked for **15 minutes** - Lockout applies to both username and IP address - Login form shows remaining attempts after each failure - Clear message when locked with time remaining ### Automatic Recovery - Lockouts automatically expire after 15 minutes - No action needed - just wait for the timer to expire - Successful login clears all failed attempt counters ### Manual Recovery (Admin) Administrators with API access can manually reset lockouts: ```bash # Reset lockout for a specific username curl -X POST http://localhost:7655/api/security/reset-lockout \ -H "X-API-Token: your-api-token" \ -H "Content-Type: application/json" \ -d '{"identifier":"username"}' # Reset lockout for an IP address curl -X POST http://localhost:7655/api/security/reset-lockout \ -H "X-API-Token: your-api-token" \ -H "Content-Type: application/json" \ -d '{"identifier":"192.168.1.100"}' ``` ## Troubleshooting **Account locked?** Wait 15 minutes or contact admin for manual reset **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 value, not the hash) **CORS errors?** Configure ALLOWED_ORIGINS for your domain **Forgot password?** Start fresh - delete your Pulse data and restart