All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.floragunn.searchguard.auditlog.sink.KafkaSink Maven / Gradle / Ivy

/*
 * Copyright 2016-2018 by floragunn GmbH - All rights reserved
 * 
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed here is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * This software is free of charge for non-commercial and academic use. 
 * For commercial use in a production environment you have to obtain a license 
 * from https://floragunn.com
 * 
 */

package com.floragunn.searchguard.auditlog.sink;

import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Properties;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.LongSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.settings.Settings;

import com.floragunn.searchguard.auditlog.impl.AuditMessage;

public class KafkaSink extends AuditLogSink {

    private final String[] mandatoryProperties = new String []{"bootstrap_servers","topic_name"};
	private boolean valid = true;
	private Producer producer;
	private String topicName;

	public KafkaSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) {
		super(name, settings, settingsPrefix, fallbackSink);
		
		Settings sinkSettings = settings.getAsSettings(settingsPrefix);
		checkMandatorySinkSettings(sinkSettings);

		if (!valid) {
			log.error("Failed to configure Kafka producer, please check the logfile.");
			return;
		}
		
        final Properties producerProps = new Properties();
        
        for(String key: sinkSettings.names()) {
            if(!key.equals("topic_name")) {
                producerProps.put(key.replace('_', '.'), sinkSettings.get(key));
            }
        }
        
		producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName());
		producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
		topicName = sinkSettings.get("topic_name");

		//map path of
		//ssl.keystore.location
		//ssl.truststore.location
		//sasl.kerberos.kinit.cmd		
		
		final SecurityManager sm = System.getSecurityManager();

        if (sm != null) {
            sm.checkPermission(new SpecialPermission());
        }
        
        try {
            this.producer = AccessController.doPrivileged(new PrivilegedExceptionAction>() {
                @Override
                public KafkaProducer run() throws Exception {
                    return new KafkaProducer(producerProps);
                }
            });
        } catch (PrivilegedActionException e) {
            log.error("Failed to configure Kafka producer due to {}", e.getException(), e.getException());
            this.valid = false;
        }

	}

	@Override
	protected boolean doStore(AuditMessage msg) {
		if (!valid || producer == null) {
			return false;
		}
		
		ProducerRecord data = new ProducerRecord(topicName, msg.toJson());
		producer.send(data, new Callback() {
            
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
               if(exception == null) {
                   //log trace?
               } else {
                   log.error("Could not store message on Kafka topic {}", topicName, exception);
                   fallbackSink.store(msg);
               }
                
            }
        });
		return true;
	}

	@Override
	public boolean isHandlingBackpressure() {
		return true;
	}

	private void checkMandatorySinkSettings(Settings sinkSettings) {
	    for(String mandatory: mandatoryProperties) {
	        String value = sinkSettings.get(mandatory);
	        if (value == null || value.length() == 0) {
	            log.error("No value for {} provided in configuration, this endpoint will not work.", value);
	            this.valid = false;
	        }
	    }
	}

    @Override
    public void close() throws IOException {
        if(producer != null) {
            valid = false;
            producer.close();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy