Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports. * jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls. * Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results. * Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable. * Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate. * Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do. * Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence. * Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags. * Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check. * Rugnux: Clear error messages when a data set needs more GPU or host memory than is available. Reviewed-on: #83 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
132 lines
8.9 KiB
Markdown
132 lines
8.9 KiB
Markdown
# Security
|
|
|
|
Jungfraujoch is a data-acquisition and analysis system for X-ray detectors, designed to run
|
|
**inside a controlled facility network**. This document describes what the software does and does
|
|
not protect against, the current known limitations, and the authentication work in progress.
|
|
|
|
## Threat model and scope
|
|
|
|
The security model targets a **semi-trusted internal facility network**. The concern is a peer on
|
|
that network reaching a Jungfraujoch service with **little or no effort** — a mistyped host/port, a
|
|
curious colleague, a mis-pointed script, a stray browser tab — not a determined attacker and not
|
|
passive wire capture (which is the responsibility of the network layer: 802.1x, VLANs, facility
|
|
infrastructure).
|
|
|
|
The asset that matters most is the **confidentiality of live analysis data**: the diffraction
|
|
images and derived metadata (unit cell, resolution, spot counts, sample name) that reveal *which
|
|
sample is being measured*. This matters for industrial and proprietary experiments. By contrast,
|
|
**acquisition control** (start / stop / configure) is treated as low risk — scientists operate their
|
|
own experiments and there is little to gain from restricting it.
|
|
|
|
Security is **best-effort**: measures that materially impede normal operation get turned off, so the
|
|
design favours a few high-value, low-friction controls over comprehensive lockdown.
|
|
|
|
> **Out of scope.** Jungfraujoch is **not** designed to be exposed to an untrusted network or the
|
|
> public internet. Do not do this.
|
|
|
|
## 1. Good practice — what is and is not protected
|
|
|
|
### What you can secure (and should)
|
|
|
|
These controls work and a deployment should apply them (see also `DEPLOYMENT.md`):
|
|
|
|
- **Network isolation.** Keep the broker and its data streams on a controlled segment. The
|
|
broker ↔ writer ↔ receiver traffic should run on a **dedicated back-end network**, with the
|
|
data-socket addresses pinned to that interface and the ports firewalled.
|
|
- **Reverse proxy for TLS.** The broker speaks plain HTTP. To get HTTPS, put a reverse proxy
|
|
(Apache / nginx) in front that terminates TLS and pin the broker to `localhost` behind it. The
|
|
desktop viewer supports `https://` endpoints — choose the scheme in the *Open HTTP Connection*
|
|
dialog.
|
|
- **Filesystem confinement of written data.** The writer creates NXmx HDF5 files on shared
|
|
storage. Confidentiality of that data **at rest** is enforced by the filesystem: run the writer
|
|
under a dedicated identity and use directory ownership / ACLs (and setgid) so that only the owning
|
|
experiment can read its files.
|
|
- **Firewall the ZeroMQ ports.** The image / preview / metadata / republish streams have no access
|
|
control of their own (see below), so restrict who can reach those ports at the network layer.
|
|
|
|
### What the software does NOT provide
|
|
|
|
The gaps are listed explicitly so a deployment does not assume protection that is not there:
|
|
|
|
- **No authentication or authorization in the broker.** The HTTP/REST API currently has **no login,
|
|
token, or access control**. Anyone who can reach the broker's host and port has full read access
|
|
(live images, unit cell, resolution, sample metadata) and full write access (start, cancel,
|
|
reconfigure). Confidentiality currently depends **entirely** on network/firewall isolation. This
|
|
is being addressed — see §3.
|
|
- **No transport encryption in the broker.** The broker serves plain HTTP; there is no built-in TLS.
|
|
Encryption must be provided by a reverse proxy.
|
|
- **No access control or encryption on the ZeroMQ streams.** The preview, metadata, image, and
|
|
republish streams are unauthenticated sockets. Any peer that can connect can subscribe to live
|
|
data. For the image `PUSH` stream specifically, an accidental extra consumer does not merely
|
|
eavesdrop — a `PULL` peer is load-balanced into the stream and will *divert* images away from the
|
|
real writer.
|
|
- **No per-user isolation.** The broker has no concept of users; it cannot separate one operator's
|
|
access from another's.
|
|
- **No application-level audit trail** of who accessed or changed what.
|
|
|
|
### Recommended deployment checklist
|
|
|
|
- [ ] Broker and back-end streams on an isolated network; **never** exposed to a general/untrusted network.
|
|
- [ ] ZeroMQ data-socket addresses pinned to the back-end interface; ports firewalled to known peers.
|
|
- [ ] TLS terminated by a reverse proxy; broker bound to `localhost` behind it.
|
|
- [ ] Writer run under a dedicated identity; data directories owned / ACL'd per experiment (setgid) so users read only their own data.
|
|
- [ ] ZeroMQ compatibility streams (preview / metadata / republish) enabled only if actually consumed, and only on the trusted back-end.
|
|
|
|
## 2. Known issues
|
|
|
|
| # | Issue | Impact | Mitigation today |
|
|
|---|-------|--------|------------------|
|
|
| 1 | Broker HTTP API has no authentication for control, and read access is protected only per dataset | Anyone who can reach it can start / stop / configure; a dataset started without `tokens` is readable by anyone | Per-dataset bearer tokens (§3) for the read endpoints; network / firewall isolation for the rest |
|
|
| 2 | Broker binds all interfaces, plain HTTP | Reachable from anywhere routable; no encryption | Expose only on the trusted segment; TLS via reverse proxy |
|
|
| 3 | ZeroMQ preview / metadata / image / republish streams are unauthenticated and unencrypted | Live-data exfiltration; a rogue `PULL` on the image stream diverts/steals images | Firewall the ports; run only on the back-end network |
|
|
| 4 | Web frontend has no login | The bundled UI takes a dataset token (key button) but nothing identifies its user | Serve and reach it only on the trusted network |
|
|
|
|
**Input robustness.** Services parse framed data from peers on the (trusted) data path. Hardening of
|
|
untrusted-frame handling (size caps, overflow guards) is ongoing; these paths are not intended to
|
|
face an untrusted network.
|
|
|
|
## 3. Authenticated read access — per-dataset bearer tokens
|
|
|
|
The confidential **read** endpoints are protected with a **best-effort, low-friction** scheme;
|
|
acquisition control stays open.
|
|
|
|
**How it works.** `/start` accepts an optional list of `tokens` - plain strings, any number, all
|
|
equivalent. A typical pair is one constant beamline secret and one secret minted for the
|
|
experiment, so both the beamline staff and the experiment's own users can open the data. While the
|
|
current dataset has tokens, the endpoints below answer `401` unless the request carries
|
|
`Authorization: Bearer <one of them>`; the 401 says nothing about the dataset. Every accepted
|
|
`/start` replaces the previous tokens, so a run started without them is open, and the next run's
|
|
users cannot read this one. No endpoint returns the tokens; the broker only compares strings
|
|
(constant-time) and keeps them in memory. Whoever runs `/start` hands the token to the viewers.
|
|
An accepted `/start` also clears what the previous run left readable - its statistics, plots and
|
|
buffered images - in the same step that installs the new tokens, so the previous run is never
|
|
served under the new tokens, and the new run's name never under the old ones. A refused `/start`
|
|
(wrong state, invalid settings) changes nothing.
|
|
|
|
| Endpoint | With tokens set |
|
|
|----------|-----------------|
|
|
| `/statistics/data_collection` (dataset name, unit cell, ...) | 401 without a token |
|
|
| `/result/scan` (dataset name, cell of a grid scan / rotation) | 401 without a token |
|
|
| `/image_buffer/start.cbor`, `/image_buffer/image.cbor`, `/image_buffer/image.jpeg`, `/image_buffer/image.tiff` (the images and the start message) | 401 without a token |
|
|
| `/preview/plot`, `/preview/plot.bin` (per-image plots, unit cell) | 401 without a token |
|
|
| `/statistics` (the aggregate the web UI polls) | 200, but the `measurement` block is omitted |
|
|
| everything else (`/status`, `/config/*`, `/start`, `/cancel`, masks, pedestal, ...) | open |
|
|
|
|
**Clients.**
|
|
|
|
- *jfjoch_viewer* - the token field of **File ▸ Open HTTP** (password echo), the
|
|
`JUNGFRAUJOCH_HTTP_TOKEN` environment variable, or D-Bus (`LoadFile(url, image, sum, token)` /
|
|
`SetHttpToken(token)`); the dialog overrides both. A 401 clears the display and puts a note on
|
|
the status bar - no dialog, since a changed dataset is the normal reason.
|
|
- *Web frontend* - the key button in the top bar; the token lives in the tab's `sessionStorage`
|
|
and is sent with the protected calls only. The start form has a field for the tokens of a new run.
|
|
- *Python client* - `Configuration(host=..., access_token="<token>")`.
|
|
- *Anything else* - `curl -H "Authorization: Bearer <token>" ...`.
|
|
|
|
**What it does not do.** The broker still speaks plain HTTP, so the token crosses the network in
|
|
clear unless a TLS reverse proxy fronts the broker (§1); on a facility network this raises the bar
|
|
from "type the IP" to "capture packets", which is the aim. It does not authenticate users or
|
|
control, does not touch the ZeroMQ streams (issue #3), and `/status`'s free-text `message` may
|
|
still quote a path. Datasets of different users within one session are separated by their tokens
|
|
alone; there is no long-lived login.
|