SSF: Derive emit audit event data from typed events (#50720) - #50728
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses SSF data minimization for the /events/emit admin endpoint by ensuring synthetic SSF event payloads are no longer persisted verbatim in admin event representations (preventing payload PII from being stored beyond SSF delivery), while keeping bounded correlation metadata and validating behavior via integration tests.
Changes:
- Redacts the audited
eventDatastored in admin events to a bounded key skeleton withREDACTEDvalues. - Introduces bounds for maximum audited payload keys and maximum key length.
- Adds integration tests asserting redaction, delivery still containing verbatim payload, and bounding behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| ssf/tests/base/src/test/java/org/keycloak/tests/ssf/transmitter/SsfTransmitterEventEmitterTests.java | Adds integration coverage to ensure admin event audit data is redacted/bounded while SET delivery remains unredacted. |
| ssf/services/src/main/java/org/keycloak/ssf/services/admin/SsfAdminResource.java | Replaces persisted admin-event payload with a bounded, redacted key skeleton to prevent PII persistence in admin event representations. |
d4245e1 to
3e9facf
Compare
3e9facf to
c3f4cf4
Compare
9a3bd2a to
56d3d68
Compare
56d3d68 to
c635f16
Compare
9217237 to
1ad707d
Compare
1ad707d to
49fdb41
Compare
49fdb41 to
b8d63e8
Compare
| @Override | ||
| public Map<String, Object> createAdminDetails() { | ||
| var adminRepresentation = super.createAdminDetails(); | ||
| adminRepresentation.put("credential_type", resolvedCredentialType.getType()); |
There was a problem hiding this comment.
@thomasdarimont Claude Fable found this:
The cached resolvedCredentialType is only populated inside setCredentialType(), so anything that sets credentialType without going through the setter — a subclass writing the protected field directly, a future @JsonCreator, a copy path — leaves it null and this line NPEs. It works today only because Jackson happens to prefer the setter over the @JsonProperty-annotated field, and validate() checks the string field, not the resolved enum, so it wouldn't catch the mismatch.
Worse, the audit rep is built after the SET has been dispatched (SsfAdminResource.emitEvent), so the NPE would surface as a 500 for an event that already went out, with no admin event recorded.
The cache doesn't buy anything — deriving it here removes the hidden state entirely, and fromString never returns null (falls back to CUSTOM) while validate() guarantees credentialType is non-null on this path:
| adminRepresentation.put("credential_type", resolvedCredentialType.getType()); | |
| // fromString() collapses caller-supplied free-text credential types to the | |
| // closed CaepCredentialType vocabulary so free-form values can't leak into | |
| // the admin event store | |
| adminRepresentation.put("credential_type", CaepCredentialType.fromString(credentialType).getType()); |
Then the resolvedCredentialType field, its getter, and the @JsonIgnore import can be dropped.
|
@thomasdarimont Please fix conflicts |
Instead of persisting the verbatim emit request payload in the admin event, EmitEventResult now carries the validated typed SsfEvent and the audit representation is built via the new SsfEvent.createAdminDetails() hook. Each event class contributes only its audit-safe fields (e.g. credential_type/change_type for credential-change, status/reason for stream-updated, the standard CAEP claims on CaepEvent); the default is an empty map, so free-form payload data and PII never reach the admin event store. EventEmitterService now also rejects payloads that do not materialise as a typed SsfEvent. Fixes keycloak#50720 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Compute credential_type for adminDetails ad-hoc. Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
b8d63e8 to
0e1f586
Compare
|
@ssilvert I just rebased the branch and applied your suggestions while solving the conflicts. |
…eycloak#50728) * SSF: Derive emit audit event data from typed events (keycloak#50720) Instead of persisting the verbatim emit request payload in the admin event, EmitEventResult now carries the validated typed SsfEvent and the audit representation is built via the new SsfEvent.createAdminDetails() hook. Each event class contributes only its audit-safe fields (e.g. credential_type/change_type for credential-change, status/reason for stream-updated, the standard CAEP claims on CaepEvent); the default is an empty map, so free-form payload data and PII never reach the admin event store. EventEmitterService now also rejects payloads that do not materialise as a typed SsfEvent. Fixes keycloak#50720 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com> * SSF: Handle SET validation exceptions in push endpoint Compute credential_type for adminDetails ad-hoc. Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com> --------- Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Instead of persisting the verbatim emit request payload in the admin
event, EmitEventResult now carries the validated typed SsfEvent and the
audit representation is built via the new SsfEvent.createAdminDetails()
hook. Each event class contributes only its audit-safe fields
(e.g. credential_type/change_type for credential-change, status/reason
for stream-updated, the standard CAEP claims on CaepEvent); the default
is an empty map, so free-form payload data and PII never reach the
admin event store. EventEmitterService now also rejects payloads that
do not materialise as a typed SsfEvent.
Fixes #50720