Release: v0.1.0-alpha
Some checks failed
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
Some checks failed
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
This commit is contained in:
51
docs/README.md
Normal file
51
docs/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Keywarden Documentation
|
||||
|
||||
Welcome to the official documentation for **Keywarden** — a self-hosted, centralized SSH key management and deployment platform.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Quick Start Guide](quickstart.md) — Get Keywarden running in minutes
|
||||
2. [Installation & Deployment](deployment.md) — Docker setup, reverse proxy, production deployment
|
||||
3. [Architecture Overview](architecture.md) — System design, components, technology stack
|
||||
4. [User Guide](user-guide.md) — Day-to-day usage for all users
|
||||
5. [Administration Guide](admin-guide.md) — Server management, deployments, access assignments, cron jobs
|
||||
6. [Roles & Permissions](roles.md) — Owner, Admin, and User role details
|
||||
7. [Security](security.md) — Authentication, MFA, encryption, CSRF, hardening
|
||||
8. [Environment Variables](environment-variables.md) — Complete configuration reference
|
||||
9. [Email Configuration](email.md) — SMTP setup, login notifications, invitations
|
||||
10. [API Reference](api-reference.md) — Health check endpoint and internal APIs
|
||||
11. [Backup & Restore](backup-restore.md) — Encrypted database backup and restore
|
||||
12. [Troubleshooting](troubleshooting.md) — Common issues and solutions
|
||||
13. [Contributing](contributing.md) — Development setup, project structure, guidelines
|
||||
|
||||
---
|
||||
|
||||
## What is Keywarden?
|
||||
|
||||
Keywarden provides a clean web UI to generate, import, and securely store SSH keys (RSA, Ed25519, and Ed448) in one central place. Managed keys can then be deployed to registered Linux servers — individually or via server groups — with a single click. A complete audit log tracks every action in the system.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **SSH Key Management** — Generate (RSA 2048/4096, Ed25519, Ed448) or import existing key pairs
|
||||
- **Encrypted Storage** — All private keys stored with AES-256-GCM encryption at rest
|
||||
- **System Master Key** — Auto-generated Ed25519 key used for all server authentication
|
||||
- **Host & Group Management** — Register servers, organize them into groups, deploy keys to one or many
|
||||
- **Access Assignments** — Map users + keys to target hosts/groups with specific system users, sudo rights, and user creation
|
||||
- **Temporary Access (Cron Jobs)** — Schedule time-limited access with automatic key removal, user disabling, or user deletion on expiry
|
||||
- **Three-Tier Role System** — Owner, Admin, and User roles with clear permission boundaries
|
||||
- **User Invitations** — Invite new users via secure email links with self-service password setup
|
||||
- **TOTP Two-Factor Authentication** — Optional or enforced MFA for all users
|
||||
- **Password Policies** — Configurable complexity requirements with account lockout
|
||||
- **Email Notifications** — Login alerts and invitation emails via SMTP
|
||||
- **Comprehensive Audit Log** — Every action logged with user, timestamp, IP address, and details
|
||||
- **Encrypted Backup/Restore** — Full database export with AES-256 password encryption
|
||||
- **Docker-Native** — Single-container deployment with SQLite, no external database needed
|
||||
- **Security Hardened** — CSRF protection, CSP headers, rate limiting, request size limits
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Keywarden is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0-or-later)](../LICENSE).
|
||||
|
||||
© 2026 Patrick Asmus ([scriptos](https://git.techniverse.net/scriptos))
|
||||
248
docs/admin-guide.md
Normal file
248
docs/admin-guide.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Administration Guide
|
||||
|
||||
This guide covers administrative features available to users with the **Admin** or **Owner** role. For basic user operations, see the [User Guide](user-guide.md). For role details, see [Roles & Permissions](roles.md).
|
||||
|
||||
## Server Management
|
||||
|
||||
Admins manage the inventory of remote SSH servers that Keywarden can deploy keys to.
|
||||
|
||||
### Adding a Server
|
||||
|
||||
1. Navigate to **Servers** → **Add Server**
|
||||
2. Fill in:
|
||||
- **Name** — Descriptive name (e.g., "Web Server 1")
|
||||
- **Hostname** — IP address or DNS name
|
||||
- **Port** — SSH port (default: 22)
|
||||
- **Username** — SSH admin user for connections (typically `root`)
|
||||
- **Description** — Optional description
|
||||
- **Server Groups** — Optionally assign the server to one or more groups
|
||||
3. Click **Save**
|
||||
|
||||
### Testing Server Connectivity
|
||||
|
||||
From the server list, you can run two types of tests:
|
||||
|
||||
- **Connection Test** — TCP connectivity check (is the port reachable?)
|
||||
- **Auth Test** — Full SSH authentication test using the system master key
|
||||
|
||||
Both tests help verify that Keywarden can reach and authenticate to the server.
|
||||
|
||||
### Editing / Deleting Servers
|
||||
|
||||
Use the edit and delete buttons on the server list. Deleting a server removes it from all groups and cancels related assignments.
|
||||
|
||||
## Server Groups
|
||||
|
||||
Server groups allow you to organize servers and deploy keys to multiple servers at once.
|
||||
|
||||
### Creating a Group
|
||||
|
||||
1. Navigate to **Groups** → **Add Group**
|
||||
2. Enter a **name** and optional **description**
|
||||
3. Click **Create**
|
||||
|
||||
### Managing Group Members
|
||||
|
||||
From the group edit page:
|
||||
- **Add servers** to the group by selecting them from the list of all servers
|
||||
- **Remove servers** from the group
|
||||
|
||||
Server groups are used as targets for:
|
||||
- Group deployments
|
||||
- Access assignments
|
||||
- Cron jobs (temporary access)
|
||||
|
||||
## Key Deployment
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
1. Navigate to **Deploy**
|
||||
2. Select an **SSH key** from the dropdown (shows all keys from all users)
|
||||
3. Select a **target server**
|
||||
4. Click **Deploy**
|
||||
|
||||
Keywarden connects to the target server using the system master key and appends the selected public key to the server user's `~/.ssh/authorized_keys`.
|
||||
|
||||
### Group Deployment
|
||||
|
||||
1. Navigate to **Deploy**
|
||||
2. Select an **SSH key**
|
||||
3. Select a **server group**
|
||||
4. Click **Deploy to Group**
|
||||
|
||||
The key is deployed to all servers in the group sequentially.
|
||||
|
||||
### Deployment History
|
||||
|
||||
The deploy page shows the last 50 deployment results with status (success/failed) and error messages.
|
||||
|
||||
## Access Assignments
|
||||
|
||||
Access assignments are the core feature for managing who has access to which servers. They provide a declarative model: define the desired state, and Keywarden syncs it to the servers.
|
||||
|
||||
### Creating an Assignment
|
||||
|
||||
1. Navigate to **Assignments** → **Add Assignment**
|
||||
2. Fill in:
|
||||
- **User** — The Keywarden user to grant access to
|
||||
- **SSH Key** — Which key to deploy (from that user's keys)
|
||||
- **Target Type** — Single server or server group
|
||||
- **Target** — Select the server or group
|
||||
- **System User** — The Linux username on the target server
|
||||
- **Desired State** — `present` (deploy key) or `absent` (remove key)
|
||||
- **Sudo** — Grant NOPASSWD sudo privileges to the system user
|
||||
- **Create User** — Create the Linux user if it doesn't exist
|
||||
3. Click **Save**
|
||||
|
||||
After creation, the assignment is **automatically synced** — Keywarden immediately connects to the target server(s) and applies the configuration.
|
||||
|
||||
### What Sync Does
|
||||
|
||||
When an assignment is synced with `desired_state = "present"`:
|
||||
|
||||
1. **Creates the system user** (if `create_user` is enabled and user doesn't exist)
|
||||
- Uses `useradd -m -s /bin/bash`
|
||||
- Sets an initial password if one is configured (auto-generated if empty)
|
||||
2. **Adds sudo privileges** (if `sudo` is enabled)
|
||||
- Creates `/etc/sudoers.d/<username>` with `NOPASSWD:ALL`
|
||||
3. **Deploys the SSH public key** to the system user's `authorized_keys`
|
||||
|
||||
When an assignment is synced with `desired_state = "absent"`:
|
||||
- Removes the SSH key from the system user's `authorized_keys`
|
||||
|
||||
### Manual Re-Sync
|
||||
|
||||
Click the **Sync** button on any assignment to re-apply it. This is useful if the server was reinstalled or if a previous sync failed.
|
||||
|
||||
### Deleting an Assignment
|
||||
|
||||
When deleting an assignment, you have two options:
|
||||
|
||||
- **Remove key only** — Only removes the SSH key from the system user's `authorized_keys`
|
||||
- **Delete system user** — Completely removes the system user account, their home directory, and sudo privileges from the target server(s)
|
||||
|
||||
The cleanup operation runs on all target servers (including all servers in a group).
|
||||
|
||||
### Assignment Status
|
||||
|
||||
| Status | Meaning |
|
||||
|---|---|
|
||||
| `pending` | Not yet synced |
|
||||
| `synced` | Successfully applied to all targets |
|
||||
| `failed` | Sync failed (see error message) |
|
||||
|
||||
## Cron Jobs (Temporary Access)
|
||||
|
||||
Cron jobs provide time-limited access to servers. They are essentially scheduled access assignments with an expiry.
|
||||
|
||||
### Creating a Cron Job
|
||||
|
||||
1. Navigate to **Temporary Access** → **Add Job**
|
||||
2. Configure:
|
||||
- **Name** — Descriptive job name
|
||||
- **Target User** — Keywarden user to grant access to
|
||||
- **SSH Key** — Which key to deploy
|
||||
- **Target** — Single server or server group
|
||||
- **System User** — Linux username on the target
|
||||
- **Create User** — Create the system user if needed
|
||||
- **Sudo** — Grant sudo privileges
|
||||
- **Schedule** — `once`, `hourly`, `daily`, `weekly`, or `monthly`
|
||||
- **Scheduled Time** — When the job should run (timezone-aware)
|
||||
- **Remove After** — Minutes after deployment to remove access (0 = permanent)
|
||||
- **Expiry Action** — What to do when access expires:
|
||||
- `remove_key` — Only remove the SSH key
|
||||
- `disable_user` — Lock the account and set shell to nologin
|
||||
- `delete_user` — Completely delete the system user
|
||||
3. Click **Save**
|
||||
|
||||
### Schedule Types
|
||||
|
||||
| Schedule | Parameters | Behavior |
|
||||
|---|---|---|
|
||||
| `once` | Date + Time | Runs exactly once at the specified time |
|
||||
| `hourly` | Minute of hour | Runs every hour at the specified minute |
|
||||
| `daily` | Time of day | Runs every day at the specified time |
|
||||
| `weekly` | Day of week + Time | Runs every week on the specified day |
|
||||
| `monthly` | Day of month + Time | Runs monthly (clamped to last day if needed) |
|
||||
|
||||
### Cron Job Lifecycle
|
||||
|
||||
1. **Active** — Waiting for next run
|
||||
2. **Running** — Currently executing
|
||||
3. **Done** — One-time job completed
|
||||
4. **Paused** — Manually paused by admin
|
||||
5. **Failed** — Execution failed (recurring jobs stay active, one-time jobs remain failed)
|
||||
|
||||
### Expiry Timer
|
||||
|
||||
When `remove_after_min > 0`, a background timer starts after successful deployment. When it fires, the configured expiry action is executed on all target servers.
|
||||
|
||||
## User Management
|
||||
|
||||
### Creating Users
|
||||
|
||||
1. Navigate to **Users** → **Add User**
|
||||
2. Fill in:
|
||||
- **Username** — Must be unique
|
||||
- **Email** — Must be unique
|
||||
- **Role** — `user`, `admin`, or `owner` (see [Roles & Permissions](roles.md))
|
||||
- **Password** — Set a password, or...
|
||||
- **Send Invitation** — If email is configured, send an invitation email instead of setting a password
|
||||
3. Click **Create**
|
||||
|
||||
When using invitations, the user receives an email with a secure link to set their own password.
|
||||
|
||||
### Editing Users
|
||||
|
||||
Admins can change a user's username, email, and role. Additional actions:
|
||||
- **Reset password** — Set a new password (the user will be prompted to change it)
|
||||
- **Force password change** — Flag the user to change password on next login
|
||||
- **Unlock account** — Clear a lockout from failed login attempts
|
||||
|
||||
### Deleting Users
|
||||
|
||||
Deleting a user removes their SSH keys, server records, and all related data (CASCADE delete).
|
||||
|
||||
> **Protection:** You cannot delete the last owner account.
|
||||
|
||||
## System Information
|
||||
|
||||
Navigate to **System** to view runtime information:
|
||||
|
||||
- Go version, OS, architecture
|
||||
- CPU count, goroutine count
|
||||
- Memory allocation
|
||||
- Runtime environment (Docker or native)
|
||||
- Hostname and uptime
|
||||
|
||||
## Admin Settings (Owner Only)
|
||||
|
||||
See [Roles & Permissions](roles.md) for details on which settings are owner-only.
|
||||
|
||||
Navigate to **Admin Settings** (owner only) to configure:
|
||||
|
||||
### Application Settings
|
||||
|
||||
- **App Name** — Custom application name displayed in the UI
|
||||
- **Default Key Type** — Default key type for generation (ed25519, rsa)
|
||||
- **Default Key Bits** — Default key size
|
||||
- **Session Timeout** — Inactivity timeout in minutes (default: 60)
|
||||
|
||||
### Security Settings
|
||||
|
||||
- **Password Policy** — Minimum length, uppercase, lowercase, digit, special character requirements
|
||||
- **Account Lockout** — Number of failed attempts before lockout and lockout duration
|
||||
- **MFA Enforcement** — Require all users to enable TOTP MFA
|
||||
|
||||
### Master Key
|
||||
|
||||
- View the system master key's public key and fingerprint
|
||||
- **Regenerate** the master key (requires password confirmation)
|
||||
|
||||
### Email Test
|
||||
|
||||
Send a test email to verify SMTP configuration.
|
||||
|
||||
### Backup & Restore
|
||||
|
||||
See [Backup & Restore](backup-restore.md) for details.
|
||||
154
docs/api-reference.md
Normal file
154
docs/api-reference.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# API Reference
|
||||
|
||||
Keywarden is primarily a web application with server-rendered HTML pages. It provides a limited JSON API for health monitoring and internal use.
|
||||
|
||||
## Public Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
```
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
Returns the application health status. No authentication required. Used by Docker HEALTHCHECK and external monitoring tools.
|
||||
|
||||
**Response (healthy):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"uptime": "2d 5h 30m",
|
||||
"uptime_seconds": 194400,
|
||||
"checks": {
|
||||
"database": {
|
||||
"status": "ok"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (unhealthy):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "unhealthy",
|
||||
"uptime": "0m",
|
||||
"uptime_seconds": 30,
|
||||
"checks": {
|
||||
"database": {
|
||||
"status": "fail"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Status Code | Meaning |
|
||||
|---|---|
|
||||
| `200 OK` | Application is healthy |
|
||||
| `503 Service Unavailable` | Database unreachable or other critical failure |
|
||||
|
||||
## Internal API Endpoints
|
||||
|
||||
These endpoints require authentication and are used by the web UI.
|
||||
|
||||
### Cron Keys API
|
||||
|
||||
```
|
||||
GET /api/cron/keys?user_id={id}
|
||||
```
|
||||
|
||||
Returns SSH keys for a specific user as JSON. Used by the cron job creation form to dynamically load keys when the target user is selected.
|
||||
|
||||
**Access**: Admin and Owner only.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "My Ed25519 Key",
|
||||
"key_type": "ed25519",
|
||||
"fingerprint": "SHA256:abc..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## HTTP Routes Overview
|
||||
|
||||
### Public Routes (No Authentication)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET/POST | `/login` | Login page and authentication |
|
||||
| POST | `/login/mfa` | MFA code verification |
|
||||
| GET | `/logout` | Session termination |
|
||||
| GET/POST | `/invite/{token}` | Invitation acceptance |
|
||||
|
||||
### Authenticated Routes (All Users)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Redirect to dashboard |
|
||||
| GET | `/dashboard` | Main dashboard |
|
||||
| GET/POST | `/password/change` | Forced password change |
|
||||
| GET | `/keys` | SSH key list |
|
||||
| GET/POST | `/keys/generate` | Generate new SSH key |
|
||||
| GET/POST | `/keys/import` | Import existing SSH key |
|
||||
| GET/POST | `/keys/{id}/{action}` | Key actions (download, delete) |
|
||||
| GET/POST | `/settings` | User account settings |
|
||||
| POST | `/settings/theme` | Change theme preference |
|
||||
| GET/POST | `/settings/mfa/setup` | Enable MFA |
|
||||
| POST | `/settings/mfa/disable` | Disable MFA |
|
||||
| POST | `/settings/email/notify` | Toggle email notifications |
|
||||
| POST | `/settings/avatar` | Upload profile picture |
|
||||
| GET | `/avatar/{id}` | Serve user avatar |
|
||||
| GET | `/audit` | Audit log viewer |
|
||||
| GET | `/my/access` | View own access assignments |
|
||||
| GET/POST | `/mfa/setup` | MFA enforcement setup page |
|
||||
|
||||
### Admin Routes (Admin + Owner)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET | `/servers` | Server list |
|
||||
| GET/POST | `/servers/add` | Add server |
|
||||
| POST | `/servers/test` | Test server connection |
|
||||
| POST | `/servers/test-auth` | Test SSH authentication |
|
||||
| GET/POST | `/servers/{id}/{action}` | Edit/delete server |
|
||||
| GET | `/groups` | Server group list |
|
||||
| GET/POST | `/groups/add` | Add server group |
|
||||
| GET/POST | `/groups/{id}/{action}` | Edit/delete group, manage members |
|
||||
| GET/POST | `/deploy` | Manual key deployment |
|
||||
| POST | `/deploy/group` | Group deployment |
|
||||
| GET | `/cron` | Cron job list |
|
||||
| GET/POST | `/cron/add` | Add cron job |
|
||||
| GET/POST | `/cron/{id}/{action}` | Edit/delete/pause cron job |
|
||||
| GET | `/users` | User list |
|
||||
| GET/POST | `/users/add` | Add user |
|
||||
| GET/POST | `/users/{id}/{action}` | Edit/delete/unlock user |
|
||||
| GET | `/assignments` | Access assignment list |
|
||||
| GET/POST | `/assignments/add` | Add assignment |
|
||||
| GET/POST | `/assignments/{id}/{action}` | Edit/delete/sync assignment |
|
||||
| GET | `/system` | System information |
|
||||
| GET | `/api/cron/keys` | Get keys by user (JSON) |
|
||||
|
||||
### Owner Routes
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET/POST | `/admin/settings` | Application and security settings |
|
||||
| POST | `/admin/settings/email/test` | Send test email |
|
||||
| POST | `/admin/masterkey/regenerate` | Regenerate system master key |
|
||||
| POST | `/admin/backup/export` | Export encrypted database backup |
|
||||
| POST | `/admin/backup/import` | Import encrypted database backup |
|
||||
|
||||
## Static Assets
|
||||
|
||||
| Path | Description |
|
||||
|---|---|
|
||||
| `/static/css/` | Tabler CSS framework |
|
||||
| `/static/js/` | Tabler JavaScript |
|
||||
| `/static/css/fonts/` | Tabler icons font |
|
||||
|
||||
Static assets are embedded in the binary and served with long cache headers.
|
||||
152
docs/architecture.md
Normal file
152
docs/architecture.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Architecture Overview
|
||||
|
||||
This document describes the system architecture and design of Keywarden.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Component | Technology |
|
||||
|---|---|
|
||||
| Language | Go 1.26 |
|
||||
| Database | SQLite 3 (WAL mode, embedded) |
|
||||
| Web Framework | Go standard library (`net/http`) |
|
||||
| Template Engine | Go `html/template` |
|
||||
| UI Framework | [Tabler](https://tabler.io) (Bootstrap-based) |
|
||||
| SSH Library | `golang.org/x/crypto/ssh` |
|
||||
| Encryption | AES-256-GCM (Go `crypto/aes`, `crypto/cipher`) |
|
||||
| Password Hashing | bcrypt (`golang.org/x/crypto/bcrypt`) |
|
||||
| Ed448 Support | `github.com/cloudflare/circl` |
|
||||
| Containerization | Docker (Alpine Linux) |
|
||||
|
||||
## Application Structure
|
||||
|
||||
Keywarden is a single Go binary with embedded static assets and templates. It serves a web UI and handles all SSH operations internally.
|
||||
|
||||
```
|
||||
cmd/keywarden/main.go ← Application entry point
|
||||
internal/
|
||||
audit/ ← Audit logging service
|
||||
auth/ ← Authentication, users, MFA, password policy, invitations
|
||||
config/ ← Environment-based configuration
|
||||
cron/ ← Scheduled temporary access jobs
|
||||
database/ ← SQLite connection, migrations, backup/restore
|
||||
deploy/ ← SSH key deployment to remote servers
|
||||
encryption/ ← AES-256-GCM encryption service
|
||||
handlers/ ← HTTP handlers and routing (all UI logic)
|
||||
keys/ ← SSH key management, system master key
|
||||
logging/ ← Structured logging with levels
|
||||
mail/ ← SMTP email service (notifications, invitations)
|
||||
models/ ← Data models (User, SSHKey, Server, etc.)
|
||||
security/ ← CSRF, security headers, rate limiting, proxy detection
|
||||
servers/ ← Server and server group management, access assignments
|
||||
sshutil/ ← SSH key generation (RSA, Ed25519, Ed448)
|
||||
web/
|
||||
embed.go ← Go embed directives for templates and static files
|
||||
static/ ← CSS, JS, fonts (Tabler UI framework)
|
||||
templates/ ← HTML templates (Go template syntax)
|
||||
```
|
||||
|
||||
## Startup Sequence
|
||||
|
||||
1. **Load configuration** from environment variables
|
||||
2. **Initialize logging** with the configured log level
|
||||
3. **Create data directories** (`/data`, `/data/keys`, `/data/master`)
|
||||
4. **Initialize SQLite database** with WAL mode and run all migrations
|
||||
5. **Initialize services**: encryption, auth, keys, servers, deploy, audit, cron, mail
|
||||
6. **Create initial owner account** (if no users exist) with auto-generated password
|
||||
7. **Ensure system master key** exists (generates on first run)
|
||||
8. **Configure security** subsystem (trusted proxy parsing)
|
||||
9. **Set up HTTP routes** and load templates
|
||||
10. **Start session cleanup** goroutine (removes expired sessions every minute)
|
||||
11. **Apply middleware chain**: request logger → security headers → rate limiting → size limiting → CSRF
|
||||
12. **Start cron scheduler** (checks for pending jobs every 30 seconds)
|
||||
13. **Start HTTP server**
|
||||
|
||||
## Database Design
|
||||
|
||||
Keywarden uses SQLite with the following tables:
|
||||
|
||||
| Table | Purpose |
|
||||
|---|---|
|
||||
| `users` | User accounts (username, email, password hash, role, MFA, themes) |
|
||||
| `ssh_keys` | SSH key pairs (public key, encrypted private key, fingerprint) |
|
||||
| `servers` | Managed remote servers (hostname, port, SSH username) |
|
||||
| `server_groups` | Named groups of servers |
|
||||
| `server_group_members` | Many-to-many relation: servers ↔ groups |
|
||||
| `access_assignments` | Maps users + keys → hosts/groups with system user config |
|
||||
| `cron_jobs` | Scheduled temporary access jobs |
|
||||
| `key_deployments` | Deployment history log |
|
||||
| `audit_log` | Full audit trail of all actions |
|
||||
| `settings` | Key-value application settings |
|
||||
| `invitation_tokens` | One-time invitation links for new users |
|
||||
| `_migrations` | Tracks applied database migrations |
|
||||
|
||||
Database migrations are idempotent and run automatically on every startup. New columns are added via `ALTER TABLE` with migration tracking to prevent duplicate additions.
|
||||
|
||||
## Request Flow
|
||||
|
||||
```
|
||||
Client → [Nginx/Caddy] → Keywarden HTTP Server
|
||||
│
|
||||
├── Request Logger Middleware
|
||||
├── Security Headers Middleware
|
||||
├── Rate Limit Middleware (login endpoints)
|
||||
├── Size Limit Middleware
|
||||
├── CSRF Middleware (double-submit cookie)
|
||||
│
|
||||
├── Public Routes (/login, /invite/*)
|
||||
├── Auth Routes (requireAuth → all authenticated users)
|
||||
├── Admin Routes (requireAdmin → admin + owner)
|
||||
└── Owner Routes (requireOwner → owner only)
|
||||
```
|
||||
|
||||
## Session Management
|
||||
|
||||
Sessions are stored in-memory (not in the database) as a map of session tokens to session data. Each session tracks:
|
||||
|
||||
- User ID
|
||||
- Last activity timestamp (for sliding timeout)
|
||||
- MFA setup requirement flag
|
||||
|
||||
Session tokens are cryptographically random 32-byte hex strings stored in an HTTP-only cookie (`keywarden_session`). Sessions expire after the configured timeout (default: 60 minutes of inactivity). A background goroutine cleans up expired sessions every minute.
|
||||
|
||||
## Encryption Architecture
|
||||
|
||||
### Private Key Storage
|
||||
|
||||
All SSH private keys are encrypted at rest using AES-256-GCM:
|
||||
|
||||
1. The `KEYWARDEN_ENCRYPTION_KEY` environment variable is hashed with SHA-256 to derive a 32-byte AES key
|
||||
2. Each private key is encrypted with a random 12-byte nonce
|
||||
3. The encrypted blob (nonce + ciphertext + GCM tag) is base64-encoded and stored in the database
|
||||
|
||||
### Backup Encryption
|
||||
|
||||
Database backups are encrypted with a user-provided password using the same AES-256-GCM scheme. The password is hashed with SHA-256 to derive the encryption key.
|
||||
|
||||
### Password Storage
|
||||
|
||||
User passwords are hashed with bcrypt at the default cost factor.
|
||||
|
||||
## System Master Key
|
||||
|
||||
The system master key is an Ed25519 SSH key pair generated on first startup. It is used for all SSH connections to managed servers. The private key is stored encrypted in the `settings` table.
|
||||
|
||||
The master key enables Keywarden to:
|
||||
- Deploy SSH keys to server `authorized_keys`
|
||||
- Create and manage system users on remote servers
|
||||
- Add/remove sudo privileges
|
||||
- Disable or delete system users during access expiry
|
||||
|
||||
## Cron Scheduler
|
||||
|
||||
The cron service runs a tick every 30 seconds, checking for jobs where `next_run <= now` and `status == 'active'`. For each due job, it:
|
||||
|
||||
1. Marks the job as `running`
|
||||
2. Resolves the SSH key and target servers
|
||||
3. Deploys the key to the specified system user
|
||||
4. If `remove_after_min > 0`, starts a background timer that triggers the expiry action
|
||||
5. Updates the job status and calculates the next run time
|
||||
|
||||
Supported schedules: `once`, `hourly`, `daily`, `weekly`, `monthly`.
|
||||
|
||||
Expiry actions: `remove_key` (default), `disable_user`, `delete_user`.
|
||||
93
docs/backup-restore.md
Normal file
93
docs/backup-restore.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Backup & Restore
|
||||
|
||||
Keywarden provides a built-in encrypted backup and restore feature for the entire database. This is an **owner-only** feature accessible from the Admin Settings page.
|
||||
|
||||
## Overview
|
||||
|
||||
- Backups include **all data**: users, SSH keys (encrypted), servers, groups, assignments, cron jobs, settings, audit log, and deployment history
|
||||
- Backups are encrypted with a **user-provided password** using AES-256-GCM
|
||||
- Backup files use the `.kwbak` extension
|
||||
- Restoring a backup **completely replaces** the current database
|
||||
|
||||
## Exporting a Backup
|
||||
|
||||
1. Navigate to **Admin Settings** (owner only)
|
||||
2. In the **Backup & Restore** section, enter a **backup password** and confirm it
|
||||
3. The password must comply with the configured password policy
|
||||
4. Click **Export Backup**
|
||||
5. A file named `keywarden-backup-{timestamp}.kwbak` is downloaded
|
||||
|
||||
### What's Included
|
||||
|
||||
| Data | Included |
|
||||
|---|---|
|
||||
| Users (with password hashes, MFA secrets) | ✅ |
|
||||
| SSH Keys (with encrypted private keys) | ✅ |
|
||||
| Servers | ✅ |
|
||||
| Server Groups + Members | ✅ |
|
||||
| Access Assignments | ✅ |
|
||||
| Cron Jobs | ✅ |
|
||||
| Key Deployment History | ✅ |
|
||||
| Audit Log | ✅ |
|
||||
| Application Settings | ✅ |
|
||||
|
||||
> **Note:** SSH private keys are stored with double encryption in backups — first with the application's `KEYWARDEN_ENCRYPTION_KEY`, then with the backup password. Both keys are needed to access the private keys.
|
||||
|
||||
## Importing a Backup
|
||||
|
||||
1. Navigate to **Admin Settings** (owner only)
|
||||
2. In the **Backup & Restore** section, select the `.kwbak` file
|
||||
3. Enter the **backup password** that was used during export
|
||||
4. Click **Import Backup**
|
||||
|
||||
### Important Warnings
|
||||
|
||||
- **Importing a backup completely replaces all data** in the current database
|
||||
- All current users, keys, servers, and settings are deleted and replaced
|
||||
- The current session remains valid (you stay logged in as the owner)
|
||||
- After import, you may need to log in again with credentials from the backup
|
||||
- The `KEYWARDEN_ENCRYPTION_KEY` must match the one used when the backup was created — otherwise restored SSH private keys cannot be decrypted
|
||||
|
||||
### Error Handling
|
||||
|
||||
| Error | Cause |
|
||||
|---|---|
|
||||
| "Failed to decrypt backup" | Wrong backup password |
|
||||
| "Failed to parse backup" | Corrupt or invalid backup file |
|
||||
| "Failed to import" | Database error during restore |
|
||||
|
||||
## Backup Security
|
||||
|
||||
- Backups are encrypted with AES-256-GCM using a key derived from SHA-256 of the backup password
|
||||
- The encrypted blob is a single binary file (not JSON)
|
||||
- Without the correct password, the backup cannot be read or modified
|
||||
- Use strong, unique passwords for backups
|
||||
- Store backup files and passwords separately
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
1. **Regular exports**: Export a backup weekly or after significant changes
|
||||
2. **Secure storage**: Store `.kwbak` files in a separate, secure location
|
||||
3. **Password management**: Store backup passwords in a password manager
|
||||
4. **Test restores**: Periodically verify backups by restoring to a test instance
|
||||
5. **Encryption key backup**: Keep a secure copy of `KEYWARDEN_ENCRYPTION_KEY`
|
||||
|
||||
### Docker Volume Backup
|
||||
|
||||
In addition to the application-level backup, you can also back up the Docker volume directly:
|
||||
|
||||
```bash
|
||||
# Stop the container
|
||||
docker compose down
|
||||
|
||||
# Backup the volume
|
||||
docker run --rm -v keywarden_keywarden_data:/data -v $(pwd):/backup \
|
||||
alpine tar czf /backup/keywarden-volume-backup.tar.gz /data
|
||||
|
||||
# Start the container
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This captures the raw SQLite database file and all data files. Note that this backup is **not encrypted** — protect it accordingly.
|
||||
151
docs/contributing.md
Normal file
151
docs/contributing.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Contributing
|
||||
|
||||
Guide for developers who want to contribute to Keywarden or build from source.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.26+** with CGO enabled (required for SQLite)
|
||||
- **GCC / C compiler** (required by `go-sqlite3`)
|
||||
- **Git** for version control
|
||||
|
||||
### Platform-Specific Requirements
|
||||
|
||||
**Linux (Debian/Ubuntu):**
|
||||
```bash
|
||||
sudo apt install gcc sqlite3 libsqlite3-dev
|
||||
```
|
||||
|
||||
**Alpine Linux:**
|
||||
```bash
|
||||
apk add gcc musl-dev sqlite-dev
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
Install [TDM-GCC](https://jmeubank.github.io/tdm-gcc/) or MinGW-w64, then set `CGO_ENABLED=1`.
|
||||
|
||||
## Building from Source
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://git.techniverse.net/scriptos/keywarden.git
|
||||
cd keywarden
|
||||
|
||||
# Download dependencies
|
||||
go mod download
|
||||
|
||||
# Build
|
||||
CGO_ENABLED=1 go build -o keywarden ./cmd/keywarden/
|
||||
|
||||
# Run
|
||||
./keywarden
|
||||
```
|
||||
|
||||
### Docker Build
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
keywarden/
|
||||
├── cmd/keywarden/main.go # Application entry point
|
||||
├── internal/
|
||||
│ ├── audit/audit.go # Audit logging service (action constants, logging, queries)
|
||||
│ ├── auth/auth.go # Authentication service (login, register, MFA, password policy,
|
||||
│ │ # account lockout, invitations, settings)
|
||||
│ ├── config/config.go # Environment-based configuration loader
|
||||
│ ├── cron/cron.go # Cron scheduler (temporary access jobs, scheduling logic)
|
||||
│ ├── database/
|
||||
│ │ ├── database.go # SQLite connection, migrations, schema
|
||||
│ │ └── backup.go # Encrypted backup export/import
|
||||
│ ├── deploy/deploy.go # SSH key deployment (deploy, remove, user management,
|
||||
│ │ # sudo, disable, delete system users)
|
||||
│ ├── encryption/encryption.go # AES-256-GCM encryption/decryption
|
||||
│ ├── handlers/handlers.go # All HTTP handlers, routing, middleware, templates
|
||||
│ ├── keys/keys.go # SSH key management, system master key
|
||||
│ ├── logging/logging.go # Structured logging with levels, request logger
|
||||
│ ├── mail/mail.go # SMTP email service (notifications, invitations, templates)
|
||||
│ ├── models/models.go # Data models (User, SSHKey, Server, CronJob, etc.)
|
||||
│ ├── security/
|
||||
│ │ ├── csrf.go # CSRF double-submit cookie middleware
|
||||
│ │ ├── headers.go # Security headers middleware (CSP, X-Frame-Options, etc.)
|
||||
│ │ ├── proxy.go # Trusted proxy IP extraction
|
||||
│ │ ├── ratelimit.go # IP-based rate limiting middleware
|
||||
│ │ └── sizelimit.go # Request body size limit middleware
|
||||
│ ├── servers/servers.go # Server and group management, access assignments
|
||||
│ └── sshutil/keygen.go # SSH key generation (RSA, Ed25519, Ed448)
|
||||
├── web/
|
||||
│ ├── embed.go # Go embed directives
|
||||
│ ├── static/ # CSS, JS, fonts (Tabler UI)
|
||||
│ └── templates/ # HTML templates
|
||||
├── docs/ # Documentation
|
||||
├── Dockerfile # Multi-stage Docker build
|
||||
├── docker-compose.yml # Docker Compose configuration
|
||||
├── go.mod # Go module definition
|
||||
└── LICENSE # AGPL-3.0-or-later
|
||||
```
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
- **Single binary**: All assets (templates, CSS, JS) are embedded via Go's `embed` package
|
||||
- **No external database**: SQLite with WAL mode, embedded in the binary
|
||||
- **Standard library HTTP**: No web framework — uses `net/http` directly
|
||||
- **Service pattern**: Each domain has its own service struct with a database dependency
|
||||
- **Handler pattern**: One large handler file with all routes and template rendering
|
||||
- **Middleware chain**: Security features implemented as composable HTTP middleware
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Roles
|
||||
|
||||
Three roles: `owner`, `admin`, `user`. The owner is created on first startup. Admins are created by the owner. Users can be created by admins, owner or via invitations.
|
||||
|
||||
### Master Key
|
||||
|
||||
A single system-wide Ed25519 SSH key pair is used for all server connections. This simplifies deployment: only one public key needs to be added to target servers.
|
||||
|
||||
### Access Assignments
|
||||
|
||||
Instead of ad-hoc key deployment, access assignments provide a declarative model. The desired state is stored in the database and synced to servers on demand.
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
Temporary access is implemented as cron jobs that deploy keys on a schedule and remove them after a timeout using background timers.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
Test files are co-located with their packages (e.g., `auth_test.go`, `config_test.go`, `encryption_test.go`).
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Module | Purpose |
|
||||
|---|---|
|
||||
| `github.com/mattn/go-sqlite3` | SQLite3 driver (CGO) |
|
||||
| `golang.org/x/crypto` | bcrypt, SSH key operations |
|
||||
| `github.com/cloudflare/circl` | Ed448 key support |
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use `gofmt` for formatting
|
||||
- Follow standard Go conventions
|
||||
- Error messages should be lowercase
|
||||
- Log messages use the structured logging package (`logging.Info`, `logging.Debug`, etc.)
|
||||
|
||||
## License
|
||||
|
||||
All contributions must be compatible with the [AGPL-3.0-or-later](../LICENSE) license.
|
||||
|
||||
Copyright (C) 2026 Patrick Asmus (scriptos)
|
||||
209
docs/deployment.md
Normal file
209
docs/deployment.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Installation & Deployment
|
||||
|
||||
This guide covers production deployment of Keywarden using Docker.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
Keywarden is designed as a single-container application with an embedded SQLite database. No external database server is required.
|
||||
|
||||
### Docker Image
|
||||
|
||||
Build from source or use the pre-built image:
|
||||
|
||||
```bash
|
||||
# Build from source
|
||||
docker compose build
|
||||
|
||||
# Or build manually
|
||||
docker build -t keywarden .
|
||||
```
|
||||
|
||||
### Multi-Stage Build
|
||||
|
||||
The Dockerfile uses a two-stage build:
|
||||
|
||||
1. **Builder stage** (`golang:1.26-alpine`): Compiles the Go binary with CGO (required for SQLite)
|
||||
2. **Runtime stage** (`alpine:3.21`): Minimal image with only the compiled binary and runtime dependencies (`ca-certificates`, `sqlite-libs`, `tzdata`, `curl`)
|
||||
|
||||
The runtime container runs as a non-root user (`keywarden`).
|
||||
|
||||
### Docker Compose
|
||||
|
||||
A complete `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
keywarden:
|
||||
build: .
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${KEYWARDEN_PORT:-8080}/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
### Environment File (.env)
|
||||
|
||||
Create a `.env` file alongside `docker-compose.yml`:
|
||||
|
||||
```env
|
||||
# Security (REQUIRED - change these!)
|
||||
KEYWARDEN_SESSION_KEY=generate-a-random-string-of-at-least-32-chars
|
||||
KEYWARDEN_ENCRYPTION_KEY=generate-another-random-string-32-chars
|
||||
|
||||
# Application
|
||||
KEYWARDEN_PORT=8080
|
||||
KEYWARDEN_LOG_LEVEL=INFO
|
||||
|
||||
# Initial admin (only used on first startup)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# HTTPS / Reverse Proxy
|
||||
KEYWARDEN_BASE_URL=https://keywarden.example.com
|
||||
KEYWARDEN_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
KEYWARDEN_SECURE_COOKIES=true
|
||||
|
||||
# Rate Limiting
|
||||
KEYWARDEN_RATE_LIMIT_LOGIN=10
|
||||
KEYWARDEN_MAX_REQUEST_SIZE=10485760
|
||||
|
||||
# Email (optional)
|
||||
KEYWARDEN_SMTP_HOST=smtp.example.com
|
||||
KEYWARDEN_SMTP_PORT=587
|
||||
KEYWARDEN_SMTP_USER=keywarden@example.com
|
||||
KEYWARDEN_SMTP_PASSWORD=smtp-password
|
||||
KEYWARDEN_SMTP_FROM=keywarden@example.com
|
||||
KEYWARDEN_SMTP_TLS=true
|
||||
```
|
||||
|
||||
See [Environment Variables](environment-variables.md) for a complete reference.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
All persistent data is stored in the `/data` volume:
|
||||
|
||||
| Path | Content |
|
||||
|---|---|
|
||||
| `/data/keywarden.db` | SQLite database (users, keys, servers, settings, audit log) |
|
||||
| `/data/keys/` | Reserved for future use |
|
||||
| `/data/master/` | Reserved for future use |
|
||||
| `/data/avatars/` | User profile pictures |
|
||||
|
||||
> **Important:** The SQLite database contains encrypted private keys. Back up the `/data` volume regularly. See [Backup & Restore](backup-restore.md).
|
||||
|
||||
## Reverse Proxy Setup
|
||||
|
||||
For production use, place Keywarden behind a reverse proxy with TLS termination.
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name keywarden.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/keywarden.crt;
|
||||
ssl_certificate_key /etc/ssl/private/keywarden.key;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Recommended limits
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Caddy
|
||||
|
||||
```caddyfile
|
||||
keywarden.example.com {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
### Traefik
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.keywarden.rule=Host(`keywarden.example.com`)"
|
||||
- "traefik.http.routers.keywarden.tls=true"
|
||||
- "traefik.http.services.keywarden.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
||||
### Important Notes for Reverse Proxy
|
||||
|
||||
1. **Set `KEYWARDEN_BASE_URL`**: Required for correct email links and cookie configuration
|
||||
2. **Set `KEYWARDEN_TRUSTED_PROXIES`**: Configure the CIDR range of your reverse proxy so Keywarden can extract the real client IP from `X-Forwarded-For` headers
|
||||
3. **Set `KEYWARDEN_SECURE_COOKIES=true`**: Enable secure cookie flag when using HTTPS (auto-derived from `KEYWARDEN_BASE_URL` if the URL starts with `https://`)
|
||||
|
||||
## Health Check
|
||||
|
||||
Keywarden provides a health check endpoint at `/api/health` that returns JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"uptime": "2d 5h 30m",
|
||||
"uptime_seconds": 194400,
|
||||
"checks": {
|
||||
"database": {
|
||||
"status": "ok"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Docker HEALTHCHECK is configured automatically in the Dockerfile.
|
||||
|
||||
## Updating
|
||||
|
||||
To update Keywarden:
|
||||
|
||||
```bash
|
||||
# Pull latest changes (if building from source)
|
||||
git pull
|
||||
|
||||
# Rebuild and restart
|
||||
docker compose down
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Database migrations run automatically on startup. No manual migration steps are required.
|
||||
|
||||
## System Master Key
|
||||
|
||||
On first startup, Keywarden generates an **Ed25519 system master key**. This key is used for all SSH connections to managed servers (deploying keys, creating users, etc.).
|
||||
|
||||
The public key is displayed in:
|
||||
- The startup log output
|
||||
- The Admin Settings page (owner only)
|
||||
|
||||
You must deploy this public key to every server you want to manage:
|
||||
|
||||
```bash
|
||||
# On each target server
|
||||
echo "<master-public-key>" >> /root/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
The master key can be regenerated from the Admin Settings page if needed (owner only). After regeneration, redeploy the new public key to all servers.
|
||||
110
docs/email.md
Normal file
110
docs/email.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Email Configuration
|
||||
|
||||
Keywarden supports SMTP-based email for login notifications and user invitations. Email is optional — all core functionality works without it.
|
||||
|
||||
## Enabling Email
|
||||
|
||||
Set the `KEYWARDEN_SMTP_HOST` environment variable to enable email:
|
||||
|
||||
```env
|
||||
KEYWARDEN_SMTP_HOST=smtp.example.com
|
||||
KEYWARDEN_SMTP_PORT=587
|
||||
KEYWARDEN_SMTP_USER=keywarden@example.com
|
||||
KEYWARDEN_SMTP_PASSWORD=your-password
|
||||
KEYWARDEN_SMTP_FROM=keywarden@example.com
|
||||
KEYWARDEN_SMTP_TLS=true
|
||||
```
|
||||
|
||||
If `KEYWARDEN_SMTP_HOST` is empty, email is completely disabled and all email-related features are hidden from the UI.
|
||||
|
||||
## TLS Configuration
|
||||
|
||||
| Port | Mode | Setting |
|
||||
|---|---|---|
|
||||
| `587` | STARTTLS | `KEYWARDEN_SMTP_TLS=true` (default) |
|
||||
| `465` | Implicit TLS | `KEYWARDEN_SMTP_TLS=true` + `KEYWARDEN_SMTP_PORT=465` |
|
||||
| `25` | Unencrypted | `KEYWARDEN_SMTP_TLS=false` (not recommended) |
|
||||
|
||||
- Port **587** uses STARTTLS (upgrade from plaintext to TLS after connection)
|
||||
- Port **465** uses implicit TLS (TLS from the start)
|
||||
- Minimum TLS version: TLS 1.2
|
||||
|
||||
## Email Features
|
||||
|
||||
### Login Notifications
|
||||
|
||||
Users can individually enable login notification emails in their account settings. When enabled, each successful login triggers an email containing:
|
||||
|
||||
- Username
|
||||
- Client IP address
|
||||
- Timestamp
|
||||
- User agent (browser information)
|
||||
|
||||
Emails are sent asynchronously in a background goroutine to avoid slowing down the login flow.
|
||||
|
||||
### User Invitations
|
||||
|
||||
When an admin creates a new user, they can choose to send an **invitation email** instead of setting a password manually. The invitation email contains:
|
||||
|
||||
- The username
|
||||
- A secure one-time link (`/invite/{token}`)
|
||||
- Expiration time (48 hours)
|
||||
|
||||
The invitation token is a 32-byte cryptographic random value, base32-encoded. Once used, the token is marked as consumed and cannot be reused.
|
||||
|
||||
### Test Email
|
||||
|
||||
The owner can send a test email from the Admin Settings page to verify that SMTP configuration is working correctly.
|
||||
|
||||
## Email Templates
|
||||
|
||||
All emails are sent as **multipart/alternative** (both plain text and HTML). The HTML versions use responsive, inline-styled templates.
|
||||
|
||||
### Login Notification Email
|
||||
|
||||
- **Subject**: `Keywarden: Login notification for {username}`
|
||||
- **Content**: IP address, timestamp, user agent
|
||||
- **Trigger**: Successful login when the user has notifications enabled
|
||||
|
||||
### Invitation Email
|
||||
|
||||
- **Subject**: `Keywarden: You have been invited – {username}`
|
||||
- **Content**: Username, registration link, expiration time
|
||||
- **Trigger**: Admin creates a user with "Send Invitation" enabled
|
||||
|
||||
### Test Email
|
||||
|
||||
- **Subject**: `Keywarden: SMTP Test Email`
|
||||
- **Content**: Confirmation that SMTP is working
|
||||
|
||||
## Base URL Requirement
|
||||
|
||||
For invitation emails to contain the correct link, set the `KEYWARDEN_BASE_URL` environment variable:
|
||||
|
||||
```env
|
||||
KEYWARDEN_BASE_URL=https://keywarden.example.com
|
||||
```
|
||||
|
||||
If not set, invitation links use relative paths which may not work in email clients.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email Not Sent
|
||||
|
||||
1. Check that `KEYWARDEN_SMTP_HOST` is set
|
||||
2. Verify SMTP credentials
|
||||
3. Check the application logs for SMTP errors (`KEYWARDEN_LOG_LEVEL=DEBUG` for details)
|
||||
4. Try sending a test email from Admin Settings
|
||||
5. Verify network connectivity from the container to the SMTP server
|
||||
|
||||
### TLS Errors
|
||||
|
||||
- Ensure the SMTP server supports TLS 1.2+
|
||||
- For self-signed certificates, the default TLS configuration may reject them
|
||||
- Try different port/TLS combinations
|
||||
|
||||
### Authentication Failures
|
||||
|
||||
- Verify username and password
|
||||
- Some providers require app-specific passwords (e.g., Gmail, Microsoft 365)
|
||||
- Check if the SMTP server requires a specific authentication mechanism
|
||||
115
docs/environment-variables.md
Normal file
115
docs/environment-variables.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Environment Variables
|
||||
|
||||
Complete reference of all configuration options for Keywarden. All settings are read from environment variables at startup.
|
||||
|
||||
## Core Settings
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `KEYWARDEN_PORT` | `8080` | HTTP server listen port |
|
||||
| `KEYWARDEN_DB_PATH` | `./data/keywarden.db` | Path to the SQLite database file |
|
||||
| `KEYWARDEN_DATA_DIR` | `./data` | Base directory for persistent data |
|
||||
| `KEYWARDEN_KEYS_DIR` | `./data/keys` | Directory for key storage (reserved) |
|
||||
| `KEYWARDEN_MASTER_DIR` | `./data/master` | Directory for master key storage (reserved) |
|
||||
| `KEYWARDEN_LOG_LEVEL` | `INFO` | Log level: `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` |
|
||||
|
||||
## Security
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `KEYWARDEN_SESSION_KEY` | `change-me-in-production-please` | Secret key for session cookie signing. **Change this!** |
|
||||
| `KEYWARDEN_ENCRYPTION_KEY` | `change-me-encryption-key-32chars` | Encryption key for SSH private keys (AES-256). **Change this!** |
|
||||
| `KEYWARDEN_BASE_URL` | _(empty)_ | External base URL (e.g., `https://keywarden.example.com`). Used for email links and cookie configuration. Auto-derives `KEYWARDEN_SECURE_COOKIES` from scheme. |
|
||||
| `KEYWARDEN_TRUSTED_PROXIES` | _(empty)_ | Comma-separated CIDR ranges or IPs of trusted reverse proxies (e.g., `10.0.0.0/8,172.16.0.0/12`). When set, `X-Forwarded-For` is only honored from these networks. |
|
||||
| `KEYWARDEN_SECURE_COOKIES` | _(auto)_ | Set `true` to enable `Secure` flag on cookies. Auto-derived from `KEYWARDEN_BASE_URL` if it starts with `https://`. |
|
||||
| `KEYWARDEN_RATE_LIMIT_LOGIN` | `10` | Maximum login POST attempts per IP per minute. Set to `0` to disable. |
|
||||
| `KEYWARDEN_MAX_REQUEST_SIZE` | `10485760` | Maximum request body size in bytes (default: 10 MB). Set to `0` for no limit. |
|
||||
|
||||
## Initial Admin Account
|
||||
|
||||
These variables are only used on first startup when no users exist in the database:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `KEYWARDEN_ADMIN_USER` | `admin` | Username for the initial owner account |
|
||||
| `KEYWARDEN_ADMIN_EMAIL` | `admin@keywarden.local` | Email for the initial owner account |
|
||||
|
||||
The initial password is auto-generated (20 characters, alphanumeric) and printed to the startup log. It must be changed on first login.
|
||||
|
||||
## Email / SMTP
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `KEYWARDEN_SMTP_HOST` | _(empty)_ | SMTP server hostname. Email is disabled if not set. |
|
||||
| `KEYWARDEN_SMTP_PORT` | `587` | SMTP server port. Use `587` for STARTTLS or `465` for implicit TLS. |
|
||||
| `KEYWARDEN_SMTP_USER` | _(empty)_ | SMTP authentication username |
|
||||
| `KEYWARDEN_SMTP_PASSWORD` | _(empty)_ | SMTP authentication password |
|
||||
| `KEYWARDEN_SMTP_FROM` | `keywarden@localhost` | Sender email address (`From` header) |
|
||||
| `KEYWARDEN_SMTP_TLS` | `true` | Enable TLS for SMTP connections. Set `false` for unencrypted SMTP (not recommended). |
|
||||
|
||||
## Docker-Specific Defaults
|
||||
|
||||
When running in the Docker container, these defaults are set in the Dockerfile:
|
||||
|
||||
| Variable | Docker Default |
|
||||
|---|---|
|
||||
| `KEYWARDEN_PORT` | `8080` |
|
||||
| `KEYWARDEN_DB_PATH` | `/data/keywarden.db` |
|
||||
| `KEYWARDEN_DATA_DIR` | `/data` |
|
||||
| `KEYWARDEN_KEYS_DIR` | `/data/keys` |
|
||||
| `KEYWARDEN_MASTER_DIR` | `/data/master` |
|
||||
|
||||
## Example .env File
|
||||
|
||||
```env
|
||||
# ──────────────────────────────────────────────
|
||||
# Keywarden Configuration
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
# Security (REQUIRED - change these!)
|
||||
KEYWARDEN_SESSION_KEY=Rj9kL2mN4pQ8sT1vW3xY5zA7bC0dF6gH
|
||||
KEYWARDEN_ENCRYPTION_KEY=mX9nP2qR4sT6uV8wY0zA1bC3dE5fG7hI
|
||||
|
||||
# Application
|
||||
KEYWARDEN_PORT=8080
|
||||
KEYWARDEN_LOG_LEVEL=INFO
|
||||
|
||||
# Initial admin (only used on first startup)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# Reverse proxy / HTTPS
|
||||
KEYWARDEN_BASE_URL=https://keywarden.example.com
|
||||
KEYWARDEN_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
|
||||
# Rate limiting
|
||||
KEYWARDEN_RATE_LIMIT_LOGIN=10
|
||||
KEYWARDEN_MAX_REQUEST_SIZE=10485760
|
||||
|
||||
# Email (optional)
|
||||
KEYWARDEN_SMTP_HOST=smtp.example.com
|
||||
KEYWARDEN_SMTP_PORT=587
|
||||
KEYWARDEN_SMTP_USER=keywarden@example.com
|
||||
KEYWARDEN_SMTP_PASSWORD=your-smtp-password
|
||||
KEYWARDEN_SMTP_FROM=keywarden@example.com
|
||||
KEYWARDEN_SMTP_TLS=true
|
||||
```
|
||||
|
||||
## Application Settings (Database)
|
||||
|
||||
In addition to environment variables, the following settings are configured through the web UI (Admin Settings page, owner only) and stored in the database:
|
||||
|
||||
| Setting Key | Default | Description |
|
||||
|---|---|---|
|
||||
| `app_name` | `Keywarden` | Application display name in the UI |
|
||||
| `default_key_type` | `ed25519` | Default key type for generation |
|
||||
| `default_key_bits` | `256` | Default key size |
|
||||
| `session_timeout` | `60` | Session inactivity timeout in minutes |
|
||||
| `pw_min_length` | `8` | Password minimum length |
|
||||
| `pw_require_upper` | `true` | Require uppercase letter |
|
||||
| `pw_require_lower` | `true` | Require lowercase letter |
|
||||
| `pw_require_digit` | `true` | Require digit |
|
||||
| `pw_require_special` | `false` | Require special character |
|
||||
| `lockout_attempts` | `5` | Failed login attempts before lockout (0 = disabled) |
|
||||
| `lockout_duration` | `15` | Lockout duration in minutes |
|
||||
| `mfa_required` | `false` | Enforce MFA for all users |
|
||||
122
docs/quickstart.md
Normal file
122
docs/quickstart.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get Keywarden running in under 5 minutes using Docker Compose.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- A Linux host (or any system that runs Docker)
|
||||
|
||||
## 1. Create Project Directory
|
||||
|
||||
```bash
|
||||
mkdir keywarden && cd keywarden
|
||||
```
|
||||
|
||||
## 2. Create Environment File
|
||||
|
||||
Create a `.env` file with at minimum these settings:
|
||||
|
||||
```env
|
||||
# REQUIRED: Change these for security!
|
||||
KEYWARDEN_SESSION_KEY=your-random-session-key-at-least-32-characters
|
||||
KEYWARDEN_ENCRYPTION_KEY=your-random-encryption-key-at-least-32-chars
|
||||
|
||||
# Optional: Admin credentials (defaults: admin / auto-generated password)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# Optional: Port (default: 8080)
|
||||
KEYWARDEN_PORT=8080
|
||||
```
|
||||
|
||||
> **Important:** The `KEYWARDEN_ENCRYPTION_KEY` is used to encrypt all private keys at rest. If you lose this key, stored private keys cannot be decrypted. Keep it safe!
|
||||
|
||||
## 3. Create docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
keywarden:
|
||||
image: git.techniverse.net/scriptos/keywarden:latest
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
Or, to build from source:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
keywarden:
|
||||
build: .
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
## 4. Start Keywarden
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 5. Get the Initial Password
|
||||
|
||||
On first startup, Keywarden creates an owner account and generates a secure random password. Check the logs:
|
||||
|
||||
```bash
|
||||
docker compose logs keywarden
|
||||
```
|
||||
|
||||
Look for output like:
|
||||
|
||||
```
|
||||
════════════════════════════════════════════════════════════
|
||||
Initial owner account created
|
||||
Username: admin
|
||||
Password: AbCdEf1234567890XyZw
|
||||
Please change this password after first login!
|
||||
════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## 6. Log In
|
||||
|
||||
Open your browser and navigate to `http://your-host:8080`, then log in with the credentials from the logs.
|
||||
|
||||
You will be prompted to change the initial password on first login.
|
||||
|
||||
## 7. Deploy the Master Key
|
||||
|
||||
After login, Keywarden displays the **system master key** (an Ed25519 public key). This key must be placed in the `~/.ssh/authorized_keys` file of the admin/root user on every server you want to manage.
|
||||
|
||||
The master key is shown on the **Admin Settings** page and in the startup logs.
|
||||
|
||||
```bash
|
||||
# On each target server, as root:
|
||||
echo "ssh-ed25519 AAAA... keywarden-system-master" >> ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
|
||||
- [Full Deployment Guide](deployment.md) — Production setup with HTTPS and reverse proxy
|
||||
- [User Guide](user-guide.md) — How to manage SSH keys
|
||||
- [Admin Guide](admin-guide.md) — How to manage servers and access assignments
|
||||
- [Environment Variables](environment-variables.md) — All configuration options
|
||||
122
docs/roles.md
Normal file
122
docs/roles.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Roles & Permissions
|
||||
|
||||
Keywarden uses a three-tier role system: **Owner**, **Admin**, and **User**. Each role inherits all permissions of the role below it.
|
||||
|
||||
## Role Hierarchy
|
||||
|
||||
```
|
||||
Owner → Admin → User
|
||||
```
|
||||
|
||||
## Role Comparison
|
||||
|
||||
| Capability | User | Admin | Owner |
|
||||
|---|:---:|:---:|:---:|
|
||||
| **SSH Keys** | | | |
|
||||
| Generate SSH keys | ✅ | ✅ | ✅ |
|
||||
| Import SSH keys | ✅ | ✅ | ✅ |
|
||||
| View own keys | ✅ | ✅ | ✅ |
|
||||
| Download own keys | ✅ | ✅ | ✅ |
|
||||
| Delete own keys | ✅ | ✅ | ✅ |
|
||||
| View all users' keys | ❌ | ✅ | ✅ |
|
||||
| Delete any user's keys | ❌ | ✅ | ✅ |
|
||||
| **Account Settings** | | | |
|
||||
| Change own password | ✅ | ✅ | ✅ |
|
||||
| Change theme | ✅ | ✅ | ✅ |
|
||||
| Enable/disable MFA | ✅ | ✅ | ✅ |
|
||||
| Upload avatar | ✅ | ✅ | ✅ |
|
||||
| Toggle email notifications | ✅ | ✅ | ✅ |
|
||||
| **Access** | | | |
|
||||
| View own access assignments | ✅ | ✅ | ✅ |
|
||||
| View own audit log | ✅ | ✅ | ✅ |
|
||||
| **Server Management** | | | |
|
||||
| Add/edit/delete servers | ❌ | ✅ | ✅ |
|
||||
| Add/edit/delete server groups | ❌ | ✅ | ✅ |
|
||||
| Test server connectivity | ❌ | ✅ | ✅ |
|
||||
| **Deployments** | | | |
|
||||
| Manual key deployment | ❌ | ✅ | ✅ |
|
||||
| Group deployment | ❌ | ✅ | ✅ |
|
||||
| **Access Assignments** | | | |
|
||||
| Create/edit/delete assignments | ❌ | ✅ | ✅ |
|
||||
| Sync assignments | ❌ | ✅ | ✅ |
|
||||
| **Temporary Access (Cron)** | | | |
|
||||
| Create/edit/delete cron jobs | ❌ | ✅ | ✅ |
|
||||
| Pause/resume cron jobs | ❌ | ✅ | ✅ |
|
||||
| **User Management** | | | |
|
||||
| Create/edit/delete users | ❌ | ✅ | ✅ |
|
||||
| Unlock locked accounts | ❌ | ✅ | ✅ |
|
||||
| Force password change | ❌ | ✅ | ✅ |
|
||||
| Send user invitations | ❌ | ✅ | ✅ |
|
||||
| **System** | | | |
|
||||
| View system information | ❌ | ✅ | ✅ |
|
||||
| View full audit log | ❌ | ✅ | ✅ |
|
||||
| **Administration** | | | |
|
||||
| Application settings | ❌ | ❌ | ✅ |
|
||||
| Security settings (password policy, MFA enforcement) | ❌ | ❌ | ✅ |
|
||||
| Regenerate master key | ❌ | ❌ | ✅ |
|
||||
| Backup / Restore | ❌ | ❌ | ✅ |
|
||||
| Send test email | ❌ | ❌ | ✅ |
|
||||
|
||||
## Role Details
|
||||
|
||||
### User
|
||||
|
||||
The **User** role is the default role for new accounts. Users can:
|
||||
|
||||
- Manage their own SSH keys (generate, import, download, delete)
|
||||
- View their own access assignments (read-only)
|
||||
- Manage their account settings (password, theme, MFA, avatar, email notifications)
|
||||
- View their own audit log entries
|
||||
|
||||
Users **cannot** manage servers, deploy keys, create access assignments, or view other users' data.
|
||||
|
||||
### Admin
|
||||
|
||||
The **Admin** role has full operational access. In addition to all User permissions, admins can:
|
||||
|
||||
- Manage servers and server groups (add, edit, delete, test connectivity)
|
||||
- Deploy SSH keys to servers (manual and group deployments)
|
||||
- Create and manage access assignments (including sync and cleanup)
|
||||
- Create and manage cron jobs for temporary access
|
||||
- Manage users (create, edit, delete, unlock, force password change, send invitations)
|
||||
- View system information
|
||||
- View the complete audit log (excluding owner entries)
|
||||
|
||||
Admins **cannot** access the Admin Settings page, regenerate the master key, manage backups, or modify security policies.
|
||||
|
||||
### Owner
|
||||
|
||||
The **Owner** role has unrestricted access. In addition to all Admin permissions, the owner can:
|
||||
|
||||
- Access the Admin Settings page
|
||||
- Configure application settings (app name, session timeout, default key type)
|
||||
- Configure security settings (password policy, account lockout, MFA enforcement)
|
||||
- View and regenerate the system master key
|
||||
- Export and import encrypted database backups
|
||||
- Send test emails
|
||||
- View all audit log entries (including owner actions)
|
||||
|
||||
#### Owner Protections
|
||||
|
||||
- The last owner account cannot be deleted
|
||||
- The owner can always access Admin Settings, even when MFA enforcement would otherwise redirect them (to prevent lockout)
|
||||
- On first startup, the initial account is always created with the `owner` role
|
||||
- If no owner exists (e.g., after a migration from an older version), the first admin is automatically promoted to owner
|
||||
|
||||
## Audit Log Visibility
|
||||
|
||||
The audit log has role-based filtering:
|
||||
|
||||
| Viewer | Sees |
|
||||
|---|---|
|
||||
| User | Own actions only |
|
||||
| Admin | All actions except those from owner accounts |
|
||||
| Owner | All actions from all users |
|
||||
|
||||
## Initial Setup
|
||||
|
||||
On first startup, Keywarden creates a single **owner** account. The owner should then:
|
||||
|
||||
1. Change the initial password
|
||||
2. (Optional) Create additional admin accounts
|
||||
3. (Optional) Create regular user accounts or send invitations
|
||||
211
docs/security.md
Normal file
211
docs/security.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# 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
|
||||
215
docs/troubleshooting.md
Normal file
215
docs/troubleshooting.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Troubleshooting
|
||||
|
||||
Common issues and solutions for Keywarden.
|
||||
|
||||
## Startup Issues
|
||||
|
||||
### "Failed to initialize database"
|
||||
|
||||
**Cause**: SQLite database file cannot be created or accessed.
|
||||
|
||||
**Solutions**:
|
||||
- Check that the `/data` directory exists and is writable
|
||||
- Verify the `KEYWARDEN_DB_PATH` environment variable
|
||||
- In Docker: ensure the volume is correctly mounted and the `keywarden` user has write access
|
||||
|
||||
### "Failed to create directory"
|
||||
|
||||
**Cause**: Data directories (`/data`, `/data/keys`, `/data/master`) cannot be created.
|
||||
|
||||
**Solutions**:
|
||||
- Check filesystem permissions
|
||||
- In Docker: the container runs as user `keywarden` — ensure the volume has correct ownership
|
||||
|
||||
### Initial Password Not Showing
|
||||
|
||||
**Cause**: The initial owner password is only printed on the **first startup** when no users exist.
|
||||
|
||||
**Solutions**:
|
||||
- Check the very first startup logs: `docker compose logs keywarden`
|
||||
- If you missed the password, delete the database and restart to trigger a fresh setup:
|
||||
```bash
|
||||
docker compose down
|
||||
docker volume rm keywarden_keywarden_data
|
||||
docker compose up -d
|
||||
docker compose logs keywarden
|
||||
```
|
||||
|
||||
## Login Issues
|
||||
|
||||
### "Invalid username or password"
|
||||
|
||||
- Verify the username (case-sensitive)
|
||||
- Check for typos in the password
|
||||
- If this is the initial login, find the auto-generated password in the startup logs
|
||||
|
||||
### "Account is temporarily locked"
|
||||
|
||||
**Cause**: Too many failed login attempts.
|
||||
|
||||
**Solutions**:
|
||||
- Wait for the lockout period to expire (default: 15 minutes)
|
||||
- Ask an administrator to unlock the account from the user management page
|
||||
- If you're the only owner: wait for the lockout to expire, or delete and recreate the database
|
||||
|
||||
### MFA Code Invalid
|
||||
|
||||
- Verify your authenticator app has the correct time (TOTP is time-based)
|
||||
- Allow ±30 seconds of clock skew
|
||||
- If you lost your MFA device, an admin with database access will need to manually disable MFA
|
||||
|
||||
### "Forbidden – invalid or missing CSRF token"
|
||||
|
||||
**Cause**: CSRF token mismatch. This can happen if:
|
||||
- Your session expired and you submitted a form on a stale page
|
||||
- Cookies are blocked by your browser
|
||||
- A proxy is stripping or modifying cookies
|
||||
|
||||
**Solutions**:
|
||||
- Refresh the page and try again
|
||||
- Clear your browser cookies for the Keywarden domain
|
||||
- Ensure cookies are not being blocked
|
||||
|
||||
## SSH Deployment Issues
|
||||
|
||||
### "System master key not available"
|
||||
|
||||
**Cause**: The system master key is missing or corrupted in the settings table.
|
||||
|
||||
**Solutions**:
|
||||
- Check the startup logs for the master key output
|
||||
- Navigate to Admin Settings and view the master key
|
||||
- If corrupted, regenerate the master key (owner only)
|
||||
|
||||
### "Connection failed" / "Cannot reach server"
|
||||
|
||||
**Cause**: Keywarden cannot establish a TCP connection to the target server.
|
||||
|
||||
**Solutions**:
|
||||
- Verify the server hostname and port
|
||||
- Use the **Connection Test** feature to check TCP connectivity
|
||||
- Ensure the Keywarden container can reach the server's network
|
||||
- Check firewall rules on both sides
|
||||
|
||||
### "SSH authentication failed"
|
||||
|
||||
**Cause**: The system master key is not authorized on the target server.
|
||||
|
||||
**Solutions**:
|
||||
1. Get the master public key from Admin Settings or startup logs
|
||||
2. Add it to the target server:
|
||||
```bash
|
||||
echo "<master-public-key>" >> /root/.ssh/authorized_keys
|
||||
chmod 600 /root/.ssh/authorized_keys
|
||||
```
|
||||
3. Ensure the server's SSH daemon accepts public key authentication
|
||||
4. Use the **Auth Test** feature to verify
|
||||
5. If using a non-root admin user on the target server, ensure that user has permissions to manage `authorized_keys` for other users
|
||||
|
||||
### "Failed to create system user"
|
||||
|
||||
**Cause**: The `useradd` command failed on the target server.
|
||||
|
||||
**Solutions**:
|
||||
- Verify the server's admin user has sufficient privileges (root or sudo)
|
||||
- Check if the username conflicts with an existing user
|
||||
- Review the server's `/var/log/auth.log` for details
|
||||
|
||||
### "Failed to deploy key for user"
|
||||
|
||||
**Cause**: Key deployment to a specific system user failed.
|
||||
|
||||
**Solutions**:
|
||||
- Verify the system user exists (or enable "Create User" in the assignment)
|
||||
- Check directory permissions on the target server
|
||||
- Ensure the admin user can write to other users' `.ssh` directories
|
||||
|
||||
## Email Issues
|
||||
|
||||
### "Email is not configured"
|
||||
|
||||
**Cause**: `KEYWARDEN_SMTP_HOST` is not set.
|
||||
|
||||
**Solution**: Configure SMTP settings in the `.env` file. See [Email Configuration](email.md).
|
||||
|
||||
### SMTP Connection Errors
|
||||
|
||||
- Verify the SMTP host, port, and credentials
|
||||
- Check if the Docker container can reach the SMTP server
|
||||
- Try different TLS settings (`KEYWARDEN_SMTP_TLS=true/false`)
|
||||
- For port 465, ensure implicit TLS is supported by the server
|
||||
- Check if the SMTP server requires app-specific passwords
|
||||
|
||||
### Invitation Emails Not Arriving
|
||||
|
||||
- Verify the recipient's email address
|
||||
- Check spam/junk folders
|
||||
- Review application logs for SMTP errors (`KEYWARDEN_LOG_LEVEL=DEBUG`)
|
||||
- Verify `KEYWARDEN_BASE_URL` is set correctly (needed for the invitation link)
|
||||
- Send a test email from Admin Settings to verify SMTP works
|
||||
|
||||
## Backup Issues
|
||||
|
||||
### "Failed to decrypt backup"
|
||||
|
||||
**Cause**: Wrong backup password.
|
||||
|
||||
**Solution**: Use the exact password that was provided during backup export.
|
||||
|
||||
### "Failed to parse backup"
|
||||
|
||||
**Cause**: The backup file is corrupt or not a valid `.kwbak` file.
|
||||
|
||||
**Solution**: Ensure the file was not modified or corrupted during transfer.
|
||||
|
||||
### SSH Keys Not Working After Restore
|
||||
|
||||
**Cause**: The `KEYWARDEN_ENCRYPTION_KEY` in the current environment doesn't match the one used when the backup was created.
|
||||
|
||||
**Solution**: Set `KEYWARDEN_ENCRYPTION_KEY` to the same value that was in use when the backup was created.
|
||||
|
||||
## Performance
|
||||
|
||||
### Slow Page Loads
|
||||
|
||||
- Check the log level — `TRACE` and `DEBUG` can be verbose
|
||||
- SQLite WAL mode is enabled by default for better concurrent read performance
|
||||
- The in-memory session store scales well for typical deployments
|
||||
|
||||
### High Memory Usage
|
||||
|
||||
- Sessions are stored in memory — many active sessions increase memory
|
||||
- The session cleanup goroutine runs every minute to remove expired sessions
|
||||
- Avatar images are served from disk, not stored in memory
|
||||
|
||||
## Logs
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
docker compose logs keywarden
|
||||
docker compose logs -f keywarden # follow
|
||||
|
||||
# Log levels
|
||||
KEYWARDEN_LOG_LEVEL=DEBUG # more detail
|
||||
KEYWARDEN_LOG_LEVEL=TRACE # maximum verbosity
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
| Level | Output |
|
||||
|---|---|
|
||||
| `ERROR` | Only errors |
|
||||
| `WARN` | Errors + warnings |
|
||||
| `INFO` | Errors + warnings + informational (default) |
|
||||
| `DEBUG` | All of the above + debug details |
|
||||
| `TRACE` | Maximum verbosity, including request/response details |
|
||||
|
||||
### Request Logging
|
||||
|
||||
Every HTTP request is logged with:
|
||||
- Method, path, status code
|
||||
- Response time
|
||||
- Client IP address
|
||||
- Username (if authenticated)
|
||||
153
docs/user-guide.md
Normal file
153
docs/user-guide.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# User Guide
|
||||
|
||||
This guide covers everyday usage of Keywarden for all authenticated users. For administrative tasks (server management, access assignments, etc.), see the [Admin Guide](admin-guide.md).
|
||||
|
||||
## Logging In
|
||||
|
||||
Navigate to your Keywarden instance in a web browser and enter your username and password.
|
||||
|
||||
- If **MFA is enabled** on your account, you will be prompted for a TOTP code after entering your password.
|
||||
- If your account is **locked** due to too many failed login attempts, wait for the lockout period to expire or ask an administrator to unlock it.
|
||||
- If you were **invited via email**, use the invitation link to set your password before logging in.
|
||||
|
||||
## Dashboard
|
||||
|
||||
The dashboard provides an overview of your environment:
|
||||
|
||||
- **Key Count** — Number of SSH keys you own
|
||||
- **Server Count** — Servers you have access to (admins/owners see all servers)
|
||||
- **Group Count** — Server groups (admins/owners see all groups)
|
||||
- **Assignment Count** — Access assignments related to you
|
||||
- **Recent Keys** — Latest SSH keys
|
||||
- **Recent Audit Log** — Your recent activity (admins see global activity)
|
||||
- **Recent Deployments** — Latest key deployment results
|
||||
|
||||
## SSH Key Management
|
||||
|
||||
### Generating Keys
|
||||
|
||||
1. Navigate to **Keys** → **Generate Key**
|
||||
2. Fill in the form:
|
||||
- **Name**: A descriptive name for the key (e.g., "Production Deploy Key")
|
||||
- **Key Type**: Choose between:
|
||||
- **Ed25519** (recommended) — Fast, secure, compact. 256-bit.
|
||||
- **RSA 2048** — Widely compatible
|
||||
- **RSA 4096** — Maximum RSA security
|
||||
- **Ed448** — 224-bit security level (experimental)
|
||||
- **Comment**: Optional comment embedded in the public key
|
||||
3. Click **Generate**
|
||||
|
||||
The private key is encrypted with AES-256-GCM and stored in the database. It never touches the filesystem in plaintext.
|
||||
|
||||
### Importing Keys
|
||||
|
||||
1. Navigate to **Keys** → **Import Key**
|
||||
2. Enter a **name** for the key
|
||||
3. Paste the **private key** (PEM format) into the text area
|
||||
4. Click **Import**
|
||||
|
||||
Keywarden automatically detects the key type (RSA, Ed25519, Ed448) and extracts the public key and fingerprint.
|
||||
|
||||
### Viewing Keys
|
||||
|
||||
The **Keys** page lists all your SSH keys with:
|
||||
- Name, type, key size
|
||||
- SHA-256 fingerprint
|
||||
- Creation date
|
||||
|
||||
Admins and owners see all keys in the system, grouped by owner.
|
||||
|
||||
### Downloading Keys
|
||||
|
||||
From the key list, you can download:
|
||||
- **Public Key** — For deployment to servers
|
||||
- **Private Key** — Decrypted and downloaded (use with caution)
|
||||
|
||||
### Deleting Keys
|
||||
|
||||
Click the delete button next to a key. This permanently removes both the public and encrypted private key from the database.
|
||||
|
||||
> **Note:** Deleting a key from Keywarden does **not** remove it from servers where it was previously deployed.
|
||||
|
||||
## My Access
|
||||
|
||||
Navigate to **My Access** to see all access assignments that grant you access to servers:
|
||||
|
||||
- **Target** — Server or server group
|
||||
- **System User** — The Linux user account on the target server
|
||||
- **SSH Key** — Which of your keys is deployed
|
||||
- **Sudo** — Whether sudo privileges are granted
|
||||
- **Status** — Current sync status (pending, synced, failed)
|
||||
- **Initial Password** — If a system user was created for you, the initial password is shown here
|
||||
|
||||
This is a read-only view. Only administrators can create, modify, or delete access assignments.
|
||||
|
||||
## User Settings
|
||||
|
||||
Navigate to **Settings** to manage your account:
|
||||
|
||||
### Theme
|
||||
|
||||
Choose between:
|
||||
- **Auto** — Follows your system/browser preference
|
||||
- **Light** — Always light mode
|
||||
- **Dark** — Always dark mode
|
||||
|
||||
### Password Change
|
||||
|
||||
Change your password. The new password must comply with the configured password policy (displayed on the form).
|
||||
|
||||
### Two-Factor Authentication (MFA)
|
||||
|
||||
#### Enabling MFA
|
||||
|
||||
1. Go to **Settings** → **Two-Factor Authentication**
|
||||
2. Click **Enable MFA**
|
||||
3. Scan the QR code with an authenticator app (Google Authenticator, Authy, etc.)
|
||||
4. Enter the 6-digit code from your app to confirm
|
||||
5. MFA is now active for your account
|
||||
|
||||
#### Disabling MFA
|
||||
|
||||
Click **Disable MFA** in settings. This is only available if the administrator has not enforced MFA system-wide.
|
||||
|
||||
> If MFA is enforced by the administrator, you **must** set it up before you can access any other page.
|
||||
|
||||
### Email Notifications
|
||||
|
||||
If email is configured, you can enable **Login Notifications**. Every time someone logs into your account, you'll receive an email with:
|
||||
- IP address
|
||||
- Timestamp
|
||||
- User agent (browser)
|
||||
|
||||
### Profile Picture
|
||||
|
||||
Upload a profile picture (avatar) that is displayed next to your name in the navigation. Supported formats: JPEG, PNG, GIF, WebP. Maximum size is limited by the server's request size limit.
|
||||
|
||||
## Audit Log
|
||||
|
||||
Navigate to **Audit** to view the activity log:
|
||||
|
||||
- **Regular users** see their own activity
|
||||
- **Admins** see activity from all non-owner users
|
||||
- **Owners** see all activity
|
||||
|
||||
The audit log records every significant action including logins, key operations, deployments, settings changes, and administrative actions.
|
||||
|
||||
## Forced Password Change
|
||||
|
||||
If an administrator flags your account for a mandatory password change, you will be redirected to the password change page on every request until you set a new password. This typically happens:
|
||||
- After your initial account creation
|
||||
- If an admin resets your password
|
||||
- If there's a security concern
|
||||
|
||||
## Invitation Flow
|
||||
|
||||
If you receive an invitation email:
|
||||
|
||||
1. Click the invitation link
|
||||
2. You'll see a registration page with your pre-assigned username
|
||||
3. Choose a password that meets the password policy
|
||||
4. Confirm the password
|
||||
5. Click **Set Password**
|
||||
6. You can now log in with your username and new password
|
||||
Reference in New Issue
Block a user