Build local product stack with Vault-managed secrets (#42)
This commit is contained in:
1 parent
8e2ef7aa88
commit
184a3be413
32 files changed
+1191
-10
No files matched your search
@@ -0,0 +1,182 @@
|
||||
# Local development
|
||||
|
||||
Task [#42](https://git.sendico.io/tech/sxchnge/issues/42), branch `dev-env-42`.
|
||||
|
||||
## Build and bootstrap
|
||||
|
||||
Requires Docker with Compose v2 and BuildKit, Python 3.10+ and Git. Docker builds
|
||||
Go, protobuf bindings and Flutter web; host Go, protoc and Flutter installations
|
||||
are not required for `make build && make up`. The first Flutter SDK download can
|
||||
take several minutes. Both ARM64 and AMD64 build from the same source definitions.
|
||||
|
||||
```sh
|
||||
make build && make up
|
||||
```
|
||||
|
||||
`make build` resolves the current stable upstream releases, pulls infrastructure
|
||||
and builds NATS packaging, notifications, BFF and frontend images. Vault and Mongo
|
||||
use upstream images directly. Resolution excludes prereleases and selects Nginx's
|
||||
stable channel. The exact versions and manifest digests are saved in the ignored
|
||||
`.local/dev/images.env`; Flutter's stable release is pinned by Git commit. Build
|
||||
fails if upstream metadata cannot be resolved. Run `make build` again to refresh
|
||||
versions. Review database release upgrade requirements before updating a populated
|
||||
local volume across major versions; the tooling does not perform data migrations.
|
||||
|
||||
`make up` starts persistent Vault, initializes/unseals it, provisions secrets and
|
||||
AppRoles, initializes an authenticated single-member MongoDB replica set, configures
|
||||
Vault's MongoDB database plugin, and starts authenticated NATS JetStream. It then
|
||||
starts notifications, BFF and the frontend when the required email settings exist.
|
||||
|
||||
**On the first run, `make up` intentionally exits with an error after infrastructure
|
||||
bootstrap because Resend credentials have not been supplied.** No placeholder API
|
||||
key, mock delivery mode or embedded credential is installed. The three infrastructure
|
||||
services remain running so you can configure Vault, then rerun `make up`.
|
||||
|
||||
## Configure Resend in Vault
|
||||
|
||||
The default email driver is `resend`. In the Vault UI at
|
||||
[http://127.0.0.1:18200](http://127.0.0.1:18200), edit the KV v2 secret **`kv/email`**
|
||||
(API path `kv/data/email`):
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| `driver` | `resend` |
|
||||
| `api_key` | Your Resend API key |
|
||||
| `sender_address` | Your verified Resend sender email |
|
||||
| `sender_name` | `SXCHNGE` or your desired display name |
|
||||
|
||||
Local administrative login material is in the private, ignored
|
||||
`.local/dev/admin/init.json` (`root_token`). Keep that file private. Product
|
||||
containers never mount it. You can also provision the same secret with a hidden
|
||||
terminal prompt, without placing the API key in shell history:
|
||||
|
||||
```sh
|
||||
python3 dev/local.py email
|
||||
make up
|
||||
```
|
||||
|
||||
The startup check rejects an empty key or the bootstrap sender placeholder. It
|
||||
does not validate the key against Resend or prove domain verification; an invalid
|
||||
key fails at delivery. Vault Agent fetches the key and email settings and injects
|
||||
them into the notifications process. There is no `.env` secret fallback. Changing
|
||||
`kv/email` restarts the notification process automatically within about 10 seconds.
|
||||
Bootstrapping again preserves your existing email secret.
|
||||
|
||||
## Open the product
|
||||
|
||||
After providing Resend credentials:
|
||||
|
||||
- Frontend and proxied API: [https://localhost:5126](https://localhost:5126).
|
||||
- BFF health: [https://localhost:5126/api/v1/health](https://localhost:5126/api/v1/health).
|
||||
- Vault UI/API: [http://127.0.0.1:18200](http://127.0.0.1:18200).
|
||||
|
||||
Trust `.local/dev/ca.pem` in your local browser/OS certificate store to use HTTPS
|
||||
without a certificate error. Trust installation is a separate local action, not
|
||||
performed by the bootstrap. For a command-line check without changing trust:
|
||||
|
||||
```sh
|
||||
curl --cacert .local/dev/ca.pem https://localhost:5126/api/v1/health
|
||||
```
|
||||
|
||||
HTTPS keeps the existing Secure/HttpOnly refresh cookie contract. Nginx serves
|
||||
Flutter and proxies `/api/` to the BFF on the same origin. The bundle selects the
|
||||
existing backend auth adapter with the public client ID `sxchnge-local-web`.
|
||||
Bootstrap inserts this public browser client if absent; it has no client secret.
|
||||
No frontend widget, service or provider architecture changes are needed.
|
||||
|
||||
The rest of the app retains its existing capabilities and limitations: wallet,
|
||||
KYC and settings fixtures are not turned into production integrations by this
|
||||
container setup. Full email signup/login requires a working Resend account.
|
||||
|
||||
## Secret and connection lifecycle
|
||||
|
||||
| Consumer | Vault access |
|
||||
| --- | --- |
|
||||
| BFF | `database/creds/bff`, JWT key, notification caller token and CA |
|
||||
| Notifications | `database/creds/notifications`, NATS token, caller token, TLS certificate/key and email settings |
|
||||
| NATS | NATS authentication token only |
|
||||
| Bootstrap | Infrastructure administration, PKI and initial identities |
|
||||
|
||||
The database engine is configured at `database/config/mongo` with
|
||||
`mongodb-database-plugin`. It uses its own `vault-manager` Mongo user, whose
|
||||
password is rotated by Vault immediately after configuration. The manager has
|
||||
`userAdminAnyDatabase`; applications never receive that credential. The independent
|
||||
local Mongo administrator and replica-set key come from Vault's random generator
|
||||
and are retained in `kv/bootstrap/mongo` for local provisioning.
|
||||
|
||||
The BFF role has `readWrite` only on `sxchnge`; notifications has `readWrite` only
|
||||
on `sxchnge_notifications`. Mongo URIs come from dynamic Vault credentials, not
|
||||
static application users. Leases default to one hour with a 24-hour maximum.
|
||||
Vault Agent renews leases, obtains replacement credentials, and restarts its child
|
||||
process when credentials change. Apps retain their existing shared database and
|
||||
messaging connection wrappers. No new direct application database path is added.
|
||||
|
||||
Each agent authenticates using its own AppRole with a renewable periodic token.
|
||||
Local SecretIDs are reusable for container restarts; each container mounts only
|
||||
its own identity directory under `.local/dev/agents/`. Revoking an AppRole SecretID
|
||||
requires removing its matching local `secret-id` file and rerunning `make up` to
|
||||
issue a replacement. Revoking an active token/lease terminates its credentials;
|
||||
Agent's retries and the container restart policy recover when Vault is reachable.
|
||||
|
||||
The notification gRPC connection uses a Vault-issued certificate for
|
||||
`notifications` and the BFF validates the Vault CA and hostname. Frontend HTTPS
|
||||
certificates also come from Vault PKI. Leaf certificates last 30 days; `make up`
|
||||
refreshes them when less than seven days remain and reloads Nginx. Run it regularly
|
||||
for a development stack kept online for weeks. Agents restart when their TLS KV
|
||||
secret changes. Application secrets are never put into the frontend bundle.
|
||||
|
||||
Vault, Mongo and JetStream data use project-scoped named Docker volumes. Local
|
||||
Vault unseal material, administrator login, per-service bootstrap identities and
|
||||
frontend TLS material are under ignored `.local/dev/`, outside image build contexts.
|
||||
The local Vault automatically unseals using its local key on container restart.
|
||||
These convenience credentials and loopback HTTP Vault access are for this local
|
||||
machine; this is not a production Vault deployment. Do not share `.local/dev/`.
|
||||
Keep it paired with its matching Docker volumes for recovery.
|
||||
|
||||
Mongo and NATS expose no host ports. The `sxchnge-local` network, volumes and
|
||||
container names are independent of existing Sendico stacks. Only the frontend
|
||||
and Vault bind host loopback ports. No Sendico database, message bus or secrets
|
||||
are consumed.
|
||||
|
||||
## Manage and verify
|
||||
|
||||
```sh
|
||||
make ps
|
||||
make logs
|
||||
make down # preserves named volumes and local bootstrap material
|
||||
make up # idempotent provisioning and readiness checks
|
||||
make test-dev-infra # works before Resend provisioning
|
||||
make test-dev # requires all six services running
|
||||
```
|
||||
|
||||
The infrastructure check exercises AppRole access restrictions, real Vault
|
||||
MongoDB credential issuance, per-database access, lease renewal and user deletion
|
||||
on revocation. The complete check also verifies service health and frontend/API
|
||||
HTTPS with the generated CA. These checks never send a real email.
|
||||
|
||||
Backend regression tests use fresh `mongo:latest` and `nats:latest` official
|
||||
stable images, pulled before `make test-backend`. Normal validation remains
|
||||
`make test` and `make lint-backend` and requires the host SDKs described in the
|
||||
repository guides.
|
||||
|
||||
Deleting Docker volumes removes local data; `make down` deliberately keeps them.
|
||||
If starting from scratch, stop the stack and explicitly remove this project's
|
||||
three volumes **and** its matching `.local/dev/` bootstrap state together. Never
|
||||
reuse old unseal material with a newly initialized Vault volume, or retain Mongo
|
||||
users while discarding the Vault data that manages their leases.
|
||||
|
||||
## Sources and checked releases
|
||||
|
||||
On 2026-09-12 the resolver selected Vault 2.1.0, MongoDB 8.3.9, NATS 2.14.6,
|
||||
Go 1.27.1, Nginx stable 1.30.4, Ubuntu 26.04 and Flutter stable 3.47.4. These are
|
||||
verification records, not fixed future defaults. Release selection follows the
|
||||
[Vault releases](https://releases.hashicorp.com/vault/),
|
||||
[MongoDB stable release notes](https://www.mongodb.com/docs/manual/release-notes/),
|
||||
[NATS releases](https://github.com/nats-io/nats-server/releases/latest),
|
||||
[Nginx downloads](https://nginx.org/en/download.html),
|
||||
[Go releases](https://go.dev/dl/) and
|
||||
[Flutter stable channel](https://github.com/flutter/flutter/tree/stable).
|
||||
The implementation follows HashiCorp's
|
||||
[MongoDB database plugin](https://developer.hashicorp.com/vault/docs/secrets/databases/mongodb)
|
||||
and [Agent process supervisor](https://developer.hashicorp.com/vault/docs/agent-and-proxy/agent/process-supervisor)
|
||||
contracts.
|
||||
Reference in new issue
Block a user