Put it behind a gateway¶
Azure API Management, AWS API Gateway, an ingress controller or a plain reverse proxy. The server serves its own dashboard, so the browser and the API are the same origin and stay that way behind a gateway: what changes is the address people arrive at, the path the app is served under, and who the server thinks is calling.
Two settings cover most of it, and four more cover a gateway that gives each endpoint a path of its own.
| Setting | What it does |
|---|---|
VECTRIXDB_PUBLIC_URL |
the address people type, path included: https://apim.company.com/vectrixdb |
VECTRIXDB_TRUSTED_PROXIES |
addresses and networks whose X-Forwarded-For says who is calling: 10.0.0.0/8 |
VECTRIXDB_PREFIX |
the path every route lives under, your choice: acme |
VECTRIXDB_GATEWAY_PATHS |
each route's own gateway path, as the gateway team hands them over: api/v1=/files/search, auth=/files/auth |
VECTRIXDB_KEY_HEADER |
the header an app's key arrives in; api-key unless named |
VECTRIXDB_TOKEN_HEADER |
the header a person's access token arrives in; Authorization unless named |
VECTRIXDB_ROOT_PATH exists for the case where the gateway adds a path but no
public URL is set. Left out, the path of VECTRIXDB_PUBLIC_URL is the root
path, so an enterprise sets one thing and a plain install sets nothing.
VECTRIXDB_PUBLIC_URL=https://apim.company.com/vectrixdb
VECTRIXDB_TRUSTED_PROXIES=10.0.0.0/8
vectrixdb check --env-file vectrixdb.env # says what a gateway deployment gets wrong
Why the trusted proxies matter¶
Every request behind a gateway arrives from the gateway, so without this setting the server thinks one caller is making all of them. Two things key on the caller's address: the sign-in lockout and the access log. One person's wrong codes would lock out everyone behind the gateway.
The header is a claim anybody can make, so it is believed only from the addresses you name. With nothing named it is ignored, which is right for a server on its own.
What the gateway team needs from you¶
Send them the OpenAPI document. A running server serves it at /openapi.json
with no key, every release ships the same document as
docs/reference/openapi.json, and the release
workflow attaches it to the release itself, so a policy can be rebuilt from a
version. APIM and API Gateway both import it and make their operations from
it, so nobody retypes a route list and a new route is a diff rather than an
email.
Ask them to publish these, as wildcards rather than one operation per endpoint, because a per-operation policy has to be edited every time a route is added:
| Publish | Methods | What it is |
|---|---|---|
/api/v1/* |
GET, POST, PUT, DELETE | the data API |
/api/v2/* |
POST | create a collection, write points |
/auth/* |
GET, POST, DELETE | sign-in, needed when people sign in through the gateway, emergency sign-in included |
/dashboard/*, /brand.json, /brand.css, /brand/logo* |
GET | the dashboard itself, and its brand |
/, /health, /openapi.json, /docs |
GET | index, probe, schema, interactive docs |
Four routes worth naming to them:
GET /api/v1/auditis the audit trail, and it is more sensitive than the index it audits. It is already refused unless a key is set and the request carries it; consider not publishing it outside at all.POST /api/v1/collections/{name}/documentsandPOST /api/v1/documentscarry files, up toVECTRIXDB_MAX_UPLOAD_BYTES, 100 MiB by default. Size and timeout exceptions belong here.GET /api/v1/evaluations/{run}/goldenhands back the golden questions, for an admin signed in as a person and never a key.PUT /api/v1/collections/{name}/policydecides who may search a collection, so it is the one to log or gate.
What the policy must not do¶
- Strip
CookieorSet-Cookie. The session is a__Host-cookie; without it everyone arrives as a guest. - Strip
x-csrf-token. Every write from the dashboard carries it, and the server refuses writes that do not. - Strip
api-keyorAuthorization. Scripts sign in with one of those. A gateway that keeps them for itself can pass the key and the token in headers of their own instead, named byVECTRIXDB_KEY_HEADERandVECTRIXDB_TOKEN_HEADER; with a token header of its own, the server leavesAuthorizationto the gateway and never reads it. - Add its own CORS while the server adds CORS too. Two sets of headers
break browsers. Keep CORS in the gateway policy and leave
VECTRIXDB_CORS_ORIGINSunset; the dashboard is same-origin and needs none. - Cap the body below your documents. AWS API Gateway stops at 10 MB and
about 29 seconds for an integration, which is under the 100 MiB the
documents route takes; APIM forwards with a 300 second default. Either lower
VECTRIXDB_MAX_UPLOAD_BYTESto what the gateway allows, or keep ingestion off the gateway path.
A subscription key authenticates the caller to the gateway, not the person to VectrixDB. Keep sign-in or named API keys on: the gateway cannot express "this person may read this collection", and the server can. An app on the other side of the gateway wants a key scoped to its own collections, with an end date: see Build an app on it.
The path, if there has to be one¶
A hostname of its own is simpler than a path: APIM with an empty API suffix,
or an AWS HTTP API on a custom domain with a $default stage. Where a path is
unavoidable, set it and the app handles both shapes, whether the gateway
forwards /vectrixdb/api/v1/... or strips the prefix and sends /api/v1/....
The dashboard reads its own address to find the API, and the /openapi.json a
deployment serves carries the prefix, so an import through the gateway is
right the first time.
A path for each endpoint¶
Some gateway teams hand over one address and, for each endpoint or family of endpoints, a path of their own, and your prefix goes between the two:
https://gateway.example.com / files/search / acme / api/v1/collections/handbook/text-search
the gateway its path prefix the route
VECTRIXDB_PUBLIC_URL=https://gateway.example.com
VECTRIXDB_PREFIX=acme
VECTRIXDB_GATEWAY_PATHS=api/v1=/files/search, auth=/files/auth, dashboard=/files/dash
- Names. A route is named as it is served with no prefix, alone or as a
family:
api/v1is every API route, andapi/v1/collections/handbookis one collection's. The longest name that fits a path is the one that counts. A name no route falls under stops the server from starting, so a typing mistake is found then and not by a caller. - Both shapes. Each listed route answers with its gateway path in front or
without it, and with the prefix or without it, so the gateway may pass them
on or take them off. A gateway path opens only its own routes:
/files/authin front of the search API is not found. - No redirects. A slash too many is not found rather than redirected, because a redirect names the address the server sees and sends the caller round the gateway. The one exception is the dashboard's own address without its slash, which redirects relatively, so it holds through the gateway.
- What the server writes. Everything it hands out is written the way the
caller reaches it: the address single sign-on returns to, the path its cookie
is kept for, the links in sign-in emails and in
vectrixdb people add, and a map the dashboard is told, so each call it makes goes to the gateway path its route was published under. Register the return address ashttps://gateway.example.com/files/auth/acme/auth/oidc/callbackwith the identity provider. - The document.
/openapi.jsonnames the prefix as the server's base, which is what the gateway team imports.
vectrixdb check reads the four settings the way the server does, says where
each name is published, and refuses a name no route falls under.
Checking it from outside¶
vectrixdb check --url https://apim.company.com/vectrixdb
vectrixdb check --url https://gateway.example.com --prefix acme --gateway-paths "api/v1=/files/search, auth=/files/auth"
The prefix and the gateway paths come from the server's own settings,
VECTRIXDB_PREFIX and VECTRIXDB_GATEWAY_PATHS, or --env-file, unless the
command is given them, and each route is then asked where it is published.
vectrixdb check reads settings; this asks the public address what a caller
would, because most of what goes wrong behind a gateway is not in the settings
at all. Every request is a GET, and the key it sends is wrong on purpose, to
see whose refusal comes back. It reports:
- whether the address reaches the server's
/healthat all; - whether each gateway path reaches the server, in the server's own words;
- whether
/openapi.jsonsays the API is under the path the gateway serves it on, which is what a generated client and an imported policy will call; - whether
api-keyandAuthorizationreach the server, by whether the server's ownInvalid API keycomes back, the gateway's error page does, or the server answers as if no key had been sent; - whether the dashboard is served and a redirect keeps the path;
- whether two sets of CORS headers come back.
It exits 1 on an error, so it can close a deployment pipeline. It cannot see the caller's address from outside; for that, read the access log.
With sign-in on, the access log is the proof that the addresses arrived: each line names the person, the action and the collection, and the address it came from should be the caller's, not the gateway's. See Sign people in and Deploy the server.