Serving the REST API from the fleet

Serving the REST API from the fleet

A consumer built on the hosted provider's client library does not open Postgres: it calls the provider's HTTP API (/rest/v1/<table>) with the project's URL and an API key. Moving its database with migrate leaves it calling the provider until the fleet serves that API too:

stado database rest place <name> --port <port> --key-valid-days <days>

runs on the host the fleet database runs on, or reaches it over the host channel from any machine of the fleet, and places PostgREST beside it as the unit <name>-rest. The server is the release stado-rs/data/declarations/postgrest.json pins, installed under ~/.local/share/stado-postgrest/<version> when the host lacks it and refused unless its archive's sha256 is the pinned one, so no host has to carry a PostgREST of its own (the macOS archive links Homebrew's libpq). On the database it makes the roles the API switches to, anon, authenticated and service_role (NOLOGIN; a dump restored from the provider already carries them and its row policies name them), and authenticator (LOGIN, with a password generated here, a member of the three). It writes ~/.stado/databases/<name>/postgrest.conf, readable by its owner only since it holds that password and the signing secret, and onto <name>-database what a consumer reads instead of the provider's values:

field what it holds
rest_url http://<the host's routable address>:<port>, the base a consumer puts in place of the provider's project URL
anon_key, service_role_key HS256 JSON Web Tokens with role anon and service_role, issuer stado, valid for --key-valid-days from keys_issued_at
jwt_secret the secret the API verifies keys with, for a product that mints its own user tokens

The API exposes the public schema. Stado assumes no port and no key lifetime: both are the caller's. A new placement refuses a port that is not the API's to take, before anything is written: one a server of a database on the same host declares, even while that server is down, whether another database's REST API (server-port in its postgrest.conf) or a sign-in server, this database's own included (PORT in its auth.env) (<name>: port <port> is the one <path> declares for the <REST API|sign-in server> of <other>, so the REST API would exit at every start with 'Address already in use'; choose another --port), and one another program listens on now (<name>: port <port> cannot be listened on here (<error>), so its REST API would exit at every start; lsof -nP -iTCP:<port> -sTCP:LISTEN names what holds it; choose another --port). A placement already made is reported reused; --rotate-keys writes a new secret and new keys and restarts the unit so it verifies with them, after which every consumer holding the old keys is refused by the API. The provider's management API does not expose a project's own signing secret, so the keys it issued cannot be carried over: each consumer is given the fleet's.

What the fleet does not serve yet: the provider's Realtime and Storage APIs. A consumer that uses them keeps reaching the provider's project for those calls until it is changed. Sign-in is served below.

Refusals: a database that is not a fleet database (<name> is not a fleet database (<item>.provider is <provider>); ... move it first with stado database migrate <name> --to fleet), a placement the database's host refused (the REST API of <name> was not placed on <host>: <cause>), an item without host, address or port, a server that could not be installed (the REST server of <name> cannot be installed: <cause>: no archive declared for this platform, a failed download, a digest other than the pinned one, or a server that does not answer its release, such as a macOS one whose libpq is missing), and a unit that could not be installed after the keys were written (<item> carries the API's keys and <config> is written, but the unit <unit> was not installed: <cause>), which a second run finishes.

stado database rest retire <name> is the inverse: it stops and forgets <name>-rest on the database's host and takes the API's fields off <name>-database, so a consumer that still reads them is refused by name rather than left calling an API that is gone. The roles stay on the database, since the data's policies name them. destroy retires the API before the database. A database with neither the fields nor the unit is refused: <name> has no REST API to retire.

Source: this website