# Self-hosted provisioner

Temporary-access targets choose **where provisioning runs** via the executor. By
default (`in_do`) the seekrit broker decrypts your database admin credential
transiently to run `CREATE ROLE` / `DROP USER` itself. The **`remote`** executor
moves that work into your network: the broker signs each command and sends it to
a **`seekrit-provisioner`** daemon you run, which holds the admin credential
locally. seekrit orchestrates but never sees it — **pure zero-knowledge even for
provisioning.**

> **Note:** The remote executor is for the **Postgres** and **MySQL/MariaDB** providers. SSH certificate signing is `in_do`-only for now.

## How it works

The broker holds only a **shared HMAC key** (wrapped to its per-org keypair, like
any admin secret). Your **database admin credential** lives only on the daemon.
Every command the broker sends is signed HMAC-SHA256; the daemon verifies the
signature, rejects stale or replayed commands, and runs the already-rendered SQL.

```
 broker (seekrit)                         your network
 ────────────────                         ────────────
 signs ProvisionCommand ──── POST ───▶ seekrit-provisioner ──▶ Postgres / MySQL
 (HMAC, shared key)                    verify sig · freshness ·
                                       replay · run SQL as admin
```

The credential you hand a consumer stays zero-knowledge as always — the consumer
generates it and sends only a verifier. The daemon only ever handles the *admin*
credential, which now never leaves your network.

## 1. Mint the shared key

```sh
seekrit provisioner keygen
# → a base64 key on stdout. Keep it secret.
```

Use the **same** value in both places below: `--hmac-key` when you register the
target, and `SEEKRIT_PROVISIONER_HMAC_KEY` on the daemon.

## 2. Register a remote target

The admin connection string does **not** go to seekrit here — only the HMAC key
is wrapped and uploaded. Point `--provisioner-url` at where the daemon will
listen.

```sh
KEY=$(seekrit provisioner keygen)

seekrit pg target add \
  --name prod-db --host db.internal --database app \
  --access readonly \
  --executor remote \
  --provisioner-url https://provisioner.internal:8088/ \
  --hmac-key "$KEY"
```

`seekrit mysql target add` takes the same `--executor remote --provisioner-url
--hmac-key` flags. For the `readonly` / `readwrite` presets, run the one-time
group-role setup SQL the command prints (Postgres) as a database admin.

## 3. Run the daemon

Run it where it can reach the database, holding the same key plus the real admin
connection string:

```sh
docker run -d --name seekrit-provisioner -p 8088:8088 \
  -e SEEKRIT_PROVISIONER_HMAC_KEY="$KEY" \
  -e SEEKRIT_PROVISIONER_DATABASE_URL="postgres://admin:secret@db.internal:5432/app" \
  seekritdev/provisioner:latest
```

Prebuilt multi-arch images (`amd64` + `arm64`) are published to Docker Hub as
[`seekritdev/provisioner`](https://hub.docker.com/r/seekritdev/provisioner) —
pin a release tag (e.g. `:0.1.0`) or `:edge` for the latest `main`. The image is
a single static binary on `scratch` — no OS, no shell. `GET /healthz` returns
`{"ok":true}` for liveness probes.

### Environment

| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| `SEEKRIT_PROVISIONER_HMAC_KEY` | yes | — | Base64 shared key; must match the target's `--hmac-key`. |
| `SEEKRIT_PROVISIONER_DATABASE_URL` | yes | — | Admin `postgres://…` or `mysql://…` string. **Held only here.** |
| `SEEKRIT_PROVISIONER_PROVIDER` | no | inferred from URL | `postgres` or `mysql`, if ambiguous. |
| `SEEKRIT_PROVISIONER_DB_TLS` | no | `require` | `require` or `disable` (local/plaintext DB). |
| `SEEKRIT_PROVISIONER_ADDR` | no | `0.0.0.0:8088` | Listen address. |
| `SEEKRIT_PROVISIONER_MAX_SKEW_SECS` | no | `300` | Freshness window (and replay-cache TTL). |

## Lease as usual

Nothing changes for the consumer. `seekrit pg lease prod-db` mints a credential
exactly as with the in-DO executor — the difference is entirely in where the
`CREATE ROLE` ran.

> **Warning:** The HMAC authenticates and integrity-protects every command regardless of transport, so plain HTTP behind your own ingress is safe. Still, front the daemon with TLS if it is reachable beyond localhost, so command contents stay confidential in transit. The daemon holds a privileged database credential — keep it on a private network and scope that credential to only what provisioning needs.

## Security properties

- **Authenticity** — a missing or bad signature is rejected (`401`) before any
  database work; verification is constant-time.
- **Anti-replay** — commands outside a ±5-minute window are rejected (`400`), and
  each nonce is single-use within that window.
- **Least exposure** — the daemon runs only the SQL the broker rendered, returns
  no query results, and never logs the admin URL, the key, or the statements.
