Skip to content

multi-ingress: cross-IdP SSO#5212

Open
supersven wants to merge 1 commit into
developfrom
sventennie/multi-ingress-sso_cross_idp
Open

multi-ingress: cross-IdP SSO#5212
supersven wants to merge 1 commit into
developfrom
sventennie/multi-ingress-sso_cross_idp

Conversation

@supersven

@supersven supersven commented May 1, 2026

Copy link
Copy Markdown
Contributor

I've covered this usecase in the documentation. To avoid duplication, please look at the docs in this PR.

Ticket: https://wearezeta.atlassian.net/browse/WPB-25161

Security notes:

  • This requires a final sign off by @wireapp/security
  • The algorithm for multi-ingress SSO cross-IdP logins has been discussed with @emil-wire
  • This PR also lowers the condition to be found by /sso/get-by-email from SCIM SSO to SSO user

Further reading:

Checklist

  • Add a new entry in an appropriate subdirectory of changelog.d
  • Read and follow the PR guidelines

@zebot zebot added the ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist label May 1, 2026
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch 3 times, most recently from 70c63ab to 63c5b7d Compare May 5, 2026 17:18
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch 3 times, most recently from c70220c to e7f3bf2 Compare May 12, 2026 08:33
@supersven supersven changed the title POC: multi ingress sso cross idp multi-ingress: cross-IdP SSO May 12, 2026
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from e7f3bf2 to d27326c Compare May 12, 2026 09:37
@supersven
supersven requested review from Copilot and sanojwr May 12, 2026 10:06
@supersven
supersven marked this pull request as ready for review May 12, 2026 10:07
@supersven
supersven requested review from a team as code owners May 12, 2026 10:07
@supersven
supersven requested a review from emil-wire May 12, 2026 10:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for multi-ingress “cross-IdP” SSO logins in Spar by falling back to a team-wide lookup (by email NameID) when the primary (issuer, NameID) lookup fails, and updates /sso/get-by-email discovery to work for any SSO user (not only SCIM-managed).

Changes:

  • Extend Spar SAML finalize-login handling to support cross-IdP migration in multi-ingress mode (and add a dedicated config error for non-email NameIDs).
  • Relax /sso/get-by-email lookup condition from “SCIM+SSO” to “SSO user”.
  • Add integration and unit tests plus documentation/changelog updates covering the new behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
services/spar/test-integration/Test/Spar/AppSpec.hs Updates integration test helper to call updated verdictHandler signature with SAML config.
services/spar/src/Spar/Error.hs Adds and renders a new error for invalid multi-ingress cross-IdP configuration (non-email NameID).
services/spar/src/Spar/App.hs Implements multi-ingress cross-IdP fallback flow + structured logging changes.
services/spar/src/Spar/API.hs Plumbs SAML config/host info into finalize-login handling and updates logger constraints.
libs/wire-subsystems/test/unit/Wire/IdPSubsystem/InterpreterSpec.hs Adjusts unit test expectation to “non SSO user” behavior for get-by-email.
libs/wire-subsystems/src/Wire/IdPSubsystem/Interpreter.hs Changes /sso/get-by-email eligibility check from SCIM+SSO to SSO.
integration/test/Test/Spar/MultiIngressCrossIdpSso.hs Adds new integration test suite for cross-IdP migration and related error cases.
integration/integration.cabal Registers new integration test module.
docs/src/developer/reference/config-options.md Documents cross-IdP fallback algorithm and updates get-by-email criteria.
changelog.d/2-features/multi-ingress-cross-IdP-SSO Adds changelog entry describing cross-IdP SSO behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/spar/src/Spar/App.hs
Comment thread docs/src/developer/reference/config-options.md Outdated
Comment thread docs/src/developer/reference/config-options.md Outdated
Comment thread changelog.d/2-features/multi-ingress-cross-IdP-SSO Outdated
Comment on lines +124 to +128
-- This used to check if the user is SCIM AND SSO! The RFC ("2025-05-12
-- RFC: Default SSO flow for team by host domain") is not really
-- unambiguous about this. The customer currently provisions non-SCIM.
isSsoUser :: User -> Bool
isSsoUser = isJust . userSSOId

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be subject to change in case the customer decides to go for SCIM. Then, we could make this condition stricter (requiring SSO AND SCIM).

@fisx fisx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the docs and have some questions, more feedback coming up!

Comment thread docs/src/developer/reference/config-options.md Outdated
Comment on lines +1529 to +1530
1. **NameID must be an email address.** Username-based `NameID`s are rejected
to avoid ambiguity across authenticating IdPs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] admins need to manually guarantee that email addresses are unique, so why can't they do the same thing here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emil-wire and I were uncertain about this as well. I made the hand-wavy decision, because in my experience with other web services, my email address is much less likely to conflict than my desired username when I register somewhere.

