diff --git a/docs/concepts/placement.md b/docs/concepts/placement.md index 9fb1881..20b7d79 100644 --- a/docs/concepts/placement.md +++ b/docs/concepts/placement.md @@ -39,7 +39,7 @@ distinct values can never silently merge: | Token | Source | |---|---| -| `{user}` | IdP username (trusted identity) | +| `{user}` | username — the account's label, not its internal id | | `{workspace}` | workspace displayName | | `{templateName}` | template `metadata.name` | | `{os}` | template `spec.os` (`linux`/`windows`) | @@ -76,6 +76,121 @@ Keeping the shared pattern, as above, avoids the question entirely. The portal shows the resolved namespace at creation time, and the template editor lists the valid placeholders. +## How names are built — and what it constrains {#name-rules} + +A Kubernetes namespace name is a **DNS-1123 label**: lowercase letters, +digits and dashes, 63 characters maximum. Nothing else. Every value that +enters a name is therefore normalized first (accents folded, uppercase +lowered, anything else collapsed into `-`), and that normalization is +**lossy**. Three consequences are worth knowing before you pick a +pattern or hand out usernames. + +### 1. Two usernames must not normalize to the same thing + +`alice.smith`, `alice_smith`, `alice-smith` and `Alice Smith` all become +`alice-smith`. Your directory treats them as four distinct people; the +namespace cannot. WaaS refuses the second account rather than renaming +it behind your back: + +- **creating a local account** returns `409` and names both sides: + + ``` + username "alice_smith" collides with the existing account "alice.smith": + both resolve to the personal namespace "waas-alice-smith" — pick a username + that differs by more than case, accents or separators + ``` + +- **first SSO login** fails with the generic + *"SSO login failed for this account — contact an administrator"*. The + detail never reaches the login page (the caller is not authenticated + yet and it would disclose another account's username) — it is in the + audit trail, action `user.sso_placement_conflict`: + + ``` + username "alice.smith" resolves to the personal namespace "waas-alice-smith", + already used by account "alice-smith" + ``` + +**Recommendation.** Treat it as directory hygiene, not as a WaaS +setting. Directories already disambiguate homonyms (`jdoe`, `jdoe2`) — +apply the same convention here, and make usernames differ by more than +case, accents or separators. The most common way to hit this is a +**local account created before SSO was wired**, then re-created by the +IdP under a different separator: delete the stale local account rather +than renaming the person in the directory. + +:::note This rule holds whatever your pattern is + +The check does not ask whether the resolved namespace is per-user. Two +accounts indistinguishable in every DNS-derived name are a defect on +their own, and a template added later can put `{user}` back into a +pattern at any time. + +::: + +### 2. Usernames in a non-Latin script are handled, not refused + +`иван`, `王五`, `Ωμέγα` and `علي` leave **no** usable character at all — +Kubernetes accepts none of them in a namespace name, nor in a label +value (only annotation values are free-form UTF-8, which is where WaaS +keeps the username itself). Rather than collapsing every such account +into one namespace, `{user}` resolves through the account id — its first +and last groups: + +``` +account a1b2c3d4-5e6f-7890-abcd-ef1234567890 → waas-a1b2c3d4-ef1234567890 +account f0e9d8c7-6b5a-4321-9876-543210fedcba → waas-f0e9d8c7-543210fedcba +``` + +Predictable rather than hashed, on purpose: you can go from a namespace +back to its owner with a prefix query, and the name derives from the +same key as the namespace's `waas.xorhub.io/owner` label. Nothing to +configure, no account is ever refused for this reason. + +### 3. Long values get truncated — and each token's share is fixed + +The 63 characters are split as `(63 − literals) ÷ number of tokens`, +computed from the **pattern alone**. A value that fits is kept whole; a +value that overflows its share is cut and given a short hash so two long +values can never silently merge: + +| Pattern | Tokens | Budget each | A value is hashed above | +|---|---|---|---| +| `waas-{user}` (built-in) | 1 | 58 | 58 characters — unreachable in practice | +| `waas-{user}-{workspace}` | 2 | 28 | 28 characters | +| `waas-{os}-{templateName}` | 2 | 28 | 28 characters | +| `waas-{user}-{templateName}-{workspace}` | 3 | 18 | 18 characters | + +The share is **fixed**: a short value does not lend its unused +characters to a long one. + +``` +pattern waas-{user}-{workspace} user "alice", 28 characters each + +"Poste de travail graphique" → waas-alice-poste-de-travail-graphique +"Poste de travail graphique Ubuntu" → waas-alice-poste-de-travail-graph-8b0d3 + hashed, although the full name would + have fit in 44 of the 63 available +``` + +This is deliberate: making the split adaptive would move the **future** +workspaces of users whose names are truncated today, for a cosmetic +gain. + +**Recommendations.** + +- **Prefer few tokens.** Each one you add roughly halves, then thirds, + the room. The built-in `waas-{user}` is the only shape where + truncation is effectively out of reach. +- **Think twice before putting `{workspace}` in a pattern.** Display + names are typed by users and routinely exceed 28 characters, so hashed + namespaces become the norm rather than the exception. `{templateName}` + and `{os}` are admin-controlled and short — much safer companions. +- **Check the preview.** The creation dialog shows the resolved + namespace before you commit, and the template editor shows it for the + pattern you are writing. A hash in the preview is your signal that the + pattern is too tight for the values it will meet. + ## What the operator bootstraps in a new namespace Created on first workload if missing — never modified afterward (your diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index fd11c64..0594189 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -36,6 +36,28 @@ A user whose group mirror is empty matches only subjects-less policies — that's the "everyone gets the default policy" symptom, not a priority bug: groups sync from the IdP at every SSO login (or via admin edit). +## A user cannot log in via SSO + +The login page always shows the same generic message — *"SSO login +failed for this account — contact an administrator"* — because the +caller is not authenticated yet and a precise message would disclose +another account. **The reason is in the audit trail**, and there are two +of them: + +| Audit action | Meaning | Fix | +|---|---|---| +| `user.sso_link_conflict` | the IdP's username claim matches an **existing** account bound to a different subject (or a local one). Treated as an attempted takeover — many IdPs let users pick their own username claim | rename or delete the conflicting account; never repoint the IdP's `sub` | +| `user.sso_placement_conflict` | the username is distinct, but it **normalizes** onto an existing account's namespace (`alice.smith` vs `alice_smith`) | see [how names are built](concepts/placement#name-rules) — usually a stale local account created before SSO was wired; delete it, or rename one side in the directory | + +```sh +# both carry the two usernames and the namespace in their detail +curl -s -H "Authorization: Bearer $TOKEN" \ + 'https://waas.example.com/api/v1/admin/audit-logs?action=user.sso_placement_conflict' | jq +``` + +A user in a non-Latin script (`иван`, `王五`) is **never** refused for +this reason — those resolve through the account id instead. + ## Workspace Running but not connectable - `Ready=True` but `ConnectionReady=False`: the pod runs but the