diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/FilteredSlf4jRequestLogWriter.java b/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/FilteredSlf4jRequestLogWriter.java new file mode 100644 index 0000000000..a86d03d680 --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/FilteredSlf4jRequestLogWriter.java @@ -0,0 +1,30 @@ +package net.ripe.db.whois.api.httpserver; + +import org.eclipse.jetty.server.Slf4jRequestLogWriter; + +import java.io.IOException; +import java.util.regex.Pattern; + +public class FilteredSlf4jRequestLogWriter extends Slf4jRequestLogWriter { + private final String keyToFilter; + // Replace value passed to apikey with "FILTERED" but leave the last 3 characters if its API keys + private static final Pattern ApiKeyPattern = Pattern.compile("(?<=(?i)(apikey=))(.+?(?=\\S{3}[&|\\s]))"); + private static final Pattern PasswordPattern = Pattern.compile("(?<=(?i)(password=))([^&]*)"); + + public FilteredSlf4jRequestLogWriter(String keyToFilter) { + this.keyToFilter = keyToFilter; + } + + @Override + public void write(String requestEntry) throws IOException { + String filtered; + + if (keyToFilter != null && keyToFilter.equalsIgnoreCase("apikey")) { + filtered = ApiKeyPattern.matcher(requestEntry).replaceAll("FILTERED"); + } else { + filtered = PasswordPattern.matcher(requestEntry).replaceAll("FILTERED"); + } + + super.write(filtered); + } +} diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/JettyBootstrap.java b/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/JettyBootstrap.java index 6553f5f2e1..1101a37027 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/JettyBootstrap.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/httpserver/JettyBootstrap.java @@ -216,6 +216,6 @@ public void stop(final boolean force) { // Log requests to org.eclipse.jetty.server.RequestLog private RequestLog createRequestLog() { - return new CustomRequestLog(new Slf4jRequestLogWriter(), EXTENDED_RIPE_LOG_FORMAT); + return new CustomRequestLog(new FilteredSlf4jRequestLogWriter("password"), EXTENDED_RIPE_LOG_FORMAT); } } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/log/JettyRequestLogTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/log/JettyRequestLogTestIntegration.java index 100dc9985b..06c75942be 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/log/JettyRequestLogTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/log/JettyRequestLogTestIntegration.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.nio.file.Files; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -92,6 +93,69 @@ public void log_request_x_forwarded_for() throws Exception { } + @Test + public void password_filtered() throws Exception { + RestTest.target(getPort(), "whois/test/person/TP1-TEST?password=some-api_key-123") + .request() + .get(WhoisResources.class); + + + String actual = fileToString(getRequestLogFilename()); + assertThat(actual, containsString("GET /whois/test/person/TP1-TEST?password=FILTERED")); + assertThat(actual, not(containsString("some-api_key-123"))); + } + + @Test + public void multiple_password_filtered() throws Exception { + RestTest.target(getPort(), "whois/test/person/TP1-TEST?password=pass1&password=pass2") + .request() + .get(WhoisResources.class); + + + String actual = fileToString(getRequestLogFilename()); + assertThat(actual, containsString("GET /whois/test/person/TP1-TEST?password=FILTERED&password=FILTERED")); + assertThat(actual, not(containsString("pass1"))); + assertThat(actual, not(containsString("pass2"))); + } + + @Test + public void multiple_query_password_filtered() throws Exception { + RestTest.target(getPort(), "whois/test/person/TP1-TEST?password=pass1&key=value") + .request() + .get(WhoisResources.class); + + + String actual = fileToString(getRequestLogFilename()); + assertThat(actual, containsString("GET /whois/test/person/TP1-TEST?password=FILTERED&key=value")); + assertThat(actual, containsString("key=value")); + assertThat(actual, not(containsString("pass1"))); + } + + @Test + public void multiple_query_password_last_filtered() throws Exception { + RestTest.target(getPort(), "whois/test/person/TP1-TEST?key=value&password=pass1") + .request() + .get(WhoisResources.class); + + + String actual = fileToString(getRequestLogFilename()); + assertThat(actual, containsString("GET /whois/test/person/TP1-TEST?key=value&password=FILTERED")); + assertThat(actual, containsString("key=value")); + assertThat(actual, not(containsString("pass1"))); + } + + @Test + public void password_filtered_case_insensitive() throws Exception { + RestTest.target(getPort(), "whois/test/person/TP1-TEST?PassWord=pass1") + .request() + .get(WhoisResources.class); + + + String actual = fileToString(getRequestLogFilename()); + assertThat(actual.toLowerCase(), containsString("password=FILTERED".toLowerCase())); + assertThat(actual, not(containsString("pass1"))); + } + // helper methods private static void cleanupRequestLogDirectory() throws IOException {