Sign inSign up

pc2upb/perseus-gateway

By pc2upb

Updated 11 days ago

Compute project management for scientific HPC centers

Image
0

694

pc2upb/perseus-gateway repository overview

PERSEUS Gateway

This service is the Perseus Gateway (FastAPI).

Prerequisites

  • Python: see pyproject.toml for the required Python version.
  • Install uv for dependency management via pipx install uv or see the documentation.

Install and run

  • Run uv sync to install dependencies.
  • Start the gateway for development via uv run fastapi dev app/main.py.
  • OpenAPI/Swagger UI becomes available at http://localhost:8000/gateway/docs (adjust for FASTAPI_ROOT_PATH or custom host/port).

Configuration

The gateway reads its configuration from environment variables (or a .env file) defined in app/core/settings.py. Set the variables before starting the app, e.g.

export PERSEUS_API_TOKEN="real-token"
uv run fastapi dev app/main.py

Allowed PERSEUS endpoints are listed in allowed_endpoints.json in the project root, which is versioned with the service. The file contains an endpoints array where every object defines the relative path (without a leading slash), the permitted HTTP methods, and whether authentication is required for matching requests. Paths accept Unix shell-style wildcards, so you can express dynamic segments such as OIDs. Use "*" in the methods array when every verb should be allowed. Restart the gateway after updating the file so the new rules are loaded.

{
  "endpoints": [
    {
      "path": "country",
      "methods": [
        "GET"
      ],
      "require_login": false
    },
    {
      "path": "service/UserPortal/user",
      "methods": [
        "GET",
        "POST"
      ],
      "require_login": true
    }
  ]
}

Set ALLOWED_ENDPOINTS_CONFIG_PATH to point to a different JSON file if you do not want to use the checked-in config.

VariablePurposeDefault
PERSEUS_API_TOKENShared secret used to authenticate against the Perseus API.perseus_api_key
PERSEUS_BASE_URLBase URL of the Perseus API.http://perseus.localhost
PERSEUS_REQUEST_TIMEOUTTimeout in seconds for requests to the Perseus API.15
ALLOWED_ENDPOINTS_CONFIG_PATHJSON file containing the allowed PERSEUS endpoints.allowed_endpoints.json
FASTAPI_ROOT_PATHRoot path FastAPI mounts the application under./gateway
ANDROMEDA_URLBase URL of the Andromeda frontend used for login and callback redirects.http://andromeda.localhost:6173
ANDROMEDA_PROFILE_PATHProfile page path used after account-link flows./profile
KEYCLOAK_URLBase URL of the Keycloak server.http://localhost:8080
KEYCLOAK_REALMKeycloak realm name.perseus
KEYCLOAK_CLIENT_IDOIDC client ID for the gateway.perseus-gateway
KEYCLOAK_CLIENT_SECRETOIDC client secret for the gateway.perseus-gateway-secret
SESSION_COOKIE_NAMEName of the session cookie.perseus_session
SESSION_COOKIE_SAMESITESameSite policy for the session cookie.lax
SESSION_COOKIE_SECUREWhether the session cookie is only sent over HTTPS.true
SESSION_COOKIE_DOMAINDomain scope for the session cookie.unset
SESSION_TTL_SECONDSSession lifetime in seconds.86400
SESSION_SECRET_KEYSecret key for signing session cookies.perseus_session_secret
DATABASE_URLConnection string for the gateway's persistence layer.sqlite:///./perseus_gateway.db
RATE_LIMIT_PER_MINUTERequests allowed per minute for a client.5
CACHE_EXPIRE_SECONDSTTL for cached responses in seconds.60
VALIDATE_CACHE_SECONDSTTL in seconds for cached authentication validation responses.5
LOGIN_OPTIONSSource for /auth/login-options; supported modes are KEYCLOAK and FILE.KEYCLOAK
LOGIN_OPTIONS_PATHJSON file used for login options when LOGIN_OPTIONS=FILE.login_options.json
USERNAME_SAFE_IDPkc_idp_hint of the identity provider trusted to provide stable, unique usernames; users authenticating via it are matched by username before falling back to email.ldap

Authentication flow

The gateway currently supports Keycloak-based login for /auth/login. It uses a hybrid session model: the browser receives a signed cookie from Starlette's SessionMiddleware, but the durable authentication state lives in the gateway database. The cookie stores the generated session_id; the database record stores the PERSEUS person OID, expiry, validity flag, and optional Keycloak token data.

/auth/login-options is adjacent to this flow, but separate from it: depending on LOGIN_OPTIONS, the gateway either fetches available providers from Keycloak or serves a static list from login_options.json.

