Orthanc <> orthanc-auth-service <> keycloak: Some misconfiguration

Intro

I was doing so well.
Thanks to the help @alainmazy gave me I managed to get everything working, but I don’t know what I did that broke everything. I was getting ready to put everything into production, changing the secrets, deleting the test databases and creating new databases for the orthanc index and the Keycloak realm.

But now when I try to access Orthanc I can authenticate but I can’t see any functionality in OE2 and in the logs I get:

orthanc-auth-service-1 | INFO: 172.18.0.4:54764 - "GET /settings/roles HTTP/1.1" 401 Unauthorized
orthanc-auth-service-1 | INFO: 172.18.0.4:54776 - "POST /user/get-profile HTTP/1.1" 401 Unauthorized

I don’t know what I might have misconfigured by accident.

Configuration

services:
  # Container 1: Proxy Reverso com SSL
  nginx:
    image: orthancteam/orthanc-nginx:24.7.2
    depends_on: [ orthanc, orthanc-dicom-tls, orthanc-dicom, orthanc-auth-service, keycloak, ohif ]
    restart: unless-stopped
    ports: [ "443:443" ]
    volumes:
      - ./tls/crt.pem:/etc/nginx/tls/crt.pem:ro
      - ./tls/key.pem:/etc/nginx/tls/key.pem:ro
    environment:
      ENABLE_ORTHANC: "true"
      ENABLE_KEYCLOAK: "true"
      ENABLE_ORTHANC_TOKEN_SERVICE: "false"
      ENABLE_HTTPS: "true"
      ENABLE_OHIF: "true"

  # Container 2: Static OHIF
  ohif:
    image: orthancteam/ohif-v3:24.7.2
    restart: unless-stopped

  # Container 3: ORTHANC PACS (DICOMweb, GUI)
  orthanc:
    image: orthancteam/orthanc:24.8.3
    restart: unless-stopped
    volumes:
      - ./tmp/orthancDicomWeb-logs:/logs
      - ./orthancDicomWeb.json:/etc/orthanc/orthanc.json:ro
    environment:
      # Console Logs
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      # Logs
      LOGDIR: "/logs"
      ORTHANC__DE_IDENTIFY_LOGS: "false"

  # Container 4: ORTHANC PACS (DISME) 
  orthanc-dicom-tls:
    image: orthancteam/orthanc:24.8.3
    restart: unless-stopped
    ports:
      - "2762:4242"
    volumes:
      - ./tmp/orthancDicomTLS-logs:/logs
      - ./orthancDicomTLS.json:/etc/orthanc/orthanc.json:ro
      - ./tls:/tls:ro
    environment:
      # Console Logs
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      # Logs
      LOGDIR: "/logs"
      ORTHANC__DE_IDENTIFY_LOGS: "false"

  # Container 5: ORTHANC PACS (DISME)
  orthanc-dicom:
    image: orthancteam/orthanc:24.8.3
    restart: unless-stopped
    ports:
      - "104:4242"
    volumes:
      - ./tmp/orthancDicom-logs:/logs
      - ./orthancDicom.json:/etc/orthanc/orthanc.json:ro
    environment:
      # Console Logs
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      # Logs
      LOGDIR: "/logs"
      ORTHANC__DE_IDENTIFY_LOGS: "false"

  # Container 6: Auth Interface Orthanc < > KeyCloak
  orthanc-auth-service:
    image: orthancteam/orthanc-auth-service:24.7.2
    volumes:
      - ./permissions.json:/orthanc_auth_service/permissions.json
    depends_on: [ keycloak ]
    restart: unless-stopped
    env_file:
      - "./.auth.env"

  # Container 7: Auth
  keycloak:
    image: orthancteam/orthanc-keycloak:24.7.2
    restart: unless-stopped
    env_file:
      - "./.keycloak.env"

The idea of ​​this configuration is that I have NGINX to observe HTTPS communications (Layer 7: Application Layer) and redirect to:

  • OHIF,
  • Orthanc Web Server (DICOMweb) and Remote Access,
  • Orthanc-auth-service,
  • KeyCloak

