Skip to content

Conversation

mgoworko
Copy link
Collaborator

@mgoworko mgoworko commented Oct 1, 2025

🚀New (ES) Audit log serializer compliant with Elastic Common Schema (ECS)
🧐Enhancement (ES) Enable nested field definitions in the configurable audit log serializer for more flexible audit logging

@mgoworko mgoworko changed the title [RORDEV-1567] draft [RORDEV-1567] ECS serializer and improved configurable serializer Oct 12, 2025
@mgoworko mgoworko marked this pull request as ready for review October 12, 2025 13:24
@mgoworko mgoworko requested a review from coutoPL October 12, 2025 13:30
Copy link

coderabbitai bot commented Oct 12, 2025

📝 Walkthrough

Walkthrough

The change adds ECS v1 audit log serialization. It introduces EcsV1AuditLogSerializer with a predefined ECS field map, including nested fields, and integrates it into AuditingSettingsDecoder via a new "ecs" serializer type and version parsing (V1). AuditFieldValueDescriptor is extended with ProcessingDurationNanos, BooleanValue, NumericValue, and Nested; AuditSerializationHelper resolves these, including recursive nested object construction. AuditFieldValueDescriptorParser recognizes PROCESSING_DURATION_NANOS. AuditingSettingsDecoder now decodes nested JSON into Nested descriptors. Tests are expanded to cover ECS serializer outputs, nested fields, and deterministic contexts, with utilities to normalize timestamps.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Config as Config (YAML/JSON)
  participant Decoder as AuditingSettingsDecoder
  participant ECS as EcsV1AuditLogSerializer
  participant Helper as AuditSerializationHelper
  participant Resp as AuditResponseContext

  Note over Config,Decoder: Build serializer from configuration
  Config->>Decoder: audit.serializer = "ecs", version = "v1"
  Decoder->>Decoder: Parse serializerType ("ecs") and version (V1)
  Decoder->>ECS: Construct EcsV1AuditLogSerializer(allowedEventMode)

  Note over Resp,Helper: Serialize an audit response using ECS v1 mapping
  Resp->>ECS: onResponse(responseContext)
  ECS->>Helper: serialize(responseContext, fields, allowedEventMode)
  Helper->>Helper: Resolve descriptors<br/>- primitives (string/number/boolean)<br/>- ProcessingDurationMillis/Nanos<br/>- Nested (recursive map -> JSONObject)
  Helper-->>ECS: JSONObject
  ECS-->>Resp: Option[JSONObject]
Loading
sequenceDiagram
  autonumber
  participant Conf as Serializer Config (fields)
  participant Decoder as AuditingSettingsDecoder
  participant Parser as AuditFieldValueDescriptorParser

  Note over Conf,Decoder: Decode field descriptors (configurable path)
  Conf->>Decoder: fields: { "a": "PROCESSING_DURATION_NANOS", "b": { "x": "...", "y": true } }
  alt scalar string
    Decoder->>Parser: "PROCESSING_DURATION_NANOS"
    Parser-->>Decoder: ProcessingDurationNanos
  else object (nested)
    Decoder->>Decoder: recursively decode object
    Decoder-->>Decoder: Nested( "x" -> ..., "y" -> BooleanValue(true) )
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The PR delivers the new ECS serializer with comprehensive unit tests for its output but omits an end-to-end integration test that triggers and verifies a stored audit entry as well as the required documentation update showing how to enable the ECS serializer and sample output. Add an integration test that writes an audit event through the ECS serializer and verifies the persisted document contains all ECS fields, and update the project documentation with an “ECS serializer” section detailing configuration steps and a sample output.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change by referencing the new ECS serializer and the enhancements to the configurable serializer, matching the PR’s focus and objectives.
Out of Scope Changes Check ✅ Passed All modifications relate directly to introducing the ECS serializer or enhancing its supporting serialization and parsing logic, without introducing unrelated or purely cosmetic changes.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala (1)

285-291: Fix invalid serializer type message (include 'ecs').

Error message still lists “[static, configurable]”. Add “ecs”.

-              AuditingSettingsCreationError(Message(s"Invalid serializer type '$other', allowed values [static, configurable]"))
+              AuditingSettingsCreationError(Message(s"Invalid serializer type '$other', allowed values [static, configurable, ecs]"))
🧹 Nitpick comments (5)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/AuditFieldValueDescriptorParser.scala (1)

29-31: Avoid double uppercasing in placeholder parsing.

placeholder uppercases the key, and deserializerAuditFieldValueDescriptor uppercases again. Drop one for clarity.

-  private val placeholder: P[Either[String, AuditFieldValueDescriptor]] =
-    (lbrace *> key <* rbrace).map(k => deserializerAuditFieldValueDescriptor(k.trim.toUpperCase).toRight(k))
+  private val placeholder: P[Either[String, AuditFieldValueDescriptor]] =
+    (lbrace *> key <* rbrace).map(k => deserializerAuditFieldValueDescriptor(k.trim).toRight(k))
audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (1)

239-242: Preserve numeric fidelity (avoid 123 → 123.0 and float rounding).

Representing numbers as Double can introduce rounding and formatting (e.g., 123.0). Prefer BigDecimal and pass java.math.BigDecimal to JSONObject.

-    final case class NumericValue(value: Double) extends AuditFieldValueDescriptor
+    final case class NumericValue(value: BigDecimal) extends AuditFieldValueDescriptor

And in the resolver:

-      case AuditFieldValueDescriptor.NumericValue(value) => value
+      case AuditFieldValueDescriptor.NumericValue(value) => value.bigDecimal

Note: This also requires decoding numbers as BigDecimal in AuditingSettingsDecoder (see comment there).

Also applies to: 123-125

core/src/main/scala/tech/beshu/ror/accesscontrol/audit/ecs/EcsV1AuditLogSerializer.scala (1)

35-36: Drop redundant @timestamp in fields map.

createEntry already injects @timestamp. Keeping it here duplicates a put; safe but unnecessary.

-    AuditFieldName("@timestamp") -> AuditFieldValueDescriptor.Timestamp,
core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala (2)

376-393: Decode numbers as BigDecimal to avoid precision/format issues.

Current decoding uses toDouble. Prefer BigDecimal to preserve exact values and avoid 123 → 123.0.

-      jsonNumber = n => Right(AuditFieldValueDescriptor.NumericValue(n.toDouble)),
+      jsonNumber = n =>
+        n.toBigDecimal
+          .map(bd => AuditFieldValueDescriptor.NumericValue(bd))
+          .toRight(DecodingFailure("Cannot decode number", cursor.history)),

Note: This pairs with changing NumericValue to BigDecimal and resolver to put bigDecimal (see AuditSerializationHelper comment).


309-313: Nit: rename given to ecsSerializerVersionDecoder.

Minor typo (“ccs”). Improves readability.

-  private given ccsSerializerVersionDecoder: Decoder[EcsSerializerVersion] = Decoder.decodeString.map(_.toLowerCase).emap {
+  private given ecsSerializerVersionDecoder: Decoder[EcsSerializerVersion] = Decoder.decodeString.map(_.toLowerCase).emap {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbd71d and 1c5f7a4.

📒 Files selected for processing (5)
  • audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (4 hunks)
  • core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/AuditFieldValueDescriptorParser.scala (1 hunks)
  • core/src/main/scala/tech/beshu/ror/accesscontrol/audit/ecs/EcsV1AuditLogSerializer.scala (1 hunks)
  • core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala (5 hunks)
  • core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/AuditFieldValueDescriptorParser.scala (1)
audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (2)
  • AuditFieldValueDescriptor (171-251)
  • ProcessingDurationNanos (192-192)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/ecs/EcsV1AuditLogSerializer.scala (1)
audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (31)
  • ror (28-299)
  • AllowedEventMode (161-165)
  • AuditFieldName (167-167)
  • AuditFieldValueDescriptor (171-251)
  • serialize (32-45)
  • serialize (47-66)
  • Timestamp (195-195)
  • Nested (245-245)
  • Nested (247-249)
  • CorrelationId (199-199)
  • HttpPath (215-215)
  • RemoteAddress (219-219)
  • LocalAddress (221-221)
  • HttpMethod (211-211)
  • Content (223-223)
  • ContentLengthInBytes (225-225)
  • User (180-180)
  • ImpersonatedByUser (182-182)
  • Id (197-197)
  • Action (184-184)
  • Type (208-208)
  • FinalState (176-176)
  • ProcessingDurationNanos (192-192)
  • ErrorType (204-204)
  • ErrorMessage (206-206)
  • EsClusterName (233-233)
  • EsNodeName (231-231)
  • TaskId (201-201)
  • InvolvedIndices (186-186)
  • AclHistory (188-188)
  • XForwardedForHttpHeader (217-217)
core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala (4)
audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (9)
  • ror (28-299)
  • Reason (178-178)
  • AllowedEventMode (161-165)
  • AuditFieldName (167-167)
  • AuditFieldValueDescriptor (171-251)
  • BooleanValue (239-239)
  • NumericValue (241-241)
  • Nested (245-245)
  • Nested (247-249)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/AuditFieldValueDescriptorParser.scala (2)
  • AuditFieldValueDescriptorParser (23-92)
  • parse (41-57)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/ConfigurableAuditLogSerializer.scala (1)
  • ConfigurableAuditLogSerializer (24-30)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/ecs/EcsV1AuditLogSerializer.scala (2)
  • EcsV1AuditLogSerializer (25-31)
  • EcsV1AuditLogSerializer (33-83)
core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala (2)
audit/src/main/scala/tech/beshu/ror/audit/AuditResponseContext.scala (2)
  • AuditResponseContext (28-55)
  • Forbidden (40-41)
audit/src/test/scala/tech/beshu/ror/audit/SerializerTest.scala (19)
  • DummyAuditRequestContext (295-345)
  • timestamp (296-296)
  • id (298-298)
  • correlationId (300-300)
  • indices (302-302)
  • action (304-304)
  • headers (306-306)
  • requestHeaders (308-308)
  • uriPath (310-310)
  • history (312-312)
  • content (314-314)
  • contentLength (316-316)
  • remoteAddress (318-318)
  • localAddress (320-320)
  • type (322-322)
  • taskId (324-324)
  • httpMethod (326-326)
  • loggedInUserName (328-328)
  • impersonatedByUserName (330-330)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: ror (Run all tests IT_es67x)
  • GitHub Check: ror (Run all tests IT_es70x)
  • GitHub Check: ror (Run all tests IT_es80x)
  • GitHub Check: ror (Run all tests IT_es710x)
  • GitHub Check: ror (Required checks AUDIT_BUILD_CHECK)
  • GitHub Check: ror (Run all tests IT_es810x)
  • GitHub Check: ror (Run all tests IT_es717x)
  • GitHub Check: ror (Required checks COMPILE_CODEBASE_CHECK)
  • GitHub Check: ror (Run all tests WIN_IT_es818x)
  • GitHub Check: ror (Run all tests IT_es816x)
  • GitHub Check: ror (Run all tests WIN_IT_es91x)
  • GitHub Check: ror (Run all tests IT_es818x)
  • GitHub Check: ror (Required checks LICENSE_CHECK)
  • GitHub Check: ror (Run all tests Unit tests)
  • GitHub Check: ror (Run all tests IT_es91x)
  • GitHub Check: ror (Optional checks CVE_CHECK)
🔇 Additional comments (2)
core/src/main/scala/tech/beshu/ror/accesscontrol/audit/configurable/AuditFieldValueDescriptorParser.scala (1)

68-69: New nanos placeholder mapping looks correct.

PROCESSING_DURATION_NANOSProcessingDurationNanos is consistent with resolver changes.

audit/src/main/scala/tech/beshu/ror/audit/utils/AuditSerializationHelper.scala (1)

103-104: Nanos support LGTM.

ProcessingDurationNanos correctly resolves via duration.toNanos.

Comment on lines +231 to +232
.left.map(withAuditingSettingsCreationErrorMessage(msg => s"Configurable serializer is used, but the 'verbosity_level_serialization_mode' setting is invalid: $msg"))
serializer = version match {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Correct error context for ECS verbosity mode.

Message says “Configurable serializer…”, but this path is for ECS.

-        .left.map(withAuditingSettingsCreationErrorMessage(msg => s"Configurable serializer is used, but the 'verbosity_level_serialization_mode' setting is invalid: $msg"))
+        .left.map(withAuditingSettingsCreationErrorMessage(msg => s"ECS serializer is used, but the 'verbosity_level_serialization_mode' setting is invalid: $msg"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.left.map(withAuditingSettingsCreationErrorMessage(msg => s"Configurable serializer is used, but the 'verbosity_level_serialization_mode' setting is invalid: $msg"))
serializer = version match {
.left.map(withAuditingSettingsCreationErrorMessage(msg => s"ECS serializer is used, but the 'verbosity_level_serialization_mode' setting is invalid: $msg"))
serializer = version match {
🤖 Prompt for AI Agents
In
core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala
around lines 231-232, the error message incorrectly refers to "Configurable
serializer" although this path is validating the ECS verbosity mode; update the
mapped error message to reference ECS verbosity_mode (or
verbosity_level_serialization_mode for ECS) so it accurately describes the
context — e.g. change the string to indicate the ECS verbosity mode setting is
invalid and include the original msg; keep the same left.map wrapping and
interpolation style.

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