Skip to content

Commit 9a88a59

Browse files
committed
multi-ingress: Cross-IdP migration
Allow users to login with all team IdPs. This is required because there's usually one IdP per ingress in a team and users should be able to login on all ingresses.
1 parent d27ea44 commit 9a88a59

11 files changed

Lines changed: 864 additions & 108 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
When a team uses multiple SAML IdPs (one per ingress domain) in a multi-ingress
2+
setup, users can now authenticate via any of the team's IdPs even if their
3+
account was originally provisioned under a different one. Spar resolves the
4+
correct account by email-based NameID lookup across all team IdPs and migrates
5+
the user's SSO identity to the authenticating IdP transparently.
6+
7+
**Important:** Email addresses (`NameID`s) must be unique across configured
8+
IdPs! Otherwise, users may be logged in into wrong accounts!
9+
10+
Please refer to the documentation for further information.

docs/src/developer/reference/config-options.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,8 @@ Given an email address, the SSO code is looked up by these criteria:
13311331
- The mapping must be unambiguous (there must be exactly one matching IdP).
13321332
In multi-ingress mode, IdPs are always bound to one domain; the request domain
13331333
must match the IdP's configured domain.
1334-
- The user was created via SCIM
1334+
- The user is a SSO user. So it was created via SCIM with SSO enabled (any IdP
1335+
configured in SCIM token) OR created via SSO (no SCIM involved).
13351336

13361337
The last condition ensures that team admins cannot get into locked-out
13371338
situations due to misconfigured IdPs.
@@ -1614,6 +1615,60 @@ Putting it differently: We require an unambiguous mapping `(team, domain) -> IdP
16141615

16151616
For multi-ingress setups, the [`idpCertFingerprintAllowlist`](#idp-certificate-fingerprint-allowlist)
16161617
must be configured to restrict which X.509 certificates can be used in IdP metadata.
1618+
This restriction was introduced to mitigate risks of
1619+
[Multi-ingress cross-IdP SSO (fallback)](#multi-ingress-cross-idp-sso-fallback).
1620+
1621+
#### Multi-ingress cross-IdP SSO (fallback)
1622+
1623+
Terms used below:
1624+
1625+
- _Authenticating IdP_ — the external identity provider that issued the SAML
1626+
assertion, identified by the `Issuer` URI inside it.
1627+
- _IdP configuration_ — backend's IdP representation registered via
1628+
`/identity-providers`, storing the issuer URI, the associated multi-ingress
1629+
domain, and the team.
1630+
1631+
In the normal SSO flow spar looks up the authenticating user by their `(issuer,
1632+
NameID)` pair — matching the assertion's issuer against the IdP configuration
1633+
the user was provisioned under.
1634+
1635+
In a multi-ingress setup each domain has its own IdP configuration with its own
1636+
issuer URI. A user provisioned under domain _A_ has their SSO identity tied to
1637+
issuer _A_'s URI. When that user later authenticates via domain _B_, the IdP
1638+
authentication response's assertion carries issuer _B_'s URI, so the primary
1639+
`(issuer, NameID)` lookup finds nothing. Two IdPs can't have the same Issuer
1640+
ID because those must be globally unique, and each external identity provider
1641+
controls its own issuer URI (spar can't override it).
1642+
1643+
When this primary lookup finds no user, spar therefore attempts a cross-IdP
1644+
migration when multi-ingress is configured:
1645+
1646+
1. **NameID must be an email address.** Username-based `NameID`s are rejected
1647+
to avoid ambiguity across authenticating IdPs.
1648+
2. **The matching IdP configuration is resolved.** Spar looks for an IdP
1649+
configuration in the team whose issuer URI and configured domain both match
1650+
the assertion's issuer and the incoming `Z-Host` header (exact match).
1651+
If this condition is unmet, the login is rejected.
1652+
3. **Team-wide user search.** The authenticating IdP is now known (step 2), but
1653+
the user may still be registered under a *different* team IdP from an
1654+
earlier login. Spar therefore searches every IdP configuration in the team,
1655+
pairing each one's issuer with the assertion's email NameID, and tries the
1656+
primary `(issuer, NameID)` lookup for each pairing until one matches.
1657+
4. **Migrate or provision:**
1658+
- _Exactly one match found:_ The user's SSO identity is updated to point to
1659+
the IdP configuration for the authenticating IdP's issuer, so subsequent
1660+
logins hit the primary lookup directly. This saves the complexity of the IdP
1661+
configuration lookup and keeps the backend's representations of the user's
1662+
SSO data sound.
1663+
- _No match found:_ A new user account is auto-provisioned under the
1664+
authenticating IdP's configuration.
1665+
- _No matching IdP configuration can be resolved:_ Login is rejected.
1666+
1667+
##### Security considerations
1668+
1669+
It must be ensured that email `NameID`s are unique across IdPs by IdP
1670+
administrators. Otherwise, users are falsely logged in into other user's
1671+
accounts!
16171672

16181673
### Webapp
16191674

integration/integration.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ library
211211
Test.Spar
212212
Test.Spar.CertFingerprintAllowlist
213213
Test.Spar.GetByEmail
214+
Test.Spar.MultiIngressCrossIdpSso
214215
Test.Spar.MultiIngressIdp
215216
Test.Spar.MultiIngressSSO
216217
Test.Spar.STM

0 commit comments

Comments
 (0)