SOLR-13905 Make findRequestType in AuditEvent more robust (#1014)

(cherry picked from commit e45c5ce9b9e70650f119976b8b2d91b3c760cb48)
This commit is contained in:
Jan Høydahl 2019-11-22 23:34:20 +01:00 committed by Jan Høydahl
parent 4b37fb0c8f
commit 29e172f6e2
4 changed files with 63 additions and 22 deletions

View File

@ -75,6 +75,8 @@ Improvements
* SOLR-13907: In the Cloud/tree section of the Admin UI, fix metadata panel and force an horizontal scrollbar for the tree
(Richard Goodman via Tomás Fernández Löbbe)
* SOLR-13905: Make findRequestType in AuditEvent more robust (janhoy)
Optimizations
---------------------
(No changes)

View File

@ -131,7 +131,6 @@ public class AuditEvent {
this.solrPort = httpRequest.getLocalPort();
this.solrIp = httpRequest.getLocalAddr();
this.clientIp = httpRequest.getRemoteAddr();
this.resource = ServletUtils.getPathAfterContext(httpRequest);
this.httpMethod = httpRequest.getMethod();
this.httpQueryString = httpRequest.getQueryString();
this.headers = getHeadersFromRequest(httpRequest);
@ -141,6 +140,7 @@ public class AuditEvent {
this.solrParams.put(sp.getKey(), Arrays.asList(sp.getValue()));
});
setResource(ServletUtils.getPathAfterContext(httpRequest));
setRequestType(findRequestType());
if (exception != null) setException(exception);
@ -167,7 +167,7 @@ public class AuditEvent {
this(eventType, httpRequest);
this.collections = authorizationContext.getCollectionRequests()
.stream().map(r -> r.collectionName).collect(Collectors.toList());
this.resource = authorizationContext.getResource();
setResource(authorizationContext.getResource());
this.requestType = RequestType.convertType(authorizationContext.getRequestType());
authorizationContext.getParams().forEach(p -> {
this.solrParams.put(p.getKey(), Arrays.asList(p.getValue()));
@ -380,7 +380,7 @@ public class AuditEvent {
}
public AuditEvent setResource(String resource) {
this.resource = resource;
this.resource = normalizeResourcePath(resource);
return this;
}
@ -452,30 +452,35 @@ public class AuditEvent {
}
private RequestType findRequestType() {
if (ADMIN_PATH_REGEXES.stream().map(Pattern::compile)
.anyMatch(p -> p.matcher(resource).matches())) return RequestType.ADMIN;
if (SEARCH_PATH_REGEXES.stream().map(Pattern::compile)
.anyMatch(p -> p.matcher(resource).matches())) return RequestType.SEARCH;
if (INDEXING_PATH_REGEXES.stream().map(Pattern::compile)
.anyMatch(p -> p.matcher(resource).matches())) return RequestType.UPDATE;
if (STREAMING_PATH_REGEXES.stream().map(Pattern::compile)
.anyMatch(p -> p.matcher(resource).matches())) return RequestType.STREAMING;
if (resource == null) return RequestType.UNKNOWN;
if (SEARCH_PATH_PATTERNS.stream().anyMatch(p -> p.matcher(resource).matches())) return RequestType.SEARCH;
if (INDEXING_PATH_PATTERNS.stream().anyMatch(p -> p.matcher(resource).matches())) return RequestType.UPDATE;
if (STREAMING_PATH_PATTERNS.stream().anyMatch(p -> p.matcher(resource).matches())) return RequestType.STREAMING;
if (ADMIN_PATH_PATTERNS.stream().anyMatch(p -> p.matcher(resource).matches())) return RequestType.ADMIN;
return RequestType.UNKNOWN;
}
protected String normalizeResourcePath(String resourcePath) {
if (resourcePath == null) return "";
return resourcePath.replaceFirst("^/____v2", "/api");
}
private static final List<String> ADMIN_PATH_REGEXES = Arrays.asList(
"^/admin/.*",
"^/(____v2|api)/(c|collections)$",
"^/(____v2|api)/(c|collections)/[^/]+/config$",
"^/(____v2|api)/(c|collections)/[^/]+/schema$",
"^/(____v2|api)/(c|collections)/[^/]+/shards.*",
"^/(____v2|api)/cores.*$",
"^/(____v2|api)/node$",
"^/(____v2|api)/cluster$");
"^/api/(c|collections)$",
"^/api/(c|collections)/[^/]+/config$",
"^/api/(c|collections)/[^/]+/schema$",
"^/api/(c|collections)/[^/]+/shards.*",
"^/api/cores.*$",
"^/api/node.*$",
"^/api/cluster.*$");
private static final List<String> STREAMING_PATH_REGEXES = Collections.singletonList(".*/stream.*");
private static final List<String> INDEXING_PATH_REGEXES = Collections.singletonList(".*/update.*");
private static final List<String> SEARCH_PATH_REGEXES = Arrays.asList(".*/select.*", ".*/query.*");
private static final List<Pattern> ADMIN_PATH_PATTERNS = ADMIN_PATH_REGEXES.stream().map(Pattern::compile).collect(Collectors.toList());
private static final List<Pattern> STREAMING_PATH_PATTERNS = STREAMING_PATH_REGEXES.stream().map(Pattern::compile).collect(Collectors.toList());
private static final List<Pattern> INDEXING_PATH_PATTERNS = INDEXING_PATH_REGEXES.stream().map(Pattern::compile).collect(Collectors.toList());
private static final List<Pattern> SEARCH_PATH_PATTERNS = SEARCH_PATH_REGEXES.stream().map(Pattern::compile).collect(Collectors.toList());
}

View File

@ -17,11 +17,13 @@
package org.apache.solr.security;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandles;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@ -38,6 +40,7 @@ import com.codahale.metrics.Timer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
@ -216,6 +219,17 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
assertAuditEvent(events.get(2), ERROR,"/select", SEARCH, null, 400);
}
@Test
public void illegalAdminPathError() throws Exception {
setupCluster(false, null, false);
String baseUrl = testHarness.get().cluster.getJettySolrRunner(0).getBaseUrl().toString();
expectThrows(FileNotFoundException.class, () -> {
IOUtils.toString(new URL(baseUrl.replace("/solr", "") + "/api/node/foo"), StandardCharsets.UTF_8);
});
final List<AuditEvent> events = testHarness.get().receiver.waitForAuditEvents(1);
assertAuditEvent(events.get(0), ERROR, "/api/node/foo", ADMIN, null, 404);
}
@Test
public void authValid() throws Exception {
setupCluster(false, null, true);

View File

@ -91,6 +91,18 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 {
.setDate(SAMPLE_DATE)
.setCollections(Collections.singletonList("streamcoll"))
.setResource("/stream");
protected static final AuditEvent EVENT_HEALTH_API = new AuditEvent(AuditEvent.EventType.COMPLETED)
.setUsername("Jan")
.setHttpMethod("GET")
.setMessage("Healthy")
.setDate(SAMPLE_DATE)
.setResource("/api/node/health");
protected static final AuditEvent EVENT_HEALTH_V2 = new AuditEvent(AuditEvent.EventType.COMPLETED)
.setUsername("Jan")
.setHttpMethod("GET")
.setMessage("Healthy")
.setDate(SAMPLE_DATE)
.setResource("/____v2/node/health");
private MockAuditLoggerPlugin plugin;
private HashMap<String, Object> config;
@ -134,6 +146,7 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 {
assertFalse(plugin.shouldLog(EVENT_ANONYMOUS.getEventType()));
assertFalse(plugin.shouldLog(EVENT_AUTHENTICATED.getEventType()));
assertFalse(plugin.shouldLog(EVENT_AUTHORIZED.getEventType()));
assertFalse(plugin.shouldLog(EVENT_AUTHORIZED.getEventType()));
}
@Test(expected = SolrException.class)
@ -167,7 +180,14 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 {
assertEquals(1, plugin.typeCounts.getOrDefault("REJECTED", new AtomicInteger()).get());
assertEquals(2, plugin.events.size());
}
@Test
public void v2ApiPath() {
assertEquals("/api/node/health", EVENT_HEALTH_API.getResource());
// /____v2/ is mapped to /api/
assertEquals("/api/node/health", EVENT_HEALTH_V2.getResource());
}
@Test
public void jsonEventFormatter() {
assertEquals("{\"message\":\"Anonymous\",\"level\":\"INFO\",\"date\":" + SAMPLE_DATE.getTime() + ",\"solrParams\":{},\"solrPort\":0,\"resource\":\"/collection1\",\"httpMethod\":\"GET\",\"eventType\":\"ANONYMOUS\",\"status\":-1,\"qtime\":-1.0}",