Skip to content

Add SAML traffic recorder to compare EngineBlock output between commits - #2138

Open
kayjoosten wants to merge 1 commit into
mainfrom
feature/2136-saml2-baseline-and-seams
Open

kayjoosten wants to merge 1 commit into
mainfrom
feature/2136-saml2-baseline-and-seams

Conversation

@kayjoosten

@kayjoosten kayjoosten commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part of #2136 (Phase 0a, the safety net before the saml2 migration).

Adds opt-in, test-only tooling that records the SAML messages EngineBlock and the Behat mocks send during a Behat run, and normalises them so two runs can be compared with diff.

Why

The Behat suite only asserts selected fields of the messages. A change to a message that no step checks goes unnoticed. The migration to simplesamlphp/saml2 v7 rewrites the code that builds and signs messages (about 84 files reference SAML2\), so a wire level comparison is needed before that work starts.

What

  • SamlTrafficRecorder: kernel response subscriber that records every SAMLRequest and SAMLResponse leaving the application (HTTP-Redirect and HTTP-POST). It covers EngineBlock and the mocks.
  • SamlXmlNormalizer: dumps a message as a prefix independent text tree. Volatile values (timestamps, identifiers, signature and digest values, time based mock fixtures) are masked, and references between messages stay visible as ID-n.
  • engineblock:saml:normalize-recording: normalises a recording directory.
  • SamlRecordingContext: switches recording on when EB_SAML_RECORD_DIR is set. Behat talks to Apache over HTTP, so the setting reaches the application through a marker file in /tmp/eb-fixtures, which is removed after the suite.

Everything is only registered in the ci and test environments (config/bundles.php, config/services_ci.yaml). Nothing is recorded unless EB_SAML_RECORD_DIR is set, and no production code is touched.

How to use

Inside the container as www-data, reset the ci schema before each run:

EB_SAML_RECORD_DIR=/tmp/saml-rec-A ./vendor/bin/behat -c ./tests/behat.yml --suite default
./bin/console engineblock:saml:normalize-recording /tmp/saml-rec-A /tmp/saml-norm-A

# check out the other commit, then repeat with B
diff -r /tmp/saml-norm-A /tmp/saml-norm-B

Verification

  • Determinism: two full default-suite runs on identical code record 904 messages in 257 scenarios and the normalised diff is empty.
  • Sensitivity: changing the Comparison of the Stepup AuthnRequest from minimum to exact leaves all 25 Stepup scenarios green, but the recording diff flags 20 of them (@Comparison: minimum disappears). The change was reverted.
  • Inert by default: with EB_SAML_RECORD_DIR unset the suite passes 300/300 and nothing is written.
  • CI equivalent checks: phpmd, phpcs (src and legacy), docheader, lint:twig. PHPUnit: eb4 244, unit 1064, functional 123, integration 104. Behat default suite 300/300.

Lifetime

This is a temporary migration aid, not a permanent test suite. It is used on the v5, v6 and v7 upgrade commits and is meant to be removed once the v7 commit has landed. The permanent safety net (unit tests on the EngineBlock owned seams and a corpus of inbound IdP responses) follows in separate PRs.

Known limits: it only sees what Behat exercises, it does not show whether the library becomes stricter about inbound IdP responses, and signature values are masked.

Not in this PR

The EngineBlock owned seams (Phase 0b) and the staged v5, v6 and v7 upgrade commits. Those follow separately. This PR is independent of #2135.

# Why is this change needed?
Prior to this change, there was no way to see whether a library upgrade
changed the SAML messages EngineBlock sends. The Behat suite only asserts
selected fields, so a change in a message that no step checks goes
unnoticed. The staged migration to simplesamlphp/saml2 v7 rewrites the code
that builds and signs messages, so a wire level safety net is needed first.

# How does it address the issue?
This change adds test-only tooling that is only loaded in the ci and test
environments:

- a kernel response subscriber that records every SAMLRequest and
  SAMLResponse that leaves EngineBlock or one of the mocks, for both
  HTTP-Redirect and HTTP-POST
- a normaliser that dumps a message as a prefix independent text tree and
  masks volatile values (timestamps, identifiers, signature and digest
  values, time based mock fixtures) while keeping references between
  messages visible
- the engineblock:saml:normalize-recording command to normalise a recording
- a Behat context that switches recording on when EB_SAML_RECORD_DIR is
  set. Behat reaches the application over HTTP, so the setting is handed
  over through a marker file in /tmp/eb-fixtures

Nothing is recorded unless EB_SAML_RECORD_DIR is set. To compare two
commits, run the default Behat suite on each with a different directory,
normalise both recordings and run diff -r on the results.

Two full runs on identical code give an empty diff over 904 messages in 257
scenarios. Changing the Comparison of the Stepup request leaves all 25
Stepup scenarios green, but the recording diff flags 20 of them.

# Links
#2136
@kayjoosten
kayjoosten force-pushed the feature/2136-saml2-baseline-and-seams branch from afa8686 to 68ccbca Compare October 22, 2026 07:47
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