Admin API License Watermarking Filter
Documents the servlet filter that adds the X-S2R-License header and a customer_id MDC key to all Admin API requests for license tracking.
Overview
The LicenseWatermarkFilter is a servlet filter that intercepts every request to the Admin API. Its primary function is to "watermark" each request with information derived from the currently active product license. This serves two main purposes:
- Response Identification: It adds a unique
X-S2R-LicenseHTTP header to responses, allowing API consumers and support teams to identify the specific license associated with a given API interaction. - Centralized Logging: It injects the
customer_idinto the Mapped Diagnostic Context (MDC) for logging. This ensures that all log entries generated while processing a request are automatically tagged with the customer identifier, simplifying log analysis and troubleshooting.
The filter is designed to run on every request after authentication and authorization have been established but before the core business logic is executed. This ensures that the license context is available throughout the request's lifecycle.
How It Works
The LicenseWatermarkFilter is implemented as a OncePerRequestFilter, guaranteeing it executes exactly once for each request that enters the application.
sequenceDiagram
participant C as API Consumer
participant F as LicenseWatermarkFilter
participant V as LicenseValidator
participant MDC
participant Chain as Filter Chain
C->>+F: Sends HTTP Request
F->>V: currentState()
V-->>F: Returns LicenseState
F->>MDC: put("customer_id", ...)
F->>Chain: doFilter(request, response)
Note right of Chain: Request is processed by<br/>downstream filters and controllers.
Chain-->>-F: Returns
F->>MDC: remove("customer_id")
F-->>-C: Sends HTTP Response<br/>(with X-S2R-License header)The processing sequence for an incoming request is as follows:
- Path Evaluation: The filter first checks if the request path starts with
/actuator/. If it does, the filter does nothing and immediately passes the request to the next filter in the chain. This exclusion prevents liveness and readiness probes from depending on the license validation logic, which may involve database access. - State Retrieval: The filter calls the
LicenseValidatorto get the currentLicenseState. If the license state cannot be determined due to an error, the process continues as if the system is unlicensed. Such failures are logged at aDEBUGlevel. - MDC Population: Based on the
LicenseState, a customer identifier is resolved and placed into the MDC with the keycustomer_id. This value is used for tagging all subsequent log messages for the duration of the request. - Header Injection: The filter resolves a value for the
X-S2R-Licenseresponse header. If a value is determined, it is set on theHttpServletResponse. If the license state is unlicensed or unavailable, the header is omitted entirely. - Chain Progression: The request is passed down the filter chain for processing by other components and the target API controller.
- MDC Cleanup: In a
finallyblock, thecustomer_idis removed from the MDC. This is a critical step to ensure that the identifier does not leak into the logging context of another request that might be processed by the same thread.
Request Path Matching
IMPORTANT
The filter's decision to skip processing for /actuator/ paths is based on the request path as resolved by the application's routing mechanism. This ensures that path-obfuscation techniques (such as URL encoding or matrix parameters) cannot be used to bypass the watermarking logic for non-actuator endpoints.
Response Header: X-S2R-License
The filter adds the X-S2R-License header to all responses for watermarked requests. The value of this header depends on the current license status.
| License State | X-S2R-License Header Value |
|---|---|
| Full License | The first 16 hexadecimal characters of the SHA-256 hash of the license's installation ID (licenseId). |
| Bootstrap (Activation Grace) | The literal string bootstrap. |
| Unlicensed or Unavailable | The header is omitted from the response. |
Hash Generation for Full Licenses
When a full license is active, the header value is a truncated SHA-256 hash. The hash is calculated on the UTF-8 bytes of the license's installation ID UUID string. Only the first 8 bytes of the resulting digest are used, which are then formatted as 16 lowercase hexadecimal characters.
For example, if a license's installation ID is a1b2c3d4-e5f6-a7b8-c9d0-e1f2a3b4c5d6, the header value would be the first 16 characters of its SHA-256 hash.
Logging Context (MDC)
For each processed request, the filter adds the customer_id key to the MDC. This allows structured logging systems to correlate log entries with a specific customer.
| License State | customer_id MDC Value |
|---|---|
| Full License | The customer ID string (customerId) from the license. If the ID is null or blank, it defaults to unlicensed. |
| Bootstrap (Activation Grace) | The literal string bootstrap. |
| Unlicensed or Unavailable | The literal string unlicensed. |
Troubleshooting
License State Unavailable
If the LicenseValidator component throws an exception while retrieving the current license state, the LicenseWatermarkFilter will catch it and log a debug message. In this scenario, the filter operates as if the system is unlicensed:
- The
X-S2R-Licenseheader will be omitted from the response. - The
customer_idkey in the MDC will be set tounlicensed.
This ensures that a failure in the licensing subsystem does not cause a total failure of the Admin API.
See Also
- Authentication and Authorization Filters: The
LicenseWatermarkFilteris ordered to run after theIdpAuthFilterandAdminAuthorizationFilter, ensuring that its logic operates on authenticated and authorized requests. - Licensing Architecture: This component is part of the platform's overall licensing and entitlement system.
