Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

log-viewer

Tails the SLURM log files of a B-Fabric workunit in the browser, live.

URLs

URL Purpose
/?jwt=<launch token> entry point for a B-Fabric app launch, and the OAuth redirect URI
/log/<instance>/<workunit id> redirects to that workunit's primary log
/log/<instance>/<workunit id>/<file name> the viewer page — bookmarkable
/ws/<instance>/<workunit id>/<file name> the file's content, then its live tail (WebSocket)
/health, /logout status, session teardown

<instance> is a key of the instances config section (prod, test, trace). A launch does not carry it in the URL: the instance is read from the launch token's iss claim and matched against that config section, which doubles as the allowlist. Log URLs do name it, because workunit IDs are not unique across instances — the test instance is a restore of production, so after the restore the same ID means different jobs with identically named log files in different directories.

Authentication

There is exactly one way into a session: an OAuth authorization-code login (PKCE) against the B-Fabric instance named in the URL. Opening a log URL without a grant for that workunit — a bookmark, an expired cookie, someone else's link, a fresh launch — starts that login and returns to the requested log. That is why log URLs are worth bookmarking and sharing: the recipient logs in as themselves.

A launch from B-Fabric is therefore not a credential. Its short-lived JWT is read for two claims only — iss (which instance) and entityId (which workunit) — and redirected to /log/<instance>/<workunit id>, which logs the user in like any other log URL. The claims are read without verifying the signature, because nothing depends on them: the resulting URL grants nothing by itself, so a forged launch link buys a login prompt.

Whether a user may see a workunit's logs is decided by reading that workunit as them. If B-Fabric does not return it, the answer is 404 — no file is read. That check happens once, at login, and its result is what the signed cookie carries: the instance and the workunit IDs B-Fabric confirmed. No token and no B-Fabric client is kept server-side, so sessions survive a restart, and serving a log costs no API call. The cookie is the whole session, so LOG_VIEWER_SECRET_KEY is what its integrity rests on — rotating that key logs everyone out at once. session_ttl bounds an idle session: starlette re-signs the cookie whenever the session changes, so recording a new grant extends the window for the grants already in it.

Configuration

The file describes the deployment; the environment carries what must not be committed.

CONFIG_FILE (default /app/config.yml) points at a YAML file — config/config.local.yml is a minimal one, and the deployment's own lives in web-apps/log-viewer/config/config.yml. It holds public_base_url and the instances map, and nothing else: every other setting (oauth_scope, session_ttl, max_connections, max_file_size) has a default in log_viewer/config.py, so name one only to override it. An unknown key is an error rather than a no-op, and cookie_secure is derived from the public_base_url scheme rather than set.

Credentials come from the environment (in the deployment, from its config/secrets.env):

  • LOG_VIEWER_SECRET_KEY — signs the session cookie
  • LOG_VIEWER_CLIENT_ID_<INSTANCE> and LOG_VIEWER_CLIENT_SECRET_<INSTANCE> — that instance's OAuth client, one pair per instance in the instances map

A missing one fails at startup, listing everything that is missing at once.

LOG_FILE (default /app/logs/app.log) is the app's own log: rotated at 10 MB, kept 10 days, also written to stdout.

OAuth client registration

Each instance is registered separately, and both halves of the resulting client go in the deployment's secrets.env — nothing about a registration is committed. Every one of those clients registers the same redirect_uri — exactly {public_base_url}/, the same URL as the B-Fabric application's weburl — because the app tells the instances apart by the launch token's iss, not by the URL.

The only grant type the app uses is authorization_code. It never exchanges a launch token and never refreshes: the client it builds from the redeemed code makes one read and is discarded. bfabric.oauth.register_webapp() cannot express that — it always requests token-exchange, refresh_token and authorization_code — so register least-privilege in two steps instead (both need an employee bearer token):

from bfabric.oauth import register_client

oauth = register_client(
    base_url=base_url,
    token=employee_token,
    client_name="Log Viewer",
    redirect_uri=f"{public_base_url}/",
    scope="api:read openid profile email groups",
    grant_types=["authorization_code"],
)
client.save(
    "application",
    {
        "name": "Log Viewer",
        "type": "WebApp",
        "weburl": f"{public_base_url}/",
        "oauthclientid": oauth["id"],
    },
)

Development

make test runs the test suite. There is no way to run the app without B-Fabric: OAuth is the only way to get a session, so a local run needs a real instance.

export LOG_VIEWER_SECRET_KEY=$(openssl rand -base64 32)
export LOG_VIEWER_CLIENT_ID_TEST=<client id>
export LOG_VIEWER_CLIENT_SECRET_TEST=<client secret>
mkdir -p logs
while true; do echo "$(date '+[%Y-%m-%d %H:%M:%S]') INFO demo line" >> logs/workunitid-1.log; sleep 1; done &
CONFIG_FILE=config/config.local.yml LOG_FILE=logs/app-local.log make dev
# then launch from a workunit, or open the log URL directly and log in:
# http://localhost:8000/log/test/1/workunitid-1.log

The OAuth client's redirect_uri must be http://localhost:8000/, matching public_base_url in that file.

make lint runs ruff, and make docker-build builds the image locally. CI runs the tests and ruff on every push and pull request.

bfabric is resolved from bfabricPy's main branch rather than PyPI, because the authorization-code helpers this app uses are not in a release yet; uv.lock pins the commit, and make lock is what moves it. Drop the [tool.uv.sources] entry once bfabric 1.23.0 is on PyPI.

Release

The image is published to ghcr.io/fgcz/log-viewer by the publish workflow, which builds linux/amd64 and linux/arm64 (the deployment host is arm64) after the test suite passes on the tagged commit:

# bump `version` in pyproject.toml and add its section to CHANGELOG.md, then
uv lock
git commit -am "Release 0.7.0"
git tag v0.7.0
git push && git push --tags

Deployment

There is one deployment, on fgcz-r-039 host port 9402, serving every configured B-Fabric instance. It lives in the web-apps repository under log-viewer/, which holds the production Compose file, config/config.yml and config/secrets.env — nothing of the application itself. Deploying a release is bumping IMAGE_TAG in that directory's .env to the new vX.Y.Z and running make deploy; see its README. For a local run from source, see Development above.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages