feat(proxy): add saml_init for proactive SAML SSO login#33
Conversation
Update golang.org/x packages to fix HTTP/2 vulnerability (GO-2026-4918). - golang.org/x/crypto: v0.48.0 -> v0.50.0 - golang.org/x/net: v0.51.0 -> v0.53.0 (fixes infinite loop in HTTP/2) - golang.org/x/sync: v0.19.0 -> v0.20.0 - golang.org/x/sys: v0.41.0 -> v0.43.0 - golang.org/x/term: v0.40.0 -> v0.42.0 - golang.org/x/text: v0.34.0 -> v0.36.0 Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
Enable Kerberos/SPNEGO authentication for plugins with automatic SAML SSO redirect handling. Supports dynamic SPN derivation from request hostnames and enhanced variable resolution for flexible configuration. - Add KerberosProvider with dynamic SPN support (derives from hostname) - Implement SAML POST binding detection and auto-replay for SSO flows - Add |hostname modifier and nested defaults to config var resolver - Enable per-request domain allowlisting for SSO redirects - Initialize specialized Kerberos HTTP client in plugin manager The SAML handler automatically follows meta-refresh and form-based redirects, extracts hidden fields, and POSTs back to originating server to complete SSO authentication flows. Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
… validation Fixed critical vulnerability in proxy per-request domain registration that allowed credential theft to attacker-controlled servers. Implemented defense- in-depth approach with multiple security layers. Security fixes: - CRITICAL: Enforce subdomain validation - per-request domains must be subdomains of base domains declared in plugin.yaml or init_ok - HIGH: Add cumulative domain cap (50 total per plugin) to prevent allowlist bloat and DoS via domain accumulation across multiple requests - LOW: Normalize domains (trailing dots, case, Unicode/IDNA) to prevent bypass via homoglyphs, punycode, or formatting variations Implementation: - Created internal/domaincheck package with shared validation logic for consistent security checks across init-time and per-request paths - Added BaseDomains field to PluginAuth to track trusted anchor domains - Modified Execute() to validate domains against base domains before adding - Updated AddAllowedDomains() to enforce cumulative domain cap - Deduplicated validation code from proxy and plugin packages Attack scenarios prevented: - Direct exfiltration: jenkins_url=https://attacker.com/ → rejected - Subdomain spoofing: eviljenkins.com → rejected (not subdomain) - Allowlist flooding: 100 requests × 10 domains → capped at 50 - Unicode bypass: attacker․com (Unicode period) → normalized, validated - Trailing dot duplicates: evil.com and evil.com. → deduplicated Test coverage: - 19 subdomain validation test cases - Cumulative cap enforcement tests - Unicode/IDNA normalization tests - All existing proxy and plugin tests passing Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
… startup Adds a `saml_init` field to the auth config that tells the core to proactively authenticate via SAML during plugin initialization. This supports sites that silently degrade to anonymous sessions instead of returning 401/403 challenges. The InitSAMLSession function handles two SAML patterns: - Form-first: GET init URL returns a SAMLRequest form, POST to IdP, POST SAMLResponse back to origin (3-step) - Redirect-first: delegates to the existing handleSAMLSSO for sites that use meta-refresh redirects to the IdP SAML init failure is fatal — the plugin will not load if auth fails, preventing silent degradation to empty/anonymous results. Co-Authored-By: André "decko" de Brito <decko@redhat.com> Co-Authored-By: Sergio Correia <scorreia@redhat.com> Co-Authored-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
9c1d256 to
aebb1b6
Compare
|
@decko reviewed with some agents, and this is what they find and I agree we should at least discuss: BLOCKING Issues - Must Fix Before Merge
File: internal/proxy/proxy.go, lines 694-696 Issue: isDomainAllowed uses strings.EqualFold while isDomainAllowedForSSO and all other Impact: Domains with trailing dots, Unicode characters, or punycode could bypass validation Fix: Replace the strings.EqualFold loop with domaincheck.Contains:
File: internal/proxy/proxy.go, lines 175-193 Issue: AddBaseDomains doesn't enforce maxTotalDomains cap, while AddAllowedDomains does. Fix: Add the same cap check that exists in AddAllowedDomains: NON-BLOCKING Issues - Acceptable to Defer
Consensus: Safe due to domain validation, but may fail against some real IdPs
Recommendation: Track as high-priority follow-up to replace with golang.org/x/net/html for Minor issues
|
F1 (CRITICAL): Prevent SPNEGO token leakage on cross-domain redirects
by disabling automatic redirect following during SAML flows. All SAML
HTTP calls now use a no-redirect client copy.
F2 (HIGH): Fix hiddenInputRe requiring fixed attribute ordering. Parse
<input> tags by extracting all attributes into a map, then check for
type=hidden. Works with any attribute order.
F3 (HIGH): Use domaincheck.Contains for normalized domain comparison
in isDomainAllowedForSSO, preventing bypasses via trailing dots, IDNA,
or case variations.
F4 (HIGH): Gate request replay in trySAMLSSO on idempotency. SSO flow
still runs (to establish cookies), but non-idempotent requests are not
replayed after authentication.
F5 (MEDIUM): Check :- before :+ in resolveVarExpression to prevent
misparsing when :+ appears literally in a :- default value.
F6 (MEDIUM): Only count ${ (not bare {) as depth increment in
resolveVarsFunc, matching findTopLevelPattern behavior.
F7 (MEDIUM): Thread context through all SAML HTTP requests via
NewRequestWithContext for proper cancellation on shutdown.
F8 (MEDIUM): Resolve relative action URLs in parseSAMLForm by
prepending baseURL, preventing isDomainAllowedForSSO from rejecting
scheme-less URLs.
F9 (MEDIUM): Return false for non-SAML 200 responses in handleSAMLSSO
instead of true, preventing unnecessary request retries.
Co-Authored-By: André "decko" de Brito <decko@redhat.com>
Co-Authored-By: Sergio Correia <scorreia@redhat.com>
Co-Authored-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
Replace regex-based extractRedirectURL and parseSAMLForm with a proper HTML tokenizer. This correctly handles any attribute ordering, malformed HTML, and edge cases that regex patterns miss. Also normalizes baseHost comparison in isDomainAllowedForSSO using domaincheck.Contains (V7 fix). Promotes golang.org/x/net from indirect to direct dependency. Co-Authored-By: André "decko" de Brito <decko@redhat.com> Co-Authored-By: Sergio Correia <scorreia@redhat.com> Co-Authored-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
LGTM. |
Summary
saml_initconfig option for proactive SAML login during plugin startupProblem
The reactive SAML SSO from #32 triggers on 401/403 + text/html responses. Some sites (e.g. Igloo-based platforms) never return 401/403 — they silently degrade to anonymous sessions with empty results. The only way to authenticate is to explicitly initiate the SAML redirect flow.
Solution
New
saml_initfield in the auth config:When set, the core proactively follows the SAML flow during plugin startup:
saml_initURLHandles two patterns:
handleSAMLSSOfor meta-refresh redirects (Jenkins, etc.)SAML init failure is fatal — the plugin won't load, preventing silent degradation to empty results.
Changes
internal/plugin/manifest.go— addSAMLInitfield toAuthServiceConfiginternal/proxy/saml.go— addInitSAMLSession+followSAMLFormChaininternal/proxy/saml_test.go— 5 new tests (form-first, redirect-first, no content, blocked domain, absolute URL)internal/plugin/manager.go— callInitSAMLSessionduringpreparePluginif configuredTest plan
go test ./...)saml_initconfigured🤖 Generated with Claude Code