> For the complete documentation index, see [llms.txt](https://docs.we360.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.we360.ai/deployment-and-it-ops/deployment/infrastructure-management-on-premise/ldap-kerberos-login/ldap-kerberos-setup-runbook.md).

# LDAP / Kerberos SSO — Setup & Troubleshooting Runbook

End-to-end configuration for silent AD login: a domain-joined Windows client runs the standard app, which auto-discovers the tenant from a pre-provisioned `zs-origin.json`, opens the browser to Keycloak, the browser negotiates Kerberos (SPNEGO) with no prompt, and core-service JIT-provisions the user on first login.

> Reproducible local rig (Samba AD DC + Keycloak federation + Windows join scripts) lives in `java-apps/.deployment/ad-kerberos/`. This page is the "why", the correct settings, and the failure modes.

## The chain (what must line up)

```
domain-joined Windows client (domain-user session → Kerberos TGT)
   │  reads C:\Windows\System32\zs-origin.json  → tenant + apiKeycloakUrl + realm
   │  browser (Chrome/Edge) with SPNEGO allowlist for the Keycloak host
   ▼
Keycloak (developer/tenant realm)
   │  LDAP user federation (LDAPS) + Kerberos, editMode = UNSYNCED
   │  SPN HTTP/<keycloak-host> + AES keytab
   ▼
core-service  (JIT provisioning: /api/v2/onboarding/ensure-federated-identity)
   │  tenant StandardConfig.autoCreateUsers = true
   ▼
We360 Identity created; identity_id claim written back to Keycloak
```

## Keycloak federation — the settings that matter

| Setting       | Value                                                                                    | Why                                                                                                                                                                            |
| ------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connection    | `ldaps://<dc>:636`                                                                       | Samba blocks cleartext simple binds; **do not** set `ldap server require strong auth = no`.                                                                                    |
| TLS cert      | Must carry an **IP SAN** if Keycloak dials the DC by IP                                  | Java LDAPS verifies the host; a DNS-only SAN fails on an IP dial.                                                                                                              |
| Truststore    | DC CA in Keycloak `conf/truststores/`                                                    | Loaded at startup only — **restart Keycloak** after adding.                                                                                                                    |
| **editMode**  | **`UNSYNCED`** (not `READ_ONLY`)                                                         | READ\_ONLY makes the imported user's federated storage non-writable, so the `identity_id` attribute can't be stored → JIT fails with *"Federated storage is not writable"*.    |
| importEnabled | `true`                                                                                   | The `identity_id` attribute lives on the imported (local) copy.                                                                                                                |
| Mappers       | username→`sAMAccountName`, email→`mail`, **first name→`givenName`**, last name→`sn`      | The API-created federation is easy to leave **missing the first-name mapper**; then `firstName` is null and JIT fails validation *"First name and last name cannot be empty"*. |
| Kerberos      | `allowKerberosAuthentication=true`, `serverPrincipal=HTTP/<host>@REALM`, `keyTab=<path>` | `<host>` MUST equal the host in the manifest's `apiKeycloakUrl` (see below).                                                                                                   |

## Kerberos SPN / keytab — must match the manifest host and be AES

* The browser derives the SPN from the URL it opens: `apiKeycloakUrl` host → `HTTP/<that-host>`. The AD SPN, the exported keytab, and Keycloak's `serverPrincipal` must **all** use that exact host. A mismatch = Keycloak can't decrypt the ticket → silent fallback to the username/password form.
* **Use AES, not RC4.** Set `msDS-SupportedEncryptionTypes = 24` (AES128+AES256) on the service account, then re-export the keytab. JDK 17+/21 (Keycloak) **rejects RC4** with *"Encryption type RC4 with HMAC is not supported/enabled"*, which also falls back to the form. Verify the keytab: `ktutil -k <file> list` should show `aes256-cts-hmac-sha1-96` / `aes128-…`, not `arcfour-hmac`.
* After changing enctype, purge the client's cached ticket (`klist purge` in the domain-user session) so it fetches a fresh AES service ticket.

## The Windows client

1. **Domain-joined** and the interactive desktop session must be a **domain user** (e.g. `DOMAIN\einstein`) — a *local* account has **no Kerberos TGT**, so SPNEGO cannot happen regardless of everything else. Check with `klist` in that session.
2. **Browser SPNEGO allowlist (whitelist the Keycloak host).** By default Chrome and Edge do **not** perform Kerberos/Negotiate for any site — they do **not** honour the IE "Local Intranet" zone for this. You must explicitly allowlist the Keycloak host via the **`AuthServerAllowlist`** policy. The host you list MUST be the host in the manifest's `apiKeycloakUrl` — the same host the SPN/keytab use (e.g. `api.local.we360.ai`). Listing the wrong host = no Negotiate = password prompt.

   **What to allowlist.** A comma-separated list of hosts; wildcards are allowed, so a whole domain works — e.g. `api.local.we360.ai` or `*.we360.ai`. Do **not** include a scheme or port (`https://`, `:8080`) — host only.

   **Deploy via Group Policy (fleet).** Import the Chrome/Edge ADMX templates, then set:

   * Chrome: *Computer Configuration → Administrative Templates → Google → Google Chrome → HTTP authentication → **Authentication server allowlist*** = `<host or *.domain>`
   * Edge: *… → Microsoft Edge → **Authentication server allowlist*** = `<host or *.domain>`

   **Or set the registry directly** (per-machine, `/reg:64` on ARM64/64-bit Windows):

   ```
   reg add HKLM\SOFTWARE\Policies\Google\Chrome  /v AuthServerAllowlist /t REG_SZ /d "*.we360.ai" /f /reg:64
   reg add HKLM\SOFTWARE\Policies\Microsoft\Edge  /v AuthServerAllowlist /t REG_SZ /d "*.we360.ai" /f /reg:64
   ```

   Set the policy for **every** browser the app may open — the standard app launches the OS **default browser**, which is often Edge. **Fully quit and relaunch** the browser (kill all `chrome.exe` / `msedge.exe`) so it reloads the policy — a browser that was already running will ignore a freshly-set allowlist.

   `AuthNegotiateDelegateAllowlist` (same key) is **only** needed if the Keycloak host must delegate the user's ticket onward (constrained delegation); plain SSO does not need it — leave it unset unless you have that requirement.

   **Firefox** (if used) doesn't read these policies — set `network.negotiate-auth.trusted-uris = <host or .domain>` (via `about:config` or a policy) instead.

   **Reading the symptom:**

   * **Basic-auth popup** (browser's own username/password box) → host not allowlisted (or browser not restarted). Fix the allowlist.
   * **Proper Keycloak login form** → Negotiate *was* attempted but Keycloak rejected the ticket (server-side GSS: RC4 vs AES, or SPN/host mismatch — see above).
3. Post-domain-join, the network profile switches to **Domain** — if OpenSSH (or any management) was firewalled to Private/Public only, it goes dark. Widen the rule to `-Profile Any` if you manage the box remotely.

## `zs-origin.json` (pre-provisioned discovery)

* Content is exactly the origin by-base-domain response — open in a browser: `https://origin.in.we360.ai/discover/tenant_by_base_domain/?baseDomain=<baseDomain>` and save the JSON verbatim. See *Pre-Provisioned Origin Data* for schema/trust.
* Windows path: `C:\Windows\System32\zs-origin.json`.
* **WOW64 caveat (ARM64 Windows).** The app reads the **real** System32 (it's a 64-bit/ARM64 process). A **32-bit** shell (e.g. x86 PowerShell over OpenSSH) writing to `C:\Windows\System32` is silently redirected to `SysWOW64` — and a read-back in the same 32-bit shell also hits SysWOW64, so it *looks* placed but the app never sees it. Write via `C:\Windows\Sysnative\zs-origin.json` (or a native-arch shell) and verify from an ARM64/x64 process.

## JIT provisioning (core-service)

* Gated by the tenant's `StandardConfig.autoCreateUsers` (`core_master.tenant_settings.standard.auto_create_users`). Off → `ensure-federated-identity` returns FORBIDDEN.
* On first login the endpoint creates the identity and writes `identity_id` back to Keycloak; the client refreshes its token to pick up the claim. When the identity already exists the endpoint is a **fast no-op** — it must not re-touch Keycloak.
* AD user needs: `sAMAccountName` (username), `mail` (email, e.g. `user@realm`), **`givenName` + `sn`** (first/last — required), `userPrincipalName`.

## Troubleshooting — symptom → cause → fix

| Symptom                                      | Cause                                                      | Fix                                                                           |
| -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Browser shows **basic-auth popup**           | Host not in the browser's SPNEGO allowlist                 | Set Chrome/Edge `AuthServerAllowlist`; restart browser                        |
| Browser shows **Keycloak form** (not silent) | SPNEGO reached Keycloak but GSS failed                     | Check keytab: RC4 (→ set AES) or SPN/host mismatch with `apiKeycloakUrl`      |
| Keycloak log: *RC4 with HMAC not supported*  | Service ticket is RC4; JDK rejects it                      | `msDS-SupportedEncryptionTypes=24`, re-export keytab, `klist purge` on client |
| Prompt even with policy set                  | Interactive session is a **local** user (no TGT)           | Sign in as a **domain** user; verify `klist`                                  |
| App **hangs after login**                    | `ensure-federated-identity` erroring/slow                  | Check core-service log (see below); usually one of the next three rows        |
| *First name and last name cannot be empty*   | Missing first-name mapper / no `givenName`                 | Add `givenName→firstName` mapper; set `givenName`+`sn` in AD                  |
| *Federated storage is not writable*          | editMode = `READ_ONLY`                                     | Set editMode = **UNSYNCED**, re-sync                                          |
| *User exists with same email* (409)          | KC user lookup by username, but federated username ≠ email | Fixed in core-service (`searchByEmail`); rebuild required                     |
| `zs-origin.json` "not found" by app          | Written to SysWOW64 via a 32-bit shell                     | Write to `C:\Windows\Sysnative\...`; verify from ARM64 process                |

## Getting core-service logs locally

core-service defaults to stdout-only JSON. For local debugging, enable the file appender with `-Dcore.logToFile=true` (path via `-Dcore.logFile=…`). `./dev.sh api` sets this automatically and writes `tmp/logs/core-service-app.log`; for an IDE run, add the flag to the run configuration's VM options.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.we360.ai/deployment-and-it-ops/deployment/infrastructure-management-on-premise/ldap-kerberos-login/ldap-kerberos-setup-runbook.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
