Skip to content

feat(ee): SAML 2.0 foundation - python3-saml deps, strict SP settings and SP metadata - #441

Open
grumpy-miner wants to merge 1 commit into
mainfrom
auto/saml-foundation
Open

grumpy-miner wants to merge 1 commit into
mainfrom
auto/saml-foundation

Conversation

@grumpy-miner

@grumpy-miner grumpy-miner commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Board card: SAML 2.0 sign-in (ee/) (PVTI_lADOElDmdM4Bn50jzhHLaw0)

Draft card, so no Closes. This is slice 1 of 5 from the card ("library + Docker deps"). It contains no assertion-validation code, so nothing here is the security-critical part the card asks the owner to review before merge.

What this PR does

  • ee/backend/pyproject.toml: adds python3-saml>=1.16,<2, xmlsec>=1.3.18,<2, lxml>=6.1,<7 to the ganek-ee package only. The core AGPL package and the public image get no SAML code or deps.
  • ee/backend/ganek_ee/saml.py (new):
    • check_native_bindings() refuses to run when lxml and xmlsec load different libxml2 builds.
    • IdpConfig, sp_entity_id(), acs_url() (/api/v1/auth/saml/metadata, /api/v1/auth/saml/acs, same prefix rule as oidc_redirect_uri).
    • build_settings() / build_settings_dict(): strict toolkit settings. wantAssertionsSigned, wantNameId, rejectDeprecatedAlgorithm (no SHA-1), rejectUnsolicitedResponsesWithInResponseTo (no IdP-initiated login), no repeated attribute names, RSA-SHA256/SHA-256, debug off. It refuses a missing IdP cert and plain-http URLs (except http://localhost).
    • sp_metadata(): SP metadata XML, validated by the toolkit.
  • backend/Dockerfile hosted stage: runs check_native_bindings() at build time, so a mismatched wheel pair fails the hosted image build and not the first sign-in. No apt packages are needed: the cp314 manylinux wheels for xmlsec and lxml bundle libxmlsec1/libxml2 (amd64 and aarch64 wheels exist).
  • ee/backend/tests/test_saml.py: 11 tests (native binding match + mismatch refusal, URL building, strict flags, toolkit accepts the settings, missing cert, http refusal, localhost allowed, metadata entityID / POST ACS / WantAssertionsSigned, no DOCTYPE).

Research summary

  • Library: python3-saml (SAML-Toolkits, ex OneLogin) over pysaml2. python3-saml is SP-focused, does in-process signature checks via the xmlsec binding, and has explicit strict flags for the attacks the card names. pysaml2 shells out to the xmlsec1 binary and needs an apt package plus a much larger config surface. Its CVE history (CVE-2021-21238/21239) is in exactly that signature path.
  • Top user pain with python3-saml: the "lxml & xmlsec libxml2 library version mismatch" crash and failing xmlsec source builds (python-xmlsec issues feat(interviews): cancel and reminder mails to both sides, invite.ics download #324/docs: refresh demo seed counts, frontend route map, question coverage #352, Indico forum threads). Fix: xmlsec 1.3.18 and lxml 6.1.3 both ship cp314 manylinux wheels built on libxml2 2.14.6 (checked here). The floors pin that, and the guard plus the build-time check catch any future drift.
  • Competitors: Greenhouse, Ashby and Workable put SAML SSO (and SCIM) on enterprise tiers. Buyers widely criticise this "SSO tax" (1Password/Kolide write-ups, SSOJet). Ganek already gives OIDC (Okta, Entra ID, Keycloak, Authentik) to everyone in core. SAML stays in ee/ per ARCHITECTURE, for IdPs that only offer SAML.

Remaining slices (not in this PR)

  1. ACS for existing accounts (signed assertion validation, replay guard on assertion id + NotOnOrAfter, account match on IdP entity id + NameID), plus the ee route mount gated by the licence check. Needs owner review before merge.
  2. Invite accept over SAML. 4. Admin IdP metadata config UI + "Continue with ". 5. E2E against a test IdP + docs page.

Owner decision needed: the card's AC says "Owner reviewed the validation code before merge", but auto/* PRs auto-merge on green without review (2026-11-07 decision). The agent therefore stops after this slice. Options: (a) review slice 2 as an agent PR that the agent opens as draft or on a non-auto/ branch, or (b) waive the review AC. Card set to In review until you decide.

Testing

  • Cloud container, Python 3.14.6: uv sync, uv pip install ../ee/backend, ruff check + ruff format --check on ee/backend clean, pytest ../ee/backend/tests: 74 passed (11 new). check_native_bindings() passes with the installed wheels (libxml2 2.14.6 on both sides).
  • Hosted Docker target not built locally (no docker daemon in the agent container). CI does not build the hosted target either, so the new RUN line is first exercised on the next hosted build.
  • Same deterministic edit script in both clones: script md5 8f3cdea9..., git diff --cached | md5sum e694e184... on both sides.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RPybyrwWRrPcN25xgn8RXW

Status (run 2026-11-09 ~17:55 UTC) - blocked, needs the owner

CI (all 7 jobs incl. the ee/ job), E2E and the auto-merge workflow are green and auto_merge is enabled. GitHub has not merged: the ruleset requires the cla-assistant check, and the cla workflow run 42666647964 ended in startup_failure with no jobs. This is the same blocker as #439 and #440: every cla run since 16:15 UTC has failed to start, and the last success was at 15:16 UTC on #438. The PR was not merged by hand and no workflow was edited. To unblock, open the cla run page for the startup error or comment recheck. The PR then merges itself. Separately, slice 2 needs your decision on the owner-review AC (see above). Card set to In review.

… and SP metadata

Slice 1 of the "SAML 2.0 sign-in (ee/)" card (board item
PVTI_lADOElDmdM4Bn50jzhHLaw0). Pins python3-saml on the xmlsec and lxml
wheels in the ee/ package, guards the lxml/xmlsec libxml2 mismatch (also
at hosted image build time), and builds the hardened SP settings and SP
metadata the ACS slices will use. No route is mounted and no assertion is
validated yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPybyrwWRrPcN25xgn8RXW
@github-actions
github-actions Bot enabled auto-merge (squash) November 9, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant