feat(client-logger): redact headers based on configuration
All checks were successful
Build / build (pull_request) Successful in 2m9s
All checks were successful
Build / build (pull_request) Successful in 2m9s
This commit is contained in:
parent
d1acb1a0ee
commit
21913626ad
12 changed files with 286 additions and 5 deletions
|
@ -16,6 +16,16 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -40,7 +50,7 @@
|
|||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1</minimum>
|
||||
<minimum>0.92</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
public class LowerCaseStringConverter implements Converter<String> {
|
||||
@Override
|
||||
public String convert(String value) throws IllegalArgumentException, NullPointerException {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
|
@ -6,23 +6,36 @@ import io.vertx.core.buffer.Buffer;
|
|||
import io.vertx.core.http.HttpClientRequest;
|
||||
import io.vertx.core.http.HttpClientResponse;
|
||||
import jakarta.enterprise.context.Dependent;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.client.api.ClientLogger;
|
||||
|
||||
/**
|
||||
* This is based on org.jboss.resteasy.reactive.client.logging.DefaultClientLogger,
|
||||
* with the only change being that the value of "Authorization" header, when present,
|
||||
* is redacted.
|
||||
* with the only change being that headers are redacted based on the Set provided
|
||||
* by the configuration.
|
||||
*/
|
||||
@Dependent
|
||||
public class RedactingClientLogger implements ClientLogger {
|
||||
|
||||
private static final Logger log = Logger.getLogger(RedactingClientLogger.class);
|
||||
|
||||
private static final String REDACTED_VALUE = "*****";
|
||||
|
||||
private final Set<String> redactedHeaders;
|
||||
|
||||
private int bodySize;
|
||||
|
||||
@Inject
|
||||
public RedactingClientLogger(RedactingClientLoggerConfiguration configuration) {
|
||||
this.redactedHeaders = configuration
|
||||
.headers()
|
||||
.redact()
|
||||
.orElse(RedactingClientLoggerConfiguration.Headers.DEFAULT_REDACTED_HEADERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBodySize(int bodySize) {
|
||||
this.bodySize = bodySize;
|
||||
|
@ -97,7 +110,7 @@ public class RedactingClientLogger implements ClientLogger {
|
|||
}
|
||||
|
||||
var key = entry.getKey();
|
||||
var value = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(key) ? "*****" : entry.getValue();
|
||||
var value = redactedHeaders.contains(key.toLowerCase()) ? REDACTED_VALUE : entry.getValue();
|
||||
|
||||
sb.append(key).append('=').append(value);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithConverter;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@ConfigMapping(prefix = "phoenix.client-logger")
|
||||
public interface RedactingClientLoggerConfiguration {
|
||||
|
||||
Headers headers();
|
||||
|
||||
interface Headers {
|
||||
|
||||
Set<String> DEFAULT_REDACTED_HEADERS = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
|
||||
|
||||
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTestProfile;
|
||||
import java.util.Map;
|
||||
|
||||
public class InfoLevelProfile implements QuarkusTestProfile {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfigOverrides() {
|
||||
return Map.of("quarkus.log.category.\"ch.phoenix.oss.quarkus.commons.client.logger\".level", "INFO");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(InfoLevelProfile.class)
|
||||
class InfoLevelTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class RedactingClientLoggerTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClientAndNullBody() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTestProfile;
|
||||
import java.util.Map;
|
||||
|
||||
public class ScopeNoneProfile implements QuarkusTestProfile {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfigOverrides() {
|
||||
return Map.of("quarkus.rest-client.logging.scope", "none");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(ScopeNoneProfile.class)
|
||||
class ScopeNoneTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@RegisterRestClient(configKey = "test")
|
||||
public interface TestClient {
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String get(
|
||||
@HeaderParam("Authorization") String authorization,
|
||||
@HeaderParam("X-Request-ID") String requestId,
|
||||
@HeaderParam("X-Something-Else") String somethingElse);
|
||||
|
||||
@POST
|
||||
@Path("/")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String post(
|
||||
@HeaderParam("Authorization") String authorization,
|
||||
@HeaderParam("X-Request-ID") String requestId,
|
||||
@HeaderParam("X-Something-Else") String somethingElse,
|
||||
String body);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/")
|
||||
public class TestResource {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String get() {
|
||||
return "get";
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String post(String body) {
|
||||
return body;
|
||||
}
|
||||
}
|
20
quarkus-client-logger/src/test/resources/application.yaml
Normal file
20
quarkus-client-logger/src/test/resources/application.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
quarkus:
|
||||
http:
|
||||
test-port: 8087
|
||||
rest-client:
|
||||
logging:
|
||||
scope: request-response
|
||||
body-limit: 10000
|
||||
test:
|
||||
url: http://localhost:${quarkus.http.test-port}
|
||||
log:
|
||||
category:
|
||||
"ch.phoenix.oss.quarkus.commons.client.logger":
|
||||
level: DEBUG
|
||||
|
||||
phoenix:
|
||||
client-logger:
|
||||
headers:
|
||||
redact:
|
||||
- AUTHORIZATION
|
||||
- X-SOMETHING-ELSE
|
Loading…
Add table
Add a link
Reference in a new issue