440 lines
20 KiB
Bash
Executable File
440 lines
20 KiB
Bash
Executable File
#!/bin/bash
|
|
# keycloak-configure.sh — wire Keycloak to FreeIPA, and provision OIDC clients
|
|
#
|
|
# Two jobs:
|
|
# 1. LDAP user federation, so FreeIPA stays the single source of truth for
|
|
# accounts and groups and Keycloak only ever mirrors it (READ_ONLY).
|
|
# 2. A confidential OIDC client per relying party (Nextcloud, Proxmox VE/PBS,
|
|
# OPNsense), with a "groups" claim so those apps can authorize on IPA
|
|
# group membership. Secrets land in ./.oidc-secrets (0600, gitignored).
|
|
#
|
|
# CheckMK is deliberately NOT here: the Community/Raw edition supports neither
|
|
# SAML nor OIDC, so it binds FreeIPA over LDAP directly — see
|
|
# ./checkmk-ldap-configure.sh.
|
|
#
|
|
# Run this AFTER both FreeIPA and Keycloak are fully up. Safe to re-run: every
|
|
# object is created-or-updated, and existing clients keep their secrets.
|
|
# Reads settings from environment variables or a .env file in the same directory.
|
|
#
|
|
# Required env vars:
|
|
# IPA_SERVER FreeIPA server FQDN
|
|
# IPA_DOMAIN FreeIPA domain
|
|
# IPA_DM_PASSWORD Directory Manager password (used as LDAP bind credential
|
|
# unless IPA_BIND_DN / IPA_BIND_PASSWORD override it)
|
|
# KC_ADMIN_PASSWORD Keycloak admin password
|
|
#
|
|
# Optional env vars (defaults shown):
|
|
# KC_URL http://localhost:8080
|
|
# KC_ADMIN admin
|
|
# KC_REALM freeipa (realm to create)
|
|
# KC_REALM_DISPLAY <IPA_DOMAIN>
|
|
# IPA_REALM <IPA_DOMAIN uppercased>
|
|
# IPA_BIND_DN cn=Directory Manager
|
|
# IPA_BIND_PASSWORD <IPA_DM_PASSWORD>
|
|
# IPA_USE_LDAPS false
|
|
# IPA_LDAP_PORT 389 (or 636 if LDAPS)
|
|
# SYNC_FULL_PERIOD 604800 (1 week)
|
|
# SYNC_CHANGED_PERIOD 86400 (1 day)
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
[[ -f "$SCRIPT_DIR/.env" ]] && set -a && source "$SCRIPT_DIR/.env" && set +a
|
|
|
|
# ANSI-C quoting ($'...') so these hold real escape characters. With plain
|
|
# single quotes they are the literal text \033[0;32m, which `echo -e` renders
|
|
# but the summary heredocs below (plain `cat`) would print raw.
|
|
RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'
|
|
CYAN=$'\033[0;36m'; NC=$'\033[0m'
|
|
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
error() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
|
info() { echo -e "${CYAN}[i]${NC} $*"; }
|
|
|
|
: "${IPA_SERVER:?IPA_SERVER is required}"
|
|
: "${IPA_DOMAIN:?IPA_DOMAIN is required}"
|
|
: "${IPA_DM_PASSWORD:?IPA_DM_PASSWORD is required}"
|
|
: "${KC_ADMIN_PASSWORD:?KC_ADMIN_PASSWORD is required}"
|
|
|
|
# Must include the relative path Keycloak is served under: docker-compose.yml
|
|
# sets KC_HTTP_RELATIVE_PATH=/auth so the gateway can proxy it at a native
|
|
# subpath, which moves EVERY endpoint (admin API, realms, token) under /auth.
|
|
KC_URL="${KC_URL:-http://localhost:8080/auth}"
|
|
KC_URL="${KC_URL%/}"
|
|
KC_ADMIN="${KC_ADMIN:-admin}"
|
|
KC_REALM="${KC_REALM:-freeipa}"
|
|
KC_REALM_DISPLAY="${KC_REALM_DISPLAY:-$IPA_DOMAIN}"
|
|
IPA_REALM="${IPA_REALM:-${IPA_DOMAIN^^}}"
|
|
IPA_BIND_DN="${IPA_BIND_DN:-cn=Directory Manager}"
|
|
IPA_BIND_PASSWORD="${IPA_BIND_PASSWORD:-$IPA_DM_PASSWORD}"
|
|
IPA_USE_LDAPS="${IPA_USE_LDAPS:-false}"
|
|
IPA_LDAP_SCHEME="ldap"
|
|
IPA_LDAP_PORT=389
|
|
[[ "$IPA_USE_LDAPS" == "true" ]] && IPA_LDAP_SCHEME="ldaps" && IPA_LDAP_PORT=636
|
|
IPA_LDAP_URL="${IPA_LDAP_URL:-${IPA_LDAP_SCHEME}://${IPA_SERVER}:${IPA_LDAP_PORT}}"
|
|
IPA_BASEDN="dc=${IPA_DOMAIN/./,dc=}"
|
|
SYNC_FULL_PERIOD="${SYNC_FULL_PERIOD:-604800}"
|
|
SYNC_CHANGED_PERIOD="${SYNC_CHANGED_PERIOD:-86400}"
|
|
|
|
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
kc_token() {
|
|
curl -sf -X POST \
|
|
"$KC_URL/realms/master/protocol/openid-connect/token" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "client_id=admin-cli&grant_type=password" \
|
|
-d "username=$KC_ADMIN" \
|
|
--data-urlencode "password=$KC_ADMIN_PASSWORD" \
|
|
| jq -r '.access_token'
|
|
}
|
|
|
|
kc_get() { curl -sf -H "Authorization: Bearer $TOKEN" "$KC_URL$1"; }
|
|
kc_post() { curl -sf -X POST -H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" -d "$2" "$KC_URL$1"; }
|
|
kc_put() { curl -sf -X PUT -H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" -d "$2" "$KC_URL$1"; }
|
|
|
|
kc_status() { curl -sf -o /dev/null -w "%{http_code}" \
|
|
-H "Authorization: Bearer $TOKEN" "$KC_URL$1"; }
|
|
|
|
# ─── Wait for Keycloak ────────────────────────────────────────────────────────
|
|
# Probe the master realm, not /health/ready: the health endpoints live on the
|
|
# separate management port 9000 and are NOT under KC_HTTP_RELATIVE_PATH, so
|
|
# "$KC_URL/health/ready" 404s forever on this deployment. /realms/master
|
|
# answers 200 on the serving port as soon as Keycloak is actually usable.
|
|
info "Waiting for Keycloak at $KC_URL..."
|
|
for i in $(seq 1 60); do
|
|
curl -sf "$KC_URL/realms/master" &>/dev/null && break
|
|
[[ $i -eq 60 ]] && { error "Keycloak not ready after 120s."; exit 1; }
|
|
sleep 2
|
|
done
|
|
log "Keycloak is ready."
|
|
|
|
# ─── Authenticate ─────────────────────────────────────────────────────────────
|
|
TOKEN=$(kc_token)
|
|
[[ -z "$TOKEN" || "$TOKEN" == "null" ]] && { error "Failed to obtain Keycloak token."; exit 1; }
|
|
log "Admin token obtained."
|
|
|
|
# ─── Create realm ─────────────────────────────────────────────────────────────
|
|
REALM_STATUS=$(kc_status "/admin/realms/$KC_REALM")
|
|
if [[ "$REALM_STATUS" == "200" ]]; then
|
|
warn "Realm '$KC_REALM' already exists — updating."
|
|
kc_put "/admin/realms/$KC_REALM" \
|
|
"{\"realm\":\"$KC_REALM\",\"displayName\":\"$KC_REALM_DISPLAY\",\"enabled\":true,
|
|
\"ssoSessionMaxLifespan\":36000,\"accessTokenLifespan\":300}" >/dev/null
|
|
else
|
|
kc_post "/admin/realms" \
|
|
"{\"realm\":\"$KC_REALM\",\"displayName\":\"$KC_REALM_DISPLAY\",\"enabled\":true,
|
|
\"ssoSessionMaxLifespan\":36000,\"accessTokenLifespan\":300}" >/dev/null
|
|
log "Realm '$KC_REALM' created."
|
|
fi
|
|
|
|
TOKEN=$(kc_token)
|
|
|
|
# ─── LDAP user federation ─────────────────────────────────────────────────────
|
|
log "Configuring FreeIPA LDAP user federation..."
|
|
|
|
LDAP_COMPONENT=$(cat <<JSON
|
|
{
|
|
"name": "freeipa-ldap",
|
|
"providerId": "ldap",
|
|
"providerType": "org.keycloak.storage.UserStorageProvider",
|
|
"config": {
|
|
"enabled": ["true"],
|
|
"priority": ["1"],
|
|
"editMode": ["READ_ONLY"],
|
|
"syncRegistrations": ["false"],
|
|
"vendor": ["rhds"],
|
|
"usernameLDAPAttribute": ["uid"],
|
|
"rdnLDAPAttribute": ["uid"],
|
|
"uuidLDAPAttribute": ["ipaUniqueID"],
|
|
"userObjectClasses": ["inetOrgPerson, organizationalPerson"],
|
|
"connectionUrl": ["$IPA_LDAP_URL"],
|
|
"usersDn": ["cn=users,cn=accounts,$IPA_BASEDN"],
|
|
"authType": ["simple"],
|
|
"bindDn": ["$IPA_BIND_DN"],
|
|
"bindCredential": ["$IPA_BIND_PASSWORD"],
|
|
"searchScope": ["1"],
|
|
"validatePasswordPolicy": ["false"],
|
|
"trustEmail": ["false"],
|
|
"useTruststoreSpi": ["ldapsOnly"],
|
|
"connectionPooling": ["true"],
|
|
"pagination": ["true"],
|
|
"batchSizeForSync": ["1000"],
|
|
"fullSyncPeriod": ["$SYNC_FULL_PERIOD"],
|
|
"changedSyncPeriod": ["$SYNC_CHANGED_PERIOD"],
|
|
"importEnabled": ["true"],
|
|
"cachePolicy": ["DEFAULT"],
|
|
"kerberosRealm": ["$IPA_REALM"],
|
|
"serverPrincipal": ["HTTP/$IPA_SERVER@$IPA_REALM"],
|
|
"useKerberosForPasswordAuthentication": ["false"],
|
|
"allowKerberosAuthentication": ["false"],
|
|
"debug": ["false"]
|
|
}
|
|
}
|
|
JSON
|
|
)
|
|
|
|
EXISTING_ID=$(kc_get "/admin/realms/$KC_REALM/components?type=org.keycloak.storage.UserStorageProvider&name=freeipa-ldap" \
|
|
| jq -r '.[0].id // empty')
|
|
|
|
if [[ -n "$EXISTING_ID" ]]; then
|
|
warn "LDAP provider already exists (id=$EXISTING_ID) — updating."
|
|
kc_put "/admin/realms/$KC_REALM/components/$EXISTING_ID" "$LDAP_COMPONENT" >/dev/null
|
|
LDAP_ID="$EXISTING_ID"
|
|
else
|
|
LDAP_ID=$(kc_post "/admin/realms/$KC_REALM/components" "$LDAP_COMPONENT" \
|
|
| jq -r '.id // empty')
|
|
# Keycloak returns 201 with Location header, not a body with id — extract from header or re-query
|
|
if [[ -z "$LDAP_ID" ]]; then
|
|
LDAP_ID=$(kc_get "/admin/realms/$KC_REALM/components?type=org.keycloak.storage.UserStorageProvider&name=freeipa-ldap" \
|
|
| jq -r '.[0].id')
|
|
fi
|
|
log "LDAP provider created (id=$LDAP_ID)."
|
|
fi
|
|
|
|
# ─── Attribute mappers ────────────────────────────────────────────────────────
|
|
log "Adding LDAP attribute mappers..."
|
|
|
|
add_mapper() {
|
|
local name="$1" type="$2" ldap_attr="$3" user_attr="$4"
|
|
local payload
|
|
payload=$(cat <<JSON
|
|
{
|
|
"name": "$name",
|
|
"providerId": "$type",
|
|
"providerType": "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
|
|
"parentId": "$LDAP_ID",
|
|
"config": {
|
|
"ldap.attribute": ["$ldap_attr"],
|
|
"user.model.attribute": ["$user_attr"],
|
|
"read.only": ["true"],
|
|
"always.read.value.from.ldap": ["false"],
|
|
"is.mandatory.in.ldap": ["false"]
|
|
}
|
|
}
|
|
JSON
|
|
)
|
|
local exists
|
|
exists=$(kc_get "/admin/realms/$KC_REALM/components?parent=$LDAP_ID&name=$name" \
|
|
| jq -r '.[0].id // empty')
|
|
if [[ -z "$exists" ]]; then
|
|
kc_post "/admin/realms/$KC_REALM/components" "$payload" >/dev/null
|
|
log " mapper: $name"
|
|
else
|
|
warn " mapper '$name' already exists — skipping."
|
|
fi
|
|
}
|
|
|
|
add_mapper "email" "user-attribute-ldap-mapper" "mail" "email"
|
|
add_mapper "first-name" "user-attribute-ldap-mapper" "givenName" "firstName"
|
|
add_mapper "last-name" "user-attribute-ldap-mapper" "sn" "lastName"
|
|
add_mapper "uid-number" "user-attribute-ldap-mapper" "uidNumber" "uidNumber"
|
|
|
|
# Group mapper (maps IPA groups to Keycloak groups)
|
|
GROUP_MAPPER=$(cat <<JSON
|
|
{
|
|
"name": "freeipa-groups",
|
|
"providerId": "group-ldap-mapper",
|
|
"providerType": "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
|
|
"parentId": "$LDAP_ID",
|
|
"config": {
|
|
"groups.dn": ["cn=groups,cn=accounts,$IPA_BASEDN"],
|
|
"group.name.ldap.attribute": ["cn"],
|
|
"group.object.classes": ["groupOfNames"],
|
|
"preserve.group.inheritance": ["false"],
|
|
"membership.ldap.attribute": ["member"],
|
|
"membership.attribute.type": ["DN"],
|
|
"mode": ["READ_ONLY"],
|
|
"user.roles.retrieve.strategy": ["LOAD_GROUPS_BY_MEMBER_ATTRIBUTE"],
|
|
"mapped.group.attributes": [""],
|
|
"drop.non.existing.groups.during.sync": ["false"]
|
|
}
|
|
}
|
|
JSON
|
|
)
|
|
exists=$(kc_get "/admin/realms/$KC_REALM/components?parent=$LDAP_ID&name=freeipa-groups" \
|
|
| jq -r '.[0].id // empty')
|
|
if [[ -z "$exists" ]]; then
|
|
kc_post "/admin/realms/$KC_REALM/components" "$GROUP_MAPPER" >/dev/null
|
|
log " mapper: freeipa-groups"
|
|
fi
|
|
|
|
# ─── Trigger initial sync ──────────────────────────────────────────────────────
|
|
log "Triggering initial user sync..."
|
|
SYNC_RESULT=$(kc_post "/admin/realms/$KC_REALM/user-storage/$LDAP_ID/sync?action=triggerFullSync" "" 2>/dev/null || echo "{}")
|
|
ADDED=$(echo "$SYNC_RESULT" | jq -r '.added // 0')
|
|
UPDATED=$(echo "$SYNC_RESULT" | jq -r '.updated // 0')
|
|
log "Sync complete: $ADDED added, $UPDATED updated."
|
|
|
|
# ─── Enable email login ────────────────────────────────────────────────────────
|
|
kc_put "/admin/realms/$KC_REALM" \
|
|
'{"loginWithEmailAllowed":true,"duplicateEmailsAllowed":false}' >/dev/null
|
|
log "Email login enabled on realm '$KC_REALM'."
|
|
|
|
TOKEN=$(kc_token)
|
|
|
|
# ─── Group membership in tokens ───────────────────────────────────────────────
|
|
# Relying parties authorize on FreeIPA group membership (Nextcloud quota groups,
|
|
# Proxmox permissions, ...), so every token needs a "groups" claim. The LDAP
|
|
# group mapper above only imports groups INTO Keycloak — a protocol mapper is
|
|
# what actually puts them in the ID token / userinfo response.
|
|
#
|
|
# full.path=false emits bare names ("usr_nextcloud") rather than "/usr_nextcloud",
|
|
# because that is what the consumers below match against.
|
|
log "Adding realm-wide 'groups' claim mapper..."
|
|
GROUPS_SCOPE_ID=$(kc_get "/admin/realms/$KC_REALM/client-scopes" \
|
|
| jq -r '.[] | select(.name=="groups") | .id // empty' | head -1)
|
|
|
|
if [[ -z "$GROUPS_SCOPE_ID" ]]; then
|
|
kc_post "/admin/realms/$KC_REALM/client-scopes" '{
|
|
"name": "groups",
|
|
"protocol": "openid-connect",
|
|
"attributes": {"include.in.token.scope":"true","display.on.consent.screen":"false"}
|
|
}' >/dev/null 2>&1 || true
|
|
GROUPS_SCOPE_ID=$(kc_get "/admin/realms/$KC_REALM/client-scopes" \
|
|
| jq -r '.[] | select(.name=="groups") | .id // empty' | head -1)
|
|
fi
|
|
|
|
if [[ -n "$GROUPS_SCOPE_ID" ]]; then
|
|
HAS_GM=$(kc_get "/admin/realms/$KC_REALM/client-scopes/$GROUPS_SCOPE_ID/protocol-mappers/models" \
|
|
| jq -r '.[] | select(.name=="groups") | .id // empty' | head -1)
|
|
if [[ -z "$HAS_GM" ]]; then
|
|
kc_post "/admin/realms/$KC_REALM/client-scopes/$GROUPS_SCOPE_ID/protocol-mappers/models" '{
|
|
"name": "groups",
|
|
"protocol": "openid-connect",
|
|
"protocolMapper": "oidc-group-membership-mapper",
|
|
"config": {
|
|
"full.path": "false",
|
|
"id.token.claim": "true",
|
|
"access.token.claim": "true",
|
|
"userinfo.token.claim": "true",
|
|
"claim.name": "groups"
|
|
}
|
|
}' >/dev/null 2>&1 || true
|
|
fi
|
|
# Hand the scope to every new client automatically.
|
|
kc_put "/admin/realms/$KC_REALM/default-default-client-scopes/$GROUPS_SCOPE_ID" "" >/dev/null 2>&1 || true
|
|
log " 'groups' client scope ready."
|
|
else
|
|
warn " could not create the 'groups' client scope — add it by hand if apps need group claims."
|
|
fi
|
|
|
|
# ─── OIDC clients ─────────────────────────────────────────────────────────────
|
|
# One confidential client per relying party. Secrets are written to
|
|
# .oidc-secrets (mode 0600, gitignored) so nextcloud-configure.sh and the
|
|
# Proxmox/OPNsense setup steps can pick them up without a trip through the UI.
|
|
#
|
|
# Clients whose base URL is not configured in .env are skipped — set PVE_URL,
|
|
# PBS_URL or OPNSENSE_URL and re-run to add them later. Re-running is safe: an
|
|
# existing client is updated in place and keeps its secret.
|
|
SECRETS_FILE="$SCRIPT_DIR/.oidc-secrets"
|
|
|
|
kc_client() {
|
|
local client_id="$1" name="$2" root_url="$3" redirect="$4"
|
|
local payload existing cid secret
|
|
|
|
payload=$(jq -n \
|
|
--arg id "$client_id" --arg name "$name" \
|
|
--arg root "$root_url" --arg redir "$redirect" \
|
|
'{
|
|
clientId: $id, name: $name, enabled: true, protocol: "openid-connect",
|
|
publicClient: false, bearerOnly: false,
|
|
standardFlowEnabled: true, directAccessGrantsEnabled: false,
|
|
serviceAccountsEnabled: false, implicitFlowEnabled: false,
|
|
rootUrl: $root, baseUrl: $root,
|
|
redirectUris: [$redir], webOrigins: ["+"],
|
|
attributes: {"post.logout.redirect.uris": $root + "/*"}
|
|
}')
|
|
|
|
existing=$(kc_get "/admin/realms/$KC_REALM/clients?clientId=$client_id" \
|
|
| jq -r '.[0].id // empty')
|
|
|
|
if [[ -n "$existing" ]]; then
|
|
kc_put "/admin/realms/$KC_REALM/clients/$existing" "$payload" >/dev/null
|
|
cid="$existing"
|
|
warn " client '$client_id' already existed — updated (secret unchanged)."
|
|
else
|
|
kc_post "/admin/realms/$KC_REALM/clients" "$payload" >/dev/null
|
|
cid=$(kc_get "/admin/realms/$KC_REALM/clients?clientId=$client_id" \
|
|
| jq -r '.[0].id // empty')
|
|
log " client '$client_id' created."
|
|
fi
|
|
|
|
[[ -z "$cid" ]] && { error " could not resolve client '$client_id'."; return 1; }
|
|
|
|
secret=$(kc_get "/admin/realms/$KC_REALM/clients/$cid/client-secret" \
|
|
| jq -r '.value // empty')
|
|
[[ -z "$secret" ]] && { error " could not read secret for '$client_id'."; return 1; }
|
|
|
|
# Rewrite this client's line in place, keeping the others.
|
|
touch "$SECRETS_FILE"; chmod 600 "$SECRETS_FILE"
|
|
grep -v "^${client_id}=" "$SECRETS_FILE" > "$SECRETS_FILE.tmp" 2>/dev/null || true
|
|
mv "$SECRETS_FILE.tmp" "$SECRETS_FILE"
|
|
echo "${client_id}=${secret}" >> "$SECRETS_FILE"
|
|
chmod 600 "$SECRETS_FILE"
|
|
}
|
|
|
|
log "Provisioning OIDC clients..."
|
|
|
|
# Nextcloud — user_oidc's callback is always <base>/apps/user_oidc/code
|
|
NC_PUBLIC_URL="${NC_PUBLIC_URL:-}"
|
|
if [[ -n "$NC_PUBLIC_URL" ]]; then
|
|
NC_PUBLIC_URL="${NC_PUBLIC_URL%/}"
|
|
kc_client "nextcloud" "Nextcloud" "$NC_PUBLIC_URL" "$NC_PUBLIC_URL/apps/user_oidc/code" || true
|
|
else
|
|
warn " NC_PUBLIC_URL unset — skipping the Nextcloud client."
|
|
fi
|
|
|
|
# Proxmox VE / PBS — the OpenID realm posts back to the web UI root.
|
|
if [[ -n "${PVE_URL:-}" ]]; then
|
|
kc_client "proxmox-ve" "Proxmox VE" "${PVE_URL%/}" "${PVE_URL%/}/*" || true
|
|
else
|
|
warn " PVE_URL unset — skipping the Proxmox VE client."
|
|
fi
|
|
if [[ -n "${PBS_URL:-}" ]]; then
|
|
kc_client "proxmox-bs" "Proxmox Backup Server" "${PBS_URL%/}" "${PBS_URL%/}/*" || true
|
|
else
|
|
warn " PBS_URL unset — skipping the Proxmox Backup Server client."
|
|
fi
|
|
if [[ -n "${OPNSENSE_URL:-}" ]]; then
|
|
kc_client "opnsense" "OPNsense" "${OPNSENSE_URL%/}" "${OPNSENSE_URL%/}/*" || true
|
|
else
|
|
warn " OPNSENSE_URL unset — skipping the OPNsense client."
|
|
fi
|
|
|
|
[[ -f "$SECRETS_FILE" ]] && log "Client secrets written to $SECRETS_FILE (mode 0600)."
|
|
|
|
# ─── Summary ─────────────────────────────────────────────────────────────────
|
|
cat <<EOF
|
|
|
|
${GREEN}Keycloak ↔ FreeIPA configuration complete.${NC}
|
|
|
|
Keycloak URL: $KC_URL
|
|
Realm: $KC_REALM (display: $KC_REALM_DISPLAY)
|
|
LDAP provider: $IPA_LDAP_URL
|
|
Users DN: cn=users,cn=accounts,$IPA_BASEDN
|
|
Groups DN: cn=groups,cn=accounts,$IPA_BASEDN
|
|
Sync schedule: full=${SYNC_FULL_PERIOD}s / changed=${SYNC_CHANGED_PERIOD}s
|
|
|
|
Admin console: $KC_URL/admin/$KC_REALM/console
|
|
User login: $KC_URL/realms/$KC_REALM/account
|
|
|
|
OIDC clients: $(ls "$SECRETS_FILE" &>/dev/null && cut -d= -f1 "$SECRETS_FILE" | tr '\n' ' ' || echo "(none — set NC_PUBLIC_URL / PVE_URL / PBS_URL / OPNSENSE_URL)")
|
|
Client secrets: $SECRETS_FILE
|
|
|
|
Discovery URL (give this to relying parties):
|
|
$KC_URL/realms/$KC_REALM/.well-known/openid-configuration
|
|
|
|
Next steps:
|
|
• Verify users are visible: Admin console → Users
|
|
• Run ./nextcloud-configure.sh to point Nextcloud at IPA LDAP + this realm
|
|
• CheckMK Community edition has no OIDC/SAML — run ./checkmk-ldap-configure.sh
|
|
to bind it straight to FreeIPA instead
|
|
• Proxmox VE / PBS: add an OpenID Connect realm with the discovery URL above
|
|
and the proxmox-ve / proxmox-bs client secret
|
|
• For production: switch Keycloak to 'start' mode with a TLS cert
|
|
• For Kerberos/SPNEGO: supply an HTTP service keytab and set
|
|
allowKerberosAuthentication=true in the LDAP provider config
|
|
|
|
EOF
|