blob: 5a7a7f43b18d816a1a0ac71c9ab6e155810b7c64 [file] [log] [blame]
/*
* 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.ranger.audit.destination;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ranger.audit.model.AuditEventBase;
import org.apache.ranger.audit.model.AuthzAuditEvent;
import org.apache.ranger.audit.provider.MiscUtil;
import org.apache.ranger.audit.utils.InMemoryJAASConfiguration;
import org.apache.ranger.audit.utils.KerberosAction;
import org.apache.ranger.audit.utils.KerberosUser;
import org.apache.ranger.audit.utils.KerberosJAASConfigUser;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
import org.apache.solr.client.solrj.impl.LBHttpSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.login.LoginException;
import java.util.Arrays;
import java.util.Optional;
public class SolrAuditDestination extends AuditDestination {
private static final Log LOG = LogFactory
.getLog(SolrAuditDestination.class);
public static final String PROP_SOLR_URLS = "urls";
public static final String PROP_SOLR_ZK = "zookeepers";
public static final String PROP_SOLR_COLLECTION = "collection";
public static final String PROP_SOLR_FORCE_USE_INMEMORY_JAAS_CONFIG = "force.use.inmemory.jaas.config";
public static final String DEFAULT_COLLECTION_NAME = "ranger_audits";
public static final String PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG = "java.security.auth.login.config";
private volatile SolrClient solrClient = null;
private volatile KerberosUser kerberosUser = null;
public SolrAuditDestination() {
}
@Override
public void init(Properties props, String propPrefix) {
LOG.info("init() called");
super.init(props, propPrefix);
init();
connect();
}
@Override
public void stop() {
LOG.info("SolrAuditDestination.stop() called..");
logStatus();
if (solrClient != null) {
try {
solrClient.close();
} catch (IOException ioe) {
LOG.error("Error while stopping slor!", ioe);
} finally {
solrClient = null;
}
}
if (kerberosUser != null) {
try {
kerberosUser.logout();
} catch (LoginException excp) {
LOG.error("Error logging out keytab user", excp);
} finally {
kerberosUser = null;
}
}
}
synchronized void connect() {
SolrClient me = solrClient;
if (me == null) {
synchronized(SolrAuditDestination.class) {
me = solrClient;
if (solrClient == null) {
KeyManager[] kmList = getKeyManagers();
TrustManager[] tmList = getTrustManagers();
SSLContext sslContext = getSSLContext(kmList, tmList);
if(sslContext != null) {
SSLContext.setDefault(sslContext);
}
String urls = MiscUtil.getStringProperty(props, propPrefix
+ "." + PROP_SOLR_URLS);
if (urls != null) {
urls = urls.trim();
}
if (urls != null && urls.equalsIgnoreCase("NONE")) {
urls = null;
}
List<String> solrURLs = new ArrayList<String>();
String zkHosts = null;
solrURLs = MiscUtil.toArray(urls, ",");
zkHosts = MiscUtil.getStringProperty(props, propPrefix + "."
+ PROP_SOLR_ZK);
if (zkHosts != null && zkHosts.equalsIgnoreCase("NONE")) {
zkHosts = null;
}
String collectionName = MiscUtil.getStringProperty(props,
propPrefix + "." + PROP_SOLR_COLLECTION);
if (collectionName == null
|| collectionName.equalsIgnoreCase("none")) {
collectionName = DEFAULT_COLLECTION_NAME;
}
LOG.info("Solr zkHosts=" + zkHosts + ", solrURLs=" + urls
+ ", collectionName=" + collectionName);
if (zkHosts != null && !zkHosts.isEmpty()) {
LOG.info("Connecting to solr cloud using zkHosts="
+ zkHosts);
try {
// Instantiate
Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
SolrHttpClientBuilder kb = krbBuild.getBuilder();
HttpClientUtil.setHttpClientBuilder(kb);
final List<String> zkhosts = new ArrayList<String>(Arrays.asList(zkHosts.split(",")));
final CloudSolrClient solrCloudClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction<CloudSolrClient>() {
@Override
public CloudSolrClient run() throws Exception {
CloudSolrClient solrCloudClient = new CloudSolrClient.Builder(zkhosts, Optional.empty()).build();
return solrCloudClient;
};
});
solrCloudClient.setDefaultCollection(collectionName);
me = solrClient = solrCloudClient;
} catch (Throwable t) {
LOG.fatal("Can't connect to Solr server. ZooKeepers="
+ zkHosts, t);
}
finally {
resetInitializerInSOLR();
}
} else if (solrURLs != null && !solrURLs.isEmpty()) {
try {
LOG.info("Connecting to Solr using URLs=" + solrURLs);
Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
SolrHttpClientBuilder kb = krbBuild.getBuilder();
HttpClientUtil.setHttpClientBuilder(kb);
final List<String> solrUrls = solrURLs;
final LBHttpSolrClient lbSolrClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction<LBHttpSolrClient>() {
@Override
public LBHttpSolrClient run() throws Exception {
LBHttpSolrClient.Builder builder = new LBHttpSolrClient.Builder();
builder.withBaseSolrUrl(solrUrls.get(0));
builder.withConnectionTimeout(1000);
LBHttpSolrClient lbSolrClient = builder.build();
return lbSolrClient;
};
});
for (int i = 1; i < solrURLs.size(); i++) {
lbSolrClient.addSolrServer(solrURLs.get(i));
}
me = solrClient = lbSolrClient;
} catch (Throwable t) {
LOG.fatal("Can't connect to Solr server. URL="
+ solrURLs, t);
}
finally {
resetInitializerInSOLR();
}
}
}
}
}
}
private void resetInitializerInSOLR() {
javax.security.auth.login.Configuration solrConfig = javax.security.auth.login.Configuration.getConfiguration();
String solrConfigClassName = solrConfig.getClass().getName();
String solrJassConfigEnd = "SolrJaasConfiguration";
if (solrConfigClassName.endsWith(solrJassConfigEnd)) {
try {
Field f = solrConfig.getClass().getDeclaredField("initiateAppNames");
if (f != null) {
f.setAccessible(true);
HashSet<String> val = new HashSet<String>();
f.set(solrConfig, val);
if ( LOG.isDebugEnabled() ) {
LOG.debug("resetInitializerInSOLR: successfully reset the initiateAppNames");
}
} else {
if ( LOG.isDebugEnabled() ) {
LOG.debug("resetInitializerInSOLR: not applying on class [" + solrConfigClassName + "] as it does not have initiateAppNames variable name.");
}
}
} catch (Throwable t) {
logError("resetInitializerInSOLR: Unable to reset SOLRCONFIG.initiateAppNames to be empty", t);
}
}
else {
if ( LOG.isDebugEnabled() ) {
LOG.debug("resetInitializerInSOLR: not applying on class [" + solrConfigClassName + "] as it does not endwith [" + solrJassConfigEnd + "]");
}
}
}
@Override
public boolean log(Collection<AuditEventBase> events) {
boolean ret = false;
try {
logStatusIfRequired();
addTotalCount(events.size());
if (solrClient == null) {
connect();
if (solrClient == null) {
// Solr is still not initialized. So need return error
addDeferredCount(events.size());
return ret;
}
}
final Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
for (AuditEventBase event : events) {
AuthzAuditEvent authzEvent = (AuthzAuditEvent) event;
// Convert AuditEventBase to Solr document
SolrInputDocument document = toSolrDoc(authzEvent);
docs.add(document);
}
try {
final UpdateResponse response = addDocsToSolr(solrClient, docs);
if (response.getStatus() != 0) {
addFailedCount(events.size());
logFailedEvent(events, response.toString());
} else {
addSuccessCount(events.size());
ret = true;
}
} catch (SolrException ex) {
addFailedCount(events.size());
logFailedEvent(events, ex);
}
} catch (Throwable t) {
addDeferredCount(events.size());
logError("Error sending message to Solr", t);
}
return ret;
}
/*
* (non-Javadoc)
*
* @see org.apache.ranger.audit.provider.AuditProvider#flush()
*/
@Override
public void flush() {
}
SolrInputDocument toSolrDoc(AuthzAuditEvent auditEvent) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", auditEvent.getEventId());
doc.addField("access", auditEvent.getAccessType());
doc.addField("enforcer", auditEvent.getAclEnforcer());
doc.addField("agent", auditEvent.getAgentId());
doc.addField("repo", auditEvent.getRepositoryName());
doc.addField("sess", auditEvent.getSessionId());
doc.addField("reqUser", auditEvent.getUser());
doc.addField("reqData", auditEvent.getRequestData());
doc.addField("resource", auditEvent.getResourcePath());
doc.addField("cliIP", auditEvent.getClientIP());
doc.addField("logType", auditEvent.getLogType());
doc.addField("result", auditEvent.getAccessResult());
doc.addField("policy", auditEvent.getPolicyId());
doc.addField("repoType", auditEvent.getRepositoryType());
doc.addField("resType", auditEvent.getResourceType());
doc.addField("reason", auditEvent.getResultReason());
doc.addField("action", auditEvent.getAction());
doc.addField("evtTime", auditEvent.getEventTime());
doc.addField("seq_num", auditEvent.getSeqNum());
doc.setField("event_count", auditEvent.getEventCount());
doc.setField("event_dur_ms", auditEvent.getEventDurationMS());
doc.setField("tags", auditEvent.getTags());
doc.setField("cluster", auditEvent.getClusterName());
doc.setField("zoneName", auditEvent.getZoneName());
doc.setField("agentHost", auditEvent.getAgentHostname());
doc.setField("policyVersion", auditEvent.getPolicyVersion());
return doc;
}
public boolean isAsync() {
return true;
}
private void init() {
LOG.info("==>SolrAuditDestination.init()" );
try {
// SolrJ requires "java.security.auth.login.config" property to be set to identify itself that it is kerberized. So using a dummy property for it
// Acutal solrclient JAAS configs are read from the ranger-<component>-audit.xml present in components conf folder and set by InMemoryJAASConfiguration
// Refer InMemoryJAASConfiguration doc for JAAS Configuration
String confFileName = System.getProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG);
LOG.info("In solrAuditDestination.init() : JAAS Configuration set as [" + confFileName + "]");
if ( System.getProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG) == null ) {
if ( MiscUtil.getBooleanProperty(props, propPrefix + "." + PROP_SOLR_FORCE_USE_INMEMORY_JAAS_CONFIG,false) ) {
System.setProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG, "/dev/null");
} else {
LOG.warn("No Client JAAS config present in solr audit config. Ranger Audit to Kerberized Solr will fail...");
}
}
LOG.info("Loading SolrClient JAAS config from Ranger audit config if present...");
InMemoryJAASConfiguration conf = InMemoryJAASConfiguration.init(props);
KerberosUser kerberosUser = new KerberosJAASConfigUser("Client", conf);
if (kerberosUser.getPrincipal() != null) {
this.kerberosUser = kerberosUser;
}
} catch (Exception e) {
LOG.error("ERROR: Unable to load SolrClient JAAS config from Audit config file. Audit to Kerberized Solr will fail...", e);
} finally {
String confFileName = System.getProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG);
LOG.info("In solrAuditDestination.init() (finally) : JAAS Configuration set as [" + confFileName + "]");
}
LOG.info("<==SolrAuditDestination.init()" );
}
private KeyManager[] getKeyManagers() {
KeyManager[] kmList = null;
String credentialProviderPath = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_CLIENT_KEY_FILE_CREDENTIAL);
String keyStoreAlias = RANGER_POLICYMGR_CLIENT_KEY_FILE_CREDENTIAL_ALIAS;
String keyStoreFile = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_CLIENT_KEY_FILE);
String keyStoreFilepwd = MiscUtil.getCredentialString(credentialProviderPath, keyStoreAlias);
if (StringUtils.isNotEmpty(keyStoreFile) && StringUtils.isNotEmpty(keyStoreFilepwd)) {
InputStream in = null;
try {
in = getFileInputStream(keyStoreFile);
if (in != null) {
String keyStoreType = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_CLIENT_KEY_FILE_TYPE);
keyStoreType = StringUtils.isNotEmpty(keyStoreType) ? keyStoreType : RANGER_POLICYMGR_CLIENT_KEY_FILE_TYPE_DEFAULT;
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(in, keyStoreFilepwd.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(RANGER_SSL_KEYMANAGER_ALGO_TYPE);
keyManagerFactory.init(keyStore, keyStoreFilepwd.toCharArray());
kmList = keyManagerFactory.getKeyManagers();
} else {
LOG.error("Unable to obtain keystore from file [" + keyStoreFile + "]");
}
} catch (KeyStoreException e) {
LOG.error("Unable to obtain from KeyStore :" + e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
LOG.error("SSL algorithm is NOT available in the environment", e);
} catch (CertificateException e) {
LOG.error("Unable to obtain the requested certification ", e);
} catch (FileNotFoundException e) {
LOG.error("Unable to find the necessary SSL Keystore Files", e);
} catch (IOException e) {
LOG.error("Unable to read the necessary SSL Keystore Files", e);
} catch (UnrecoverableKeyException e) {
LOG.error("Unable to recover the key from keystore", e);
} finally {
close(in, keyStoreFile);
}
}
return kmList;
}
private TrustManager[] getTrustManagers() {
TrustManager[] tmList = null;
String credentialProviderPath = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_TRUSTSTORE_FILE_CREDENTIAL);
String trustStoreAlias = RANGER_POLICYMGR_TRUSTSTORE_FILE_CREDENTIAL_ALIAS;
String trustStoreFile = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_TRUSTSTORE_FILE);
String trustStoreFilepwd = MiscUtil.getCredentialString(credentialProviderPath, trustStoreAlias);
if (StringUtils.isNotEmpty(trustStoreFile) && StringUtils.isNotEmpty(trustStoreFilepwd)) {
InputStream in = null;
try {
in = getFileInputStream(trustStoreFile);
if (in != null) {
String trustStoreType = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_TRUSTSTORE_FILE_TYPE);
trustStoreType = StringUtils.isNotEmpty(trustStoreType) ? trustStoreType : RANGER_POLICYMGR_TRUSTSTORE_FILE_TYPE_DEFAULT;
KeyStore trustStore = KeyStore.getInstance(trustStoreType);
trustStore.load(in, trustStoreFilepwd.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(RANGER_SSL_TRUSTMANAGER_ALGO_TYPE);
trustManagerFactory.init(trustStore);
tmList = trustManagerFactory.getTrustManagers();
} else {
LOG.error("Unable to obtain truststore from file [" + trustStoreFile + "]");
}
} catch (KeyStoreException e) {
LOG.error("Unable to obtain from KeyStore", e);
} catch (NoSuchAlgorithmException e) {
LOG.error("SSL algorithm is NOT available in the environment :" + e.getMessage(), e);
} catch (CertificateException e) {
LOG.error("Unable to obtain the requested certification :" + e.getMessage(), e);
} catch (FileNotFoundException e) {
LOG.error("Unable to find the necessary SSL TrustStore File:" + trustStoreFile, e);
} catch (IOException e) {
LOG.error("Unable to read the necessary SSL TrustStore Files :" + trustStoreFile, e);
} finally {
close(in, trustStoreFile);
}
}
return tmList;
}
private SSLContext getSSLContext(KeyManager[] kmList, TrustManager[] tmList) {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance(RANGER_SSL_CONTEXT_ALGO_TYPE);
if (sslContext != null) {
sslContext.init(kmList, tmList, new SecureRandom());
}
} catch (NoSuchAlgorithmException e) {
LOG.error("SSL algorithm is not available in the environment", e);
} catch (KeyManagementException e) {
LOG.error("Unable to initialise the SSLContext", e);
}
return sslContext;
}
private UpdateResponse addDocsToSolr(final SolrClient solrClient, final Collection<SolrInputDocument> docs) throws Exception {
final UpdateResponse ret;
try {
final PrivilegedExceptionAction<UpdateResponse> action = () -> solrClient.add(docs);
if (kerberosUser != null) {
// execute the privileged action as the given keytab user
final KerberosAction kerberosAction = new KerberosAction<>(kerberosUser, action, LOG);
ret = (UpdateResponse) kerberosAction.execute();
} else {
ret = action.run();
}
} catch (Exception e) {
throw e;
}
return ret;
}
private InputStream getFileInputStream(String fileName) throws IOException {
InputStream in = null;
if (StringUtils.isNotEmpty(fileName)) {
File file = new File(fileName);
if (file != null && file.exists()) {
in = new FileInputStream(file);
} else {
in = ClassLoader.getSystemResourceAsStream(fileName);
}
}
return in;
}
private void close(InputStream str, String filename) {
if (str != null) {
try {
str.close();
} catch (IOException excp) {
LOG.error("Error while closing file: [" + filename + "]", excp);
}
}
}
}