# 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

