From 0f767fb0515906898f23d5868254f612809bbc08 Mon Sep 17 00:00:00 2001 From: The_miro Date: Thu, 2 Jul 2026 11:28:00 +0200 Subject: [PATCH] fix(ansipa): docker stack volume/network naming, healthchecks, client-routable CMK URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while bringing up the FreeIPA+CheckMK+Keycloak stack in a VM: - Pin volume and network names (name:) so compose and the cgroup-v2 run.sh wrapper share the same objects. Compose otherwise prefixes the project name (image_cmk-creds, image_ipa-net), so the run.sh-started freeipa container mounted a different cmk-creds volume than the compose-started checkmk wrote to — the automation secret never reached IPA and the dev_mon_* integration retried forever. run.sh also now labels the network it creates so compose adopts it instead of erroring "exists but not created by compose". - run.sh: mount cmk-creds into freeipa and include checkmk in the "all" target; both were missing, so the CheckMK path was never wired up via run.sh. - Fix both healthchecks: Keycloak has no curl/wget and serves /health on the management port 9000 only when KC_HEALTH_ENABLED=true, so the old curl probe left it permanently unhealthy — use a bash /dev/tcp probe against 9000. CheckMK's /api/1.0/version needs auth (401), so probe the login page instead. - Advertise a client-routable CheckMK URL (CMK_ADVERTISED_URL) in the dev_mon_base description instead of the container-internal 172.30.0.12, which enrolled clients cannot reach; falls back to the internal URL when unset. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi --- .../modules/FreeipaAnsible/image/.env.example | 7 +++++ .../image/ansipa-checkmk-setup.sh | 8 ++++- .../FreeipaAnsible/image/docker-compose.yml | 31 +++++++++++++++++-- setup/modules/FreeipaAnsible/image/run.sh | 23 +++++++++++--- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/setup/modules/FreeipaAnsible/image/.env.example b/setup/modules/FreeipaAnsible/image/.env.example index 22825a5..7b2b608 100644 --- a/setup/modules/FreeipaAnsible/image/.env.example +++ b/setup/modules/FreeipaAnsible/image/.env.example @@ -24,9 +24,16 @@ LUKS_KEY_UPLOAD_PASSWORD=ChangeMe_LuksUpload! # CMK_ADMIN_PASSWORD — web UI password for cmkadmin. # CMK_SITE_ID — OMD site name (default: cmk). Used in all CheckMK URLs and # agent registration. Must match whatever was used on first start. +# CMK_ADVERTISED_URL — URL enrolled CLIENTS use to reach CheckMK; stored in the +# dev_mon_base hostgroup description. Must be routable from +# the clients' network (the docker HOST address + published +# port, e.g. http://mon.corp.example.com:8090) — NOT the +# container-internal 172.30.0.12:5000. Leave empty only if +# all monitored clients run inside the same docker network. # Web UI: http://localhost:8090/cmk/ after `docker compose up -d` CMK_ADMIN_PASSWORD=ChangeMe_CMK! CMK_SITE_ID=cmk +CMK_ADVERTISED_URL= # ── Keycloak ────────────────────────────────────────────────────────────────── KC_HOSTNAME=keycloak.corp.example.com diff --git a/setup/modules/FreeipaAnsible/image/ansipa-checkmk-setup.sh b/setup/modules/FreeipaAnsible/image/ansipa-checkmk-setup.sh index 07a220e..a7f76a8 100644 --- a/setup/modules/FreeipaAnsible/image/ansipa-checkmk-setup.sh +++ b/setup/modules/FreeipaAnsible/image/ansipa-checkmk-setup.sh @@ -124,7 +124,13 @@ curl -sf \ # ── Store CheckMK credentials in IPA host-group descriptions ───────────────── # Format: cmk://:/:automation: -CMK_HOST=$(echo "$CMK_URL" | sed 's|http://||') +# CMK_ADVERTISED_URL is what CLIENTS use to reach CheckMK. It must be a +# host-routable address (e.g. http://:8090), NOT the container's +# internal 172.30.0.x address — clients outside the docker network cannot +# reach that, and every dev_mon_* policy would silently fail to register. +# Falls back to CMK_URL for docker-network-internal test setups. +CMK_CLIENT_URL="${CMK_ADVERTISED_URL:-$CMK_URL}" +CMK_HOST=$(echo "$CMK_CLIENT_URL" | sed 's|http://||') CMK_DESC="cmk://${CMK_HOST}/${CMK_SITE}:automation:${CMK_SECRET}" echo "$IPA_ADMIN_PASS" | kinit "admin@${IPA_REALM}" &>/dev/null || { diff --git a/setup/modules/FreeipaAnsible/image/docker-compose.yml b/setup/modules/FreeipaAnsible/image/docker-compose.yml index 68371fc..b57d568 100644 --- a/setup/modules/FreeipaAnsible/image/docker-compose.yml +++ b/setup/modules/FreeipaAnsible/image/docker-compose.yml @@ -25,14 +25,29 @@ # # On plain cgroup v1 hosts `docker compose up -d` works without run.sh. +# Volume names are pinned (name:) so they match what run.sh creates with plain +# `docker volume create` on cgroup-v2 hosts. Without this, compose prefixes the +# project name (image_cmk-creds, ...) and the freeipa container started by +# run.sh mounts a DIFFERENT cmk-creds volume than the compose-managed checkmk +# container writes to — the automation secret then never reaches IPA and the +# CheckMK integration retries forever. volumes: freeipa-data: + name: freeipa-data keycloak-db: + name: keycloak-db cmk-data: # CheckMK site data (persistent across restarts) + name: cmk-data cmk-creds: # shared: CheckMK writes automation.secret here; FreeIPA reads it + name: cmk-creds networks: ipa-net: + # Pin the real network name: run.sh (cgroup-v2 hosts) creates/uses a bare + # "ipa-net", while compose would otherwise create a project-prefixed + # "image_ipa-net" on the same subnet — the second one to run then fails + # with "Pool overlaps with other one on this address space". + name: ipa-net ipam: config: - subnet: 172.30.0.0/24 @@ -65,6 +80,9 @@ services: IPA_SETUP_KRA: ${IPA_SETUP_KRA:-false} SMB_SCAN_PASSWORD: ${SMB_SCAN_PASSWORD:?set SMB_SCAN_PASSWORD in .env} CMK_URL: http://172.30.0.12:5000 + # Client-facing CheckMK URL stored in the dev_mon_base description — + # must be routable from enrolled clients (e.g. http://:8090). + CMK_ADVERTISED_URL: ${CMK_ADVERTISED_URL:-} CMK_SITE_ID: ${CMK_SITE_ID:-cmk} ports: - "389:389" @@ -165,7 +183,11 @@ services: ipa-net: ipv4_address: 172.30.0.12 healthcheck: - test: ["CMD-SHELL", "curl -sf http://localhost:5000/${CMK_SITE_ID:-cmk}/check_mk/api/1.0/version >/dev/null 2>&1"] + # Probe the login page, not /api/1.0/version: the REST API version + # endpoint requires authentication in CheckMK 2.3, so an unauthenticated + # curl -f gets 401 and the container stays "unhealthy" forever even + # though the site is fully up. + test: ["CMD-SHELL", "curl -sf http://localhost:5000/${CMK_SITE_ID:-cmk}/check_mk/login.py -o /dev/null"] interval: 30s timeout: 15s retries: 20 @@ -188,6 +210,7 @@ services: KC_HTTP_PORT: 8080 KC_HTTPS_PORT: 8443 KC_HTTP_ENABLED: "true" + KC_HEALTH_ENABLED: "true" # exposes /health/* on management port 9000 (KC 25+) KC_FEATURES: preview KEYCLOAK_ADMIN: ${KC_ADMIN:-admin} KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:?set KC_ADMIN_PASSWORD in .env} @@ -202,7 +225,11 @@ services: networks: ipa-net: healthcheck: - test: ["CMD-SHELL", "curl -fs http://localhost:8080/health/ready || exit 1"] + # The official Keycloak image ships neither curl nor wget, and since + # KC 25 the health endpoints live on the management port (9000) and are + # only served when KC_HEALTH_ENABLED=true. Use bash's /dev/tcp instead + # of curl so the check works inside the stock image. + test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && grep -q '\"status\": \"UP\"' <&3"] interval: 20s timeout: 10s retries: 20 diff --git a/setup/modules/FreeipaAnsible/image/run.sh b/setup/modules/FreeipaAnsible/image/run.sh index e22da6f..32371a5 100755 --- a/setup/modules/FreeipaAnsible/image/run.sh +++ b/setup/modules/FreeipaAnsible/image/run.sh @@ -35,12 +35,23 @@ start_freeipa() { docker compose up --no-start freeipa 2>/dev/null || true NETWORK=$(docker network ls --filter name=ipa-net --format '{{.Name}}' | grep ipa-net | head -1) if [[ -z "$NETWORK" ]]; then - docker network create --subnet=172.30.0.0/24 ipa-net + # Label the network exactly as compose would so a later + # `docker compose up` (checkmk/postgres/keycloak) adopts it instead of + # failing with "network exists but was not created by compose". + docker network create --subnet=172.30.0.0/24 \ + --label com.docker.compose.network=ipa-net \ + --label com.docker.compose.project=image \ + --label com.docker.compose.version=2 \ + ipa-net NETWORK=ipa-net fi - # Ensure the data volume exists + # Ensure the data volumes exist (cmk-creds is shared with the checkmk + # container: CheckMK writes automation.secret there, the IPA container's + # ansipa-checkmk-setup.sh reads it — without this mount the whole + # dev_mon_* CheckMK integration silently never activates) docker volume create freeipa-data 2>/dev/null || true + docker volume create cmk-creds 2>/dev/null || true docker run -d \ --name freeipa \ @@ -51,6 +62,7 @@ start_freeipa() { --tmpfs /tmp \ -v /sys/fs/cgroup:/sys/fs/cgroup:rw \ -v freeipa-data:/data \ + -v cmk-creds:/cmk-creds \ --network "$NETWORK" \ --ip 172.30.0.10 \ -e IPA_DOMAIN="${IPA_DOMAIN:?}" \ @@ -62,6 +74,9 @@ start_freeipa() { -e IPA_SETUP_KRA="${IPA_SETUP_KRA:-false}" \ -e SMB_SCAN_PASSWORD="${SMB_SCAN_PASSWORD:?}" \ -e LUKS_KEY_UPLOAD_PASSWORD="${LUKS_KEY_UPLOAD_PASSWORD:?}" \ + -e CMK_URL="http://172.30.0.12:5000" \ + -e CMK_ADVERTISED_URL="${CMK_ADVERTISED_URL:-}" \ + -e CMK_SITE_ID="${CMK_SITE_ID:-cmk}" \ -p 389:389 \ -p 636:636 \ -p 88:88 \ @@ -88,8 +103,8 @@ case "$MODE" in ;; all) start_freeipa - docker compose up -d postgres keycloak - echo "Full stack started (freeipa + postgres + keycloak)." + docker compose up -d postgres keycloak checkmk + echo "Full stack started (freeipa + postgres + keycloak + checkmk)." ;; "") start_freeipa