364 lines
13 KiB
Markdown
364 lines
13 KiB
Markdown
# 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
|