package biz.karms.sinkit.ejb.impl;
import biz.karms.sinkit.ejb.ArchiveService;
import biz.karms.sinkit.ejb.elastic.ElasticService;
import biz.karms.sinkit.ejb.elastic.logstash.LogstashClient;
import biz.karms.sinkit.ejb.util.IoCIdentificationUtils;
import biz.karms.sinkit.eventlog.EventLogRecord;
import biz.karms.sinkit.eventlog.VirusTotalRequestStatus;
import biz.karms.sinkit.exception.ArchiveException;
import biz.karms.sinkit.ioc.IoCRecord;
import biz.karms.sinkit.ioc.IoCVirusTotalReport;
import com.google.gson.Gson;
import org.apache.commons.collections.CollectionUtils;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static biz.karms.sinkit.ejb.elastic.ElasticResources.ELASTIC_DATE_FORMAT;
/**
* @author Tomas Kozel
*/
@Stateless
public class ArchiveServiceEJB implements ArchiveService {
public static final String ELASTIC_IOC_INDEX = "iocs";
public static final String ELASTIC_IOC_TYPE = "intelmq";
public static final String ELASTIC_LOG_INDEX = "logs";
public static final String ELASTIC_LOG_TYPE = "match";
private static final DateFormat DATEFORMATTER = new SimpleDateFormat(ELASTIC_DATE_FORMAT);
@Inject
private Logger log;
@Inject
private Gson gson;
@EJB
private ElasticService elasticService;
@Inject
private LogstashClient logstashClient;
@Override
public List<IoCRecord> findIoCsForDeactivation(final int hours) throws ArchiveException {
//log.info("Searching archive for active IoCs with seen.last older than " + hours + " hours.");
final Calendar c = Calendar.getInstance();
c.add(Calendar.HOUR, -hours);
final String tooOld = DATEFORMATTER.format(c.getTime());
final QueryBuilder query = QueryBuilders.filteredQuery(
QueryBuilders.termQuery("active", true),
FilterBuilders.rangeFilter("seen.last").lt(tooOld)
);
return elasticService.search(query, null, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, IoCRecord.class);
}
@Override
public List<IoCRecord> findIoCsForWhitelisting(final String sourceId) throws ArchiveException {
//log.info("Searching archive for active IoCs with seen.last older than " + hours + " hours.");
final String queryString = "active: true AND NOT whitelist_name: * AND " +
"(source.id.value: " + sourceId + " OR source.id.value: *." + sourceId + ")";
final QueryBuilder query = QueryBuilders.queryStringQuery(queryString).analyzeWildcard(true);
return elasticService.search(query, null, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, IoCRecord.class);
}
@Override
public boolean archiveReceivedIoCRecord(final IoCRecord ioc) throws ArchiveException {
//compute documentId
ioc.setDocumentId(IoCIdentificationUtils.computeHashedId(ioc));
//compute uniqueReference
ioc.setUniqueRef(IoCIdentificationUtils.computeUniqueReference(ioc));
final Map<String, Map<String, Object>> fieldsToUpdate = new HashMap<>();
fieldsToUpdate.put("seen", new HashMap<>());
fieldsToUpdate.get("seen").put("last", ioc.getSeen().getLast());
if (ioc.getFeed().getAccuracy() != null) {
fieldsToUpdate.put("feed", new HashMap<>());
fieldsToUpdate.get("feed").put("accuracy", ioc.getFeed().getAccuracy());
}
return elasticService.update(ioc.getDocumentId(), fieldsToUpdate, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, ioc);
}
@Override
public boolean setVirusTotalReportToIoCRecord(final IoCRecord ioc, final IoCVirusTotalReport[] reports) throws ArchiveException {
final Map<String, IoCVirusTotalReport[]> vtReports = new HashMap<>();
vtReports.put("virus_total_reports", reports);
return elasticService.update(ioc.getDocumentId(), vtReports, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, null);
}
@Override
public IoCRecord deactivateRecord(final IoCRecord ioc) throws ArchiveException {
/**
* Deactivation in archive: Old active IoC record is deleted and new one inactive is created.
* This is done because in elastic is not possible to update id of record.
*/
//delete old active ioc
elasticService.delete(ioc, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE);
//archive deactivated ioc with new id
ioc.getTime().setDeactivated(Calendar.getInstance().getTime());
ioc.setActive(false);
//IMPORTANT - id has to be computed after the deactivated time is set because it's part of the hash
ioc.setDocumentId(IoCIdentificationUtils.computeHashedId(ioc));
return elasticService.index(ioc, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE);
}
@Override
public IoCRecord setRecordWhitelisted(final IoCRecord ioc, final String whitelistName) throws ArchiveException {
ioc.getTime().setWhitelisted(Calendar.getInstance().getTime());
ioc.setWhitelistName(whitelistName);
return elasticService.index(ioc, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE);
}
@Override
public EventLogRecord archiveEventLogRecord(final EventLogRecord logRecord) throws ArchiveException {
final DateFormat df = new SimpleDateFormat("YYYY-MM-dd");
final String index = ELASTIC_LOG_INDEX + "-" + df.format(logRecord.getLogged());
log.log(Level.FINE, "elasticService.index logging logrecord, index=" + index);
return elasticService.index(logRecord, index, ELASTIC_LOG_TYPE);
}
@Override
public EventLogRecord archiveEventLogRecordUsingLogstash(EventLogRecord logRecord) throws ArchiveException {
final DateFormat df = new SimpleDateFormat("YYYY-MM-dd");
final String index = ELASTIC_LOG_INDEX + "-" + df.format(logRecord.getLogged());
log.log(Level.FINE, "archiveService.archiveEventLogRecordUsingLogstash logging logrecord, index=" + index);
logstashClient.sentToLogstash(logRecord, index, ELASTIC_LOG_TYPE);
return logRecord;
}
@Override
public List<IoCRecord> getActiveNotWhitelistedIoCs(final int from, final int size) throws ArchiveException {
final QueryBuilder query = QueryBuilders.filteredQuery(
QueryBuilders.termQuery("active", true),
FilterBuilders.missingFilter("whitelist_name")
);
return elasticService.search(query, null, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, from, size, IoCRecord.class);
}
@Override
public IoCRecord getIoCRecordById(final String id) throws ArchiveException {
//log.log(Level.WARNING, "getIoCRecordById: id: "+id+", ELASTIC_IOC_INDEX: "+ELASTIC_IOC_INDEX+", ELASTIC_IOC_TYPE: "+ELASTIC_IOC_TYPE);
return elasticService.getDocumentById(id, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, IoCRecord.class);
}
@Override
public IoCRecord getIoCRecordByUniqueRef(final String uniqueRef) throws ArchiveException {
final QueryBuilder query = QueryBuilders.termQuery("unique_ref", uniqueRef);
final SortBuilder sort = SortBuilders.fieldSort("time.received_by_core").order(SortOrder.DESC);
final List<IoCRecord> iocs = elasticService.search(query, sort, ELASTIC_IOC_INDEX, ELASTIC_IOC_TYPE, IoCRecord.class);
if (CollectionUtils.isEmpty(iocs)) {
return null;
}
if (iocs.size() > 1) {
log.warning("Search for IoC with uniqueRef: " + uniqueRef + " returned " + iocs.size() + " records, expected max one. " +
"Record with document_id: " + iocs.get(0).getDocumentId() + " was used as a reference. Please fix this inconsistency.");
}
return iocs.get(0);
}
@Override
public EventLogRecord getLogRecordWaitingForVTScan(final int notAllowedFailedMinutes) throws ArchiveException {
final QueryBuilder query = getWaitingLogRecordQuery(VirusTotalRequestStatus.WAITING, notAllowedFailedMinutes);
final SortBuilder sort = SortBuilders.fieldSort("logged").order(SortOrder.ASC);
final List<EventLogRecord> logRecords =
elasticService.search(query, sort, ELASTIC_LOG_INDEX + "-*",
ELASTIC_LOG_TYPE, 0, 1, EventLogRecord.class);
if (CollectionUtils.isEmpty(logRecords)) {
return null;
}
return logRecords.get(0);
}
@Override
public EventLogRecord getLogRecordWaitingForVTReport(final int notAllowedFailedMinutes) throws ArchiveException {
final QueryBuilder query = getWaitingLogRecordQuery(VirusTotalRequestStatus.WAITING_FOR_REPORT, notAllowedFailedMinutes);
final SortBuilder sort = SortBuilders.fieldSort("virus_total_request.processed")
.order(SortOrder.ASC)
.unmappedType("date");
final List<EventLogRecord> logRecords =
elasticService.search(query, sort, ELASTIC_LOG_INDEX + "-*",
ELASTIC_LOG_TYPE, 0, 1, EventLogRecord.class);
if (CollectionUtils.isEmpty(logRecords)) {
return null;
}
return logRecords.get(0);
}
private QueryBuilder getWaitingLogRecordQuery(final VirusTotalRequestStatus status, final int notAllowedFailedMinutes) {
final String statusTerm = gson.toJson(status).replace("\"", "");
final String notAllowedFailedRange = "now-" + notAllowedFailedMinutes + "m";
return QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("virus_total_request.status", statusTerm))
.mustNot(QueryBuilders.rangeQuery("virus_total_request.failed").gt(notAllowedFailedRange));
}
}