288 lines
13 KiB
YAML
288 lines
13 KiB
YAML
# FreeIPA + Keycloak + PostgreSQL constellation
|
|
#
|
|
# Setup:
|
|
# cp .env.example .env && $EDITOR .env
|
|
# docker compose up -d
|
|
# docker compose logs -f freeipa # watch first-boot install (~10 min)
|
|
# # Once freeipa is healthy:
|
|
# ./keycloak-configure.sh # wire Keycloak → FreeIPA LDAP
|
|
#
|
|
# To run without Keycloak:
|
|
# docker compose up -d freeipa
|
|
#
|
|
# To scale out (clients enroll against the same IPA server):
|
|
# docker compose --profile clients up
|
|
#
|
|
# ── cgroupns NOTE ────────────────────────────────────────────────────────────
|
|
# On hosts using cgroup v2 (WSL2, recent Linux with unified hierarchy) the
|
|
# FreeIPA container runs /sbin/init (systemd) and exits immediately with code
|
|
# 255 unless the container shares the host cgroup namespace. Docker Compose
|
|
# does not yet expose a cgroupns_mode property in its schema, so use the
|
|
# provided run.sh wrapper instead of plain `docker compose up` on those hosts:
|
|
#
|
|
# ./run.sh — starts freeipa only (cgroupns host applied automatically)
|
|
# ./run.sh all — starts freeipa + postgres + keycloak
|
|
#
|
|
# 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
|
|
|
|
services:
|
|
|
|
# ── FreeIPA ─────────────────────────────────────────────────────────────────
|
|
freeipa:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
image: freeipa-server:local
|
|
container_name: freeipa
|
|
hostname: ${IPA_HOSTNAME:-ipa.example.com}
|
|
privileged: true
|
|
tmpfs:
|
|
- /run
|
|
- /tmp
|
|
volumes:
|
|
- freeipa-data:/data
|
|
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
|
- cmk-creds:/cmk-creds # read CMK secret + write push-mode requests for hosts
|
|
environment:
|
|
IPA_DOMAIN: ${IPA_DOMAIN:?set IPA_DOMAIN in .env}
|
|
IPA_REALM: ${IPA_REALM:-}
|
|
IPA_ADMIN_PASSWORD: ${IPA_ADMIN_PASSWORD:?set IPA_ADMIN_PASSWORD in .env}
|
|
IPA_DM_PASSWORD: ${IPA_DM_PASSWORD:?set IPA_DM_PASSWORD in .env}
|
|
IPA_SETUP_DNS: ${IPA_SETUP_DNS:-false}
|
|
IPA_DNS_FORWARDER: ${IPA_DNS_FORWARDER:-}
|
|
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://<docker-host>:8090).
|
|
CMK_ADVERTISED_URL: ${CMK_ADVERTISED_URL:-}
|
|
CMK_SITE_ID: ${CMK_SITE_ID:-cmk}
|
|
ports:
|
|
- "389:389"
|
|
- "636:636"
|
|
- "88:88"
|
|
- "88:88/udp"
|
|
- "464:464"
|
|
- "464:464/udp"
|
|
- "443:443"
|
|
- "445:445"
|
|
- "139:139"
|
|
- "137:137/udp"
|
|
- "138:138/udp"
|
|
networks:
|
|
ipa-net:
|
|
ipv4_address: 172.30.0.10
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "ipactl status 2>/dev/null | grep -q 'running'"]
|
|
interval: 30s
|
|
timeout: 15s
|
|
retries: 20
|
|
start_period: 600s
|
|
|
|
# ── PostgreSQL (Keycloak backend) ────────────────────────────────────────────
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
container_name: keycloak-db
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: keycloak
|
|
POSTGRES_USER: keycloak
|
|
POSTGRES_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env}
|
|
volumes:
|
|
- keycloak-db:/var/lib/postgresql/data
|
|
networks:
|
|
ipa-net:
|
|
healthcheck:
|
|
test: ["CMD", "pg_isready", "-U", "keycloak"]
|
|
interval: 10s
|
|
retries: 5
|
|
|
|
# ── CheckMK CE ───────────────────────────────────────────────────────────────
|
|
# Monitoring server. Clients register themselves via ansipa-enforce-policies.sh
|
|
# when added to dev_mon_base. The automation user's secret is written to the
|
|
# cmk-creds shared volume so ansipa-checkmk-setup.sh (in the freeipa container)
|
|
# can store it in the IPA dev_mon_base hostgroup description for client discovery.
|
|
#
|
|
# Web UI: http://localhost:8090/cmk/ (user: cmkadmin, pass: CMK_ADMIN_PASSWORD)
|
|
# Agent dl: http://localhost:8090/cmk/check_mk/agents/
|
|
# Note: CheckMK 2.3 uses port 5000 (OMD apache); host port 8090 maps to it.
|
|
checkmk:
|
|
image: checkmk/check-mk-raw:2.3.0-latest
|
|
container_name: checkmk
|
|
hostname: checkmk.test.local
|
|
restart: unless-stopped
|
|
# Override entrypoint to copy the auto-generated automation secret to the
|
|
# shared volume so the freeipa container can read it without docker exec.
|
|
# $$ → $ after Compose expansion (i.e., $$! → $! in bash = background PID).
|
|
command:
|
|
- bash
|
|
- -c
|
|
- >-
|
|
/docker-entrypoint.sh & CMK_PID=$$!;
|
|
until [ -f /omd/sites/${CMK_SITE_ID:-cmk}/var/check_mk/web/automation/automation.secret ];
|
|
do sleep 3; done; sleep 5;
|
|
mkdir -p /cmk-creds;
|
|
cp /omd/sites/${CMK_SITE_ID:-cmk}/var/check_mk/web/automation/automation.secret /cmk-creds/automation.secret;
|
|
chmod 644 /cmk-creds/automation.secret;
|
|
printf '<LocationMatch ^/cmk/(omd|wiki|nagvis|check_mk|pnp4nagios|api)>\n Order allow,deny\n Allow from all\n Satisfy any\n</LocationMatch>\n'
|
|
> /omd/sites/${CMK_SITE_ID:-cmk}/etc/apache/conf.d/cookie_auth.conf;
|
|
omd reload ${CMK_SITE_ID:-cmk} apache 2>/dev/null || true;
|
|
while true; do
|
|
for _req in /cmk-creds/push-mode-*.req; do
|
|
[ -f "$$_req" ] || continue;
|
|
_hn=$$(cat "$$_req");
|
|
_hk=$$(echo "$$_hn" | tr . _);
|
|
_mk=/omd/sites/${CMK_SITE_ID:-cmk}/etc/check_mk/conf.d/wato/ansipa/hosts.mk;
|
|
[ -f "$$_mk" ] || continue;
|
|
grep -q "explicit_host_conf.*$$_hk\|'$$_hn'.*push-agent" "$$_mk" 2>/dev/null && { rm -f "$$_req"; continue; };
|
|
printf '\nexplicit_host_conf.setdefault('"'"'cmk_agent_connection'"'"', {})\nexplicit_host_conf['"'"'cmk_agent_connection'"'"'].update({'"'"'%s'"'"': '"'"'push-agent'"'"'})\n' "$$_hn" >> "$$_mk";
|
|
su - ${CMK_SITE_ID:-cmk} -c 'cmk -O' 2>/dev/null || true;
|
|
rm -f "$$_req";
|
|
done;
|
|
sleep 30;
|
|
done &
|
|
wait $$CMK_PID
|
|
environment:
|
|
CMK_SITE_ID: ${CMK_SITE_ID:-cmk}
|
|
CMK_PASSWORD: ${CMK_ADMIN_PASSWORD:?set CMK_ADMIN_PASSWORD in .env}
|
|
ports:
|
|
- "8090:5000" # web UI (CheckMK 2.3 uses port 5000 for OMD apache)
|
|
- "8000:8000" # agent receiver (push mode / cmk-agent-ctl)
|
|
- "6557:6557" # livestatus (optional, for external tools)
|
|
volumes:
|
|
- cmk-data:/omd/sites
|
|
- cmk-creds:/cmk-creds
|
|
networks:
|
|
ipa-net:
|
|
ipv4_address: 172.30.0.12
|
|
healthcheck:
|
|
# 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
|
|
start_period: 180s
|
|
|
|
# ── Keycloak ─────────────────────────────────────────────────────────────────
|
|
# After first start, run ./keycloak-configure.sh to wire FreeIPA LDAP federation.
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:latest
|
|
container_name: keycloak
|
|
restart: unless-stopped
|
|
# Use 'start' (not start-dev) for production; requires a TLS certificate.
|
|
command: start-dev
|
|
environment:
|
|
KC_DB: postgres
|
|
KC_DB_URL: jdbc:postgresql://postgres/keycloak
|
|
KC_DB_USERNAME: keycloak
|
|
KC_DB_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env}
|
|
KC_HOSTNAME: ${KC_HOSTNAME:-localhost}
|
|
KC_HTTP_PORT: 8080
|
|
KC_HTTPS_PORT: 8443
|
|
KC_HTTP_ENABLED: "true"
|
|
KC_HEALTH_ENABLED: "true" # exposes /health/* on management port 9000 (KC 25+)
|
|
# Serve Keycloak under /auth so the ansipa nginx gateway can proxy it at a
|
|
# native subpath (no rewriting). KC_PROXY_HEADERS=xforwarded makes Keycloak
|
|
# trust X-Forwarded-* so it builds correct absolute URLs behind the gateway
|
|
# (and behind a further upstream proxy). KC_HOSTNAME_STRICT=false keeps it
|
|
# from pinning to a single hostname during testing.
|
|
KC_HTTP_RELATIVE_PATH: /auth
|
|
KC_PROXY_HEADERS: xforwarded
|
|
KC_HOSTNAME_STRICT: "false"
|
|
KC_FEATURES: preview
|
|
KEYCLOAK_ADMIN: ${KC_ADMIN:-admin}
|
|
KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:?set KC_ADMIN_PASSWORD in .env}
|
|
extra_hosts:
|
|
- "${IPA_HOSTNAME:-ipa.example.com}:172.30.0.10"
|
|
ports:
|
|
- "8080:8080"
|
|
- "8443:8443"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
networks:
|
|
ipa-net:
|
|
healthcheck:
|
|
# The official Keycloak image ships neither curl nor wget (but has bash),
|
|
# so probe with bash's /dev/tcp. Check the actual serving port under the
|
|
# configured relative path (/auth) rather than the 9000 management
|
|
# /health endpoint — with KC_HTTP_RELATIVE_PATH set, /health/ready on 9000
|
|
# returns 404, whereas /auth/realms/master reliably returns 200 once up.
|
|
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /auth/realms/master HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && head -c 15 <&3 | grep -q '200 OK'"]
|
|
interval: 20s
|
|
timeout: 10s
|
|
retries: 20
|
|
start_period: 90s
|
|
|
|
# ── nginx gateway: reverse proxy + portal start-page ─────────────────────────
|
|
# Single HTTP entry point that fronts all three web UIs on one hostname:
|
|
# / → portal start page (links to each service)
|
|
# /ipa/ → FreeIPA web UI (proxied to the freeipa container, 443)
|
|
# /cmk/ → CheckMK (proxied to the checkmk container, 5000)
|
|
# /auth/ → Keycloak (KC served under /auth relative path)
|
|
#
|
|
# It speaks plain HTTP and trusts X-Forwarded-* so it is meant to sit BEHIND
|
|
# the operator's own TLS-terminating reverse proxy (see .env ANSIPA_* vars and
|
|
# the README snippet for the one-line upstream config). Published on
|
|
# ANSIPA_HTTP_PORT for direct access/testing when no upstream proxy is used.
|
|
nginx:
|
|
image: nginx:alpine
|
|
container_name: ansipa-nginx
|
|
restart: unless-stopped
|
|
environment:
|
|
# Upstream container addresses on ipa-net (envsubst'd into the template).
|
|
ANSIPA_IPA_UPSTREAM: 172.30.0.10
|
|
ANSIPA_CMK_UPSTREAM: 172.30.0.12:5000
|
|
ANSIPA_KC_UPSTREAM: keycloak:8080
|
|
IPA_HOSTNAME: ${IPA_HOSTNAME:-ipa.example.com}
|
|
volumes:
|
|
- ./nginx/templates:/etc/nginx/templates:ro # *.template → envsubst → conf.d
|
|
- ./nginx/portal:/usr/share/nginx/portal:ro
|
|
ports:
|
|
- "${ANSIPA_HTTP_PORT:-8088}:80"
|
|
depends_on:
|
|
- freeipa
|
|
- checkmk
|
|
- keycloak
|
|
networks:
|
|
ipa-net:
|
|
healthcheck:
|
|
# Use 127.0.0.1, not localhost: inside the container localhost resolves to
|
|
# IPv6 ::1, but nginx listens on IPv4 0.0.0.0:80 only, so a localhost probe
|
|
# is refused even though the gateway is serving fine.
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/ || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 10
|
|
start_period: 20s
|