SENTRY-804: Add Audit Log Support for Solr Sentry Handlers (Gregory Chanan, Reviewed by: Vamsee Yarlagadda)
Project: http://git-wip-us.apache.org/repos/asf/incubator-sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-sentry/commit/f5445bbc Tree: http://git-wip-us.apache.org/repos/asf/incubator-sentry/tree/f5445bbc Diff: http://git-wip-us.apache.org/repos/asf/incubator-sentry/diff/f5445bbc Branch: refs/heads/hive_plugin_v2 Commit: f5445bbc602ede901ed4cc707e2ce11ee8961a31 Parents: 7eb7c7d Author: Vamsee Yarlagadda <[email protected]> Authored: Mon Jul 20 15:20:00 2015 -0700 Committer: Vamsee Yarlagadda <[email protected]> Committed: Mon Jul 20 15:20:00 2015 -0700 ---------------------------------------------------------------------- sentry-solr/solr-sentry-handlers/pom.xml | 1 - .../SecureDocumentAnalysisRequestHandler.java | 2 +- .../SecureFieldAnalysisRequestHandler.java | 2 +- .../solr/handler/SecureReplicationHandler.java | 2 +- .../solr/handler/SecureRequestHandlerUtil.java | 17 +- .../solr/handler/admin/SecureAdminHandlers.java | 16 +- .../handler/admin/SecureCollectionsHandler.java | 2 +- .../handler/admin/SecureCoreAdminHandler.java | 34 +++- .../QueryIndexAuthorizationComponent.java | 5 +- .../org/apache/solr/sentry/AuditLogger.java | 97 ++++++++++ .../RollingFileWithoutDeleteAppender.java | 176 +++++++++++++++++++ .../SentryIndexAuthorizationSingleton.java | 40 ++++- .../UpdateIndexAuthorizationProcessor.java | 26 +-- .../src/main/resources/log4j.properties | 13 ++ .../SentryIndexAuthorizationSingletonTest.java | 11 +- .../UpdateIndexAuthorizationProcessorTest.java | 36 ++-- 16 files changed, 416 insertions(+), 64 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/pom.xml b/sentry-solr/solr-sentry-handlers/pom.xml index 7acdd40..d6db69f 100644 --- a/sentry-solr/solr-sentry-handlers/pom.xml +++ b/sentry-solr/solr-sentry-handlers/pom.xml @@ -47,7 +47,6 @@ limitations under the License. <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>commons-logging</groupId> http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureDocumentAnalysisRequestHandler.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureDocumentAnalysisRequestHandler.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureDocumentAnalysisRequestHandler.java index 23886fe..9ecf139 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureDocumentAnalysisRequestHandler.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureDocumentAnalysisRequestHandler.java @@ -26,7 +26,7 @@ import org.apache.solr.response.SolrQueryResponse; public class SecureDocumentAnalysisRequestHandler extends DocumentAnalysisRequestHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - SecureRequestHandlerUtil.checkSentryCollection(req, SecureRequestHandlerUtil.QUERY_ONLY); + SecureRequestHandlerUtil.checkSentryCollection(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName()); super.handleRequestBody(req, rsp); } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureFieldAnalysisRequestHandler.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureFieldAnalysisRequestHandler.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureFieldAnalysisRequestHandler.java index 4a8809a..819227b 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureFieldAnalysisRequestHandler.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureFieldAnalysisRequestHandler.java @@ -26,7 +26,7 @@ import org.apache.solr.response.SolrQueryResponse; public class SecureFieldAnalysisRequestHandler extends FieldAnalysisRequestHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - SecureRequestHandlerUtil.checkSentryCollection(req, SecureRequestHandlerUtil.QUERY_ONLY); + SecureRequestHandlerUtil.checkSentryCollection(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName()); super.handleRequestBody(req, rsp); } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureReplicationHandler.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureReplicationHandler.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureReplicationHandler.java index 70e5c83..42213ae 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureReplicationHandler.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureReplicationHandler.java @@ -31,7 +31,7 @@ public class SecureReplicationHandler extends ReplicationHandler { // request handler collection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName(); } - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, true, collection); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, getClass().getName(), true, collection); super.handleRequestBody(req, rsp); } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureRequestHandlerUtil.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureRequestHandlerUtil.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureRequestHandlerUtil.java index 7ae5391..94341b3 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureRequestHandlerUtil.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/SecureRequestHandlerUtil.java @@ -43,17 +43,18 @@ public class SecureRequestHandlerUtil { * @param collection only relevant if checkCollection==true, * use collection (if non-null) instead pulling collection name from req (if null) */ - public static void checkSentryAdmin(SolrQueryRequest req, Set<SearchModelAction> andActions, boolean checkCollection, String collection) { - checkSentry(req, andActions, true, checkCollection, collection); + public static void checkSentryAdmin(SolrQueryRequest req, Set<SearchModelAction> andActions, + String operation, boolean checkCollection, String collection) { + checkSentry(req, andActions, operation, true, checkCollection, collection); } /** * Attempt to authorize a collection action. The collection * name will be pulled from the request. */ - public static void checkSentryCollection(SolrQueryRequest req, Set<SearchModelAction> andActions) { - checkSentry(req, andActions, false, false, null); - } + public static void checkSentryCollection(SolrQueryRequest req, Set<SearchModelAction> andActions, String operation) { + checkSentry(req, andActions, operation, false, false, null); + } /** * Attempt to sync collection privileges with Sentry when the metadata has changed. @@ -68,16 +69,16 @@ public class SecureRequestHandlerUtil { } private static void checkSentry(SolrQueryRequest req, Set<SearchModelAction> andActions, - boolean admin, boolean checkCollection, String collection) { + String operation, boolean admin, boolean checkCollection, String collection) { // Sentry currently does have AND support for actions; need to check // actions one at a time final SentryIndexAuthorizationSingleton sentryInstance = (testOverride == null)?SentryIndexAuthorizationSingleton.getInstance():testOverride; for (SearchModelAction action : andActions) { if (admin) { - sentryInstance.authorizeAdminAction(req, EnumSet.of(action), checkCollection, collection); + sentryInstance.authorizeAdminAction(req, EnumSet.of(action), operation, checkCollection, collection); } else { - sentryInstance.authorizeCollectionAction(req, EnumSet.of(action)); + sentryInstance.authorizeCollectionAction(req, EnumSet.of(action), operation); } } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureAdminHandlers.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureAdminHandlers.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureAdminHandlers.java index 5463754..88016ea 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureAdminHandlers.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureAdminHandlers.java @@ -112,7 +112,7 @@ public class SecureAdminHandlers extends AdminHandlers { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { // logging handler can be used both to read and change logs - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, false, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, getClass().getName(), false, null); super.handleRequestBody(req, rsp); } } @@ -120,7 +120,7 @@ public class SecureAdminHandlers extends AdminHandlers { public static class SecureLukeRequestHandler extends LukeRequestHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, true, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), true, null); super.handleRequestBody(req, rsp); } } @@ -128,7 +128,7 @@ public class SecureAdminHandlers extends AdminHandlers { public static class SecurePluginInfoHandler extends PluginInfoHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, true, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), true, null); super.handleRequestBody(req, rsp); } } @@ -136,7 +136,7 @@ public class SecureAdminHandlers extends AdminHandlers { public static class SecurePropertiesRequestHandler extends PropertiesRequestHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, false, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), false, null); super.handleRequestBody(req, rsp); } } @@ -145,7 +145,7 @@ public class SecureAdminHandlers extends AdminHandlers { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException, KeeperException, InterruptedException { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, true, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), true, null); super.handleRequestBody(req, rsp); } } @@ -153,7 +153,7 @@ public class SecureAdminHandlers extends AdminHandlers { public static class SecureSolrInfoMBeanHandler extends SolrInfoMBeanHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, true, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), true, null); super.handleRequestBody(req, rsp); } } @@ -171,7 +171,7 @@ public class SecureAdminHandlers extends AdminHandlers { public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { // this may or may not have the core SolrCore core = req.getCore(); - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, core != null, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), core != null, null); super.handleRequestBody(req, rsp); } } @@ -179,7 +179,7 @@ public class SecureAdminHandlers extends AdminHandlers { public static class SecureThreadDumpHandler extends ThreadDumpHandler { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, false, null); + SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, getClass().getName(), false, null); super.handleRequestBody(req, rsp); } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCollectionsHandler.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCollectionsHandler.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCollectionsHandler.java index 0a471a4..15a6ba0 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCollectionsHandler.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCollectionsHandler.java @@ -75,7 +75,7 @@ public class SecureCollectionsHandler extends CollectionsHandler { } // all actions require UPDATE privileges SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.UPDATE_ONLY, - true, collection); + (action != null ? "CollectionAction." + action.toString() : getClass().getName() + "/" + a), true, collection); super.handleRequestBody(req, rsp); /** http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCoreAdminHandler.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCoreAdminHandler.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCoreAdminHandler.java index 36ef6d0..77548b9 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCoreAdminHandler.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/admin/SecureCoreAdminHandler.java @@ -17,16 +17,14 @@ package org.apache.solr.handler.admin; * limitations under the License. */ -import java.util.EnumSet; -import org.apache.solr.core.SolrCore; -import org.apache.sentry.core.model.search.SearchModelAction; import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction; import org.apache.solr.common.params.SolrParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.SolrCore; import org.apache.solr.handler.SecureRequestHandlerUtil; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; -import org.apache.solr.core.CoreContainer; /** * Secure (sentry-aware) version of CoreAdminHandler @@ -67,7 +65,12 @@ public class SecureCoreAdminHandler extends CoreAdminHandler { action = CoreAdminAction.get(a); if (action == null) { // some custom action -- let's reqiure QUERY and UPDATE - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, true, null); + SecureRequestHandlerUtil.checkSentryAdmin( + req, + SecureRequestHandlerUtil.QUERY_AND_UPDATE, + "CoreAdminAction." + a, + true, + null); } } String collection = null; @@ -117,7 +120,12 @@ public class SecureCoreAdminHandler extends CoreAdminHandler { switch (action) { case STATUS: case REQUESTSTATUS: { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_ONLY, checkCollection, collection); + SecureRequestHandlerUtil.checkSentryAdmin( + req, + SecureRequestHandlerUtil.QUERY_ONLY, + "CoreAdminAction." + action.toString(), + checkCollection, + collection); break; } case LOAD: @@ -141,12 +149,22 @@ public class SecureCoreAdminHandler extends CoreAdminHandler { case TRANSIENT: case REQUESTBUFFERUPDATES: case OVERSEEROP: { - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.UPDATE_ONLY, checkCollection, collection); + SecureRequestHandlerUtil.checkSentryAdmin( + req, + SecureRequestHandlerUtil.UPDATE_ONLY, + "CoreAdminAction." + action.toString(), + checkCollection, + collection); break; } default: { // some custom action -- let's reqiure QUERY and UPDATE - SecureRequestHandlerUtil.checkSentryAdmin(req, SecureRequestHandlerUtil.QUERY_AND_UPDATE, checkCollection, collection); + SecureRequestHandlerUtil.checkSentryAdmin( + req, + SecureRequestHandlerUtil.QUERY_AND_UPDATE, + "CoreAdminAction." + action.toString(), + checkCollection, + collection); break; } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/QueryIndexAuthorizationComponent.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/QueryIndexAuthorizationComponent.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/QueryIndexAuthorizationComponent.java index e4b5741..8f68f40 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/QueryIndexAuthorizationComponent.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/QueryIndexAuthorizationComponent.java @@ -29,6 +29,7 @@ import java.util.List; public class QueryIndexAuthorizationComponent extends SearchComponent { + private static final String OPERATION_NAME = "query"; private static Logger log = LoggerFactory.getLogger(QueryIndexAuthorizationComponent.class); private SentryIndexAuthorizationSingleton sentryInstance; @@ -46,7 +47,7 @@ public class QueryIndexAuthorizationComponent extends SearchComponent @Override public void prepare(ResponseBuilder rb) throws IOException { sentryInstance.authorizeCollectionAction( - rb.req, EnumSet.of(SearchModelAction.QUERY)); + rb.req, EnumSet.of(SearchModelAction.QUERY), OPERATION_NAME); String collections = rb.req.getParams().get("collection"); if (collections != null) { List<String> collectionList = StrUtils.splitSmart(collections, ",", true); @@ -61,7 +62,7 @@ public class QueryIndexAuthorizationComponent extends SearchComponent // correct sentry check for (String coll : collectionList) { sentryInstance.authorizeCollectionAction(rb.req, EnumSet.of(SearchModelAction.QUERY), - coll, true); + OPERATION_NAME, coll, true); } } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/AuditLogger.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/AuditLogger.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/AuditLogger.java new file mode 100644 index 0000000..7f3e391 --- /dev/null +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/AuditLogger.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.sentry; + + +import org.apache.lucene.util.Version; +import org.noggit.CharArr; +import org.noggit.JSONWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Writes audit events to the audit log. This helps answer questions such as: + * Who did what action when from where, and what values were changed from what + * to what as a result? + */ +final class AuditLogger { + + public static final int ALLOWED = 1; + public static final int UNAUTHORIZED = 0; + + private final Logger logger; + + private static final boolean IS_ENABLED = + Boolean.valueOf( + System.getProperty(AuditLogger.class.getName() + ".isEnabled", "true")); + + private static final String SOLR_VERSION = Version.LATEST.toString(); + + + public AuditLogger() { + this.logger = LoggerFactory.getLogger(getClass()); + } + + public boolean isLogEnabled() { + return IS_ENABLED && logger.isInfoEnabled(); + } + + public void log( + String userName, + String impersonator, + String ipAddress, + String operation, + String operationParams, + long eventTime, + int allowed, + String collectionName) { + + if (!isLogEnabled()) { + return; + } + CharArr chars = new CharArr(512); + JSONWriter writer = new JSONWriter(chars, -1); + writer.startObject(); + writeField("solrVersion", SOLR_VERSION, writer); + writer.writeValueSeparator(); + writeField("eventTime", eventTime, writer); + writer.writeValueSeparator(); + writeField("allowed", allowed, writer); + writer.writeValueSeparator(); + writeField("collectionName", collectionName, writer); + writer.writeValueSeparator(); + writeField("operation", operation, writer); + writer.writeValueSeparator(); + writeField("operationParams", operationParams, writer); + writer.writeValueSeparator(); + writeField("ipAddress", ipAddress, writer); + writer.writeValueSeparator(); + writeField("username", userName, writer); + writer.writeValueSeparator(); + writeField("impersonator", impersonator, writer); + writer.endObject(); + logger.info("{}", chars); + } + + private void writeField(String key, Object value, JSONWriter writer) { + writer.writeString(key); + writer.writeNameSeparator(); + writer.write(value); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java new file mode 100644 index 0000000..ec26ef3 --- /dev/null +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java @@ -0,0 +1,176 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.solr.sentry; + +import java.io.File; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.Writer; +import java.nio.file.Files; + +import org.apache.log4j.FileAppender; +import org.apache.log4j.Layout; +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +public class RollingFileWithoutDeleteAppender extends FileAppender { + /** + * The default maximum file size is 10MB. + */ + protected long maxFileSize = 10 * 1024 * 1024; + + private long nextRollover = 0; + + /** + * The default constructor simply calls its {@link FileAppender#FileAppender + * parents constructor}. + */ + public RollingFileWithoutDeleteAppender() { + super(); + } + + /** + * Instantiate a RollingFileAppender and open the file designated by + * <code>filename</code>. The opened filename will become the ouput + * destination for this appender. + * <p> + * If the <code>append</code> parameter is true, the file will be appended to. + * Otherwise, the file desginated by <code>filename</code> will be truncated + * before being opened. + */ + public RollingFileWithoutDeleteAppender(Layout layout, String filename, + boolean append) throws IOException { + super(layout, getLogFileName(filename), append); + } + + /** + * Instantiate a FileAppender and open the file designated by + * <code>filename</code>. The opened filename will become the output + * destination for this appender. + * <p> + * The file will be appended to. + */ + public RollingFileWithoutDeleteAppender(Layout layout, String filename) + throws IOException { + super(layout, getLogFileName(filename)); + } + + /** + * Get the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + */ + public long getMaximumFileSize() { + return maxFileSize; + } + + /** + * Implements the usual roll over behaviour. + * <p> + * <code>File</code> is renamed <code>File.yyyyMMddHHmmss</code> and closed. A + * new <code>File</code> is created to receive further log output. + */ + // synchronization not necessary since doAppend is alreasy synched + public void rollOver() { + if (qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + LogLog.debug("rolling over count=" + size); + // if operation fails, do not roll again until + // maxFileSize more bytes are written + nextRollover = size + maxFileSize; + } + + this.closeFile(); // keep windows happy. + + String newFileName = getLogFileName(fileName); + try { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(newFileName, false, bufferedIO, bufferSize); + nextRollover = 0; + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("setFile(" + newFileName + ", false) call failed.", e); + } + } + + public synchronized void setFile(String fileName, boolean append, + boolean bufferedIO, int bufferSize) throws IOException { + super.setFile(fileName, append, this.bufferedIO, this.bufferSize); + if (append) { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + /** + * Set the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + * <p> + * This method is equivalent to {@link #setMaxFileSize} except that it is + * required for differentiating the setter taking a <code>long</code> argument + * from the setter taking a <code>String</code> argument by the JavaBeans + * {@link java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + * <p> + * In configuration files, the <b>MaxFileSize</b> option takes an long integer + * in the range 0 - 2^63. You can specify the value with the suffixes "KB", + * "MB" or "GB" so that the integer is interpreted being expressed + * respectively in kilobytes, megabytes or gigabytes. For example, the value + * "10KB" will be interpreted as 10240. + */ + public void setMaxFileSize(String value) { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) { + this.qw = new CountingQuietWriter(writer, errorHandler); + } + + /** + * This method differentiates RollingFileAppender from its super class. + */ + protected void subAppend(LoggingEvent event) { + super.subAppend(event); + + if (fileName != null && qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + if (size >= maxFileSize && size >= nextRollover) { + rollOver(); + } + } + } + + // Mangled file name. Append the current timestamp + private static String getLogFileName(String oldFileName) { + return oldFileName + "." + Long.toString(System.currentTimeMillis()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/SentryIndexAuthorizationSingleton.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/SentryIndexAuthorizationSingleton.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/SentryIndexAuthorizationSingleton.java index 53c8946..185884b 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/SentryIndexAuthorizationSingleton.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/sentry/SentryIndexAuthorizationSingleton.java @@ -46,6 +46,7 @@ public class SentryIndexAuthorizationSingleton { new SentryIndexAuthorizationSingleton(System.getProperty(propertyName)); private final SolrAuthzBinding binding; + private final AuditLogger auditLogger = new AuditLogger(); private SentryIndexAuthorizationSingleton(String sentrySiteLocation) { SolrAuthzBinding tmpBinding = null; @@ -85,15 +86,15 @@ public class SentryIndexAuthorizationSingleton { * use collection (if non-null) instead pulling collection name from req (if null) */ public void authorizeAdminAction(SolrQueryRequest req, - Set<SearchModelAction> actions, boolean checkCollection, String collection) + Set<SearchModelAction> actions, String operation, boolean checkCollection, String collection) throws SolrException { - authorizeCollectionAction(req, actions, "admin", true); + authorizeCollectionAction(req, actions, operation, "admin", true); if (checkCollection) { // Let's not error out if we can't find the collection associated with an // admin action, it's pretty complicated to get all the possible administrative // actions correct. Instead, let's warn in the log and address any issues we // find. - authorizeCollectionAction(req, actions, collection, false); + authorizeCollectionAction(req, actions, operation, collection, false); } } @@ -102,8 +103,8 @@ public class SentryIndexAuthorizationSingleton { * name will be pulled from the request. */ public void authorizeCollectionAction(SolrQueryRequest req, - Set<SearchModelAction> actions) throws SolrException { - authorizeCollectionAction(req, actions, null, true); + Set<SearchModelAction> actions, String operation) throws SolrException { + authorizeCollectionAction(req, actions, operation, null, true); } /** @@ -117,34 +118,61 @@ public class SentryIndexAuthorizationSingleton { * cannot be located */ public void authorizeCollectionAction(SolrQueryRequest req, - Set<SearchModelAction> actions, String collectionName, boolean errorIfNoCollection) + Set<SearchModelAction> actions, String operation, String collectionName, + boolean errorIfNoCollection) throws SolrException { Subject superUser = new Subject(System.getProperty("solr.authorization.superuser", "solr")); Subject userName = new Subject(getUserName(req)); + long eventTime = req.getStartTime(); + String paramString = req.getParamString(); + String impersonator = null; // FIXME + + String ipAddress = null; + HttpServletRequest sreq = (HttpServletRequest) req.getContext().get("httpRequest"); + if (sreq != null) { + try { + ipAddress = sreq.getRemoteAddr(); + } catch (AssertionError e) { + ; // ignore + // This is a work-around for "Unexpected method call getRemoteAddr()" + // exception during unit test mocking at + // com.sun.proxy.$Proxy28.getRemoteAddr(Unknown Source) + } + } + if (collectionName == null) { SolrCore solrCore = req.getCore(); if (solrCore == null) { String msg = "Unable to locate collection for sentry to authorize because " + "no SolrCore attached to request"; if (errorIfNoCollection) { + auditLogger.log(userName.getName(), impersonator, ipAddress, + operation, paramString, eventTime, AuditLogger.UNAUTHORIZED, collectionName); throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED, msg); } else { // just warn log.warn(msg); + auditLogger.log(userName.getName(), impersonator, ipAddress, + operation, paramString, eventTime, AuditLogger.ALLOWED, collectionName); return; } } collectionName = solrCore.getCoreDescriptor().getCloudDescriptor().getCollectionName(); } + Collection collection = new Collection(collectionName); try { if (!superUser.getName().equals(userName.getName())) { binding.authorizeCollection(userName, collection, actions); } } catch (SentrySolrAuthorizationException ex) { + auditLogger.log(userName.getName(), impersonator, ipAddress, + operation, paramString, eventTime, AuditLogger.UNAUTHORIZED, collectionName); throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED, ex); } + auditLogger.log(userName.getName(), impersonator, ipAddress, + operation, paramString, eventTime, AuditLogger.ALLOWED, collectionName); } /** http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessor.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessor.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessor.java index 8cd53d3..5e60645 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessor.java +++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessor.java @@ -27,9 +27,8 @@ import org.apache.solr.update.DeleteUpdateCommand; import org.apache.solr.update.MergeIndexesCommand; import org.apache.solr.update.RollbackUpdateCommand; import org.apache.sentry.core.model.search.SearchModelAction; + import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.EnumSet; @@ -52,46 +51,53 @@ public class UpdateIndexAuthorizationProcessor extends UpdateRequestProcessor { this.req = req; } - public void authorizeCollectionAction() throws SolrException { + private void authorizeCollectionAction(String operation) throws SolrException { sentryInstance.authorizeCollectionAction( - req, EnumSet.of(SearchModelAction.UPDATE)); + req, EnumSet.of(SearchModelAction.UPDATE), operation); } @Override public void processAdd(AddUpdateCommand cmd) throws IOException { - authorizeCollectionAction(); + authorizeCollectionAction(cmd.name()); super.processAdd(cmd); } @Override public void processDelete(DeleteUpdateCommand cmd) throws IOException { - authorizeCollectionAction(); + String operation = cmd.name(); + if (cmd.isDeleteById()) { + operation += "ById"; + } else { + operation += "ByQuery"; + } + authorizeCollectionAction(operation); super.processDelete(cmd); } @Override public void processMergeIndexes(MergeIndexesCommand cmd) throws IOException { - authorizeCollectionAction(); + authorizeCollectionAction(cmd.name()); super.processMergeIndexes(cmd); } @Override public void processCommit(CommitUpdateCommand cmd) throws IOException { - authorizeCollectionAction(); + authorizeCollectionAction(cmd.name()); super.processCommit(cmd); } @Override public void processRollback(RollbackUpdateCommand cmd) throws IOException { - authorizeCollectionAction(); + authorizeCollectionAction(cmd.name()); super.processRollback(cmd); } @Override public void finish() throws IOException { - authorizeCollectionAction(); + authorizeCollectionAction("finish"); super.finish(); } + } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/main/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/main/resources/log4j.properties b/sentry-solr/solr-sentry-handlers/src/main/resources/log4j.properties index 62fdcd4..0e61f4a 100644 --- a/sentry-solr/solr-sentry-handlers/src/main/resources/log4j.properties +++ b/sentry-solr/solr-sentry-handlers/src/main/resources/log4j.properties @@ -20,6 +20,19 @@ # Logging level log4j.rootLogger=INFO, CONSOLE +log4j.logger.org.apache.solr.sentry.AuditLogger=INFO, solrAudit +#log4j.logger.org.apache.solr.sentry.AuditLogger=OFF + +# turn off appending to A1: +#log4j.additivity.org.apache.solr.sentry.AuditLogger=false + +log4j.appender.solrAudit=org.apache.solr.sentry.RollingFileWithoutDeleteAppender +log4j.appender.solrAudit.layout=org.apache.log4j.PatternLayout +log4j.appender.solrAudit.layout.ConversionPattern=%m%n +log4j.appender.solrAudit.File=target/temp/SOLR-1-SOLR_SERVER-d554cdf32962542b8c887a4f9fcbc079 +#log4j.appender.solrAudit.File=/var/log/solr/audit/SENTRY-1-SENTRY_SERVER-d554cdf32962542b8c887a4f9fcbc079 +log4j.appender.solrAudit.MaxFileSize=100MB + log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.err log4j.appender.CONSOLE.layout=org.apache.solr.util.SolrLogLayout http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/sentry/SentryIndexAuthorizationSingletonTest.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/sentry/SentryIndexAuthorizationSingletonTest.java b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/sentry/SentryIndexAuthorizationSingletonTest.java index 4bea251..a3d7d19 100644 --- a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/sentry/SentryIndexAuthorizationSingletonTest.java +++ b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/sentry/SentryIndexAuthorizationSingletonTest.java @@ -23,11 +23,10 @@ import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.sentry.core.model.search.SearchModelAction; +import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.ModifiableSolrParams; -import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.core.SolrCore; -// import org.apache.solr.servlet.SolrHadoopAuthenticationFilter; import org.apache.solr.request.LocalSolrQueryRequest; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; @@ -47,6 +46,8 @@ public class SentryIndexAuthorizationSingletonTest extends SentryTestBase { private static CloudDescriptor cloudDescriptor; private static SentryIndexAuthorizationSingleton sentryInstance; + private static final String OPERATION_NAME = "myOperation"; + @BeforeClass public static void beforeClass() throws Exception { core = createCore("solrconfig.xml", "schema-minimal.xml"); @@ -80,7 +81,7 @@ public class SentryIndexAuthorizationSingletonTest extends SentryTestBase { private void doExpectUnauthorized(SentryIndexAuthorizationSingleton singleton, SolrQueryRequest request, Set<SearchModelAction> actions, String msgContains) throws Exception { try { - singleton.authorizeCollectionAction(request, actions); + singleton.authorizeCollectionAction(request, actions, OPERATION_NAME); Assert.fail("Expected SolrException"); } catch (SolrException ex) { assertEquals(ex.code(), SolrException.ErrorCode.UNAUTHORIZED.code); @@ -144,7 +145,7 @@ public class SentryIndexAuthorizationSingletonTest extends SentryTestBase { prepareCollAndUser(core, request, "collection1", "junit"); sentryInstance.authorizeCollectionAction( - request, EnumSet.of(SearchModelAction.ALL)); + request, EnumSet.of(SearchModelAction.ALL), OPERATION_NAME); } /** @@ -157,7 +158,7 @@ public class SentryIndexAuthorizationSingletonTest extends SentryTestBase { prepareCollAndUser(core, request, "bogusCollection", "junit"); sentryInstance.authorizeCollectionAction( - request, EnumSet.of(SearchModelAction.ALL)); + request, EnumSet.of(SearchModelAction.ALL), OPERATION_NAME); } /** http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/f5445bbc/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessorTest.java ---------------------------------------------------------------------- diff --git a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessorTest.java b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessorTest.java index e297232..8feb5a7 100644 --- a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessorTest.java +++ b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/update/processor/UpdateIndexAuthorizationProcessorTest.java @@ -19,18 +19,25 @@ package org.apache.solr.update.processor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.TreeSet; import org.apache.commons.lang.mutable.MutableInt; import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.core.SolrCore; import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.sentry.SentryTestBase; +import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.sentry.SentrySingletonTestInstance; +import org.apache.solr.sentry.SentryTestBase; +import org.apache.solr.update.AddUpdateCommand; +import org.apache.solr.update.CommitUpdateCommand; +import org.apache.solr.update.DeleteUpdateCommand; +import org.apache.solr.update.MergeIndexesCommand; +import org.apache.solr.update.RollbackUpdateCommand; import org.junit.AfterClass; -import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -66,11 +73,15 @@ public class UpdateIndexAuthorizationProcessorTest extends SentryTestBase { } private void verifyAuthorized(String collection, String user) throws Exception { - getProcessor(collection, user).processAdd(null); - getProcessor(collection, user).processDelete(null); - getProcessor(collection, user).processMergeIndexes(null); - getProcessor(collection, user).processCommit(null); - getProcessor(collection, user).processRollback(null); + SolrQueryRequestBase req = new SolrQueryRequestBase(core, new MapSolrParams(new HashMap())) {}; + getProcessor(collection, user).processAdd(new AddUpdateCommand(req)); + getProcessor(collection, user).processDelete(new DeleteUpdateCommand(req)); + DeleteUpdateCommand deleteByQueryCommand = new DeleteUpdateCommand(req); + deleteByQueryCommand.setQuery("*:*"); + getProcessor(collection, user).processDelete(deleteByQueryCommand); + getProcessor(collection, user).processMergeIndexes(new MergeIndexesCommand(null, req)); + getProcessor(collection, user).processCommit(new CommitUpdateCommand(req, false)); + getProcessor(collection, user).processRollback(new RollbackUpdateCommand(req)); getProcessor(collection, user).finish(); } @@ -83,29 +94,30 @@ public class UpdateIndexAuthorizationProcessorTest extends SentryTestBase { private void verifyUnauthorized(String collection, String user) throws Exception { MutableInt numExceptions = new MutableInt(0); String contains = "User " + user + " does not have privileges for " + collection; + SolrQueryRequestBase req = new SolrQueryRequestBase(core, new MapSolrParams(new HashMap())) {}; try { - getProcessor(collection, user).processAdd(null); + getProcessor(collection, user).processAdd(new AddUpdateCommand(req)); } catch(SolrException ex) { verifyUnauthorizedException(ex, contains, numExceptions); } try { - getProcessor(collection, user).processDelete(null); + getProcessor(collection, user).processDelete(new DeleteUpdateCommand(req)); } catch(SolrException ex) { verifyUnauthorizedException(ex, contains, numExceptions); } try { - getProcessor(collection, user).processMergeIndexes(null); + getProcessor(collection, user).processMergeIndexes(new MergeIndexesCommand(null, req)); } catch(SolrException ex) { verifyUnauthorizedException(ex, contains, numExceptions); } try { - getProcessor(collection, user).processCommit(null); + getProcessor(collection, user).processCommit(new CommitUpdateCommand(req, false)); } catch(SolrException ex) { verifyUnauthorizedException(ex, contains, numExceptions); } try { - getProcessor(collection, user).processRollback(null); + getProcessor(collection, user).processRollback(new RollbackUpdateCommand(req)); } catch(SolrException ex) { verifyUnauthorizedException(ex, contains, numExceptions); }