flowchart TD
    A[User starts login in Andromeda] --> B[Gateway /auth/login]
    B --> C[Redirect to Keycloak]
    C --> D[Keycloak authenticates user]
    D --> E[Gateway /auth/callback]
    E --> F{Token and state valid?}

    F -- No, invalid state --> G[Return HTTP 400]
    F -- No, auth failed --> H[Redirect to Andromeda with error]
    F -- Yes --> I[Start PERSEUS user provisioning]

    I --> J[Search PERSEUS by external_id=sub]
    J --> K{Person found by sub?}
    K -- Yes --> L[Update firstname lastname email and optional username]
    L --> M[Use existing person OID]

    K -- No --> K1{kc_idp_hint matches USERNAME_SAFE_IDP?}
    K1 -- Yes --> K2[Search PERSEUS by username]
    K2 --> K3{Person found by username?}
    K3 -- Yes --> K4[Link identity provider=keycloak external_id=sub]
    K4 --> R

    K1 -- No --> N
    K3 -- No --> N[Search PERSEUS by email]
    N --> O{Person found by email?}
    O -- Yes --> P[Link identity provider=keycloak external_id=sub]
    P --> Q[Update firstname lastname email and optional username]
    Q --> R[Use existing person OID]

    O -- No --> S[Create new PERSEUS person with keycloak identity]
    S --> T{Person created?}
    T -- Yes --> U[Use new person OID]
    T -- No --> V[Return HTTP 500]

    M --> W
    R --> W
    U --> W

    W -- Yes --> X[Create DB session and set signed session cookie]
    X --> Y[Redirect to Andromeda]
    W -- No, PERSEUS unavailable --> Z[Return HTTP 503]
    W -- No, provisioning failed --> AA[Return HTTP 500]

Local development environment (Docker)

The docker/ directory contains a self-contained Docker Compose environment with Keycloak, OpenLDAP, and a mock OIDC provider. It lets you test all authentication flows locally without connecting to any external or production system.

Warning: All credentials in this setup are hardcoded test values and the environment is for local development only. Never deploy these containers against a production system.

Services
ServiceImageDefault portPurpose
Keycloakquay.io/keycloak/keycloak:26.38080Identity broker, PERSEUS realm
OpenLDAPosixia/openldap:latest1389Local LDAP with test users
Mock OIDCghcr.io/navikt/mock-oauth2-server:latest8888Simulates GitHub and ORCID
Starting the environment
cd docker/
cp .env.example .env   # adjust ports only if you have conflicts
docker compose up

Keycloak starts in start-dev mode and imports the PERSEUS realm from keycloak/realm-export.json on every fresh container start. The Keycloak admin console is available at http://localhost:8080 (admin / admin).

Note: Keycloak uses an in-process H2 database (KC_DB=dev-file). All Keycloak data is lost when the container is removed. The realm is re-imported automatically from the JSON export on the next start — this is intentional for a reproducible local environment.

Test users (Keycloak with LDAP federation)

The local Keycloak instance is configured with an LDAP user federation provider connected to the OpenLDAP container. The following test users are available for authentication:

UsernamePasswordEmailNotes
m.mustermanntest[email protected]Standard test user
e.musterfrautest[email protected]For conflict/duplicate testing
testuser.noemailtest1234No mail attribute (incomplete profile)

Log in via Keycloak at http://localhost:8080/realms/perseus/account or trigger the login flow from the gateway.

Mock OIDC provider (GitHub / ORCID simulation)

The mock server exposes two issuers:

Simulated providerOIDC discovery URLkc_idp_hint
GitHubhttp://localhost:8888/github/.well-known/openid-configurationgithub
ORCIDhttp://localhost:8888/orcid/.well-known/openid-configurationorcid

To trigger a login via a specific provider, append ?kc_idp_hint=github or ?kc_idp_hint=orcid to the Keycloak login URL.

The mock server's interactive login page (interactiveLogin: true) lets you enter any claims at login time. Pre-configured usernames are github-user1, github-user2, orcid-user1, and orcid-user2 (see docker/mock-oauth2/config.json).

Important networking note: The mock server reports its URLs with mock-oauth2:8080 as the issuer host (used for Keycloak back-channel calls). The authorizationUrl in the realm import uses localhost:8888 so the browser can reach the consent page. These two values intentionally differ — do not change them unless you understand the full flow.

Port conflicts

All ports are configurable via docker/.env. Edit the relevant *_PORT variable and restart Compose.

Validating LDIF fixtures locally

If the OpenLDAP container fails to start, the LDIF import likely contains a syntax error. Validate the files with:

ldapmodify -H ldap://localhost:1389 \
  -D "cn=admin,dc=ldapmock,dc=local" \
  -w adminpassword \
  -f docker/ldap/ldif/02-users.ldif \
  -n   # dry-run, no actual changes

Development

  • Run uv run ruff check and uv run ruff format before commiting to lint and format the code.

Tag summary

Content type

Image

Digest

sha256:8f97d3f9c

Size

78.6 MB

Last updated

11 days ago

docker pull pc2upb/perseus-gateway