aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/DEPLOY.md47
-rw-r--r--docs/PROTOCOL.md30
-rw-r--r--docs/STATE.md16
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.