diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-17 21:26:20 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-17 21:26:20 +0200 |
| commit | ed1c15929d2eb2dbc6432986c26661bf1549964a (patch) | |
| tree | 4f04934edef686b19d16e1bb2f79bb5c15e142de /docs | |
| parent | 380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (diff) | |
| download | bokf-d8175a8f76bc5eefa2524f2b245cb8a5b45864ba.tar.gz bokf-d8175a8f76bc5eefa2524f2b245cb8a5b45864ba.zip | |
Add native TLS transport, TLS clients and lego cert sidecarv0.1.1
- bokfd: optional TLS listener (OpenSSL), certificate reload on change
- clients: tls:host:port targets with chain and host verification
- compose: port 8788 and an INWX/lego renewal sidecar
- Makefile: header dependency tracking (-MMD -MP)
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/DEPLOY.md | 47 | ||||
| -rw-r--r-- | docs/PROTOCOL.md | 30 | ||||
| -rw-r--r-- | docs/STATE.md | 16 |
3 files changed, 80 insertions, 13 deletions
diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 5def5df..8d36453 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -15,9 +15,13 @@ State lives in two bind mounts next to `compose.yaml`: | Host path | Container | Contents | |---|---|---| -| `var/db` | `/var/lib/bokfd` | SQLite database, `backup/`, `export/` | +| `var/db` | `/var/lib/bokfd` | SQLite database, `backup/`, `export/`, `certs/` | | `var/run` | `/run/bokfd` | Unix socket (mode 0660, owned by uid 10001) | +The `bokfd` service publishes port 8788 for the TLS listener; the `certs` +sidecar renews the certificate with lego via INWX DNS-01. Plain TCP stays +off (loopback-only if enabled); the Unix socket is for clients on the host. + The database is a single SQLite file. Back up with `backup.snapshot` (`VACUUM INTO`) and point restic at `var/db/backup` — never at the live file. @@ -112,6 +116,47 @@ The clients honor `BOKFD_SOCKET`; a host-installed client can also point at `var/run/bokfd.sock`, but that file is owned by uid 10001, so the host user must be in that group (or use `sudo`). +## TLS and external users + +Put the ACME DNS credentials in the host's `.env` once (they are never +committed): + +```sh +LEGO_DOMAIN=bokf.makandra.eu +LEGO_EMAIL=anders@makandra.eu +INWX_USERNAME=... +INWX_PASSWORD=... +``` + +Forward port 8788 on the router to the host. The `certs` sidecar obtains and +renews the certificate into `var/db/certs/certificates/`; `bokfd` reloads it +in place. On the very first `up`, `bokfd` may restart a few times until the +certificate exists — check `docker compose logs certs`. + +Clients outside the LAN connect with the DNS name: + +```sh +export BOKFD_SOCKET=tls:bokf.makandra.eu:8788 +bokftui +``` + +No CA override is needed with a public ACME certificate (the system trust +store suffices); `BOKFD_TLS_CA=/path/ca.pem` exists for private CAs. + +To invite someone, create an account and give it only the role and org it +needs — no VPN, no SSH: + +```sh +docker compose exec -e BOKFD_PASSWORD='<owner-pw>' bokfd \ + bokfctl --user admin user.create '{"username":"revisor", ...}' +docker compose exec -e BOKFD_PASSWORD='<owner-pw>' bokfd \ + bokfctl --user admin org.member_add '{"org":1,"username":"revisor","role":"viewer"}' +``` + +Agents use `token.create` instead of a password; tokens are scoped and +revocable. Only the TLS port is forwarded, and every command still requires +authentication (`meta` and `health` excepted). + ## Mock company ```sh diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 6dc8c7b..e9c4331 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -41,15 +41,20 @@ by the daemon (`backup.snapshot`), not by clients. first line of defense. - Intended for `bokftui`, `bokfctl` and agents running on the same host. -### 3.2 TCP (optional) +### 3.2 TCP and TLS (optional) -- Disabled by default. When enabled it binds `127.0.0.1` unless explicitly - configured otherwise. -- TLS is **not implemented in v1**. The intended deployments are: - - loopback + SSH tunnel (`ssh -L`), or - - a private overlay network (Tailscale/WireGuard), or - - a reverse proxy that terminates TLS in front of `bokfd`. -- Every command on TCP requires authentication, including read commands. +- Plain TCP is disabled by default. When enabled it binds `127.0.0.1` unless + explicitly configured otherwise. It is intended for loopback, an SSH + tunnel (`ssh -L`) or a private overlay network (Tailscale/WireGuard). +- A separate TLS listener (`tls`, e.g. `0.0.0.0:8788`) serves exactly the + same protocol over TLS 1.2+ using a PEM certificate chain and key + (`tls_cert`, `tls_key`). The daemon reloads the certificate when the files + change, so an ACME renewer can replace them without a restart. +- Clients select the transport with `BOKFD_SOCKET`/`--socket`: a Unix socket + path, `tcp:host:port` or `tls:host:port`. The TLS client verifies the + certificate chain and host name against the system trust store; + `BOKFD_TLS_CA` adds a PEM file for private CAs. +- Every TCP/TLS command requires authentication, including read commands. `meta` and `health` are the only unauthenticated commands. ### 3.3 Framing @@ -434,7 +439,9 @@ beyond the session and calls nothing but public commands. ## 10. Security notes - Bind nothing publicly by default. Loopback or Unix socket unless the operator - opts in. + opts in; use the TLS listener when clients connect from outside the LAN and + forward only that port. Each person or agent gets their own account or + token, never VPN access to the host network. - Passwords: Argon2id (vendored reference implementation). Tokens: 256-bit random, stored hashed, revocable, never logged. Sessions: memory only. - Audit and logs redact secrets: `session.open` records username and outcome, @@ -453,7 +460,10 @@ beyond the session and calls nothing but public commands. | Key | Default | Meaning | |---|---|---| | `socket` | `/run/bokfd/bokfd.sock` | Unix socket path | -| `tcp` | off | `host:port` to enable TCP | +| `tcp` | off | `host:port` to enable plain TCP | +| `tls` | off | `host:port` to enable the TLS listener | +| `tls_cert` | `/var/lib/bokfd/certs/fullchain.pem` | PEM certificate chain | +| `tls_key` | `/var/lib/bokfd/certs/privkey.pem` | PEM private key | | `db` | `/var/lib/bokfd/bokfd.db` | SQLite database | | `backup_dir` | `/var/lib/bokfd/backup` | destination for `backup.snapshot` | | `export_dir` | `/var/lib/bokfd/export` | SIE exports | diff --git a/docs/STATE.md b/docs/STATE.md index d9edcb0..f4a563a 100644 --- a/docs/STATE.md +++ b/docs/STATE.md @@ -20,7 +20,8 @@ server/protocol/ledger only. tenant isolation, append-only triggers, `VACUUM INTO` snapshots. Postgres deliberately rejected for now; keep DB access behind one layer for a later port. -4. **Protocol**: NDJSON over Unix socket (+ optional token TCP), protocol v1. +4. **Protocol**: NDJSON over Unix socket (+ optional plain TCP and a native + TLS listener), protocol v1. `dry_run` on every mutation, `client_ref` idempotency, stable error codes, `describe` + `agent.instructions`, money in integer öre. 5. **Auth**: multi-org; memberships owner/bookkeeper/viewer; API tokens bound @@ -55,6 +56,14 @@ server/protocol/ledger only. on the dashboard. `--org ID` bypasses the picker. 13. **Settings**: `settings.get`/`settings.set`; keys `default_series`, `attachment_dir`. +14. **Transport**: Unix socket for host clients; plain TCP loopback-only; + native TLS listener (`BOKFD_TLS`, `BOKFD_TLS_CERT/KEY`, OpenSSL, TLS 1.2+, + cert reload on file change) with client targets `tls:host:port` and + system-trust verification (`BOKFD_TLS_CA` for private CAs). Certificates + come from a lego sidecar using INWX DNS-01 (`compose.yaml`). Externals + get accounts/roles/tokens, never VPN access. `scripts/deploy.sh` builds + locally and ships over SSH, or builds on the host when architectures + differ. ## Pending decisions @@ -79,7 +88,10 @@ server/protocol/ledger only. implemented). 6. `describe` argument schemas (currently name/summary/permission only). 7. Pre-migration `VACUUM INTO` snapshot (promised in SCHEMA.md, not built). -8. Docker image + compose (multi-arch amd64/arm64, GHCR) and systemd unit. +8. ~~Docker image + compose (multi-arch amd64/arm64, GHCR) and systemd unit.~~ + Done as a Dockerfile + `compose.yaml` (amd64/arm64 build stage) and + `scripts/deploy.sh` over SSH; no registry and no systemd unit (the + container is the unit). 9. Password change, user disable, TOTP. 10. Bank import/reconciliation (CSV first, then PSD2), invoicing/reskontra, AGI/payroll if employees. |
