/* * 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 com.opensoc.enrichment.common; import java.io.IOException; import java.util.List; import java.util.Map; import org.apache.commons.configuration.Configuration; import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.task.OutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Tuple; import backtype.storm.tuple.Values; import com.opensoc.enrichment.interfaces.EnrichmentAdapter; import com.opensoc.helpers.topology.ErrorGenerator; import com.opensoc.json.serialization.JSONEncoderHelper; import com.opensoc.metrics.MetricReporter; /** * Uses an adapter to enrich telemetry messages with additional metadata * entries. For a list of available enrichment adapters see * com.opensoc.enrichment.adapters. * <p> * At the moment of release the following enrichment adapters are available: * <p> * <ul> * * <li>geo = attaches geo coordinates to IPs * <li>whois = attaches whois information to domains * <li>host = attaches reputation information to known hosts * <li>CIF = attaches information from threat intelligence feeds * <ul> * <p> * <p> * Enrichments are optional **/ @SuppressWarnings({ "rawtypes", "serial" }) public class GenericEnrichmentBolt extends AbstractEnrichmentBolt { private static final Logger LOG = LoggerFactory .getLogger(GenericEnrichmentBolt.class); private JSONObject metricConfiguration; /** * @param adapter * Adapter for doing the enrichment * @return Instance of this class */ public GenericEnrichmentBolt withAdapter(EnrichmentAdapter adapter) { _adapter = adapter; return this; } /** * @param OutputFieldName * Fieldname of the output tuple for this bolt * @return Instance of this class */ public GenericEnrichmentBolt withOutputFieldName(String OutputFieldName) { _OutputFieldName = OutputFieldName; return this; } /** * @param EnrichmentTag * Defines what tag the enrichment will be tagged with in the * telemetry message * @return Instance of this class */ public GenericEnrichmentBolt withEnrichmentTag(String EnrichmentTag) { _enrichment_tag = EnrichmentTag; return this; } /** * @param MAX_CACHE_SIZE_OBJECTS_NUM * Maximum size of cache before flushing * @return Instance of this class */ public GenericEnrichmentBolt withMaxCacheSize(long MAX_CACHE_SIZE_OBJECTS_NUM) { _MAX_CACHE_SIZE_OBJECTS_NUM = MAX_CACHE_SIZE_OBJECTS_NUM; return this; } /** * @param MAX_TIME_RETAIN_MINUTES * Maximum time to retain cached entry before expiring * @return Instance of this class */ public GenericEnrichmentBolt withMaxTimeRetain(long MAX_TIME_RETAIN_MINUTES) { _MAX_TIME_RETAIN_MINUTES = MAX_TIME_RETAIN_MINUTES; return this; } /** * @param jsonKeys * Keys in the telemetry message that are to be enriched by this * bolt * @return Instance of this class */ public GenericEnrichmentBolt withKeys(List<String> jsonKeys) { _jsonKeys = jsonKeys; return this; } /** * @param config * A class for generating custom metrics into graphite * @return Instance of this class */ public GenericEnrichmentBolt withMetricConfiguration(Configuration config) { this.metricConfiguration = JSONEncoderHelper.getJSON(config .subset("com.opensoc.metrics")); return this; } @SuppressWarnings("unchecked") public void execute(Tuple tuple) { LOG.trace("[OpenSOC] Starting enrichment"); JSONObject in_json = null; String key = null; try { key = tuple.getStringByField("key"); in_json = (JSONObject) tuple.getValueByField("message"); if (in_json == null || in_json.isEmpty()) throw new Exception("Could not parse binary stream to JSON"); if(key == null) throw new Exception("Key is not valid"); LOG.trace("[OpenSOC] Received tuple: " + in_json); JSONObject message = (JSONObject) in_json.get("message"); if (message == null || message.isEmpty()) throw new Exception("Could not extract message from JSON: " + in_json); LOG.trace("[OpenSOC] Extracted message: " + message); for (String jsonkey : _jsonKeys) { LOG.trace("[OpenSOC] Processing:" + jsonkey + " within:" + message); String jsonvalue = (String) message.get(jsonkey); LOG.trace("[OpenSOC] Processing: " + jsonkey + " -> " + jsonvalue); if (null == jsonvalue) { LOG.trace("[OpenSOC] Key " + jsonkey + "not present in message " + message); continue; } // If the field is empty, no need to enrich if ( jsonvalue.length() == 0) { continue; } JSONObject enrichment = cache.getUnchecked(jsonvalue); LOG.trace("[OpenSOC] Enriched: " + jsonkey + " -> " + enrichment); if (enrichment == null) throw new Exception("[OpenSOC] Could not enrich string: " + jsonvalue); if (!in_json.containsKey("enrichment")) { in_json.put("enrichment", new JSONObject()); LOG.trace("[OpenSOC] Starting a string of enrichments"); } JSONObject enr1 = (JSONObject) in_json.get("enrichment"); if (enr1 == null) throw new Exception("Internal enrichment is empty"); if (!enr1.containsKey(_enrichment_tag)) { enr1.put(_enrichment_tag, new JSONObject()); LOG.trace("[OpenSOC] Starting a new enrichment"); } LOG.trace("[OpenSOC] ENR1 is: " + enr1); JSONObject enr2 = (JSONObject) enr1.get(_enrichment_tag); enr2.put(jsonkey, enrichment); LOG.trace("[OpenSOC] ENR2 is: " + enr2); enr1.put(_enrichment_tag, enr2); in_json.put("enrichment", enr1); } LOG.debug("[OpenSOC] Generated combined enrichment: " + in_json); _collector.emit("message", new Values(key, in_json)); _collector.ack(tuple); if (_reporter != null) { emitCounter.inc(); ackCounter.inc(); } } catch (Exception e) { LOG.error("[OpenSOC] Unable to enrich message: " + in_json); _collector.fail(tuple); if (_reporter != null) { failCounter.inc(); } JSONObject error = ErrorGenerator.generateErrorMessage("Enrichment problem: " + in_json, e); _collector.emit("error", new Values(error)); } } public void declareOutputFields(OutputFieldsDeclarer declearer) { declearer.declareStream("message", new Fields("key", "message")); declearer.declareStream("error", new Fields("message")); } @Override void doPrepare(Map conf, TopologyContext topologyContext, OutputCollector collector) throws IOException { LOG.info("[OpenSOC] Preparing Enrichment Bolt..."); _collector = collector; try { _reporter = new MetricReporter(); _reporter.initialize(metricConfiguration, GenericEnrichmentBolt.class); this.registerCounters(); } catch (Exception e) { LOG.info("[OpenSOC] Unable to initialize metrics reporting"); } LOG.info("[OpenSOC] Enrichment bolt initialized..."); } }