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