diff --git a/local-dev/k3d-seed-data/configure-webauthn.sh b/local-dev/k3d-seed-data/configure-webauthn.sh index 2d54f2c831..052c15058e 100644 --- a/local-dev/k3d-seed-data/configure-webauthn.sh +++ b/local-dev/k3d-seed-data/configure-webauthn.sh @@ -1,7 +1,15 @@ #!/bin/bash CONFIG_PATH=/tmp/kcadm.config -/opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master +if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --realm master --client admin-api --secret ${KEYCLOAK_ADMIN_API_CLIENT_SECRET} +then +if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master +then + echo "Unable to log in to keycloak with client admin-api or username and password" + echo "If you have rotated the admin-api secret, you will need to log in and update it manually" + exit 1 +fi +fi if [ "$(/opt/keycloak/bin/kcadm.sh get authentication/flows -r "lagoon" --fields id,alias --config $CONFIG_PATH | jq -r '.[] | select(.alias==("Browser-Webauthn")) | .id')" == "" ]; then echo "Creating browser flow for webauthn" diff --git a/local-dev/k3d-seed-data/seed-example-sso.sh b/local-dev/k3d-seed-data/seed-example-sso.sh index e2b52f2ffd..5f5b7e4759 100644 --- a/local-dev/k3d-seed-data/seed-example-sso.sh +++ b/local-dev/k3d-seed-data/seed-example-sso.sh @@ -3,10 +3,15 @@ CONFIG_PATH=/tmp/kcadm.config # login to keycloak -/opt/keycloak/bin/kcadm.sh config credentials \ - --config $CONFIG_PATH --server http://localhost:8080/auth \ - --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD \ - --realm master +if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --realm master --client admin-api --secret ${KEYCLOAK_ADMIN_API_CLIENT_SECRET} +then + if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master + then + echo "Unable to log in to keycloak with client admin-api or username and password" + echo "If you have rotated the admin-api secret, you will need to log in and update it manually" + exit 1 + fi +fi if /opt/keycloak/bin/kcadm.sh get realms/sso --config $CONFIG_PATH > /dev/null; then echo "Realm sso is already created, skipping" diff --git a/local-dev/k3d-seed-data/seed-users.sh b/local-dev/k3d-seed-data/seed-users.sh index 88924d13bb..b098c2d236 100644 --- a/local-dev/k3d-seed-data/seed-users.sh +++ b/local-dev/k3d-seed-data/seed-users.sh @@ -42,8 +42,15 @@ function configure_keycloak { CONFIG_PATH=/tmp/kcadm.config echo Keycloak is running, proceeding with configuration - - /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master + if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --realm master --client admin-api --secret ${KEYCLOAK_ADMIN_API_CLIENT_SECRET} + then + if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master + then + echo "Unable to log in to keycloak with client admin-api or username and password" + echo "If you have rotated the admin-api secret, you will need to log in and update it manually" + exit 1 + fi + fi configure_user_passwords diff --git a/services/api/Dockerfile b/services/api/Dockerfile index cd289b9fba..6c157433b4 100644 --- a/services/api/Dockerfile +++ b/services/api/Dockerfile @@ -29,10 +29,9 @@ RUN yarn check --verify-tree # Making sure we run in production ENV NODE_ENV=production \ LOGSDB_ADMIN_PASSWORD=admin \ - KEYCLOAK_ADMIN_USER=admin \ - KEYCLOAK_ADMIN_PASSWORD=admin \ ELASTICSEARCH_URL=http://logs-db-service:9200 \ KEYCLOAK_API_CLIENT_SECRET=39d5282d-3684-4026-b4ed-04bbc034b61a \ + KEYCLOAK_ADMIN_API_CLIENT_SECRET=bb86d344-a52d-11ef-b872-4f4337ee24f0 \ REDIS_PASSWORD=admin # The API is not very resilient to sudden mariadb restarts which can happen when the api and mariadb are starting diff --git a/services/api/src/clients/keycloak-admin.ts b/services/api/src/clients/keycloak-admin.ts index 9605c06838..c1510a38a4 100644 --- a/services/api/src/clients/keycloak-admin.ts +++ b/services/api/src/clients/keycloak-admin.ts @@ -2,6 +2,7 @@ import { decode } from 'jsonwebtoken'; import { KeycloakAdminClient } from '@s3pweb/keycloak-admin-client-cjs'; import { logger } from '../loggers/logger'; import { config } from './keycloakClient'; +import { getConfigFromEnv } from '../util/config'; /// Helper to type check try/catch. Remove when we can stop using the @s3pweb // commonJS version. @@ -49,10 +50,12 @@ export const getKeycloakAdminClient = async (): Promise => }); await keycloakAdminClient.auth({ - username: config.user, - password: config.pass, - grantType: 'password', - clientId: 'admin-cli', + grantType: 'client_credentials', + clientId: 'admin-api', + clientSecret: getConfigFromEnv( + 'KEYCLOAK_ADMIN_API_CLIENT_SECRET', + '' + ), }); keycloakAdminClient.setConfig({ diff --git a/services/api/src/clients/keycloakClient.ts b/services/api/src/clients/keycloakClient.ts index 66208c319c..36714791e4 100644 --- a/services/api/src/clients/keycloakClient.ts +++ b/services/api/src/clients/keycloakClient.ts @@ -5,8 +5,6 @@ import { getConfigFromEnv, getLagoonRouteFromEnv } from '../util/config'; export const config = { origin: getConfigFromEnv('KEYCLOAK_URL', 'http://keycloak:8080'), - user: getConfigFromEnv('KEYCLOAK_ADMIN_USER', 'admin'), - pass: getConfigFromEnv('KEYCLOAK_ADMIN_PASSWORD', ''), realm: 'lagoon', apiClientSecret: getConfigFromEnv( 'KEYCLOAK_API_CLIENT_SECRET', diff --git a/services/keycloak/Dockerfile b/services/keycloak/Dockerfile index 1aac568ab2..933cce2312 100644 --- a/services/keycloak/Dockerfile +++ b/services/keycloak/Dockerfile @@ -70,6 +70,7 @@ ENV TMPDIR=/tmp \ KEYCLOAK_AUTH_SERVER_CLIENT_SECRET=f605b150-7636-4447-abd3-70988786b330 \ KEYCLOAK_SERVICE_API_CLIENT_SECRET=d3724d52-34d1-4967-a802-4d178678564b \ KEYCLOAK_LAGOON_UI_OIDC_CLIENT_SECRET=20580a56-6fbc-11ef-9a5b-3b4da292aa54 \ + KEYCLOAK_ADMIN_API_CLIENT_SECRET=bb86d344-a52d-11ef-b872-4f4337ee24f0 \ LAGOON_DB_VENDOR=mariadb \ LAGOON_DB_DATABASE=infrastructure \ LAGOON_DB_USER=api \ diff --git a/services/keycloak/startup-scripts/00-configure-lagoon.sh b/services/keycloak/startup-scripts/00-configure-lagoon.sh index 5f25941797..3f2ba56d17 100755 --- a/services/keycloak/startup-scripts/00-configure-lagoon.sh +++ b/services/keycloak/startup-scripts/00-configure-lagoon.sh @@ -146,6 +146,23 @@ function configure_lagoon_redirect_uris { fi } +function configure_admin_api_client { + # this client is used by the lagoon api to perform actions against keycloak without needing to use the username and password + # this allows for configuring the username and password of the admin account with 2fa if required + admin_api_client_id=$(/opt/keycloak/bin/kcadm.sh get -r master clients?clientId=admin-api --config $CONFIG_PATH) + if [ "$admin_api_client_id" != "[ ]" ]; then + echo "Client admin-api is already created, skipping basic setup" + return 0 + fi + echo Creating client admin-api + echo '{"clientId": "admin-api", "publicClient": false, "standardFlowEnabled": false, "serviceAccountsEnabled": true, "secret": "'${KEYCLOAK_ADMIN_API_CLIENT_SECRET}'"}' | /opt/keycloak/bin/kcadm.sh create clients --config $CONFIG_PATH -r master -f - + ADMIN_API_CLIENT_ID=$(/opt/keycloak/bin/kcadm.sh get -r master clients?clientId=admin-api --config $CONFIG_PATH | jq -r '.[0]["id"]') + echo Enable fine grained permissions + /opt/keycloak/bin/kcadm.sh update clients/$ADMIN_API_CLIENT_ID/management/permissions --config $CONFIG_PATH -r master -s enabled=true + + /opt/keycloak/bin/kcadm.sh add-roles -r master --uusername service-account-admin-api --rolename admin --config $CONFIG_PATH +} + ############## # Migrations # ############## @@ -866,7 +883,21 @@ function configure_keycloak { echo Keycloak is running, proceeding with configuration - /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_USER --password $KEYCLOAK_PASSWORD --realm master + # attempt to log in with the admin-api client service account + # this can fail the first time as the admin-api client might not exist because it is called by 'configure_admin_api_client' + # it will then fall back to using the username and password to authenticate against keycloak + # this has the same downside as the username/password problem in that if the user password or the admin-api client secret are ever rotated + # then they will need to be changed in keycloak at the same time that the changes are applied when rotating them via lagoon if they are being changed + # otherwise there is currently no way to change these without knowing the previous password or client secret + if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --realm master --client admin-api --secret ${KEYCLOAK_ADMIN_API_CLIENT_SECRET} + then + if ! /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_USER --password $KEYCLOAK_PASSWORD --realm master + then + echo "Unable to log in to keycloak with client admin-api or keycloak admin username and password" + echo "If you have rotated the admin-api secret, you will need to log in and update it manually" + exit 1 + fi + fi # Sets the order of migrations, add new ones at the end. import_lagoon_realm @@ -875,6 +906,7 @@ function configure_keycloak { configure_smtp_settings configure_realm_settings configure_lagoon_redirect_uris + configure_admin_api_client check_migrations_version migrate_to_custom_group_mapper