The other Orthancs have Remote Access turned off and are just gateways to DICOM operating in TCP (Layer 4: Network Layer). These are usually the ones known as DISME or SPU, I don’t know if there is a difference or if each one calls it what they want. One Orthanc with active TLS to protect communication and another without for devices that are not capable of secure communication. What I don’t know is if it is really necessary to have this unsecured server.

orthanc.json (Config Files)

orthancDicomWeb.json

{
    "Name": "SOME AWESOME NAME",
    "ConcurrentJobs": 0,
    "JobsEngineThreadsCount": {
        "ResourceModification": 1
    },
    "HttpServerEnabled": true,
    "HttpPort": 8042,
    "HttpDescribeErrors": true,
    "HttpCompressionEnabled": false,
    "WebDavEnabled": false,
    "DicomServerEnabled": false,
    "SslEnabled": false,
    "RemoteAccessAllowed": true,
    "OrthancExplorer2": {
        "IsDefaultUI": true,
        "UiOptions": {
            "EnableShares": true,
            "DefaultShareDuration": 0,
            "ShareDurations": [
                0,
                7,
                15,
                30,
                90,
                365
            ],
            "EnableOpenInOhifViewer3": true,
            "OhifViewer3PublicRoot": "https://my.already.changed.url/ohif/"
        },
        "Tokens": {
            "InstantLinksValidity": 3600,
            "ShareType": "ohif-viewer-publication"
        },
        "Keycloak": {
            "Enable": true,
            "Url": "https://my.already.changed.url/keycloak/",
            "Realm": "orthanc",
            "ClientId": "orthanc"
        }
    },
    "AuthenticationEnabled": false,
    "Authorization": {
        "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
        "WebServiceUsername": "SOME SHARE USER",
        "WebServicePassword": "SOME SHERE PASSWORD",
        "StandardConfigurations": [
            "orthanc-explorer-2",
	    "ohif"
        ],
        "CheckedLevel": "studies"
    },
    "DicomWeb": {
        "Enable": true,
        "PublicRoot": "/orthanc/dicom-web/"
    },
    "PostgreSQL": {
        "EnableIndex": true,
        "Host": "AWS RDS HOST",
        "Port": 5432,
        "Database": "orthanc",
        "Username": "postgres",
        "Password": "SOME AWESOME PASSWORD",
        "EnableSsl": true
    },
    "AwsS3Storage": {
        "BucketName": "SOME AWESOME BUCKET NAME",
        "Region": "us-east-1",
        "AccessKey": "SOME ACCESS KEY",
        "SecretKey": "SOME SECRET KEY",
        "StorageStructure": "flat",
        "UseTransferManager": true
    }
}

Why did I remove the HttpHeaders Token? I don’t intend to use API-KEY, but I saw that it is possible to generate tokens through Keycloak to pass as a query string in the URL to gain access to the resources. If the correct token is not passed, the resource will not be loaded.

I also removed RegisteredUsers since all of this will be in Keycloak.

But what would a resource be for me?

My use case:

I need Orthanc to be the gateway only, with a single user who is able to log in in case of an audit, an admin.

The OHIF will always be accessed using the studyInstaceUID to find which DICOM to load and using the Keycloak token.

So a resource is a DICOM loaded by OHIF for doctors to view.

I will not use the open button by OHIF within OE2 or share a link, much less create a user for each doctor.

So my env for Orthanc-auth-service and Keycloak looked like this:

ENV

.auth.env

SECRET_KEY="SOME AWESOME SECRET KEY (64 caracters: Uppercase, lowercase, numbers, special characters)"
ENABLE_KEYCLOAK="true"
# ENABLE_KEYCLOAK_API_KEYS="true"
# KEYCLOAK_CLIENT_SECRET="SECRET OBTAINED INSIDE KEYCLOAK"
PUBLIC_ORTHANC_ROOT="https://my.already.changed.url/orthanc/"
PUBLIC_LANDING_ROOT="https://my.already.changed.url/orthanc/ui/app/token-landing.html"
PUBLIC_OHIF_ROOT="http://my.already.changed.url/ohif/"
USERS={ "SOME SHARE USER":"SOME SHERE PASSWORD" }

.keyclock.env

