Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/test"
schedule:
interval: "weekly"
open-pull-requests-limit: 10

- package-ecosystem: "docker-compose"
directory: "/"
schedule:
interval: "weekly"

- package-ecosystem: "docker-compose"
directory: "/kubernetes"
schedule:
interval: "weekly"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
34 changes: 33 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,41 @@
name: Docker Compose Test
on: push

permissions:
contents: read

jobs:
hardening-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- run: ./check-hardening.sh

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- name: Install test dependencies
working-directory: test
run: npm ci
- name: Audit dependencies
working-directory: test
run: npm audit --audit-level=high
- name: Build the develop stack
run: docker compose -f develop.yml build
- name: Start the develop stack
run: docker compose -f develop.yml up -d
- name: Wait for the back service to be healthy
run: timeout 180 bash -c 'until curl -sf http://localhost:4010/apps/viewer/viewer.html > /dev/null; do sleep 3; done'
- name: Run test suite
working-directory: test
run: npm test
- name: Show container logs on failure
if: failure()
run: docker compose -f develop.yml logs
- name: Tear down
if: always()
run: docker compose -f develop.yml down
41 changes: 41 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Scorecard supply-chain security
on:
branch_protection_rule:
schedule:
- cron: '30 1 * * 6'
push:
branches: [ "master" ]

permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5

- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
sarif_file: results.sarif
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
.DS_Store/*
test/node_modules/*
data/pathdb/mysql/*
.db-upgrade.lock
db.pre-upgrade-*
db.pre-restore-*
db.newdata-*
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@

# caMicroscope distribution

![Docker Compose Test](https://github.com/camicroscope/distro/actions/workflows/main.yml/badge.svg)
![License](https://img.shields.io/github/license/camicroscope/distro)

run with `docker-compose -f caMicroscope.yml up`

this will build all services and run in the foreground.
Use `docker-compose -f caMicroscope.yml build` to rebuild the services.

Once everything is up, go to http://localhost:4010/ to see the landing page.
Once everything is up, go to https://localhost/ to see the landing page (a self-signed-certificate warning is expected unless you provide a real certificate, see SSL below). Orthanc's DICOM REST API/GUI is available at https://localhost:8443/.

6GB memory is recommended (in total, 2GB RAM + 4GB swap should work well)

For local development, use `develop.yml` instead -- it builds each service from its GitHub `develop` branch and runs with security disabled for convenience. If you're actively developing against local sibling checkouts of the component repos (`../caracal/`, `../alt_iip/`, ...), use `local_dev.yml` instead, which is identical to `develop.yml` but builds from those local directories rather than GitHub.

## SSL
To enable ssl, mount the private key and certificate files to the ca-back service in /root/src/ssl/privatekey.pem and /root/src/ssl/certificate.pem respectively. HTTPS mode will only be enabled if both of these files are present.
`caMicroscope.yml` fronts the whole stack with an nginx reverse proxy (the `proxy` service) that terminates TLS -- `ca-back` and `ca-dicomsrv` are no longer published directly to the host. To enable HTTPS, place your certificate and private key at `certs/certificate.pem` and `certs/privatekey.pem` respectively (this directory is gitignored). nginx will refuse to start without both files present, so for local testing you can generate a self-signed pair, e.g.:

```
openssl req -x509 -newkey rsa:4096 -keyout certs/privatekey.pem -out certs/certificate.pem -days 365 -nodes -subj "/CN=localhost"
```

Other compose variants (`kc_caMicroscope.yml`, `quip-pathdb.yml`) don't include this proxy and keep their own prior SSL setup (mounting certs directly into `ca-back` or configuring `config/httpd.conf`, respectively).

## Component Services
mongo - vanilla mongo container
Expand All @@ -28,15 +38,37 @@ back - security, data, and routing (see https://github.com/camicroscope/caracal)

back/viewer - within back, viewer files ( see https://github.com/camicroscope/caMicroscope)

dicomsrv - Orthanc-based DICOM server/indexer, providing DICOMweb and raw DICOM protocol access to slides (see https://github.com/camicroscope/dicomsrv)

proxy (`caMicroscope.yml` only) - nginx reverse proxy that TLS-terminates and fronts `back` (port 443) and `dicomsrv`'s REST API/GUI (port 8443). The raw DICOM protocol port (11112) is not published by default -- see "DICOM peer connectivity" below if you need it.

## Configuration
Logging - Logging is enabled by default with a set configurable maximum size. If you need to disable logs, for example due to HIPAA requirements, set the logging driver to none.

See backend and security config notes [here](https://github.com/camicroscope/caracal).

Image Volume - This is, by default, the images directory in this directory. If this is changed, please make the same change across all impacted services.

## Upgrading the MongoDB data directory

`caMicroscope.yml`, `kc_caMicroscope.yml`, and `quip-pathdb.yml` run `mongo:8.0` (previously `mongo:4.2-bionic`). MongoDB requires strictly sequential major-version upgrades, so if you have existing data in `./db` from before this change, starting `mongo:8.0` directly against it will fail with an error like:

```
Invalid feature compatibility version value '4.2'; expected '7.0' or '7.3' or '8.0'
```

If you're starting fresh (an empty or missing `./db`), there's nothing to do -- just start the stack normally.

If you have existing data, run `./db-upgrade.sh` from the repo root. Rather than walking the on-disk files through every intermediate MongoDB version in place, it dumps the `camic` database out of wherever it currently lives, restores that dump into a freshly initialized `mongo:8.0` instance built alongside your existing data, and verifies every collection's document count matches before touching anything -- your original `./db` is never modified: it's only renamed aside (as a timestamped sidecar, kept indefinitely) once the new data has already been verified good, and a portable copy of the dump is also kept under `backups/`. Use `./db-upgrade.sh --dry-run` first to run the full dump-restore-verify rehearsal without installing anything, and `./db-upgrade.sh --help` for all options (including `--restore` to rebuild from a saved dump, optionally at a different `--target` version).

**Compatibility caveat:** the `caracal` backend that performs all Mongo queries pins `"mongodb": "^3.6.6"`, a Node.js driver whose officially tested server-compatibility range predates MongoDB 8.0. The dump/restore itself is version-agnostic (it moves BSON documents, not on-disk storage format), but the driver talking to the resulting mongo:8.0 server afterward is running outside its documented range. Test the application thoroughly afterward. If you hit issues, `./db-upgrade.sh --restore <archive> --target 6.0` (or `7.0`) rebuilds the data at an earlier version instead.

`develop.yml` also runs `mongo:8.0` -- if you have existing local dev data from before this change, run `./db-upgrade.sh` (with no `--data-dir` override, since it also points at `./db`) before starting `develop.yml`, the same as for the other compose files.

## Securing caMicroscope

**`caMicroscope.yml` ships with security enabled by default (`DISABLE_SEC` is not set to `"true"`).** This is a behavior change: previously the flagship compose ran with authorization disabled out of the box. Before deploying `caMicroscope.yml`, you must either configure a real identity provider per the instructions below, or switch to `kc_caMicroscope.yml` for a self-contained Keycloak setup. Deploying `caMicroscope.yml` unmodified with no identity provider configured will lock you out of admin/write functionality. (`develop.yml`, used for local development, intentionally keeps `DISABLE_SEC: "true"` and is unaffected.)

### Getting an Identity Provider and Setting up Login

When selecting, an identity provider, note that we expect it to provide a JWT, and to have a certificate/public key/secret which can be used to verify such JWTs.
Expand Down Expand Up @@ -65,6 +97,15 @@ Alternatively, you can use kc_caMicroscope.yml for a keycloak configuration.

The email field is the email field (or failing that, sub field) in that priority from the identity provider.

### DICOM peer connectivity

By default, `caMicroscope.yml` does not publish the raw DICOM protocol port (11112) to the host -- most deployments don't have an external PACS or modality that needs it, and Orthanc (`dicomsrv`) ships with no DICOM-level authentication or TLS of its own. If you do need real DICOM peer connectivity:

1. Re-add `ports: ["11112:11112"]` to the `dicomsrv` service in `caMicroscope.yml`.
2. In `config/OrthancConfiguration.json`, enable `DicomTlsEnabled`, provide `DicomTlsPrivateKey`/`DicomTlsCertificate`, and populate `DicomModalities` with the AE title/IP/port of each trusted peer rather than leaving the `DicomAlwaysAllow*` flags open to everyone.

Note that `config/OrthancConfiguration.json` is shared with `develop.yml` and `kc_caMicroscope.yml` -- changes here affect those too.

## PathDB

To use PathDB, use quip-pathdb.yml instead of caMicroscope.yml.
Expand Down
18 changes: 18 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Security Policy

## Reporting a Vulnerability

If you believe you've found a security vulnerability in this repository, please report it privately using [GitHub's Security Advisories](https://github.com/camicroscope/distro/security/advisories/new) rather than opening a public issue. This lets us assess and fix the issue before it's publicly disclosed.

Please include:
- A description of the vulnerability and its potential impact
- Steps to reproduce it
- Any relevant configuration (which compose file, environment variables, etc.)

## Supported Versions

This repository tracks the latest state of the `master` branch. There is no separate long-term-support branch; fixes land on `master` and are not backported.

## Non-Security Questions

For general questions, bugs, or feature discussion that aren't security-sensitive, please use the [caMicroscope discussion forum](https://github.com/orgs/camicroscope/discussions) instead.
2 changes: 2 additions & 0 deletions backups/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
32 changes: 25 additions & 7 deletions caMicroscope.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3'

services:
mongo:
image: mongo:4.2-bionic
image: mongo:8.0
container_name: ca-mongo
restart: always
logging:
Expand All @@ -18,8 +18,6 @@ services:
viewer: "v3.12.2"
depends_on:
- "mongo"
ports:
- "4010:4010"
container_name: ca-back
restart: always
logging:
Expand All @@ -36,7 +34,7 @@ services:
JWK_URL: "https://www.googleapis.com/oauth2/v3/certs"
IIP_PATH: "http://ca-iip:8080/fcgi-bin/iipsrv.fcgi"
MONGO_URI: "mongodb://ca-mongo"
DISABLE_SEC: "true"
DISABLE_SEC: "false"
GENERATE_KEY_IF_MISSING: "true"
iip:
image: camicroscope/iipimage:version-3.11.0
Expand Down Expand Up @@ -70,9 +68,12 @@ services:
restart: unless-stopped
stdin_open: true
tty: true
ports:
- "8042:8042"
- "11112:11112"
# Port 8042 (Orthanc REST/GUI) is reached only via the "proxy" service below.
# Port 11112 (raw DICOM protocol) is intentionally not published here -- most
# deployments have no external DICOM peers. If yours does, re-add
# `ports: ["11112:11112"]` and first complete the Orthanc-native DICOM TLS
# setup in config/OrthancConfiguration.json (DicomTlsEnabled/DicomModalities);
# see the README.
volumes:
- ./jwt_keys/:/root/keys/
- ./images/:/images/
Expand All @@ -81,3 +82,20 @@ services:
DICOM_PORT: "11112"
DICOM_UI_PORT: "8042"
CARACAL_BACK_HOST_PORT: "ca-back:4010"
proxy:
image: nginx:1.27-alpine
container_name: ca-proxy
depends_on:
- back
- dicomsrv
ports:
- "443:443"
- "8443:8443"
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs/:/etc/nginx/certs/:ro
restart: always
logging:
options:
max-file: "5"
max-size: "10m"
2 changes: 2 additions & 0 deletions certs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
certificate.pem
privatekey.pem
25 changes: 25 additions & 0 deletions check-hardening.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
#
# check-hardening.sh - guards caMicroscope.yml (the flagship production
# compose) against regressing on the auth/secrets hardening decisions
# made for it. Run locally before pushing, or from CI.

set -uo pipefail

FAIL=0

if grep -q 'DISABLE_SEC: "true"' caMicroscope.yml; then
echo "FAIL: caMicroscope.yml has DISABLE_SEC set to true (the production compose must ship with auth enabled)."
FAIL=1
fi

if grep -inE '(PASSWORD|SECRET|_KEY):[[:space:]]*"?[^"$]' caMicroscope.yml | grep -v '^\s*#'; then
echo "FAIL: caMicroscope.yml appears to contain a hardcoded credential."
FAIL=1
fi

if [[ $FAIL -eq 0 ]]; then
echo "OK: caMicroscope.yml hardening checks passed."
fi

exit $FAIL
34 changes: 34 additions & 0 deletions config/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
events {}

http {
# Main app: viewer, API, loader, IIP, keycloak-proxy route (all handled internally by ca-back's own router)
server {
listen 443 ssl;

ssl_certificate /etc/nginx/certs/certificate.pem;
ssl_certificate_key /etc/nginx/certs/privatekey.pem;

location / {
proxy_pass http://ca-back:4010;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}

# Orthanc REST API / Explorer GUI -- kept on a separate port rather than a
# sub-path, since Orthanc Explorer emits root-relative links.
server {
listen 8443 ssl;

ssl_certificate /etc/nginx/certs/certificate.pem;
ssl_certificate_key /etc/nginx/certs/privatekey.pem;

location / {
proxy_pass http://ca-dicomsrv:8042;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
}
Loading
Loading