Skip to content

feat: add SAML SSO authentication for S/4HANA Public Cloud#97

Merged
oisee merged 1 commit intooisee:mainfrom
blicksten:saml-auth
Apr 12, 2026
Merged

feat: add SAML SSO authentication for S/4HANA Public Cloud#97
oisee merged 1 commit intooisee:mainfrom
blicksten:saml-auth

Conversation

@blicksten
Copy link
Copy Markdown
Contributor

Summary

Add SAML SSO authentication support for SAP S/4HANA Public Cloud systems where Basic Auth is disabled for business users.

Three new capabilities:

  • --browser-auth fix: Existing browser-based auth now works with SAML/IAS SSO flows. Improved cookie URL filtering (cookieURLsForSAP() queries 4 URL paths), smarter poll timing (500ms intervals with elapsed tracking), and verbose SAML redirect logging.

  • --saml-auth (new): Programmatic SAML SSO without a browser. Performs the full SAP→IAS→SAP SAML dance via HTTP client. Supports SP-initiated (HTTP-POST binding) and IdP-initiated flows. Automatic 401 re-auth with stampede protection (mutex + cooldown). Ideal for CI/CD pipelines. Does not support MFA — use --browser-auth for MFA-protected systems.

  • --credential-cmd (new): External credential provider integration (git-credential-helper pattern). Executes an external command that returns {"username": "...", "password": "..."} JSON. Works with any credential manager (KeePass CLI, 1Password, HashiCorp Vault, etc.). Argv-based execution — no shell interpretation.

Security hardening:

  • HTTPS→HTTP downgrade prevention at 5 enforcement points (redirects, form actions, SP-initiated flow)
  • Host validation prevents credential/assertion exfiltration via crafted form actions
  • Case-insensitive, port-normalized host comparison (canonicalHost)
  • Credential zeroing ([]byte) after each use; CredentialProvider callback re-reads on each auth attempt
  • No shell execution in --credential-cmd (argv-based exec.Command)
  • Verbose mode never logs passwords, SAML assertions, or cookie values
  • 10-hop redirect/form chain limit prevents infinite loops
  • cmd.Context() propagation for proper Ctrl+C cancellation

New dependency: golang.org/x/net (for html package — robust HTML form parsing instead of regex)

Usage examples

# Browser-based SAML SSO (supports MFA)
vsp --browser-auth --url https://your-system.s4hana.cloud.sap -v

# Programmatic SAML SSO (no browser, no MFA)
vsp --saml-auth --saml-user user@company.com --saml-password '***' \
    --url https://your-system.s4hana.cloud.sap

# With environment variables
export SAP_SAML_USER=user@company.com
export SAP_SAML_PASSWORD='***'
vsp --saml-auth --url https://your-system.s4hana.cloud.sap

# With external credential provider
vsp --saml-auth --credential-cmd 'my-credential-helper get SAP' \
    --url https://your-system.s4hana.cloud.sap

Test plan

  • go test ./pkg/adt/... — all tests pass
  • go build ./cmd/vsp — compiles clean
  • 3 rounds of security audit (credential lifecycle, host validation, shell injection, SAML assertion protection)
  • Manual test against S/4HANA Public Cloud — pending system access

Add programmatic SAML SSO (--saml-auth), fix browser-auth for SAML/IAS,
and add --credential-cmd for external credential providers.

- 4-step SAML dance: SAP SP → IAS login → SAMLResponse → SAP ACS
- SP-initiated (HTTP-POST binding) and IdP-initiated flows
- 401 re-auth with stampede protection (mutex + cooldown)
- credential-cmd: argv-based exec, JSON output, no shell
- HTTPS downgrade prevention at 5 enforcement points
- Host validation prevents credential/assertion exfiltration
- Credential zeroing after each use
- New dep: golang.org/x/net (HTML form parsing)

Co-Authored-By: Porfiry
@oisee oisee merged commit e62c7d5 into oisee:main Apr 12, 2026
@blicksten
Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @oisee! And no worries about the mode-default flag — glad it got sorted.

Live smoke test results

We tested all three scenarios against a live S/4HANA Public Cloud tenant (SAP IAS IdP):

1. --saml-auth happy path

vsp --saml-auth --saml-user user@company.com --saml-password '***' \
    --url https://<tenant>.s4hana.cloud.sap -v

# Result: successful SAML dance (SP-initiated, HTTP-POST binding),
# session cookies obtained, GetSystemInfo returned valid response.
# Also verified GetClass, SearchObject — all work with SAML session.

2. 401 recovery (session expiry → reauth → retry)

Forced session expiry by waiting beyond the SAP session timeout, then issued a new ADT request:

# First request after session expiry triggers 401 → reauth path fires →
# fresh SAML dance → new cookies → CSRF token refreshed → original request retried.
# Verified via -v output: "401 detected, re-authenticating via SAML..."
# followed by successful retry with new session.

3. Negative cases

Scenario Result
Wrong password SAML authentication failed: IAS returned HTTP 401 — no credential leak in output
Cross-host form action form action host "evil.com" does not match SAP host — rejected at enforcement point
HTTPS→HTTP downgrade refusing HTTPS→HTTP downgrade in SAML redirect — blocked

All three enforcement points work as expected on the live system.


Addressing non-blocking notes

All four are valid observations. We'll submit a follow-up PR:

  1. ParseCredentialCmd / strings.Fields — Will add a note to --credential-cmd help text about the spaces limitation and recommend wrapper scripts.

  2. string(password) immutable copy — Acknowledged as a Go language limitation. The byte slices are zeroed, but the url.Values.Set() string copy persists until GC. Will add an explicit comment documenting this boundary.

  3. SaveCookiesToFile secret-bearing — Will add a verbose-mode warning when writing the cookie file, reminding users it contains session secrets.

  4. Reauth mutex + context timeout — Will add context.WithTimeout (30s) on the reauth path so concurrent 401 handlers don't block indefinitely. This is the most impactful fix.

Follow-up PR incoming shortly. Thanks again for the careful review!

blicksten pushed a commit to blicksten/vibing-steampunk that referenced this pull request Apr 12, 2026
1. credential-cmd help: note spaces limitation, recommend wrapper scripts
2. saml_auth.go: document string(password) Go immutability limitation
3. browser_auth.go: document cookie file as secret-bearing in SaveCookiesToFile
4. main.go: warn users that saved cookie files contain session secrets
5. http.go: add 30s context timeout on reauth path to prevent indefinite
   mutex hold during slow/unresponsive IdP SAML dance

Addresses all 4 non-blocking notes from oisee's review on PR oisee#97.

Co-Authored-By: Porfiry
@blicksten blicksten deleted the saml-auth branch April 12, 2026 21:24
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.

2 participants