KEYCLOAK_ADMIN="ADMIN USER WHO HAS NOTHING TO DO WITH Orthanc-Auth-Service"
KEYCLOAK_ADMIN_PASSWORD="SOME AWESOME PASSWORD (64 caracters: Uppercase, lowercase, numbers, special characters)"
# KC_FEATURES="fips[:v1]"
# KC_FIPS_MODE="strict"
KC_DB="postgres"
KC_DB_URL="AWS RDS HOST"
KC_DB_USERNAME="postgres"
KC_DB_PASSWORD="SOME AWESOME PASSWORD"
KC_HOSTNAME_URL="https://my.already.changed.url/keycloak"
KC_HOSTNAME_ADMIN_URL="https://my.already.changed.url/keycloak"
# QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY=true

What did I do wrong? I think my eyes are so glazed that I’m not seeing the mistake I made.

I am following the information in the following links:

Docker Hub orthanc-auth-service
Github Minimal Setup Docker Compose
Orthanc orthanc-auth-service Repo

ENV Keys for Keycloak

Docker Config guide for Keycloak

Thank you for your time.

Hi @CaioBG

Analyzing a whole config without running the system is not easy at all so I strongly advise you to analyze the Orthanc logs (in verbose) when one tries to open the OE2 interface; the auth-plugin is quite verbose so that should help understand what’s going wrong.

Best regards,

Alain

When I log in it looks like this:

Full log of when I log in:

nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:51 +0000] "POST /keycloak/realms/orthanc/login-actions/authenticate?session_code=G4Nd84JALokueMD8ZbqgBqTOUdb5ruWaE8IubTA2hcs&execution=f120f7fd-c2e9-4a3c-b4b4-f0378ea83775&client_id=orthanc&tab_id=vnJ08_4XCQw HTTP/1.1" 302 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:51 +0000] "GET /orthanc/ui/app/ HTTP/1.1" 200 1205 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:51 +0000] "GET /orthanc/ui/api/pre-login-configuration HTTP/1.1" 200 156 "https://pacstera.teratelemedicina.com.br/orthanc/ui/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:52 +0000] "GET /keycloak/realms/orthanc/protocol/openid-connect/3p-cookies/step1.html HTTP/1.1" 200 1462 "https://pacstera.teratelemedicina.com.br/orthanc/ui/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:52 +0000] "GET /keycloak/realms/orthanc/protocol/openid-connect/3p-cookies/step2.html HTTP/1.1" 200 686 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:52 +0000] "GET /keycloak/realms/orthanc/protocol/openid-connect/login-status-iframe.html HTTP/1.1" 200 3150 "https://pacstera.teratelemedicina.com.br/orthanc/ui/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:52 +0000] "POST /keycloak/realms/orthanc/protocol/openid-connect/token HTTP/1.1" 200 3664 "https://pacstera.teratelemedicina.com.br/orthanc/ui/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
orthanc-auth-service-1  | INFO:     172.18.0.3:38092 - "GET /settings/roles HTTP/1.1" 401 Unauthorized
orthanc-auth-service-1  | INFO:     172.18.0.3:38094 - "POST /user/get-profile HTTP/1.1" 401 Unauthorized
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:53 +0000] "GET /orthanc/ui/api/configuration HTTP/1.1" 400 262 "https://pacstera.teratelemedicina.com.br/orthanc/ui/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"
nginx-1                 | 172.69.90.108 - - [04/Sep/2024:16:09:58 +0000] "GET /keycloak/realms/orthanc/protocol/openid-connect/login-status-iframe.html/init?client_id=orthanc&origin=https%3A%2F%2Fpacstera.teratelemedicina.com.br HTTP/1.1" 204 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "2804:14d:5c4c:103e:eac8:274c:4ab:669d"

I noticed these errors in the log:

