From 62eea9c16b19a14e09d4ad5e4c41a442e8b2d4cb Mon Sep 17 00:00:00 2001 From: iromli Date: Thu, 11 Jan 2024 15:38:28 +0700 Subject: [PATCH 1/2] fix(docker-jans-saml): unable to access KC admin console from browser Signed-off-by: iromli --- docker-jans-saml/Dockerfile | 11 +++++----- docker-jans-saml/README.md | 7 ++++-- docker-jans-saml/scripts/bootstrap.py | 6 ++++- docker-jans-saml/scripts/configure_kc.py | 14 +++++++----- docker-jans-saml/scripts/entrypoint.sh | 22 +++++++++---------- docker-jans-saml/scripts/healthcheck.py | 4 ++-- .../templates/jans-saml/keycloak.extra.conf | 2 ++ 7 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 docker-jans-saml/templates/jans-saml/keycloak.extra.conf diff --git a/docker-jans-saml/Dockerfile b/docker-jans-saml/Dockerfile index 5b4f2e6c241..70db934678c 100644 --- a/docker-jans-saml/Dockerfile +++ b/docker-jans-saml/Dockerfile @@ -161,17 +161,18 @@ ENV CN_MAX_RAM_PERCENTAGE=75.0 \ CN_AWS_SECRETS_ENDPOINT_URL="" \ CN_AWS_SECRETS_PREFIX=jans \ CN_AWS_SECRETS_REPLICA_FILE="" \ - CN_SAML_PORT=8083 \ - CN_SAML_HOST=0.0.0.0 \ + CN_SAML_HTTP_PORT=8083 \ + CN_SAML_HTTP_HOST=0.0.0.0 \ CN_SAML_JAVA_OPTIONS="" \ - KC_HEALTH_ENABLED=true - # KC_METRICS_ENABLED=true + CN_SAML_KC_CREDENTIALS_FILE=/etc/jans/conf/kc_admin_creds \ + KC_HEALTH_ENABLED=true \ + KC_METRICS_ENABLED=true # ========== # misc stuff # ========== -EXPOSE $CN_SAML_PORT +EXPOSE $CN_SAML_HTTP_PORT LABEL org.opencontainers.image.url="ghcr.io/janssenproject/jans/saml" \ org.opencontainers.image.authors="Janssen Project " \ diff --git a/docker-jans-saml/README.md b/docker-jans-saml/README.md index 935ac73963e..2952042c22f 100644 --- a/docker-jans-saml/README.md +++ b/docker-jans-saml/README.md @@ -63,6 +63,7 @@ The following environment variables are supported by the container: - `CN_COUCHBASE_KEEPALIVE_INTERVAL`: Keep-alive interval for Couchbase connection (default to `30000` milliseconds). - `CN_COUCHBASE_KEEPALIVE_TIMEOUT`: Keep-alive timeout for Couchbase connection (default to `2500` milliseconds). - `CN_SAML_JAVA_OPTIONS`: Java options passed to entrypoint, i.e. `-Xmx1024m` (default to empty-string). +- `CN_SAML_KC_CREDENTIALS_FILE`: File contains credentials for Keycloak admin user. - `GOOGLE_APPLICATION_CREDENTIALS`: Optional JSON file (contains Google credentials) that can be injected into container for authentication. Refer to https://cloud.google.com/docs/authentication/provide-credentials-adc#how-to for supported credentials. - `GOOGLE_PROJECT_ID`: ID of Google project. - `CN_GOOGLE_SECRET_VERSION_ID`: Janssen secret version ID in Google Secret Manager. Defaults to `latest`, which is recommended. @@ -117,9 +118,11 @@ As per v1.0.1, hybrid persistence supports all available persistence types. To c } ``` -### Keycloak Admin Credentials +### Keycloak Administration -Admin credentials are set in `/etc/jans/conf/kc_admin_creds` with the following format: +#### Admin Credentials + +Admin credentials are set in `/etc/jans/conf/kc_admin_creds` (can be changed via `CN_SAML_KC_CREDENTIALS_FILE` environment variable) with the following format: ``` BASE64(username:password) diff --git a/docker-jans-saml/scripts/bootstrap.py b/docker-jans-saml/scripts/bootstrap.py index 37537cf0ec6..48957825b03 100644 --- a/docker-jans-saml/scripts/bootstrap.py +++ b/docker-jans-saml/scripts/bootstrap.py @@ -33,9 +33,13 @@ def render_keycloak_conf(ctx): with open("/app/templates/jans-saml/keycloak.conf") as f: - tmpl = f.read() + defaults = f.read() + + with open("/app/templates/jans-saml/keycloak.extra.conf") as f: + extras = f.read() with open("/opt/keycloak/conf/keycloak.conf", "w") as f: + tmpl = "\n".join([defaults, extras]) f.write(tmpl % ctx) diff --git a/docker-jans-saml/scripts/configure_kc.py b/docker-jans-saml/scripts/configure_kc.py index 20c44b88535..6823430b44c 100644 --- a/docker-jans-saml/scripts/configure_kc.py +++ b/docker-jans-saml/scripts/configure_kc.py @@ -54,8 +54,8 @@ def __init__(self, admin_username, admin_password, base_dir, ctx): @property def server_url(self): - host = os.environ.get("CN_SAML_HOST", "0.0.0.0") # nosec: B104 - port = os.environ.get("CN_SAML_PORT", "8083") + host = os.environ.get("CN_SAML_HTTP_HOST", "0.0.0.0") # nosec: B104 + port = os.environ.get("CN_SAML_HTT_PORT", "8083") return f"http://{host}:{port}/kc" @property @@ -172,13 +172,15 @@ def create_user(self): def main(): manager = get_manager() - if os.path.isfile("/etc/jans/conf/kc_admin_creds"): - with open("/etc/jans/conf/kc_admin_creds") as f: + creds_file = os.environ.get("CN_SAML_KC_CREDENTIALS_FILE", "/etc/jans/conf/kc_admin_creds") + + if os.path.isfile(creds_file): + with open(creds_file) as f: creds = f.read().strip() admin_username, admin_password = base64.b64decode(creds).decode().strip().split(":") else: - admin_username = os.environ.get("KEYCLOAK_ADMIN", "") - admin_password = os.environ.get("KEYCLOAK_ADMIN_PASSWORD", "") + admin_username = os.environ.get("KEYCLOAK_ADMIN", "admin") + admin_password = os.environ.get("KEYCLOAK_ADMIN_PASSWORD", "admin") ctx = { "jans_idp_realm": "jans-api", diff --git a/docker-jans-saml/scripts/entrypoint.sh b/docker-jans-saml/scripts/entrypoint.sh index 6ec147e9b6c..19d9f1e37cc 100644 --- a/docker-jans-saml/scripts/entrypoint.sh +++ b/docker-jans-saml/scripts/entrypoint.sh @@ -21,13 +21,14 @@ get_max_ram_percentage() { } export_keycloak_admin_creds() { - if [ -f /etc/jans/conf/kc_admin_creds ]; then - creds="$(base64 -d < /etc/jans/conf/kc_admin_creds)" + creds_file=${CN_SAML_KC_CREDENTIALS_FILE:-/etc/jans/conf/kc_admin_creds} + if [ -f "${creds_file}" ]; then + creds="$(base64 -d < ${creds_file})" admin_username=$(echo "$creds" | awk -F ":" '{print $1}') admin_password=$(echo "$creds" | awk -F ":" '{print $2}') else - admin_username=${KEYCLOAK_ADMIN:-} - admin_password=${KEYCLOAK_ADMIN_PASSWORD:-} + admin_username=${KEYCLOAK_ADMIN:-admin} + admin_password=${KEYCLOAK_ADMIN_PASSWORD:-admin} fi export KEYCLOAK_ADMIN="$admin_username" export KEYCLOAK_ADMIN_PASSWORD="$admin_password" @@ -46,19 +47,16 @@ export JAVA_OPTS_APPEND="$java_opts" exec /opt/keycloak/bin/kc.sh start \ -Dlog.base=/opt/keycloak/logs/ \ -Djans.config.prop.path=/opt/keycloak/providers \ - --health-enabled=true \ - --metrics-enabled=true \ - --http-host="${CN_SAML_HOST}" \ - --http-port="${CN_SAML_PORT}" \ + --http-host="${CN_SAML_HTTP_HOST}" \ + --http-port=${CN_SAML_HTTP_PORT} \ --http-enabled=true \ --http-relative-path=/kc \ - --hostname="localhost" \ - --hostname-admin="localhost" \ --hostname-path=/kc \ - --hostname-strict-https=false \ + --hostname-strict-https=true \ --log=console \ --log-console-format='jans-saml - %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n' \ --log-file=/opt/keycloak/logs/keycloak.log \ - --log-level=INFO + --log-level=INFO \ + --proxy=edge # --db=dev-mem \ # --optimized diff --git a/docker-jans-saml/scripts/healthcheck.py b/docker-jans-saml/scripts/healthcheck.py index ba3bfbc2f0c..987a72ad767 100644 --- a/docker-jans-saml/scripts/healthcheck.py +++ b/docker-jans-saml/scripts/healthcheck.py @@ -5,8 +5,8 @@ def run_healthcheck(): - host = os.environ.get("CN_SAML_HOST", "0.0.0.0") # nosec: B104 - port = os.environ.get("CN_SAML_PORT", "8083") + host = os.environ.get("CN_SAML_HTTP_HOST", "0.0.0.0") # nosec: B104 + port = os.environ.get("CN_SAML_HTTP_PORT", "8083") req = requests.get(f"http://{host}:{port}/kc/health", timeout=5) if not req.ok: return False diff --git a/docker-jans-saml/templates/jans-saml/keycloak.extra.conf b/docker-jans-saml/templates/jans-saml/keycloak.extra.conf new file mode 100644 index 00000000000..5ab49c4bb04 --- /dev/null +++ b/docker-jans-saml/templates/jans-saml/keycloak.extra.conf @@ -0,0 +1,2 @@ +hostname=%(hostname)s +hostname-admin=%(hostname)s From 76116041856b668bc418f11388c3653ebcd29746 Mon Sep 17 00:00:00 2001 From: iromli Date: Fri, 12 Jan 2024 01:19:48 +0700 Subject: [PATCH 2/2] chore(docker-jans-saml): generate defaults KC credentials Signed-off-by: iromli --- docker-jans-saml/README.md | 10 +++------- docker-jans-saml/scripts/bootstrap.py | 24 ++++++++++++++++++++++++ docker-jans-saml/scripts/configure_kc.py | 10 +++------- docker-jans-saml/scripts/entrypoint.sh | 13 ++++--------- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/docker-jans-saml/README.md b/docker-jans-saml/README.md index 2952042c22f..8d252ca9986 100644 --- a/docker-jans-saml/README.md +++ b/docker-jans-saml/README.md @@ -122,16 +122,12 @@ As per v1.0.1, hybrid persistence supports all available persistence types. To c #### Admin Credentials -Admin credentials are set in `/etc/jans/conf/kc_admin_creds` (can be changed via `CN_SAML_KC_CREDENTIALS_FILE` environment variable) with the following format: +By defaults, Keycloak's admin username and password are self-generated during first install and saved as `kc_admin_username` (in configs layer) and `kc_admin_password` (in secrets layer) respectively. -``` -BASE64(username:password) -``` - -Example: +The credentials will be rendered as `/etc/jans/conf/kc_admin_creds` file (can be changed via `CN_SAML_KC_CREDENTIALS_FILE` environment variable) with the following format: ``` -echo admin:admin | base64 -w0 > /etc/jans/conf/kc_admin_creds +BASE64(username:password) ``` The credentials will be exported as `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` environment variables for initial admin username and password. diff --git a/docker-jans-saml/scripts/bootstrap.py b/docker-jans-saml/scripts/bootstrap.py index 48957825b03..2dd30db3bce 100644 --- a/docker-jans-saml/scripts/bootstrap.py +++ b/docker-jans-saml/scripts/bootstrap.py @@ -1,5 +1,6 @@ from __future__ import annotations +import base64 import logging.config import os import typing as _t @@ -48,6 +49,7 @@ def main(): persistence_setup = PersistenceSetup(manager) persistence_setup.import_ldif_files() render_keycloak_conf(persistence_setup.ctx) + render_keycloak_creds() class PersistenceSetup: @@ -120,6 +122,17 @@ def ctx(self) -> dict[str, _t.Any]: ).decode() self.manager.secret.set("saml_scim_client_encoded_pw", ctx["saml_scim_client_encoded_pw"]) + # keycloak credentials + ctx["kc_admin_username"] = self.manager.config.get("kc_admin_username") + if not ctx["kc_admin_username"]: + ctx["kc_admin_username"] = "admin" + self.manager.config.set("kc_admin_username", ctx["kc_admin_username"]) + + ctx["kc_admin_password"] = self.manager.secret.get("kc_admin_password") + if not ctx["kc_admin_password"]: + ctx["kc_admin_password"] = get_random_chars() + self.manager.secret.set("kc_admin_password", ctx["kc_admin_password"]) + # finalized ctx return ctx @@ -136,5 +149,16 @@ def import_ldif_files(self) -> None: self.client.create_from_ldif(file_, self.ctx) +def render_keycloak_creds(): + creds_file = os.environ.get("CN_SAML_KC_CREDENTIALS_FILE", "/etc/jans/conf/kc_admin_creds") + + if not os.path.isfile(creds_file): + with open(creds_file, "w") as f: + username = manager.config.get("kc_admin_username") + password = manager.secret.get("kc_admin_password") + creds_bytes = f"{username}:{password}".encode() + f.write(base64.b64encode(creds_bytes).decode()) + + if __name__ == "__main__": main() diff --git a/docker-jans-saml/scripts/configure_kc.py b/docker-jans-saml/scripts/configure_kc.py index 6823430b44c..66572a2d310 100644 --- a/docker-jans-saml/scripts/configure_kc.py +++ b/docker-jans-saml/scripts/configure_kc.py @@ -174,13 +174,9 @@ def main(): creds_file = os.environ.get("CN_SAML_KC_CREDENTIALS_FILE", "/etc/jans/conf/kc_admin_creds") - if os.path.isfile(creds_file): - with open(creds_file) as f: - creds = f.read().strip() - admin_username, admin_password = base64.b64decode(creds).decode().strip().split(":") - else: - admin_username = os.environ.get("KEYCLOAK_ADMIN", "admin") - admin_password = os.environ.get("KEYCLOAK_ADMIN_PASSWORD", "admin") + with open(creds_file) as f: + creds = f.read().strip() + admin_username, admin_password = base64.b64decode(creds).decode().strip().split(":") ctx = { "jans_idp_realm": "jans-api", diff --git a/docker-jans-saml/scripts/entrypoint.sh b/docker-jans-saml/scripts/entrypoint.sh index 19d9f1e37cc..74e4543f2ec 100644 --- a/docker-jans-saml/scripts/entrypoint.sh +++ b/docker-jans-saml/scripts/entrypoint.sh @@ -22,23 +22,18 @@ get_max_ram_percentage() { export_keycloak_admin_creds() { creds_file=${CN_SAML_KC_CREDENTIALS_FILE:-/etc/jans/conf/kc_admin_creds} - if [ -f "${creds_file}" ]; then - creds="$(base64 -d < ${creds_file})" - admin_username=$(echo "$creds" | awk -F ":" '{print $1}') - admin_password=$(echo "$creds" | awk -F ":" '{print $2}') - else - admin_username=${KEYCLOAK_ADMIN:-admin} - admin_password=${KEYCLOAK_ADMIN_PASSWORD:-admin} - fi + creds="$(base64 -d < ${creds_file})" + admin_username=$(echo "$creds" | awk -F ":" '{print $1}') + admin_password=$(echo "$creds" | awk -F ":" '{print $2}') export KEYCLOAK_ADMIN="$admin_username" export KEYCLOAK_ADMIN_PASSWORD="$admin_password" } -export_keycloak_admin_creds python3 "$basedir/wait.py" python3 "$basedir/bootstrap.py" python3 "$basedir/configure_kc.py" & python3 "$basedir/upgrade.py" +export_keycloak_admin_creds java_opts="$(get_max_ram_percentage) $(get_java_options)" export JAVA_OPTS_APPEND="$java_opts"