diff --git a/minimal-setup/basic-auth/docker-compose.yml b/minimal-setup/basic-auth/docker-compose.yml index bf0641c..56a8a8a 100644 --- a/minimal-setup/basic-auth/docker-compose.yml +++ b/minimal-setup/basic-auth/docker-compose.yml @@ -128,7 +128,7 @@ services: restart: unless-stopped orthanc-db: - image: postgres:14 + image: postgres:15 restart: unless-stopped volumes: ["orthanc-db:/var/lib/postgresql/data"] environment: diff --git a/minimal-setup/keycloak-meddream-full/docker-compose.yml b/minimal-setup/keycloak-meddream-full/docker-compose.yml index a9a1eed..1404dab 100644 --- a/minimal-setup/keycloak-meddream-full/docker-compose.yml +++ b/minimal-setup/keycloak-meddream-full/docker-compose.yml @@ -6,7 +6,7 @@ version: "3" services: nginx: - image: orthancteam/orthanc-nginx:24.9.0 + image: orthancteam/orthanc-nginx:24.9.1 depends_on: [orthanc, orthanc-auth-service, orthanc-for-api, meddream-viewer, keycloak] restart: unless-stopped ports: ["80:80"] @@ -24,7 +24,7 @@ services: ENABLE_ORTHANC_FOR_API: "true" orthanc: - image: orthancteam/orthanc:24.8.3 + image: orthancteam/orthanc:24.10.1 volumes: - orthanc-storage:/var/lib/orthanc/db depends_on: [orthanc-db] @@ -76,7 +76,7 @@ services: } orthanc-auth-service: - image: orthancteam/orthanc-auth-service:24.9.0 + image: orthancteam/orthanc-auth-service:24.9.1 depends_on: [keycloak, meddream-token-service] # permissions can be customized in the permissions.json file volumes: @@ -104,7 +104,7 @@ services: POSTGRES_HOST_AUTH_METHOD: "trust" keycloak: - image: orthancteam/orthanc-keycloak:24.9.0 + image: orthancteam/orthanc-keycloak:24.9.1 depends_on: [keycloak-db] restart: unless-stopped environment: @@ -130,7 +130,7 @@ services: restart: unless-stopped meddream-viewer: - image: orthancteam/meddream-viewer:24.9.0 + image: orthancteam/meddream-viewer:24.9.1 restart: unless-stopped depends_on: - orthanc-for-api @@ -152,7 +152,7 @@ services: # An orthanc dedicated for API accesses and also used by MedDream orthanc-for-api: - image: orthancteam/orthanc:24.8.3 + image: orthancteam/orthanc:24.10.1 volumes: - orthanc-storage:/var/lib/orthanc/db - ./meddream-plugin.py:/scripts/meddream-plugin.py diff --git a/minimal-setup/keycloak/docker-compose.yml b/minimal-setup/keycloak/docker-compose.yml index 81f0d60..fb8be5a 100644 --- a/minimal-setup/keycloak/docker-compose.yml +++ b/minimal-setup/keycloak/docker-compose.yml @@ -24,7 +24,8 @@ services: ENABLE_OHIF: "true" orthanc: - image: orthancteam/orthanc:24.8.3 + #image: orthancteam/orthanc:24.8.3 + image: orthancteam/orthanc-pre-release:master-unstable volumes: - orthanc-storage:/var/lib/orthanc/db depends_on: [orthanc-db] @@ -90,7 +91,7 @@ services: } orthanc-auth-service: - image: orthancteam/orthanc-auth-service:24.9.0 + image: orthancteam/orthanc-auth-service:main # always disable port mapping in production !!! # ports: ["8000:8000"] # permissions can be customized in the permissions.json file @@ -104,7 +105,7 @@ services: # ENABLE_KEYCLOAK_API_KEYS: "true" # # to enable the permissions edition UI in OE2, you need to provide a KEYCLOAK_CLIENT_SECRET # KEYCLOAK_CLIENT_SECRET: "change-me-I-am-a-secret-you-get-in-keycloak-admin-ui" - KEYCLOAK_CLIENT_SECRET: "TxOYLTicpl1iZIO0XgWzSE0jzmA40mb5" + KEYCLOAK_CLIENT_SECRET: "BW4jmAQXmnegXXDXKy7SPuStxSoPSG7M" PUBLIC_ORTHANC_ROOT: "http://localhost/orthanc/" PUBLIC_LANDING_ROOT: "http://localhost/orthanc/ui/app/token-landing.html" # to use OHIF-plugin: make sure to use http://localhost/orthanc/ohif/ @@ -143,6 +144,8 @@ services: KC_DB_USERNAME: "keycloak" KC_DB_PASSWORD: "keycloak" # KC_HOSTNAME: "https://mydomain.com/keycloak" + # volumes: + # - /home/bc/tmp:/usr/tmp keycloak-db: image: postgres:14 diff --git a/sources/keycloak/Dockerfile.orthanc-keycloak b/sources/keycloak/Dockerfile.orthanc-keycloak index aca61ea..6585cb2 100644 --- a/sources/keycloak/Dockerfile.orthanc-keycloak +++ b/sources/keycloak/Dockerfile.orthanc-keycloak @@ -18,11 +18,14 @@ FROM quay.io/keycloak/keycloak:25.0.5 COPY --from=builder /opt/keycloak/ /opt/keycloak/ COPY keycloak/realm-export.json /opt/keycloak/data/import/ +COPY keycloak/regenerate-client-secret.sh /opt/keycloak/bin/ +COPY keycloak/entrypoint.sh /opt/keycloak/bin/ ENV KC_HOSTNAME=http://localhost/keycloak -ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] -CMD ["start", "--optimized", "--import-realm", "--http-enabled", "true", "--proxy-headers", "xforwarded"] +#ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] +#CMD ["start", "--optimized", "--import-realm", "--http-enabled", "true", "--proxy-headers", "xforwarded"] +ENTRYPOINT ["/opt/keycloak/bin/entrypoint.sh"] # to play with UI/themes/css: #CMD ["start", "--optimized", "--import-realm", "--http-enabled", "true", "--spi-theme-static-max-age=-1", "--spi-theme-cache-themes=false", "--spi-theme-cache-templates=false"] @@ -35,9 +38,11 @@ CMD ["start", "--optimized", "--import-realm", "--http-enabled", "true", "--prox # - bind a volume to /usr/tmp (copose file) # - replace the last "CMD" command of current Docker file by the following one: # CMD ["export --file /usr/tmp/realm-export.json --realm orthanc"] -# - rebuild the keycloak image (adapt path for files to copy in current file: lines 13 and 20) +# - rebuild the keycloak image from the folder 'sources' with this command: +# `docker build --file keycloak/Dockerfile.orthanc-keycloak --tag kc-temp .` # - start your setup # - then keycloak will start, export the realm and exit. From that moment, your realm # (including users, roles, clients,...) will be available in the /usr/tmp/realm-export.json +# - don't forget to restore compose and current file diff --git a/sources/keycloak/entrypoint.sh b/sources/keycloak/entrypoint.sh new file mode 100755 index 0000000..569cebe --- /dev/null +++ b/sources/keycloak/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# let's begin 2 tasks: +# the first task waits the Keycloak readyness to generate and print the Keycloak client secret +# the second task is the default entry point of Keycloak +# the first task will be started in background (thanks to the '&'), so the second task will start at the same time +cd /opt/keycloak/bin/ +./regenerate-client-secret.sh & +./kc.sh start --optimized --import-realm --http-enabled true --proxy-headers xforwarded + diff --git a/sources/keycloak/realm-export.json b/sources/keycloak/realm-export.json index e87656b..052c6f8 100644 --- a/sources/keycloak/realm-export.json +++ b/sources/keycloak/realm-export.json @@ -468,6 +468,23 @@ "realmRoles" : [ "external-role", "default-roles-orthanc" ], "notBefore" : 0, "groups" : [ ] + }, { + "id" : "35704d57-da75-4d73-81f4-85cd605398f4", + "username" : "service-account-admin-cli", + "emailVerified" : false, + "createdTimestamp" : 1732096845374, + "enabled" : true, + "totp" : false, + "serviceAccountClientId" : "admin-cli", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-orthanc" ], + "clientRoles" : { + "realm-management" : [ "view-users", "manage-users", "manage-clients" ] + }, + "notBefore" : 0, + "groups" : [ ] } ], "scopeMappings" : [ { "clientScope" : "offline_access", @@ -552,10 +569,15 @@ "id" : "74a99b9d-221a-4dd1-9ba4-ec4f249c3e0a", "clientId" : "admin-cli", "name" : "${client_admin-cli}", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", "surrogateAuthRequired" : false, "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", + "secret" : "NPtsEUenl6nw8gJmM886TbvzuGPzvgt9", "redirectUris" : [ ], "webOrigins" : [ ], "notBefore" : 0, @@ -564,16 +586,66 @@ "standardFlowEnabled" : false, "implicitFlowEnabled" : false, "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : true, + "serviceAccountsEnabled" : true, + "publicClient" : false, "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { - "post.logout.redirect.uris" : "+" + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1732096845", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", + "display.on.consent.screen" : "false", + "oauth2.device.authorization.grant.enabled" : "false", + "use.jwks.url" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "d169bc7b-c8b3-4e57-b0f8-153d9e0842a3", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "14c88af5-f584-4eba-ab17-2ea10d5060ae", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "cb2eee45-a7be-49b5-afe5-8f997b80b353", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ], "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { @@ -773,8 +845,9 @@ "consentRequired" : false, "config" : { "user.session.note" : "AUTH_TIME", - "id.token.claim" : "true", "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "auth_time", "jsonType.label" : "long" @@ -1262,7 +1335,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-full-name-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper" ] } }, { "id" : "7861a143-5c23-448d-8db7-59b4443587cc", @@ -1287,7 +1360,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-full-name-mapper" ] + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "saml-role-list-mapper" ] } }, { "id" : "224bd8df-9b3c-4e5f-9373-0182a443eba3", diff --git a/sources/keycloak/regenerate-client-secret.sh b/sources/keycloak/regenerate-client-secret.sh new file mode 100755 index 0000000..7239521 --- /dev/null +++ b/sources/keycloak/regenerate-client-secret.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +########################################################################## + +# ## Goal + +# This script will replace the 'admin-cli' client default secret by +# a freshly generated one and then print it in the logs. + +# In the second part of the script, the permissions needed to regenerate +# this secret will be removed. + +# ## Usage + +# This script is not intended to be ran manually, but it is executed +# during the Keycloak boot sequence and will regenerate the new +# secret only if the read value is the default one. + +# ## Notes + +# This script being executed to avoid a remaining default secret, +# all the ids needed in the commands are hardcoded. +# It would be more elegant to use grep and other bash tools to parse +# the json answers, but this will be done only if really needed (feel +# the pain as one says). + +########################################################################## + +cd /opt/keycloak/bin/ + +# wait till Keycloak is ready + +READY=0 + +while [ $READY -eq 0 ]; do + # Try to authenticate and capture response + RESPONSE=$(./kcadm.sh config credentials --server http://localhost:8080 --realm orthanc --client admin-cli --secret NPtsEUenl6nw8gJmM886TbvzuGPzvgt9 2>&1) + + # Wait till Keycloak is ready + echo "$RESPONSE" | grep -q "Connection refused" + if [ $? -eq 0 ]; then + echo "### Keycloak is not ready (Connection refused). Retrying..." + sleep 3 + continue + fi + + # If 'Invalid' is part of the response, the secret is already regenerated, so exit + echo "$RESPONSE" | grep -q "Invalid" + if [ $? -eq 0 ]; then + echo "### Access denied with the default secret, probably already regenerated. Exiting script..." + exit 0 + else + echo "### Keycloak is ready, script authenticated..." + READY=1 + fi +done + +# from here, some lines are commented out (###) +# indeed, as explained above, everything is hardcoded for simplicity purposes +# but if there is a need to improve the script or to get new ids, the logic is here... + +# get 'admin-cli' client id: +###./kcadm.sh get clients -r orthanc --fields clientId,id + +# regenerate the secret +RESPONSE=$(./kcadm.sh create clients/74a99b9d-221a-4dd1-9ba4-ec4f249c3e0a/client-secret -r orthanc 2>&1) + +# if 'error' is part of the response, there is a problem, so warning message +if [[ "$RESPONSE" == *"error"* ]]; then + echo "### ERROR! Unable to regenerate the secret, maybe some missing permissions..." +fi + +# get this new secret +RESPONSE=$(./kcadm.sh get clients/74a99b9d-221a-4dd1-9ba4-ec4f249c3e0a/client-secret -r orthanc 2>&1) + +# print the secret +echo -e "\n##########################################################################################" +echo -e "Here is the secret to use for the KEYCLOAK_CLIENT_SECRET env var in the auth service:" +echo -e "$RESPONSE" | grep -o '"value" : "[^"]*"' | sed 's/"value" : "\(.*\)"/\1/' +echo -e "##########################################################################################\n" + +# get service account user (a kind of account behind the account) +###./kcadm.sh get clients/74a99b9d-221a-4dd1-9ba4-ec4f249c3e0a/service-account-user -r orthanc + +# get roles for this account user +###./kcadm.sh get users/35704d57-da75-4d73-81f4-85cd605398f4/role-mappings -r orthanc + +# get clientMapping only +###./kcadm.sh get users/35704d57-da75-4d73-81f4-85cd605398f4/role-mappings/clients/34c7489b-ad3c-4483-a523-e578c1c6dc45 -r orthanc + +# remove permission manage-clients +RESPONSE=$(./kcadm.sh delete users/35704d57-da75-4d73-81f4-85cd605398f4/role-mappings/clients/34c7489b-ad3c-4483-a523-e578c1c6dc45 -r orthanc -b '[{"id": "f1360e68-78d5-4df1-a7f9-de0db0de8eb7", "name": "manage-clients"}, {"id": "098dc91c-18f2-4b22-a522-cf5ed10315a5", "name": "manage-users"}]' 2>&1) + +# error case +if [ ${#RESPONSE} -ne 0 ]; then + echo -e "\n\n##### WARNING ! WARNING ! WARNING !" + echo -e "\n##### Unable to remove the permissions! Keycloak shoulnd t be used as it is!!\n\n" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/sources/keycloak/test.http b/sources/keycloak/test.http new file mode 100644 index 0000000..0db15c8 --- /dev/null +++ b/sources/keycloak/test.http @@ -0,0 +1,92 @@ +# Just a few testing stuff, but I don't want to trash them +@baseUrlKc = http://localhost/keycloak +@baseUrlOrthanc = http://localhost/orthanc + + +@admin-cli-uid = 74a99b9d-221a-4dd1-9ba4-ec4f249c3e0a +@admin-cli-secret= mlav1BfrxT0jDH61LbAt7Y5jOizNlRvS +@service-account-id = 993274db-e4f4-465d-8e3e-52e665541ff7 +@role-id = f1360e68-78d5-4df1-a7f9-de0db0de8eb7 +@client-mapping-id = 34c7489b-ad3c-4483-a523-e578c1c6dc45 + +## roles needed +## - view-users --> to keep +## - manage-clients --> to delete +## - manage-users --> to delete + +### Version +GET {{baseUrlOrthanc}}/system + +### Get Labels +GET {{baseUrlOrthanc}}/tools/labels +api-key:forwarder-api-key + +### Check KC connectivity +GET {{baseUrlKc}} + + +### Get Token +# @name getToken +POST {{baseUrlKc}}/realms/orthanc/protocol/openid-connect/token HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials +&client_id=admin-cli +&client_secret={{admin-cli-secret}} +& + + +### Get Users +GET {{baseUrlKc}}/admin/realms/orthanc/users +Authorization: Bearer {{getToken.response.body.access_token}} + +### Get Client UUID +GET {{baseUrlKc}}/admin/realms/orthanc/clients?clientId=admin-cli +Authorization: Bearer {{getToken.response.body.access_token}} + +# ### Get Client Secret +# # @name admin-cli-secret +# GET {{baseUrlKc}}/admin/realms/orthanc/clients/{{admin-cli-uid}}/client-secret +# Authorization: Bearer {{getToken.response.body.access_token}} + + +### Change Client Secret +POST {{baseUrlKc}}/admin/realms/orthanc/clients/{{admin-cli-uid}}/client-secret +Authorization: Bearer {{getToken.response.body.access_token}} + +### Get service account user +GET {{baseUrlKc}}/admin/realms/orthanc/clients/{{admin-cli-uid}}/service-account-user +Authorization: Bearer {{getToken.response.body.access_token}} + +### Get roles linked to this sercice account user --> client-mapping-id has to be fetch from the answer +GET {{baseUrlKc}}/admin/realms/orthanc/users/{{service-account-id}}/role-mappings +Authorization: Bearer {{getToken.response.body.access_token}} + +### Get roles --> role-id can be fetch from the answer (and also from he previous one) +GET {{baseUrlKc}}/admin/realms/orthanc/users/{{service-account-id}}/role-mappings/clients/{{client-mapping-id}} +Authorization: Bearer {{getToken.response.body.access_token}} + + +### Delete 'manage-clients' role for this client +DELETE {{baseUrlKc}}/admin/realms/orthanc/users/{{service-account-id}}/role-mappings/clients/{{client-mapping-id}} +Authorization: Bearer {{getToken.response.body.access_token}} +Content-Type: application/json + +[ + { + "id": "f1360e68-78d5-4df1-a7f9-de0db0de8eb7", + "name": "manage-clients" + } +] + +### Delete 'manage-users' role for this client +DELETE {{baseUrlKc}}/admin/realms/orthanc/users/{{service-account-id}}/role-mappings/clients/{{client-mapping-id}} +Authorization: Bearer {{getToken.response.body.access_token}} +Content-Type: application/json + +[ + { + "id": "098dc91c-18f2-4b22-a522-cf5ed10315a5", + "name": "manage-users" + } +]