I0904 16:09:53.141243          HTTP-19 OrthancPlugins.cpp:2478] (plugins) Delegating HTTP request to plugin for URI: /ui/api/configuration
I0904 16:09:53.141347          HTTP-19 OrthancPlugins.cpp:3248] (plugins) Plugin making REST GET call on URI /auth/settings/roles (after plugins)
I0904 16:09:53.141401          HTTP-19 OrthancPlugins.cpp:2478] (plugins) Delegating HTTP request to plugin for URI: /auth/settings/roles
I0904 16:09:53.141468          HTTP-19 PluginsManager.cpp:162] (plugins) New HTTP request to: http://orthanc-auth-service:8000/settings/roles (timeout: 10s)
I0904 16:09:53.143412          HTTP-19 PluginsManager.cpp:162] (plugins) HTTP status code 401 in 1 ms after GET request on: http://orthanc-auth-service:8000/settings/roles
E0904 16:09:53.143579          HTTP-19 PluginsManager.cpp:154] Error in HTTP request, received HTTP status 401 (Unauthorized) after GET request on: http://orthanc-auth-service:8000/settings/roles
W0904 16:09:53.143772          HTTP-19 PluginsManager.cpp:158] Could not retrieve roles from the auth-service.  The auth-service might not provide this feature or is not configured correctly.
I0904 16:09:53.143880          HTTP-19 PluginsManager.cpp:162] (plugins) null

I0904 16:09:53.143915          HTTP-19 OrthancPlugins.cpp:3248] (plugins) Plugin making REST GET call on URI /auth/user/profile (after plugins)
I0904 16:09:53.143950          HTTP-19 OrthancPlugins.cpp:2478] (plugins) Delegating HTTP request to plugin for URI: /auth/user/profile
I0904 16:09:53.144033          HTTP-19 PluginsManager.cpp:162] (plugins) New HTTP request to: http://orthanc-auth-service:8000/user/get-profile (timeout: 10s)
I0904 16:09:53.146144          HTTP-19 PluginsManager.cpp:162] (plugins) HTTP status code 401 in 2 ms after POST request on: http://orthanc-auth-service:8000/user/get-profile
E0904 16:09:53.146418          HTTP-19 PluginsManager.cpp:154] Error in HTTP request, received HTTP status 401 (Unauthorized) after POST request on: http://orthanc-auth-service:8000/user/get-profile
E0904 16:09:53.146697          HTTP-19 PluginsManager.cpp:154] Bad file format: List of strings expected in field: permissions
E0904 16:09:53.146780          HTTP-19 PluginsErrorDictionary.cpp:101] Exception inside the plugin engine: Bad file format

These 5 lines lines:

E0904 16:09:53.143579          HTTP-19 PluginsManager.cpp:154] Error in HTTP request, received HTTP status 401 (Unauthorized) after GET request on: http://orthanc-auth-service:8000/settings/roles
W0904 16:09:53.143772          HTTP-19 PluginsManager.cpp:158] Could not retrieve roles from the auth-service.  The auth-service might not provide this feature or is not configured correctly.
I0904 16:09:53.143880          HTTP-19 PluginsManager.cpp:162] (plugins) null

[...]

E0904 16:09:53.146697          HTTP-19 PluginsManager.cpp:154] Bad file format: List of strings expected in field: permissions
E0904 16:09:53.146780          HTTP-19 PluginsErrorDictionary.cpp:101] Exception inside the plugin engine: Bad file format

They made me think that the permissions.json file was poorly made but I tried removing the volume and trying without it, but it’s kind of crazy since I copied this file directly from the Github repository.

So @alainmazy, the problem was the password name of the share user, the user that both Orthanc and Orthanc-Auth-Service know to communicate with.

I’ll read your source code to learn more about how you sanitize and wait for the name and password of this user.

I tried a name following some security rules:

  • It must be a name with an unknown theme,
  • With complexity to make it difficult to guess

Like VerBena_!00 for example.

The password must follow:

  • A big entropy,
  • With 64 characters,
  • Lowercase letters: qwerty,
  • Uppercase letters: QWERTY,
  • Numbers: 0123456789,
  • Special characters: .±*"/|'!@#$%&,{[ }]?;

For example: _18znfl5=Ajcz("YL3H4b&i7!8X3oxE~6XaBGoe,M7’bahC2;k84FTpH6#)w}u8N

But apparently something is not allowed in these parameters.
Is there anything that you can see right away that could not exist in this share user?

Since the pwd must be provided in JSON to Orthanc, I would avoid the double quotes inside the pwd. But, besides that, I do not see any limitations …