diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/endpoint/BasicAuthenticatorResource.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/endpoint/BasicAuthenticatorResource.java index 1bd90e976d56..ca07c58d3be9 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/endpoint/BasicAuthenticatorResource.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/endpoint/BasicAuthenticatorResource.java @@ -29,6 +29,7 @@ import org.apache.druid.security.basic.BasicSecurityResourceFilter; import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate; import org.apache.druid.server.security.AuthValidator; +import org.apache.druid.server.security.AuthorizationUtils; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -166,7 +167,7 @@ public Response createUser( final Response response = handler.createUser(authenticatorName, userName); if (isSuccess(response)) { - final AuditInfo auditInfo = new AuditInfo(author, comment, req.getRemoteAddr()); + final AuditInfo auditInfo = AuthorizationUtils.buildAuditInfo(author, comment, req); performAudit(authenticatorName, "users.create", Collections.singletonMap("username", userName), auditInfo); } @@ -198,7 +199,7 @@ public Response deleteUser( final Response response = handler.deleteUser(authenticatorName, userName); if (isSuccess(response)) { - final AuditInfo auditInfo = new AuditInfo(author, comment, req.getRemoteAddr()); + final AuditInfo auditInfo = AuthorizationUtils.buildAuditInfo(author, comment, req); performAudit(authenticatorName, "users.delete", Collections.singletonMap("username", userName), auditInfo); } @@ -231,7 +232,7 @@ public Response updateUserCredentials( final Response response = handler.updateUserCredentials(authenticatorName, userName, update); if (isSuccess(response)) { - final AuditInfo auditInfo = new AuditInfo(author, comment, req.getRemoteAddr()); + final AuditInfo auditInfo = AuthorizationUtils.buildAuditInfo(author, comment, req); performAudit(authenticatorName, "users.update", Collections.singletonMap("username", userName), auditInfo); } diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java index d514dc5621bb..215586f86baf 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java @@ -232,9 +232,9 @@ public Response taskPost( auditManager.doAudit( AuditEntry.builder() .key(task.getDataSource()) - .type("ingestion.batch") + .type("overlord" + req.getServletPath()) .payload(new TaskIdentifier(task.getId(), task.getGroupId(), task.getType())) - .auditInfo(new AuditInfo(author, comment, req.getRemoteAddr())) + .auditInfo(AuthorizationUtils.buildAuditInfo(author, comment, req)) .build() ); } diff --git a/processing/src/main/java/org/apache/druid/audit/AuditEntry.java b/processing/src/main/java/org/apache/druid/audit/AuditEntry.java index 470905b5a014..a8a420915654 100644 --- a/processing/src/main/java/org/apache/druid/audit/AuditEntry.java +++ b/processing/src/main/java/org/apache/druid/audit/AuditEntry.java @@ -95,6 +95,11 @@ public DateTime getAuditTime() return auditTime; } + public static Builder builder() + { + return new Builder(); + } + @Override public boolean equals(Object o) { @@ -119,27 +124,18 @@ public int hashCode() return Objects.hash(key, type, auditInfo, payload, auditTime); } - public static Builder builder() - { - return new Builder(); - } - public static class Builder { private String key; private String type; private AuditInfo auditInfo; - - private String serializedPayload; private Object payload; + private String serializedPayload; private DateTime auditTime; private Builder() { - this.key = null; - this.auditInfo = null; - this.serializedPayload = null; this.auditTime = DateTimes.nowUtc(); } @@ -185,6 +181,9 @@ public AuditEntry build() } } + /** + * Payload of an {@link AuditEntry} that may be specified {@link #raw()} or {@link #serialized()}. + */ public static class Payload { private final String serialized; @@ -200,7 +199,7 @@ public static Payload fromString(String serialized) @Override public String toString() { - return serialized; + return serialized == null ? "" : serialized; } private Payload(String serialized, Object raw) @@ -209,7 +208,7 @@ private Payload(String serialized, Object raw) this.raw = raw; } - public String asString() + public String serialized() { return serialized; } diff --git a/processing/src/main/java/org/apache/druid/audit/AuditInfo.java b/processing/src/main/java/org/apache/druid/audit/AuditInfo.java index cab62c9a0289..206acbd1353d 100644 --- a/processing/src/main/java/org/apache/druid/audit/AuditInfo.java +++ b/processing/src/main/java/org/apache/druid/audit/AuditInfo.java @@ -27,27 +27,41 @@ public class AuditInfo { private final String author; + private final String identity; private final String comment; private final String ip; @JsonCreator public AuditInfo( @JsonProperty("author") String author, + @JsonProperty("identity") String identity, @JsonProperty("comment") String comment, @JsonProperty("ip") String ip ) { this.author = author; + this.identity = identity; this.comment = comment; this.ip = ip; } + public AuditInfo(String author, String comment, String ip) + { + this(author, null, comment, ip); + } + @JsonProperty public String getAuthor() { return author; } + @JsonProperty + public String getIdentity() + { + return identity; + } + @JsonProperty public String getComment() { @@ -69,16 +83,17 @@ public boolean equals(Object o) if (o == null || getClass() != o.getClass()) { return false; } - AuditInfo auditInfo = (AuditInfo) o; - return Objects.equals(author, auditInfo.author) - && Objects.equals(comment, auditInfo.comment) - && Objects.equals(ip, auditInfo.ip); + AuditInfo that = (AuditInfo) o; + return Objects.equals(this.author, that.author) + && Objects.equals(this.identity, that.identity) + && Objects.equals(this.comment, that.comment) + && Objects.equals(this.ip, that.ip); } @Override public int hashCode() { - return Objects.hash(author, comment, ip); + return Objects.hash(author, identity, comment, ip); } @Override @@ -86,6 +101,7 @@ public String toString() { return "AuditInfo{" + "author='" + author + '\'' + + ", identity='" + identity + '\'' + ", comment='" + comment + '\'' + ", ip='" + ip + '\'' + '}'; diff --git a/processing/src/test/java/org/apache/druid/audit/AuditInfoTest.java b/processing/src/test/java/org/apache/druid/audit/AuditInfoTest.java index 888a4d11f074..c0f3dc69118d 100644 --- a/processing/src/test/java/org/apache/druid/audit/AuditInfoTest.java +++ b/processing/src/test/java/org/apache/druid/audit/AuditInfoTest.java @@ -19,12 +19,18 @@ package org.apache.druid.audit; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.druid.jackson.DefaultObjectMapper; import org.apache.druid.java.util.common.DateTimes; import org.junit.Assert; import org.junit.Test; +import java.io.IOException; + public class AuditInfoTest { + private final ObjectMapper mapper = new DefaultObjectMapper(); + @Test public void testAuditInfoEquality() { @@ -34,21 +40,22 @@ public void testAuditInfoEquality() Assert.assertEquals(auditInfo1.hashCode(), auditInfo2.hashCode()); } + @Test + public void testAuditInfoSerde() throws IOException + { + final AuditInfo auditInfo = new AuditInfo("author", "comment", "ip"); + AuditInfo deserialized = mapper.readValue(mapper.writeValueAsString(auditInfo), AuditInfo.class); + Assert.assertEquals(auditInfo, deserialized); + + final AuditInfo auditInfoWithIdentity = new AuditInfo("author", "identity", "comment", "ip"); + deserialized = mapper.readValue(mapper.writeValueAsString(auditInfoWithIdentity), AuditInfo.class); + Assert.assertEquals(auditInfoWithIdentity, deserialized); + } + @Test(timeout = 60_000L) - public void testAuditEntryEquality() + public void testAuditEntrySerde() throws IOException { - final AuditEntry event1 = new AuditEntry( - "testKey", - "testType", - new AuditInfo( - "testAuthor", - "testComment", - "127.0.0.1" - ), - AuditEntry.Payload.fromString("testPayload"), - DateTimes.of("2013-01-01T00:00:00Z") - ); - final AuditEntry event2 = new AuditEntry( + AuditEntry entry = new AuditEntry( "testKey", "testType", new AuditInfo( @@ -59,7 +66,8 @@ public void testAuditEntryEquality() AuditEntry.Payload.fromString("testPayload"), DateTimes.of("2013-01-01T00:00:00Z") ); - Assert.assertEquals(event1, event2); + AuditEntry serde = mapper.readValue(mapper.writeValueAsString(entry), AuditEntry.class); + Assert.assertEquals(entry, serde); } } diff --git a/server/src/main/java/org/apache/druid/server/audit/AuditSerdeHelper.java b/server/src/main/java/org/apache/druid/server/audit/AuditSerdeHelper.java index 85b6f527e0b0..29befd71e35c 100644 --- a/server/src/main/java/org/apache/druid/server/audit/AuditSerdeHelper.java +++ b/server/src/main/java/org/apache/druid/server/audit/AuditSerdeHelper.java @@ -31,6 +31,11 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +/** + * Audit utility class that can be used by different implementations of + * {@link org.apache.druid.audit.AuditManager} to serialize/deserialize audit + * payloads based on the values configured in {@link AuditManagerConfig}. + */ public class AuditSerdeHelper { /** @@ -60,12 +65,20 @@ public AuditSerdeHelper( this.jsonMapperSkipNulls = jsonMapperSkipNulls; } + /** + * Processes the given AuditEntry for further use such as logging or persistence. + * This involves serializing and truncating the payload based on the values + * configured in {@link AuditManagerConfig}. + * + * @return A new AuditEntry with a serialized payload that can be used for + * logging or persistence. + */ public AuditEntry processAuditEntry(AuditEntry entry) { final AuditEntry.Payload payload = entry.getPayload(); - final String serialized = payload.asString() == null + final String serialized = payload.serialized() == null ? serializePayloadToString(payload.raw()) - : payload.asString(); + : payload.serialized(); final AuditEntry.Payload processedPayload = AuditEntry.Payload.fromString( truncateSerializedAuditPayload(serialized) diff --git a/server/src/main/java/org/apache/druid/server/audit/SQLAuditManager.java b/server/src/main/java/org/apache/druid/server/audit/SQLAuditManager.java index d5b7b6de87dc..019f0156f42f 100644 --- a/server/src/main/java/org/apache/druid/server/audit/SQLAuditManager.java +++ b/server/src/main/java/org/apache/druid/server/audit/SQLAuditManager.java @@ -122,7 +122,7 @@ private ServiceMetricEvent.Builder createMetricEventBuilder(AuditEntry entry) .setDimension("created_date", entry.getAuditTime().toString()); if (config.isIncludePayloadAsDimensionInMetric()) { - builder.setDimension("payload", entry.getPayload().asString()); + builder.setDimension("payload", entry.getPayload().serialized()); } return builder; diff --git a/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java b/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java index e76a7401e2e3..e0e2faed69e2 100644 --- a/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java +++ b/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java @@ -35,6 +35,7 @@ import org.apache.druid.server.coordinator.DataSourceCompactionConfig; import org.apache.druid.server.coordinator.DataSourceCompactionConfigHistory; import org.apache.druid.server.http.security.ConfigResourceFilter; +import org.apache.druid.server.security.AuthorizationUtils; import org.joda.time.Interval; import javax.servlet.http.HttpServletRequest; @@ -107,7 +108,7 @@ public Response setCompactionTaskLimit( maxCompactionTaskSlots, useAutoScaleSlots ); - return updateConfigHelper(operator, new AuditInfo(author, comment, req.getRemoteAddr())); + return updateConfigHelper(operator, AuthorizationUtils.buildAuditInfo(author, comment, req)); } @POST @@ -132,7 +133,7 @@ public Response addOrUpdateCompactionConfig( }; return updateConfigHelper( callable, - new AuditInfo(author, comment, req.getRemoteAddr()) + AuthorizationUtils.buildAuditInfo(author, comment, req) ); } @@ -183,7 +184,7 @@ public Response getCompactionConfigHistory( DataSourceCompactionConfigHistory history = new DataSourceCompactionConfigHistory(dataSource); for (AuditEntry audit : auditEntries) { CoordinatorCompactionConfig coordinatorCompactionConfig = configManager.convertBytesToCompactionConfig( - audit.getPayload().asString().getBytes(StandardCharsets.UTF_8) + audit.getPayload().serialized().getBytes(StandardCharsets.UTF_8) ); history.add(coordinatorCompactionConfig, audit.getAuditInfo(), audit.getAuditTime()); } @@ -219,7 +220,7 @@ public Response deleteCompactionConfig( return CoordinatorCompactionConfig.from(current, ImmutableList.copyOf(configs.values())); }; - return updateConfigHelper(callable, new AuditInfo(author, comment, req.getRemoteAddr())); + return updateConfigHelper(callable, AuthorizationUtils.buildAuditInfo(author, comment, req)); } private Response updateConfigHelper( diff --git a/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java b/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java index 2b2afa9a9cdd..bf54afc8afda 100644 --- a/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java +++ b/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java @@ -22,6 +22,7 @@ import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import org.apache.druid.audit.AuditInfo; import org.apache.druid.error.DruidException; import org.apache.druid.java.util.common.ISE; @@ -89,6 +90,33 @@ public static AuthenticationResult authenticationResultFromRequest(final HttpSer return authenticationResult; } + /** + * Extracts the identity from the authentication result if set as an atrribute + * of this request. + */ + public static String getAuthenticatedIdentity(HttpServletRequest request) + { + final AuthenticationResult authenticationResult = (AuthenticationResult) request.getAttribute( + AuthConfig.DRUID_AUTHENTICATION_RESULT + ); + + if (authenticationResult == null) { + return null; + } else { + return authenticationResult.getIdentity(); + } + } + + public static AuditInfo buildAuditInfo(String author, String comment, HttpServletRequest request) + { + return new AuditInfo( + author, + getAuthenticatedIdentity(request), + comment, + request.getRemoteAddr() + ); + } + /** * Check a list of resource-actions to be performed by the identity represented by authenticationResult. * diff --git a/server/src/test/java/org/apache/druid/metadata/SQLMetadataRuleManagerTest.java b/server/src/test/java/org/apache/druid/metadata/SQLMetadataRuleManagerTest.java index 6368ab08fb2c..940b770d0868 100644 --- a/server/src/test/java/org/apache/druid/metadata/SQLMetadataRuleManagerTest.java +++ b/server/src/test/java/org/apache/druid/metadata/SQLMetadataRuleManagerTest.java @@ -189,7 +189,7 @@ public void testAuditEntryCreated() throws Exception Assert.assertEquals( rules, - mapper.readValue(entry.getPayload().asString(), new TypeReference>() {}) + mapper.readValue(entry.getPayload().serialized(), new TypeReference>() {}) ); Assert.assertEquals(auditInfo, entry.getAuditInfo()); Assert.assertEquals(DATASOURCE, entry.getKey()); @@ -222,7 +222,7 @@ public void testFetchAuditEntriesForAllDataSources() throws Exception for (AuditEntry entry : auditEntries) { Assert.assertEquals( rules, - mapper.readValue(entry.getPayload().asString(), new TypeReference>() {}) + mapper.readValue(entry.getPayload().serialized(), new TypeReference>() {}) ); Assert.assertEquals(auditInfo, entry.getAuditInfo()); } diff --git a/server/src/test/java/org/apache/druid/server/audit/SQLAuditManagerTest.java b/server/src/test/java/org/apache/druid/server/audit/SQLAuditManagerTest.java index 6a0d1fbc58f6..409b7196752b 100644 --- a/server/src/test/java/org/apache/druid/server/audit/SQLAuditManagerTest.java +++ b/server/src/test/java/org/apache/druid/server/audit/SQLAuditManagerTest.java @@ -110,7 +110,7 @@ public boolean isIncludePayloadAsDimensionInMetric() Assert.assertNotNull(dbEntry); Assert.assertEquals(dbEntry.getKey(), metric.getUserDims().get("key")); Assert.assertEquals(dbEntry.getType(), metric.getUserDims().get("type")); - Assert.assertEquals(dbEntry.getPayload().asString(), metric.getUserDims().get("payload")); + Assert.assertEquals(dbEntry.getPayload().serialized(), metric.getUserDims().get("payload")); Assert.assertEquals(dbEntry.getAuditInfo().getAuthor(), metric.getUserDims().get("author")); Assert.assertEquals(dbEntry.getAuditInfo().getComment(), metric.getUserDims().get("comment")); Assert.assertEquals(dbEntry.getAuditInfo().getIp(), metric.getUserDims().get("remote_address")); @@ -257,7 +257,7 @@ public long getMaxPayloadSizeBytes() // Assert.assertNotEquals(entry.getPayload(), dbEntry.getPayload()); Assert.assertEquals( "Payload truncated as it exceeds 'druid.audit.manager.maxPayloadSizeBytes'[10].", - dbEntry.getPayload().asString() + dbEntry.getPayload().serialized() ); Assert.assertEquals(entry.getType(), dbEntry.getType()); Assert.assertEquals(entry.getAuditInfo(), dbEntry.getAuditInfo()); @@ -309,13 +309,13 @@ public boolean isSkipNullField() AuditEntry.builder().key("key1").type("type1").auditInfo(auditInfo).payload(payloadMap).build() ); AuditEntry entryWithNulls = lookupAuditEntryForKey("key1"); - Assert.assertEquals("{\"something\":null,\"version\":\"x\"}", entryWithNulls.getPayload().asString()); + Assert.assertEquals("{\"something\":null,\"version\":\"x\"}", entryWithNulls.getPayload().serialized()); auditManagerSkipNull.doAudit( AuditEntry.builder().key("key2").type("type2").auditInfo(auditInfo).payload(payloadMap).build() ); AuditEntry entryWithoutNulls = lookupAuditEntryForKey("key2"); - Assert.assertEquals("{\"version\":\"x\"}", entryWithoutNulls.getPayload().asString()); + Assert.assertEquals("{\"version\":\"x\"}", entryWithoutNulls.getPayload().serialized()); } @After @@ -362,7 +362,7 @@ private AuditEntry createAuditEntry(String key, String type, DateTime auditTime) .key(key) .type(type) .serializedPayload(StringUtils.format("Test payload for key[%s], type[%s]", key, type)) - .auditInfo(new AuditInfo("author", "comment", "127.0.0.1")) + .auditInfo(new AuditInfo("author", "identity", "comment", "127.0.0.1")) .auditTime(auditTime) .build(); } diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java index f6ce89649b66..eceae4a08787 100644 --- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java +++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java @@ -193,7 +193,7 @@ public void testGetAllDatasourcesRuleHistoryWithWrongCount() private AuditInfo createAuditInfo() { - return new AuditInfo("testAuthor", "testComment", "127.0.0.1"); + return new AuditInfo("testAuthor", "testIdentity", "testComment", "127.0.0.1"); } private AuditEntry createAuditEntry(DateTime auditTime)