Skip to main content

Identity & SSO

The identity fabric is what turns golemsec from "a pile of running apps" into "a company people can actually log into." Active Directory is the source of truth; Keycloak federates it; apps authenticate employees via OIDC SSO.

Active Directory

  • Forest corp.golemsec.io, NetBIOS GOLEMSEC, 2 DCs (DC01 root, DC02 replica + NPS/RADIUS).
  • 200 users under OU=<dept>,OU=golemsec,DC=corp,DC=golemsec,DC=io.
  • Verified healthy: ldapsearch returns all 200 users with mail attributes; user and admin binds succeed.

Keycloak federation

  • Version: Keycloak 24.0.5 (Docker, start-dev, H2), container keycloak, port 8080.
  • Realm: golemsec · LDAP provider: ad-corp (component ID 39a545bb-c6a7-47df-b540-91aa9786e6f2).
  • Bind: Administrator@corp.golemsec.io (UPN bind), users DN OU=golemsec,DC=corp,DC=golemsec,DC=io, subtree scope, editMode=READ_ONLY, importEnabled=true.
  • Result (verified): 200 users federated (192 added / 8 updated / 0 failed). Login confirmed by username and by email (itai.williams, david.cohen, linda.miller@golemsec.io), with preferred_username / email / name / given_name / family_name claims sourced from AD.

⚠️ Critical build gotcha (do not repeat)

Creating the realm and the LDAP provider with separate REST calls leaves the realm unresolvable inside Keycloak's background sync transaction → NullPointerException in LDAPStorageProviderFactory.getLDAPConfigDecorators ("realm is null"). The symptom is maddening: sync returns {"errorMessage":"UnknownError"} with no server log, and logins silently fail — even though AD LDAP is perfectly healthy.

Fix: create the realm with the LDAP provider embedded in the realm representation's components/subComponents as a single atomic POST /admin/realms import (provisioning/linux/kc-realm-import.sh). This bug is not version-specific (reproduced on 24.0.5 and 26.0.8).

Other traps:

  • GET /components masks bindCredential as **********; editing-then-PUTting the component verbatim overwrites the real password with the mask. Test the stored credential via testLDAPConnection with componentId and no inline credential.
  • To surface the swallowed exception, set fullSyncPeriod=15 so the Timer path logs the full stack at ERROR (the manual-sync endpoint swallows it).

OIDC clients (realm golemsec)

Client IDAppRedirect URI
grafanaGrafanahttp://10.50.50.213:3000/login/generic_oauth
gitlabGitLabhttp://10.50.20.107/users/auth/openid_connect/callback
nextcloudNextcloudhttp://10.50.10.51/apps/user_oidc/code
glpiGLPI(client ready; app-side wiring pending)

Client secrets are in Credentials & Break-Glass. Discovery URL for apps: http://10.50.20.133:8080/realms/golemsec/.well-known/openid-configuration

Issuer derives from the request host in start-dev mode — keep all clients and apps pointing at 10.50.20.133:8080 so issuer validation matches.

App SSO status

AppStatusHow
Grafana (NOC)✅ Live & verifiedGF_AUTH_GENERIC_OAUTH_* env; "golemsec SSO" button, 302→Keycloak w/ PKCE, token issued for AD user
GitLab (dev)✅ Live & verifiedomnibus gitlab.rb OmniAuth openid_connect block + gitlab-ctl reconfigure; SSO button rendered (raw GET → 404 is normal GitLab CSRF protection, button POSTs)
Nextcloud (files)✅ Live & verifiedinstalled (SQLite) + user_oidc app + user_oidc:provider golemsec
Zabbix (NOC)✅ Live & verifiedbackend DB repaired, then direct AD LDAP (authentication.update, 6.0 model); itai.williams + david.cohen login verified
ERPNext (ERP/finance)✅ Live & verifiedFrappe built-in LDAP Settings → AD; itai.williams + david.cohen login + JIT auto-provisioned as ERPNext users
Jenkins (CI)✅ Live & verifiedLDAP plugin + init.groovy.d AD security realm (setup wizard bypassed); itai.williams authenticates and AD groups resolve (GG-Executive)
GLPI (helpdesk/assets)✅ Live & verifiedinstalled (was at wizard) on data-tier MySQL, then AD LDAP directory via GLPI AuthLDAP (GLPIKey-encrypted bind pw); 200 employees imported via glpi:ldap:synchronize_users
SuiteCRM (CRM)✅ Liveconfig_override.php LDAPAuthenticate + auto-create → AD; Sales/Marketing sign in with company creds
Wazuh dashboard (SIEM)✅ Live & verifiedOpenSearch-security LDAP authc/authz in config.yml + roles_mapping (GG-IT/GG-Executiveall_access); verified eitan.cohenall_access
Akaunting (accounting)Local authnative LDAP/SSO is a paid module — standalone local auth by decision
OrangeHRM (HR)Local (LDAP pending)installed & working on local auth; AD LDAP = a 1-form Admin-UI step

Three integration styles, by design. OIDC via Keycloak (Grafana, GitLab, Nextcloud), direct AD LDAP (Zabbix, ERPNext, Jenkins, GLPI, SuiteCRM, Wazuh), and framework-native LDAP — all resolving to the same AD identities.

Authorization (group → role)

Beyond authentication, AD-group → app-role mapping is wired where it matters:

  • ERPNextGG-Sales→Sales roles, GG-IT→System Manager, GG-Finance→Accounts, GG-Executive→manager roles, etc. (all 10 departments; least-privilege default).
  • Jenkins — AD-group matrix authz (GG-IT=admin, GG-RnD=build, others read-only).
  • Grafana — Keycloak realm roles admin/editor → IT/NOC get Admin, dev/exec get Editor.
  • WazuhGG-IT/GG-Executiveall_access (dashboard admin).

Hardening backlog

Keycloak currently runs in start-dev/H2. Before treating it as a permanent service: set a fixed hostname, switch to production mode, and migrate H2 → PostgreSQL.