However, the customer is not much into emails as fetching those may reveal relationships as well AND @julialongtin promised that all data will be located in one database (used by all IdPs)

So, this could actually be subject to change. 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best approach is to allow nicks / usernames, but put big red warnings stickers in all the right places and emphasize that emails are best here, and nicks with mandatory, instance-specific pre- or suffixes are second-best.

If you decide against my intuition that's also fine, if anything supporting only emails would make it safer.

Comment thread docs/src/developer/reference/config-options.md Outdated
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch 2 times, most recently from a149340 to 4cce453 Compare June 9, 2026 06:17
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch 2 times, most recently from 82b25a8 to f9fea47 Compare July 6, 2026 16:15
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from f9fea47 to c134d5d Compare July 9, 2026 09:55
Comment thread services/spar/src/Spar/App.hs
Comment thread services/spar/src/Spar/App.hs
Comment thread services/spar/src/Spar/App.hs
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from 4113c24 to 9a88a59 Compare July 9, 2026 15:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment thread services/spar/src/Spar/App.hs
Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs
Comment thread changelog.d/2-features/multi-ingress-cross-IdP-SSO Outdated
Comment thread docs/src/developer/reference/config-options.md Outdated
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from 9a88a59 to cc9b20e Compare July 9, 2026 16:10
@supersven
supersven requested a review from Copilot July 9, 2026 16:16
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from e54062a to 9e877f6 Compare July 9, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread services/spar/src/Spar/App.hs Outdated
Comment thread services/spar/src/Spar/App.hs
@supersven
supersven requested a review from fisx July 10, 2026 05:49

@fisx fisx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I skimmed through the integration tests and thoroughly reviewed the rest, looks intact! I've hidden a few possibly valuable change requests in a large number of nit-picks to make processing the review more fun. :-)

I'm pre-approving because none of the change requests affect correctness and security IMO.

Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs Outdated
Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs Outdated
Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs
Comment on lines +1529 to +1530
1. **NameID must be an email address.** Username-based `NameID`s are rejected
to avoid ambiguity across authenticating IdPs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best approach is to allow nicks / usernames, but put big red warnings stickers in all the right places and emphasize that emails are best here, and nicks with mandatory, instance-specific pre- or suffixes are second-best.

If you decide against my intuition that's also fine, if anything supporting only emails would make it safer.

Comment on lines +1669 to +1670
It must be ensured that email `NameID`s are unique across IdPs by IdP
administrators. Otherwise, users may be logged into other users' accounts!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably worth adding this warning to the UI in team management, at the point where multi-ingress is enabled. Has this happened? Is there a ticket for this?

