Merge pull request 'fix(tracing): implement redact for query, add default values' (#89) from redact into main
All checks were successful
Build / build (push) Successful in 2m3s

Reviewed-on: #89
This commit is contained in:
Jorge Bornhausen 2025-06-28 02:34:16 +00:00
commit a9e1f3d8fa
6 changed files with 162 additions and 16 deletions

View file

@ -3,6 +3,7 @@ package ch.phoenix.oss.quarkus.commons.tracing;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import jakarta.ws.rs.core.HttpHeaders;
import java.util.Optional;
import java.util.Set;
@ -17,6 +18,14 @@ public interface TracingConfiguration {
interface Headers {
Set<String> DEFAULT_REDACTED = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
/**
* Optional set of headers to redact when tracing. By default, redacts
* the 'Authorization' header.
*
* @return the set of headers to be redacted
*/
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
@ -31,9 +40,18 @@ public interface TracingConfiguration {
Query query();
interface Query {
Set<String> DEFAULT_REDACTED = Set.of("access_token", "refresh_token", "apikey");
@WithDefault("false")
boolean includeRaw();
/**
* Optional set of query params to redact when tracing. By default, redacts
* the following params: 'access_token', 'refresh_token' and 'apikey'.
*
* @return the set of query params to be redacted
*/
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
}

View file

@ -9,12 +9,13 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ResourceInfo;
import java.util.List;
import java.util.Set;
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
@Unremovable
public class TracingRequestFilter {
private static final String REDACTED_VALUE = "********";
private final RoutingContext routingContext;
private final TracingService tracingService;
private final SecurityIdentity securityIdentity;
@ -57,24 +58,38 @@ public class TracingRequestFilter {
TracingConstants.REQUEST_PATH_RAW, uriInfo.getAbsolutePath().getRawPath());
}
var redactedHeaders = configuration
.requestFilter()
.headers()
.redact()
.orElse(TracingConfiguration.RequestFilterConfiguration.Headers.DEFAULT_REDACTED);
requestContext.getHeaders().forEach((key, value) -> {
var lowerCaseKey = key.toLowerCase();
var property = TracingConstants.REQUEST_HEADERS + '.' + lowerCaseKey;
if (configuration
.requestFilter()
.headers()
.redact()
.orElse(Set.of())
.contains(lowerCaseKey)) {
tracingService.trace(property, "********");
if (redactedHeaders.contains(lowerCaseKey)) {
tracingService.trace(property, REDACTED_VALUE);
} else {
tracingService.trace(property, joinStrings(value));
}
});
uriInfo.getQueryParameters()
.forEach((key, value) ->
tracingService.trace(TracingConstants.REQUEST_QUERY_PARAMS + '.' + key, joinStrings(value)));
var redactedQueryParams = configuration
.requestFilter()
.query()
.redact()
.orElse(TracingConfiguration.RequestFilterConfiguration.Query.DEFAULT_REDACTED);
uriInfo.getQueryParameters().forEach((key, value) -> {
var lowerCaseKey = key.toLowerCase();
var property = TracingConstants.REQUEST_QUERY_PARAMS + '.' + lowerCaseKey;
if (redactedQueryParams.contains(lowerCaseKey)) {
tracingService.trace(property, REDACTED_VALUE);
} else {
tracingService.trace(property, joinStrings(value));
}
});
if (configuration.requestFilter().query().includeRaw()) {
var rawQuery = uriInfo.getRequestUri().getRawQuery();
@ -87,10 +102,7 @@ public class TracingRequestFilter {
TracingConstants.REQUEST_CLIENT_IP,
routingContext.request().connection().remoteAddress(true).hostAddress());
if (Log.isTraceEnabled()) {
Log.tracef(
"Incoming request: %s %s", method, uriInfo.getAbsolutePath().getRawPath());
}
Log.debugf("Incoming request: %s %s", method, uriInfo.getAbsolutePath().getRawPath());
}
private static String joinStrings(List<String> value) {

View file

@ -34,7 +34,7 @@ public class ActorTest {
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "Basic am9uOmRvZQ==");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));

View file

@ -0,0 +1,54 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class QueryParamTest {
@InjectSpy
TracingService tracingService;
@Test
void traceQueryParams() {
var route = "/authenticated";
RestAssured.given()
.auth()
.basic("jon", "doe")
.accept(ContentType.TEXT)
.header("X-SOMETHING-ELSE", "whatever")
.queryParam("access_token", "api123")
.queryParam("refresh_token", "refresh123")
.queryParam("apikey", "apikey123")
.queryParam("grant_type", "authorization_code")
.when()
.get(route)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "jon");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.headers.x-something-else", "whatever");
verify(tracingService).trace("request.query.params.access_token", "********");
verify(tracingService).trace("request.query.params.refresh_token", "********");
verify(tracingService).trace("request.query.params.apikey", "********");
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -0,0 +1,61 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(Test2Profile.class)
public class RedactedTest {
@InjectSpy
TracingService tracingService;
@Test
void traceRedactedValues() {
var route = "/authenticated";
RestAssured.given()
.auth()
.basic("jon", "doe")
.accept(ContentType.TEXT)
.header("X-SOMETHING-ELSE", "whatever")
.queryParam("access_token", "api123")
.queryParam("refresh_token", "refresh123")
.queryParam("apikey", "apikey123")
.queryParam("grant_type", "authorization_code")
.when()
.get(route)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "jon");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.path.raw", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.headers.x-something-else", "********");
verify(tracingService).trace("request.query.params.access_token", "********");
verify(tracingService).trace("request.query.params.refresh_token", "refresh123");
verify(tracingService).trace("request.query.params.apikey", "apikey123");
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
verify(tracingService)
.trace(
"request.query.raw",
"access_token=api123&refresh_token=refresh123&apikey=apikey123&grant_type=authorization_code");
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -32,6 +32,7 @@ quarkus:
headers:
redact:
- AUTHORIZATION
- X-SOMETHING-ELSE
query:
include-raw: true
redact: