# Security This document describes Keywarden's security features, architecture, and best practices. ## Authentication ### Password Authentication - Passwords are hashed with **bcrypt** at the default cost factor (10) - Password policy is configurable by the owner (see below) - Accounts are locked after configurable failed login attempts ### Password Policy The owner can configure password requirements in Admin Settings: | Setting | Default | Description | |---|---|---| | Minimum length | 8 | Minimum number of characters | | Require uppercase | Yes | At least one uppercase letter (A-Z) | | Require lowercase | Yes | At least one lowercase letter (a-z) | | Require digit | Yes | At least one number (0-9) | | Require special character | No | At least one non-alphanumeric character | The policy is enforced on: - User registration (invitation acceptance) - Password changes (manual and forced) - Admin password resets ### Account Lockout After a configurable number of failed login attempts (default: 5), an account is locked for a configurable duration (default: 15 minutes). | Setting | Default | |---|---| | Lockout threshold | 5 attempts | | Lockout duration | 15 minutes | Setting lockout attempts to 0 disables the lockout feature. Admins can manually unlock accounts from the user management page. ### Forced Password Change Admins can flag any user to require a password change. The user will be redirected to the password change page on every request until they set a new password. This is automatically enabled for: - Newly created accounts - Accounts where an admin reset the password ## Two-Factor Authentication (MFA) Keywarden supports TOTP (Time-based One-Time Password) for MFA, compatible with: - Google Authenticator - Authy - Microsoft Authenticator - Any RFC 6238 compliant app ### Implementation Details - **Algorithm**: HMAC-SHA1 - **Code length**: 6 digits - **Period**: 30 seconds - **Clock tolerance**: ±1 time step (allows 30 seconds of clock skew) - **Secret generation**: 20 bytes of cryptographic random data - **Secret encoding**: Base32 (unpadded) ### MFA Enforcement The owner can enable system-wide MFA enforcement in Admin Settings. When enabled: - Users without MFA are redirected to the MFA setup page on every request - Users cannot disable MFA while enforcement is active - The owner can always access Admin Settings (even without MFA) to prevent lockout ## Encryption ### Private Key Encryption (At Rest) All SSH private keys stored in the database are encrypted with **AES-256-GCM**: 1. The `KEYWARDEN_ENCRYPTION_KEY` is hashed with SHA-256 → 32-byte AES key 2. A random 12-byte nonce is generated for each encryption operation 3. Plaintext is encrypted with AES-256-GCM (provides confidentiality + integrity) 4. Result: `nonce || ciphertext || GCM-tag` → base64-encoded → stored in DB > **Critical:** If `KEYWARDEN_ENCRYPTION_KEY` is changed or lost, all stored private keys become permanently inaccessible. ### Backup Encryption Database exports are encrypted with a user-provided password using the same AES-256-GCM scheme. The password is required for both export and import. ## CSRF Protection Keywarden implements the **Double-Submit Cookie** pattern: 1. A `_csrf` cookie is set on every request (32 bytes, hex-encoded, 64 chars) 2. On state-changing methods (POST, PUT, DELETE, PATCH), the request must include a matching token as: - A form field named `_csrf`, or - An `X-CSRF-Token` request header 3. Tokens are compared using constant-time comparison to prevent timing attacks The cookie is **not** HttpOnly (JavaScript must read it to inject into forms), but is: - `SameSite=Strict` - `Secure` when HTTPS is enabled - Expires after 24 hours ## Security Headers Every response includes: | Header | Value | Purpose | |---|---|---| | `X-Frame-Options` | `DENY` | Prevents clickjacking | | `X-Content-Type-Options` | `nosniff` | Prevents MIME sniffing | | `Referrer-Policy` | `strict-origin-when-cross-origin` | Controls Referer leakage | | `Permissions-Policy` | `camera=(), microphone=(), geolocation=(), payment=()` | Disables unused APIs | | `Content-Security-Policy` | See below | Restricts resource loading | | `X-Permitted-Cross-Domain-Policies` | `none` | Blocks cross-domain policy files | | `Cache-Control` | `no-store, no-cache, must-revalidate, private` | Prevents caching of authenticated pages | ### Content Security Policy ``` default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self' ``` ## Rate Limiting Login endpoints (`POST /login`, `POST /login/mfa`) are rate-limited per IP address. - **Default limit**: 10 attempts per IP per minute - **Algorithm**: Fixed-window counter - **Response when exceeded**: HTTP 429 (Too Many Requests) - **Configuration**: `KEYWARDEN_RATE_LIMIT_LOGIN` (0 = disabled) A background goroutine cleans up expired rate limit entries every 5 minutes. ## Request Size Limiting Request bodies are limited to prevent denial-of-service via large uploads. - **Default limit**: 10 MB (`10485760` bytes) - **Response when exceeded**: HTTP 413 (Request Entity Too Large) - **Configuration**: `KEYWARDEN_MAX_REQUEST_SIZE` (0 = no limit) ## Trusted Proxy Configuration When Keywarden runs behind a reverse proxy, the real client IP must be extracted from `X-Forwarded-For` or `X-Real-IP` headers. However, these headers can be spoofed by clients. ### Strict Mode (Recommended) Set `KEYWARDEN_TRUSTED_PROXIES` to the CIDR range(s) of your reverse proxy: ```env KEYWARDEN_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12 ``` In strict mode: - Proxy headers are only trusted when the direct TCP peer is from a trusted network - `X-Forwarded-For` is walked right-to-left, and the first non-trusted IP is used - This prevents client-side IP spoofing ### Legacy Mode If `KEYWARDEN_TRUSTED_PROXIES` is not set, Keywarden trusts all proxy headers unconditionally. A warning is logged at startup: ``` WARN: KEYWARDEN_TRUSTED_PROXIES not set – proxy headers (X-Forwarded-For) are trusted unconditionally ``` ## Session Security - Session tokens: 32 bytes, cryptographically random, hex-encoded - Cookie name: `keywarden_session` - Cookie flags: - `HttpOnly` — Not accessible via JavaScript - `SameSite=Lax` — Prevents CSRF from external sites - `Secure` — Only over HTTPS (when enabled) - `MaxAge=86400` — 24 hours - Sessions stored in-memory (not persisted across restarts) - Configurable inactivity timeout (default: 60 minutes) - Background cleanup runs every minute ## SSH Connection Security When deploying keys to servers, Keywarden: - Uses the system master key (Ed25519) for SSH authentication - Connects with a 10-second timeout - **Does not verify host keys** (`InsecureIgnoreHostKey`) — this is a known limitation > **Note:** Host key verification is not yet implemented. This means Keywarden is susceptible to man-in-the-middle attacks during SSH connections. Only use Keywarden in trusted network environments. ## Best Practices 1. **Change the default secrets**: Set unique values for `KEYWARDEN_SESSION_KEY` and `KEYWARDEN_ENCRYPTION_KEY` 2. **Use HTTPS**: Run behind a reverse proxy with TLS termination 3. **Configure trusted proxies**: Set `KEYWARDEN_TRUSTED_PROXIES` for accurate IP logging 4. **Enable secure cookies**: Set `KEYWARDEN_SECURE_COOKIES=true` (auto-derived from HTTPS base URL) 5. **Enable MFA enforcement**: Require all users to use two-factor authentication 6. **Use strong passwords**: Configure a strict password policy 7. **Regular backups**: Export encrypted backups regularly 8. **Network isolation**: Restrict access to Keywarden and managed servers to trusted networks 9. **Keep the encryption key safe**: Back up `KEYWARDEN_ENCRYPTION_KEY` securely — losing it means losing all private keys 10. **Monitor the audit log**: Review login activity and deployment actions regularly 11. **Enable key enforcement**: Use enforce mode to ensure only Keywarden-managed keys exist on your servers ## Key Enforcement (Bastillion-Style) Keywarden includes an enforced key management feature inspired by [Bastillion](https://www.bastillion.io/). When enabled, a background worker periodically connects to all managed servers and ensures that only authorized SSH keys are present in `authorized_keys` files. ### How It Works 1. The enforcement worker runs at a configurable interval (default: 15 minutes) 2. For each managed server and system user, it reads the current `authorized_keys` 3. It compares the keys against the **desired state** derived from: - All active access assignments (desired_state = "present") - All active cron jobs (temporary access that has not yet expired) - All direct key deployments (via the Deploy page) - The system master key (always authorized) 4. Unauthorized keys (not managed by Keywarden) are detected 5. Depending on the mode, unauthorized keys are either logged or removed ### Modes | Mode | Behavior | |---|---| | **Disabled** | No enforcement checks (default) | | **Monitor** | Detects unauthorized keys and logs them in the audit log, but does not remove them | | **Enforce** | Detects unauthorized keys and **removes them automatically**, replacing `authorized_keys` with only the authorized set | ### Configuration Key enforcement is configured in **Admin Settings → Key Enforcement**: - **Enforcement Mode**: Disabled / Monitor / Enforce - **Check Interval**: How often the worker checks servers (1–1440 minutes) - **Run Now**: Trigger an immediate enforcement check ### Audit Trail All enforcement actions are recorded in the audit log: | Action | Description | |---|---| | `enforcement_run` | An enforcement cycle completed (with summary) | | `enforcement_drift` | Unauthorized keys detected on a server | | `enforcement_applied` | Unauthorized keys were removed from a server | | `enforcement_failed` | An enforcement action failed (connection error, etc.) | | `enforcement_settings_changed` | Enforcement settings were modified | ### Important Notes - The system master key is **always** considered authorized and will never be removed - Enforcement covers all system users that have active access assignments, cron jobs, or direct deployments in Keywarden - The server's admin user (used for SSH connections) is always checked - Enforcement requires the system master key to be deployed on target servers - In **enforce** mode, `authorized_keys` is atomically replaced (write to temp file, then move) - Manual runs can be triggered from the Admin Settings page