From 11c5d88e7e40a367ceea286ac8fcc3486b6dc01d Mon Sep 17 00:00:00 2001 From: Ricky Rodriguez Alvarez Date: Tue, 2 Jun 2026 13:22:29 +0200 Subject: [PATCH] feat: add ldap configuration --- docs.json | 3 +- en/operator-manual/authentication/ldap.mdx | 195 +++++++++++++++++++++ en/operator-manual/configuration/index.mdx | 3 + 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 en/operator-manual/authentication/ldap.mdx diff --git a/docs.json b/docs.json index a755ac0..e43a870 100644 --- a/docs.json +++ b/docs.json @@ -337,7 +337,8 @@ "group": "Authentication", "pages": [ "en/operator-manual/openid/google", - "en/operator-manual/openid/microsoft" + "en/operator-manual/openid/microsoft", + "en/operator-manual/authentication/ldap" ] }, { diff --git a/en/operator-manual/authentication/ldap.mdx b/en/operator-manual/authentication/ldap.mdx new file mode 100644 index 0000000..840be3d --- /dev/null +++ b/en/operator-manual/authentication/ldap.mdx @@ -0,0 +1,195 @@ +--- +title: LDAP Directory Authentication +description: Configure Zylon to authenticate users against an LDAP directory or Active Directory, enabling centralized login without an external identity provider. +--- + +LDAP authentication lets users log in to Zylon with their existing directory credentials, whether on a standard LDAP server or Microsoft Active Directory. Because LDAP requires no connection to external identity providers like Google or Microsoft, it is the recommended authentication method for air-gapped and strictly on-prem deployments. + + + This page covers operator configuration only. No UI-based configuration is available; all settings are applied through the configuration file located at `/etc/zylon/zylon-conf.yaml`. + + +--- + +## Prerequisites + +Before enabling LDAP, confirm the following: + +- The LDAP or Active Directory host is reachable from the Zylon cluster on the port your server is configured to listen on. +- A **read-only service account** exists in the directory. Zylon uses this account (the bind account) to search for users and resolve group membership. It does not need write permissions. +- You know the **Base DN** under which all users and groups reside (e.g., `dc=company,dc=local`). +- You have the **Distinguished Names (DNs)** of any directory groups you want to map to Zylon roles. + +--- + +## How sign-in works + +When LDAP is enabled, users continue to use the standard Zylon login form. There is no separate LDAP login page. + +1. The user enters their directory username in the email field and their directory password. +2. Zylon normalizes the username: a `DOMAIN\user` prefix is stripped to `user`; an email address (`user@company.com`) and a plain username (`user`) are used as-is. +3. Zylon searches the directory for the user, then performs a bind to verify the password. +4. On success, Zylon resolves the user's directory groups and maps them to Zylon roles using `roleMapping` configuration. +5. If the account does not exist in Zylon yet, it is **created automatically** with the roles derived from group membership. + + + Roles are assigned at account creation time. If a user's directory group membership changes after their first login, Zylon roles are not updated automatically. To change a user's roles after creation, update them through the Backoffice. See [Roles and Permissions](/en/operator-manual/backoffice/roles-and-permissions) for details. + + + + If LDAP is enabled and the directory is temporarily unreachable or returns an error, Zylon falls back to local credential authentication. + + +--- + +## Configuration + + +If LDAP is enabled and any required field is missing or invalid, the application will **refuse to start** and log a descriptive error. This prevents misconfigured deployments from silently falling. + +### `/etc/zylon/zylon-conf.yaml` + +Add the `auth.ldap` block to the configuration file: + + + The config file is a `.yaml` file, so indentation, spaces, and quotes are important. Review carefully before saving. + + +```yaml +auth: + ldap: + enabled: true + host: "ldap.company.local" + port: "389" + useTls: false + bindDn: "cn=LDAP Bind,ou=service,dc=company,dc=local" + bindPassword: "CHANGE_ME" + baseDn: "dc=company,dc=local" + userFilter: "(|(sAMAccountName={username})(userPrincipalName={username})(mail={username}))" + groupFilter: "(member={userDn})" + attributes: + username: "sAMAccountName" + email: "mail" + group: "memberOf" + roleMapping: + defaultRole: "Workspace" + mappings: '[{"match":"CN=admins,OU=groups,DC=company,DC=local","role":"Operator"},{"match":"CN=developers,OU=groups,DC=company,DC=local","role":"Developer"},{"match":"CN=workspace-users,OU=groups,DC=company,DC=local","role":"Workspace"}]' +``` + +Apply the changes: + +```bash +sudo zylon-cli sync +``` + +--- + +## Configuration reference + +The table below lists every LDAP setting, its chart default, and whether it is required when `enabled` is `true`. + +| Key | Default | Required | Description | +|---|---|---|---| +| `enabled` | `false` | - | Set to `true` to activate LDAP authentication. The application will not start if enabled with an invalid configuration. | +| `host` | `""` | Yes | Hostname or IP address of the LDAP or Active Directory server. | +| `port` | `"389"` | Yes | Port as a string. Must match the port your LDAP server is listening on. Common values are `389` for plain LDAP and `636` for LDAPS, but your server may be configured differently. | +| `useTls` | `false` | No | Set to `true` to open an LDAPS connection (TLS from the first byte). | +| `bindDn` | `""` | Yes | Full Distinguished Name of the service account used to search the directory. Example: `cn=LDAP Bind,ou=service,dc=company,dc=local`. | +| `bindPassword` | `""` | Yes | Password for the bind account. | +| `baseDn` | `""` | Yes | Root of the directory subtree where user and group searches are performed. Example: `dc=company,dc=local`. | +| `userFilter` | `(\|(sAMAccountName={username})(userPrincipalName={username})(mail={username}))` | No | LDAP filter used to locate the user entry. Must contain `{username}`, which is replaced with the normalized login name at runtime. The default targets Active Directory and supports `sAMAccountName`, UPN, and mail formats. | +| `groupFilter` | `(member={userDn})` | No | LDAP filter used to find groups when `memberOf` is not populated on the user entry. Must contain `{userDn}`. | +| `attributes.username` | `sAMAccountName` | No | Directory attribute to use as the Zylon display name. | +| `attributes.email` | `mail` | No | Directory attribute to use as the Zylon account email. This attribute must be present on every user entry; if it is missing, login is rejected. | +| `attributes.group` | `memberOf` | No | Directory attribute on the user entry that lists group membership. If this attribute is absent or empty, Zylon falls back to a separate group search using `groupFilter`. | +| `roleMapping.defaultRole` | `Workspace` | No | Role assigned to a user when no group mapping matches. Valid values: `Workspace`, `Developer`, `Operator`. | +| `roleMapping.mappings` | `""` | No | Stringified JSON array of group-to-role mappings. See [Role mapping](#role-mapping) below. When empty, all users receive `defaultRole`. | + + +## Role mapping + +Group-to-role mapping is defined as a **stringified JSON array** in the `roleMapping.mappings` field. The value must always be a JSON-encoded string, both in `values.yaml` and in `zylon-conf.yaml`. Zylon evaluates the mapping at login time and assigns roles based on the directory groups the user belongs to. + +```yaml +roleMapping: + mappings: '[{"match":"CN=admins,OU=groups,DC=company,DC=local","role":"Operator"},{"match":"CN=developers,OU=groups,DC=company,DC=local","role":"Developer"},{"match":"CN=workspace-users,OU=groups,DC=company,DC=local","role":"Workspace"}]' +``` + +**Matching behaviour:** + +- Each `match` value is compared against every group DN the user belongs to. The comparison is case-insensitive. +- If a user belongs to multiple mapped groups, they receive all matching roles. +- If no mapping matches, the user receives `defaultRole`. +- Multiple groups can map to the same role; duplicates are collapsed. + + +For a complete description of what each role can do, see [Roles and Permissions](/en/operator-manual/backoffice/roles-and-permissions). + + + Roles are computed once at account creation (first login). Subsequent changes to directory group membership are not reflected in Zylon automatically. Update roles manually through the Backoffice if needed. + + +--- + +## Active Directory notes + +The default configuration targets Active Directory out of the box: + +- `userFilter` accepts logins in three formats: plain username (`jdoe`), UPN (`jdoe@company.com`), and Windows domain format (`DOMAIN\jdoe`, normalized to `jdoe` before the search). +- `attributes.username` defaults to `sAMAccountName` and `attributes.email` to `mail`, matching standard AD schemas. +- Group membership is read from the `memberOf` attribute on the user entry. + +For standard LDAP servers (OpenLDAP, 389 Directory Server, etc.) you will typically need to adjust `userFilter`, `groupFilter`, and `attributes.*` to match your schema. A common starting point: + +```yaml +userFilter: "(uid={username})" +groupFilter: "(member={userDn})" +attributes: + username: "uid" + email: "mail" + group: "memberOf" +``` + +--- + +## Verify the configuration + +After applying the configuration, follow these steps to confirm LDAP is working correctly: + + + + Check the backend pod is running. If a required field is missing, the service will not start and will log the specific field that failed validation. + + + Log in to the Zylon workspace with a directory user account. Use the username or email address format your directory supports. + + + Open the Backoffice and locate the newly created account. Confirm it has the expected roles based on the user's group membership and the `roleMapping` configuration. + + + Repeat the test for at least one user per mapped group (Operator, Developer, Workspace) to confirm the role mapping is working correctly across all groups. + + + +--- + +## Troubleshooting + +| Symptom | Likely cause | What to check | +|---------|-------------|---------------| +| Application does not start | Required field missing or `port` is not a valid integer | Check logs for the specific validation error; confirm `host`, `baseDn`, `bindDn`, `bindPassword`, and `port` are all set | +| Login fails for a valid directory user | Unreachable server, wrong `bindPassword`, or `userFilter` does not match the user entry | Verify network connectivity on the configured port; test the bind account credentials with an LDAP client; review `userFilter` | +| "Ambiguous LDAP user" error on login | `userFilter` returns more than one directory entry for the username | Tighten the filter to target a unique attribute, or remove overlapping login format matchers | +| Login succeeds but user has the wrong role | Group DN in `roleMapping.mappings` does not match the actual group DN (check for typos, OU differences, or casing) | Compare the `match` values against the exact group DNs in your directory; matching is case-insensitive but the DN structure must be correct | +| Login succeeds but user has `defaultRole` only | No group mapping matched, or `mappings` is empty | Check that `memberOf` is populated on the user entry; verify the JSON syntax of `mappings` | +| LDAP user cannot log in - missing email | The `mail` (or configured `attributes.email`) attribute is absent on the directory entry | Populate the email attribute for the user in the directory, or update `attributes.email` to point to a different attribute that is present | +| Users appear to log in with local credentials despite LDAP being enabled | LDAP returned an error (server unreachable, bad bind password), triggering a fallback | Check server connectivity and bind account credentials; review backend logs for the LDAP error that caused the fallback | + +--- + +## Security considerations + +- Use a **read-only, least-privilege** service account for the bind credentials. The account needs search permission on users and groups under `baseDn`, nothing more. +- Directory group membership changes are not reflected in Zylon after account creation. Operators must update roles manually in the Backoffice if group membership changes. + +For air-gapped deployments, LDAP requires no outbound internet access. See [Airgap Your Server](/en/operator-manual/instance-hardening/airgap-your-server) for firewall configuration guidance. diff --git a/en/operator-manual/configuration/index.mdx b/en/operator-manual/configuration/index.mdx index eb723df..ab0a90d 100644 --- a/en/operator-manual/configuration/index.mdx +++ b/en/operator-manual/configuration/index.mdx @@ -70,6 +70,9 @@ Configure your Zylon instance to meet your organization's specific requirements. Configure Microsoft Entra (Azure AD) Single Sign-On + + Authenticate users against an LDAP directory or Active Directory for air-gapped and on-prem deployments + ---