Storage. The server keeps its data in SQLite (a file in the data directory, the default) or PostgreSQL (database, KEEPORG_DATABASE_URL or -database). Every row, the KDBX versions included, is sealed with AES-256-GCM under a key derived from the master key and named by an HMAC. The master key is required to start and a different one is refused. A data directory from 0.10 or earlier is imported on first start. Several servers may share one PostgreSQL database. Go goes to 1.26 for the pure-Go drivers (modernc.org/sqlite, jackc/pgx). Hardening, after an audit of the server, both clients and the deployment: - Single sign-on approves a device only after an explicit Approve in the browser that started it, bound by a cookie and PKCE; identities are never linked by username, by verified email only when allowed, never to admins. - Sign-in attempts are limited per address and per account, passwords are checked outside the store lock with uniform timing, one-time codes work once, and starting two-factor needs the password. - Merge vaults bound KDF parameters, payload size and XML shape, check pushes before storing them, and run at most two merges at once. A version is written in the same transaction as its metadata. - Tokens in URLs only on the event stream, which ends on revocation; CSP, frame-ancestors, no-store and HSTS; sign-in CSRF; 404 for databases without access; new trusted_proxies. - Clients open only the server's approval page, keep case-variant and retired file names apart, never overwrite a save made during a pull, require https except locally, send the stream token in a header, bound their reads and escape notifications and terminal output. - The .deb creates the master key, systemd is sandboxed further, nginx sets modern TLS and HSTS, and CI deploys from main only. Server 0.12.0, desktop 0.6.0, Android 0.4.0 (versionCode 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
7.1 KiB
Markdown
149 lines
7.1 KiB
Markdown
# KeepOrg
|
|
|
|
A synchronization adapter for KeePass. Your databases stay ordinary `.kdbx`
|
|
files that KeePassXC, KeePass2 or KeePassDX opens directly; a server keeps
|
|
them in step between everyone who has access.
|
|
|
|
When two people save at once, the server can **merge** both edits: it opens
|
|
the two copies in memory, folds them together by KeePass's own rules and
|
|
stores the result. Nothing is lost, and no one has to reconcile two files by
|
|
hand.
|
|
|
|
Merging means decrypting, so a merge vault's password is kept on the server,
|
|
encrypted under a master key held outside the data directory. That makes the
|
|
server part of your trust boundary. Vaults on any other conflict strategy
|
|
keep their key off the server entirely, exactly as before.
|
|
|
|
- **Server** (`keeporgd`, Linux): hosts many KDBX databases, controls access
|
|
with a tree of user groups, accepts registrations by invite link or OIDC,
|
|
merges concurrent edits, and pushes updates to connected clients. Its web
|
|
page at `/web` is for creating a vault and setting its password.
|
|
- **Desktop client** (`keeporg`, Linux, macOS, Windows, BSD): a system tray
|
|
application that mirrors every assigned database into one directory as a
|
|
real file, pushes each save and applies what others push. The same binary
|
|
is the command line for administration and for headless machines.
|
|
- **Android client**: the same, published to every file picker as a storage
|
|
provider.
|
|
|
|
No FUSE and no cgo. The server keeps everything in SQLite or PostgreSQL,
|
|
every row sealed under a master key the database never sees, and its only
|
|
third-party code is the two pure-Go database drivers. The KDBX 4 codec,
|
|
including ChaCha20, BLAKE2b and Argon2, is part of the repository and is
|
|
tested against both the relevant RFCs and a real KeePassXC. The desktop
|
|
client's one dependency is `fyne.io/systray`, for the tray icon.
|
|
|
|
## Quick start
|
|
|
|
```sh
|
|
make check # gofmt, vet, tests
|
|
make all # keeporgd and keeporg for this host
|
|
|
|
# a server for trying it out, on 127.0.0.1:8765
|
|
KEEPORG_ADMIN_PASSWORD=dev-password-1 make run-server
|
|
|
|
# in another shell
|
|
build/linux-arm64/keeporg login -server http://127.0.0.1:8765 -user admin
|
|
build/linux-arm64/keeporg groups
|
|
build/linux-arm64/keeporg vault -create "Team Vault" -group <group-id>
|
|
build/linux-arm64/keeporg # the tray; `keeporg run` without a desktop
|
|
```
|
|
|
|
`Team Vault.kdbx` appears in `~/KeepOrg`; open it in KeePassXC with the vault
|
|
password you just set. Every save is pushed, and a save that races with a
|
|
colleague's is merged rather than refused.
|
|
|
|
## The tray
|
|
|
|
Started without a command — from the applications menu, the DMG's
|
|
`KeepOrg.app`, or `keeporg-tray.exe` on Windows — the client puts a padlock
|
|
in the system tray:
|
|
|
|
| Item | What it does |
|
|
| --- | --- |
|
|
| *status line* | signed-in account and whether everything is up to date, or what needs attention |
|
|
| **Open Mirror Folder** | the directory holding the `.kdbx` files |
|
|
| **Databases ▶** | each database with its state; choosing one opens it in your KeePass application |
|
|
| **Sync Now** | reconcile immediately instead of at the next rescan |
|
|
| **Sign In… / Sign Out** | sign in by approving a code in the browser (password, second factor or SSO); sign out revokes the session and leaves the files |
|
|
| **Open Web Interface** | `/web` on the server, for creating vaults and managing access |
|
|
| **Open Config** | the settings file; a saved edit applies at once |
|
|
| **Open Log** | the agent's log |
|
|
| **Start at Login** | LaunchAgent on macOS, XDG autostart on Linux and BSD, the Run key on Windows |
|
|
| **About**, **Quit** | |
|
|
|
|
Conflicts, merges and failures raise a desktop notification.
|
|
|
|
## Configuration
|
|
|
|
`config.json` in the user configuration directory
|
|
(`~/.config/keeporg`, `~/Library/Application Support/keeporg`,
|
|
`%AppData%\keeporg`) holds every client setting. The first start writes it
|
|
in full, and the tray reloads it when it is saved.
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `server_url` | | the server's base URL, as a browser sees it |
|
|
| `username`, `token` | | written by signing in |
|
|
| `device_label` | host name | this device in the server's session list |
|
|
| `mirror_dir` | `~/KeepOrg` | where the `.kdbx` files are kept; `~` is expanded |
|
|
| `poll_seconds` | `2` | how often local saves are looked for |
|
|
| `request_timeout_seconds` | `60` | the limit on one API request (the event stream is exempt) |
|
|
| `ca_file` | | a PEM bundle trusted besides the system roots, for a private CA |
|
|
| `log_file` | | the log; empty means `keeporg.log` beside the config for the tray, standard error for `keeporg run` |
|
|
| `notifications` | `true` | desktop notifications from the tray |
|
|
|
|
Or open `http://127.0.0.1:8765/web` and create the vault from the browser.
|
|
|
|
## Packaging
|
|
|
|
```sh
|
|
make deb # Debian packages for the client and the server
|
|
make docker # server container image, scratch-based, about 25 MB
|
|
make dmg # macOS disk image with an /Applications symlink (on macOS)
|
|
make windows # Windows zip: keeporg-tray.exe, keeporg.exe and a Task Scheduler job
|
|
make android # unsigned release APK (needs the Android SDK)
|
|
make help # every target
|
|
```
|
|
|
|
Running the server in a container:
|
|
|
|
```bash
|
|
head -c 32 /dev/urandom | base64 > master.key
|
|
podman secret create keeporg_master_key master.key
|
|
podman run -d -p 8765:8765 \
|
|
--secret keeporg_master_key \
|
|
-e KEEPORG_MASTER_KEY_FILE=/run/secrets/keeporg_master_key \
|
|
-e KEEPORG_ADMIN_PASSWORD=change-me \
|
|
-v keeporg-data:/var/lib/keeporg \
|
|
keeporgd:latest -addr :8765 -data /var/lib/keeporg -url https://keeporg.example.com
|
|
```
|
|
|
|
Keep that master key: everything in the database is encrypted under it,
|
|
and the server does not start without it. `KEEPORG_MASTER_KEY` takes the
|
|
base64 key directly if your secret store injects variables instead of
|
|
files. The data goes to a SQLite file on the volume; to use PostgreSQL
|
|
instead, add `-e KEEPORG_DATABASE_URL=postgres://keeporg:…@db/keeporg`. See
|
|
[packaging/docker/compose.yaml](packaging/docker/compose.yaml).
|
|
|
|
Server environment variables:
|
|
|
|
| Variable | Meaning |
|
|
| --- | --- |
|
|
| `KEEPORG_MASTER_KEY` | the master key, base64; overrides the other two sources |
|
|
| `KEEPORG_MASTER_KEY_FILE` | path to a file holding the master key (base64 or 32 raw bytes); never created, and the server does not start without it |
|
|
| `KEEPORG_EXTERNAL_URL` | the public base URL (`external_url`); `-url` overrides it |
|
|
| `KEEPORG_DATABASE_URL` | `postgres://…` or `sqlite:<path>` (`database`); default a SQLite file in the data directory; `-database` overrides it |
|
|
| `KEEPORG_TRUSTED_PROXIES` | reverse proxies whose `X-Forwarded-For` is believed, for per-client rate limits (`trusted_proxies`) |
|
|
| `KEEPORG_ADMIN_PASSWORD` | the first `admin` password, read only on an empty server and by `-reset-admin` |
|
|
|
|
Everything else is configured in `server.json` or by flag. The details,
|
|
including the standard proxy and CA variables, are in
|
|
[docs/HANDOVER.md](docs/HANDOVER.md#environment-variables).
|
|
|
|
## Documentation
|
|
|
|
- [docs/HANDOVER.md](docs/HANDOVER.md) — architecture, the decisions behind
|
|
it, operations, and what is left to do.
|
|
- [docs/PROTOCOL.md](docs/PROTOCOL.md) — the HTTP API.
|
|
- [android/README.md](android/README.md) — the Android module.
|