fix: recipe config cleanup, report-only CSP, logout redirect validation (#6124)
This commit is contained in:
1 parent
973631b7e8
commit
f8546ce0e0
27 files changed
+446
-75
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# Copy this file to .env next to docker-compose.yml and fill in strong
|
||||
# values. docker compose refuses to start while either is unset.
|
||||
KEYCLOAK_ADMIN_PASSWORD=
|
||||
# POSTGRES_PASSWORD is the single PostgreSQL credential: it provisions the
|
||||
# keycloak database role and Keycloak reuses it to connect (KC_DB_PASSWORD is
|
||||
# derived from it in docker-compose.yml), so there is one value, not two.
|
||||
POSTGRES_PASSWORD=
|
||||
@@ -0,0 +1,42 @@
|
||||
# Security notes for this recipe
|
||||
|
||||
## Secrets
|
||||
|
||||
This recipe no longer ships working credential values. Before `docker compose up`:
|
||||
|
||||
- Generate a fresh OAuth client secret for the `ohif_viewer` client and use it
|
||||
to replace the `REPLACE_WITH_A_GENERATED_CLIENT_SECRET` placeholder in BOTH
|
||||
`config/ohif-keycloak-realm.json` and `config/oauth2-proxy.cfg` (the two
|
||||
values must match). To generate one after the realm is imported: Keycloak
|
||||
admin console -> Clients -> ohif_viewer -> Credentials -> Regenerate.
|
||||
- Copy `.env.example` to `.env` and set `POSTGRES_PASSWORD` and
|
||||
`KEYCLOAK_ADMIN_PASSWORD` to strong values. `docker compose` refuses to
|
||||
start while either is unset. `POSTGRES_PASSWORD` is the single PostgreSQL
|
||||
credential - it both provisions the `keycloak` database role and is what
|
||||
Keycloak uses to connect (the compose file derives `KC_DB_PASSWORD` from it),
|
||||
so there is one database password to set, not two.
|
||||
|
||||
## Rotate if you deployed from an earlier checkout
|
||||
|
||||
Earlier versions of this recipe committed a fixed client secret and default
|
||||
admin/database passwords to the public repository. A committed secret stays
|
||||
burned even after this change, so any deployment created from an earlier
|
||||
checkout must rotate:
|
||||
|
||||
- the `ohif_viewer` client secret (Keycloak admin console -> Clients ->
|
||||
ohif_viewer -> Credentials -> Regenerate, then update
|
||||
`config/oauth2-proxy.cfg` to match), and
|
||||
- the Keycloak admin and PostgreSQL passwords. Note that changing
|
||||
`POSTGRES_PASSWORD` in `.env` alone does not re-password an existing
|
||||
database: Postgres only applies it when the `postgres_data` volume is first
|
||||
initialized. To actually rotate it on an existing deployment, either
|
||||
`ALTER ROLE keycloak WITH PASSWORD ...` inside the running database or
|
||||
recreate the `postgres_data` volume.
|
||||
|
||||
## CORS
|
||||
|
||||
The wildcard `Access-Control-Allow-Origin: *` defaults were removed from
|
||||
`config/nginx.conf`. The viewer is served by the same nginx as the dcm4chee
|
||||
proxy, so same-origin deployments need no CORS headers. If you host the viewer
|
||||
on a different origin, use the commented explicit-origin example in the nginx
|
||||
config - never `*` on an authenticated endpoint that serves PHI.
|
||||
@@ -54,6 +54,15 @@ http {
|
||||
gzip_comp_level 9;
|
||||
etag on;
|
||||
|
||||
# Content-Security-Policy: test in Report-Only mode first and watch
|
||||
# the browser console for violations before switching the header name
|
||||
# to the enforcing Content-Security-Policy. Note: nginx add_header does
|
||||
# not merge into a location that sets its own add_header - if you
|
||||
# promote this to an active header, re-declare it inside every location
|
||||
# block that already uses add_header (several proxy locations here do)
|
||||
# or it will silently not apply there.
|
||||
# add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https: blob:; worker-src 'self' blob:; object-src 'self' blob:; frame-src 'self' blob:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
|
||||
|
||||
location /sw.js {
|
||||
add_header Cache-Control "no-cache";
|
||||
proxy_cache_bypass $http_pragma;
|
||||
@@ -101,19 +110,25 @@ http {
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain; charset=utf-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
# CORS: this recipe serves the viewer from this same nginx, so
|
||||
# browser requests to this endpoint are same-origin and need NO
|
||||
# CORS headers at all. If you host the viewer on a different
|
||||
# origin, allow that origin explicitly and only that origin -
|
||||
# never use '*' on an authenticated endpoint that serves PHI.
|
||||
# Cross-origin example (response headers plus OPTIONS preflight):
|
||||
# add_header 'Access-Control-Allow-Origin' 'https://viewer.example.com' always;
|
||||
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
||||
# add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
|
||||
#
|
||||
# if ($request_method = OPTIONS) {
|
||||
# add_header 'Access-Control-Allow-Origin' 'https://viewer.example.com';
|
||||
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
# add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
|
||||
# add_header 'Access-Control-Max-Age' 1728000;
|
||||
# add_header 'Content-Type' 'text/plain; charset=utf-8';
|
||||
# add_header 'Content-Length' 0;
|
||||
# return 204;
|
||||
# }
|
||||
|
||||
rewrite ^/pacs/(.*) /dcm4chee-arc/aets/DCM4CHEE/rs/$1 break;
|
||||
proxy_pass http://arc:8080;
|
||||
@@ -155,19 +170,8 @@ http {
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain; charset=utf-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
# CORS: same-origin deployment - no CORS headers needed here; see
|
||||
# the note on the /pacs/ location above before adding any.
|
||||
|
||||
proxy_pass http://arc:8080;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ email_domains=["*"]
|
||||
cookie_secure="false"
|
||||
cookie_expire="9m30s"
|
||||
cookie_refresh="5m"
|
||||
client_secret="2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg"
|
||||
client_secret="REPLACE_WITH_A_GENERATED_CLIENT_SECRET"
|
||||
client_id="ohif_viewer"
|
||||
redirect_url="http://YOUR_DOMAIN/oauth2/callback"
|
||||
|
||||
|
||||
@@ -686,7 +686,7 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"secret": "2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg",
|
||||
"secret": "REPLACE_WITH_A_GENERATED_CLIENT_SECRET",
|
||||
"redirectUris": [
|
||||
"http://127.0.0.1/oauth2/callback"
|
||||
],
|
||||
|
||||
@@ -94,14 +94,17 @@ services:
|
||||
KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak'
|
||||
KC_DB_SCHEMA: public
|
||||
KC_DB_USERNAME: keycloak
|
||||
KC_DB_PASSWORD: password
|
||||
# Keycloak authenticates to Postgres as the keycloak role provisioned
|
||||
# below, so this must be the same value as POSTGRES_PASSWORD. Sourced
|
||||
# from the single POSTGRES_PASSWORD variable to keep them in lockstep.
|
||||
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in your .env}
|
||||
KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/
|
||||
KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/
|
||||
KC_HOSTNAME_STRICT_BACKCHANNEL: true
|
||||
KC_HOSTNAME_STRICT_HTTPS: false
|
||||
KC_HTTP_ENABLED: true
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:?set KEYCLOAK_ADMIN_PASSWORD in your .env}
|
||||
KC_HEALTH_ENABLED: true
|
||||
KC_METRICS_ENABLED: true
|
||||
KC_PROXY: edge
|
||||
@@ -139,7 +142,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: keycloak
|
||||
POSTGRES_USER: keycloak
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in your .env}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
|
||||
Reference in new issue
Block a user