Comment thread services/spar/src/Spar/App.hs Outdated
Comment thread libs/wire-subsystems/src/Wire/IdPSubsystem/Interpreter.hs Outdated
Comment thread libs/wire-subsystems/test/unit/Wire/IdPSubsystem/InterpreterSpec.hs Outdated
Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs Outdated
Comment thread integration/test/Test/Spar/MultiIngressCrossIdpSso.hs Outdated
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from 728c3e7 to 7cb0def Compare July 14, 2026 14:02
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.
@supersven
supersven force-pushed the sventennie/multi-ingress-sso_cross_idp branch from 700aa26 to 028e41e Compare July 15, 2026 06:38
pairing each one's issuer with the assertion's email NameID, and tries the
primary `(issuer, NameID)` lookup for each pairing until one matches.
4. **Migrate or provision:**
- _Exactly one match found:_ The user's SSO identity is updated to point to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, please correct me if I'm wrong (my Haskell isn't great) but doesn't https://github.com/wireapp/wire-server/blob/sventennie/multi-ingress-sso_cross_idp/services/spar/src/Spar/App.hs#L528
asum $ map tryIdP idps return the first matching (issuer, email NameID) across team IdPs? Meaning if there are more than one match, the "Exactly one" condition isn't satisfied?

Failure scenario:

  • Team has IdP A and IdP B.
  • Victim exists under IdP A with email x@example.
  • A different person or malicious IdP-side actor authenticates via IdP B with NameID = x@example.
  • If no primary (B, x@example) user exists, fallback searches all team IdPs and can select the victim’s IdP A mapping.
  • Outcome: attacker logs into victim’s Wire account and the SSO identity is migrated to IdP B.

This is exacerbated by the fact that we assume (but never check in code): "Different SAML IdPs must have a same user set” as a prerequisite.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asum $ map tryIdP idps return the first matching (issuer, email NameID) across team IdPs? Meaning if there are more than one match, the "Exactly one" condition isn't satisfied?

Yes, if you take this precisely for this expression: Yes, there could be e.g. 2 matching IdPs and the last one found would be ignored. Probably, this item is a bit misleading: SSO users are always bound to one IdP. The systems doesn't allow for more than one, though.
I will update the documentation accordingly.

Failure scenario:

Team has IdP A and IdP B.
Victim exists under IdP A with email x@example.
A different person or malicious IdP-side actor authenticates via IdP B with NameID = x@example.
If no primary (B, x@example) user exists, fallback searches all team IdPs and can select the victim’s IdP A mapping.
Outcome: attacker logs into victim’s Wire account and the SSO identity is migrated to IdP B.

This is exacerbated by the fact that we assume (but never check in code): "Different SAML IdPs must have a same user set” as a prerequisite.

Yes, that's exactly the scenario we're dealing with here (if you change the wording a bit, that's exactly the usecase - just s/(attacker|victim)/user/g 😉 ).

Unfortunately, we cannot ensure on our end that two IdPs contain the same user data.
Why do we need multiple IdP configurations (the representation of an IdP at Wire backend) then? A common IdP endpoint would leak information about the relationship of ingress-domains.

The countermeasures for this are:

@emil-wire

Copy link
Copy Markdown
Contributor

Not something this PR introduced, but its close enough to it that I'd rather raise it here than lose track of it and forget about it.

I don't think the rate limit on /sso/get-by-email is actually in force. The generic - path: /sso entry sits above the - path: /sso/get-by-email$ one, and both get rendered as regex locations by this template. nginx picks the first regex location that matches rather than the most specific one, so /v15/sso/get-by-email gets served by the /sso block and never reaches the block with the tighter regex.

I rendered the chart to check (helm template charts/nginz), both blocks land in the same server block in this order:

location ~* ^(/v[0-9]+)?/sso {                  ->  limit_req zone=reqs_per_addr_sso burst=10 nodelay;
location ~* ^(/v[0-9]+)?/sso/get-by-email$ {    ->  limit_req zone=reqs_per_addr_sso_get_by_email burst=15 nodelay;

So the endpoint ends up on 50r/s instead of the intended 5r/m. The $ doesn't do anything because the regex above already matched.

initiate-login and finalize-login are already above /sso, which suggests this was understood at the time and get-by-email$ just got appended at the end when it came in with #5024. Moving it above the /sso entry should be all it needs.

raisin it here because the two things interact: this PR widens which users the endpoint answers for (SCIM+SSO -> any SSO user), and config sets enableIdPByEmailDiscovery: true for every multi-ingress deployment, so more accounts become reachable through an endpoint whose brute force protection isn't running. Fixing the ordering doesn't close the endpoint as an oracle for a known address, but it does make enumeration slower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants