Install the Gateway
The Gateway is the entry point that Andromeda uses to talk to PERSEUS. Every request from Andromeda goes through the Gateway, which authenticates the user via Keycloak (OIDC), enforces an allowlist of permitted PERSEUS endpoints, manages sessions, applies rate limiting and response caching, and proxies the request to the PERSEUS API.
Why a Gateway?
Section titled “Why a Gateway?”PERSEUS holds sensitive data for the operation of a data centre, so it is often deployed only on an internal network with no direct access from the outside. Andromeda and other external sites, however, still need to read and write certain data.
The Gateway bridges this gap. It is the single component that is exposed externally, while PERSEUS itself stays on the internal network. Because every request passes through the Gateway, it can make exactly the data that should be available externally reachable — and nothing else: it requires authentication, only forwards calls to an explicit allowlist of PERSEUS endpoints, and rate-limits clients before any request reaches PERSEUS.
This guide explains how to run the Gateway with Docker.
Prerequisites
Section titled “Prerequisites”- Docker Engine.
- A running PERSEUS instance reachable from the Gateway, plus a PERSEUS API token for it.
- A Keycloak server with a realm and a client for the Gateway (see below).
- The public URL of Andromeda, used for login and callback redirects.
Keycloak
Section titled “Keycloak”The Gateway delegates authentication to a Keycloak server, which is an external component. Configuring and operating Keycloak is out of scope for this guide — please refer to the Keycloak documentation. The Gateway only needs a realm and an OIDC client in it, configured as follows:
- Client ID matching
KEYCLOAK_CLIENT_ID(e.g.perseus-gateway). - Client authentication enabled (a confidential client), so the client has a client
secret — this is the value you set as
KEYCLOAK_CLIENT_SECRET. - Standard flow enabled (the OpenID Connect authorization code flow), used to log users in.
- A valid redirect URI of
https://<host>/gateway/auth/callback. You can use an exact URI per environment or a pattern such ashttps://<host>/gateway/*. - Service accounts enabled. The Gateway also calls Keycloak’s admin REST API (to read the available identity providers and to link or unlink user accounts), authenticating with the client’s service account via the client-credentials flow.
- The service account needs the following
realm-managementclient roles, otherwise those admin calls are rejected:view-realmview-usersandmanage-usersview-identity-providers
You point the Gateway at these via the KEYCLOAK_* variables below. Which identity providers the
realm offers (LDAP, GitHub, ORCID, …) is entirely up to your Keycloak setup.
PERSEUS API token
Section titled “PERSEUS API token”The Gateway authenticates against PERSEUS with a single API token (PERSEUS_API_TOKEN). In PERSEUS,
API tokens are scoped to specific endpoints, so the token you create must be authorized for every
endpoint the Gateway forwards Andromeda’s requests to. See
API usage for how to create and scope a token.
At minimum the token needs the following endpoints:
GET /countryGET /service/Andromeda/*POST /service/Andromeda/*DELETE /service/Andromeda/*GET /service/UserPortal/*POST /service/UserPortal/*GET /service/AffiliationManager/*GET /service/ResourceManager/*GET /service/PersonSearch/*POST /service/PersonCreate/*POST /service/PersonIdentity/*POST /service/PersonEdit/*GET /service/SystemStatus/*GET /service/PriorityManager/*Pull the image
Section titled “Pull the image”docker pull docker.io/pc2upb/perseus-gateway:latestConfiguration
Section titled “Configuration”The Gateway is configured entirely through environment variables. Create a .env file with the
values for your deployment:
# PERSEUS APIPERSEUS_API_TOKEN=<your-perseus-api-token>PERSEUS_BASE_URL=https://perseus.example.org/api
# Routing — the Gateway is mounted under /gatewayFASTAPI_ROOT_PATH=/gatewayANDROMEDA_URL=https://perseus.example.org
# KeycloakKEYCLOAK_URL=https://keycloak.example.orgKEYCLOAK_REALM=perseusKEYCLOAK_CLIENT_ID=perseus-gatewayKEYCLOAK_CLIENT_SECRET=<client-secret-from-keycloak>USERNAME_SAFE_IDP=ldap
# Sessions — set a long random secret and keep cookies secure in productionSESSION_SECRET_KEY=<long-random-secret>SESSION_COOKIE_SECURE=trueSESSION_COOKIE_DOMAIN=perseus.example.org
# Persistence (SQLite file inside the container, see "Run with Docker")DATABASE_URL=sqlite:///./perseus_gateway.dbReference
Section titled “Reference”| Environment variable | Description | Default |
|---|---|---|
| PERSEUS_API_TOKEN | Shared secret used to authenticate against the PERSEUS API. | perseus_api_key |
| PERSEUS_BASE_URL | Base URL of the PERSEUS API. | http://perseus.localhost |
| PERSEUS_REQUEST_TIMEOUT | Timeout in seconds for requests to the PERSEUS API. | 15 |
| FASTAPI_ROOT_PATH | Root path the application is mounted under. Must match your reverse proxy. | /gateway |
| ANDROMEDA_URL | Base URL of the Andromeda frontend used for login and callback redirects. | http://andromeda.localhost:6173 |
| ANDROMEDA_PROFILE_PATH | Profile page path used after account-link flows. | /profile |
| KEYCLOAK_URL | Base URL of the Keycloak server. | http://localhost:8080 |
| KEYCLOAK_REALM | Keycloak realm name. | perseus |
| KEYCLOAK_CLIENT_ID | OIDC client ID for the Gateway. | perseus-gateway |
| KEYCLOAK_CLIENT_SECRET | OIDC client secret for the Gateway. | perseus-gateway-secret |
| USERNAME_SAFE_IDP |
| ldap |
| SESSION_COOKIE_NAME | Name of the session cookie. | perseus_session |
| SESSION_COOKIE_SAMESITE | SameSite policy for the session cookie. | lax |
| SESSION_COOKIE_SECURE | Whether the session cookie is only sent over HTTPS. Use true in production. | true |
| SESSION_COOKIE_DOMAIN | Domain scope for the session cookie. | unset |
| SESSION_TTL_SECONDS | Session lifetime in seconds. | 86400 |
| SESSION_SECRET_KEY | Secret key for signing session cookies. Set a long random value. | perseus_session_secret |
| DATABASE_URL | Connection string for the Gateway’s persistence layer. | sqlite:///./perseus_gateway.db |
| RATE_LIMIT_PER_MINUTE | Requests allowed per minute for a client. | 5 |
| CACHE_EXPIRE_SECONDS | TTL for cached responses in seconds. | 60 |
| VALIDATE_CACHE_SECONDS | TTL in seconds for cached authentication validation responses. | 5 |
| LOGIN_OPTIONS | Source for /auth/login-options; supported modes are KEYCLOAK and FILE. | KEYCLOAK |
| LOGIN_OPTIONS_PATH | JSON file used for login options when LOGIN_OPTIONS=FILE. | login_options.json |
| ALLOWED_ENDPOINTS_CONFIG_PATH | JSON file containing the allowed PERSEUS endpoints. | allowed_endpoints.json |
Endpoint allowlist and login options
Section titled “Endpoint allowlist and login options”The Gateway only forwards requests to PERSEUS endpoints that are explicitly allowed in
allowed_endpoints.json. Each entry defines the relative path (no leading slash), the permitted
HTTP methods, and whether authentication is required. Paths accept Unix shell-style wildcards (for
dynamic segments such as OIDs), and "*" in methods allows every verb.
{ "endpoints": [ { "path": "country", "methods": ["GET"], "require_login": false }, { "path": "service/UserPortal/user", "methods": ["GET", "POST"], "require_login": true } ]}When LOGIN_OPTIONS=FILE, the available login providers are served from login_options.json
instead of being fetched from Keycloak.
The image ships with default versions of both files which are suiteable for usage with Andromeda. To use your own, mount them into the container
and point ALLOWED_ENDPOINTS_CONFIG_PATH / LOGIN_OPTIONS_PATH at them (see below). Restart the
Gateway after changing these files so the new rules are loaded.
Run with Docker
Section titled “Run with Docker”docker run -d \ --name perseus-gateway \ --env-file .env \ -p 8099:5000 \ -v perseus-gateway-data:/usr/src/app/data \ -v "$(pwd)/allowed_endpoints.json:/usr/src/app/allowed_endpoints.json:ro" \ --restart unless-stopped \ docker.io/pc2upb/perseus-gateway:latestThe container listens on port 5000; the example maps it to 8099 on the host.
Update to the latest image
Section titled “Update to the latest image”docker pull docker.io/pc2upb/perseus-gateway:latestdocker rm -f perseus-gateway# re-run the docker run command aboveAdd a reverse proxy
Section titled “Add a reverse proxy”Expose the Gateway publicly under BASEURL/gateway. The path must match FASTAPI_ROOT_PATH. Here is an example configuration for Nginx.
server { listen 80; server_name perseus.example.org;
location /gateway/ { proxy_pass http://127.0.0.1:8099/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; }}Verify
Section titled “Verify”Once the container is running and the proxy is in place, the OpenAPI/Swagger UI is available at:
https://perseus.example.org/gateway/docs