From 766c6b20c6246f34680d36ec4d57d7b36a1b89cc Mon Sep 17 00:00:00 2001 From: pierky Date: Tue, 16 Dec 2014 18:43:29 +0100 Subject: [PATCH] First release --- CONFIGURATION.md | 212 ++++++++++ LICENSE | 22 ++ README.md | 34 ++ TRANSFORMATIONS.md | 219 +++++++++++ distrib/cron | 14 + distrib/default_trigger | 21 + distrib/new-index-template.json | 26 ++ img/config_files.png | Bin 0 -> 43627 bytes img/data_flow.png | Bin 0 -> 30288 bytes install | 136 +++++++ pmacct-to-elasticsearch | 665 ++++++++++++++++++++++++++++++++ 11 files changed, 1349 insertions(+) create mode 100644 CONFIGURATION.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 TRANSFORMATIONS.md create mode 100644 distrib/cron create mode 100755 distrib/default_trigger create mode 100644 distrib/new-index-template.json create mode 100644 img/config_files.png create mode 100644 img/data_flow.png create mode 100755 install create mode 100755 pmacct-to-elasticsearch diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..3bf558d --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,212 @@ +# Configuration of pmacct-to-elasticsearch + +## How it works + +pmacct-to-elasticsearch reads pmacct JSON output and sends it to ElasticSearch. + +It works properly with two kinds of pmacct plugins: "memory" and "print". +The former, "memory", needs data to be passed to pmacct-to-elasticsearch's +stdin, while the latter, "print", needs a file to be written by pmacct +daemons, where pmacct-to-elasticsearch is instructed to read data from. + +For "print" plugins, a crontab job is needed to run pmacct client and to +redirect its output to pmacct-to-elasticsearch; for "memory" plugins the pmacct +daemon can directly execute pmacct-to-elasticsearch. More details will follow +within the rest of this document. + +![Configuration files](https://raw.github.com/pierky/pmacct-to-elasticsearch/master/img/config_files.png) + +Print plugins are preferable because, in case of pmacct daemon graceful +restart or shutdown, data are written to the output file and the trigger +is regularly executed. + +## 1-to-1 mapping with pmacct plugins + +For each pmacct's plugin you want to be processed by pmacct-to-elasticsearch +a configuration file must be present in the *CONF_DIR* directory to tell the +program how to process its output. + +Configuration file's name must be in the format *PluginName*.conf, where +*PluginName* is the name of the pmacct plugin to which the file refer to. + +Example: + + /etc/pmacct/nfacctd.conf: + + ! nfacctd configuration example + plugins: memory[my_mem], print[my_print] + + /etc/p2es/my_mem.conf + /etc/p2es/my_print.conf + +Basically these files tell pmacct-to-elasticsearch: + +1. where to read pmacct's output from; + +2. how to send output to ElasticSearch; + +3. (optionally) which transformations must be operated. + +To run pmacct-to-elasticsearch the first argument must be the *PluginName*, +in order to allow it to figure out what to do: + + pmacct-to-elasticsearch my_print + +## Configuration file syntax + +These files are in JSON format and contain the following keys: + +- **LogFile** [required]: path to the log file used by pmacct-to-elasticsearch + to write any error encountered while processing the output. + + It can contain some macros, which are replaced during execution: + *$PluginName*, *$IndexName*, *$Type* + + Log file will be automatically rotated every 1MB, for 3 times. + + **Default**: "/var/log/pmacct-to-elasticsearch-$PluginName.log" + +- **ES_URL** [required]: URL of ElasticSearch HTTP API. + + **Default**: "http://localhost:9200" + +- **ES_IndexName** [required]: name of the ElasticSearch index used to store + pmacct-to-elasticsearch output. + + It may contain Python strftime codes (http://strftime.org/) in order + to have periodic indices. + + Example: + "netflow-%Y-%m-%d" to have daily indices (netflow-YYYY-MM-DD) + + Default: no default provided + +- **ES_Type** [required]: ElasticSearch document type (_type field) used to store + pmacct-to-elasticsearch output. Similar to tables in relational DB. + + From the official reference guide + http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_basic_concepts.html#_type: + + > Within an index, you can define one or more types. A type is a logical + > category/partition of your index whose semantics is completely up to + > you. In general, a type is defined for documents that have a set of + > common fields. For example, let.s assume you run a blogging platform + > and store all your data in a single index. In this index, you may + > define a type for user data, another type for blog data, and yet + > another type for comments data." + + Default: no default provided + +- **ES_IndexTemplateFileName** [required]: name of the file containing the + template to be used when creating a new index. The file must be in the + *CONF_DIR* directory. + + **Default**: new-index-template.json (included in pmacct-to-elasticsearch) + + The default template provided with pmacct-to-elasticsearch has the + _source field enabled; if you want to save some storage disable it + by editing the new-index-template.json file: + + "_source" : { "enabled" : false } + +- **ES_FlushSize** [required]: how often to flush data to ElasticSearch BULK API. + + Set it to 0 to only send data once the whole input has been processed. + + **Default**: 5000 lines + +- **InputFile** [optional]: used mainly when configuring pmacct print plugins. + File used by pmacct-to-elasticsearch to read input data from (it + should coincide with pmacct's print plugin output file). + If omitted pmacct-to-elasticsearch will read data from stdin. + +- **Transformations** [optional]: the transformation matrix used to add new + fields to the output document sent to ElasticSearch for indexing. + + More details in the [TRANSFORMATIONS.md](TRANSFORMATIONS.md) file. + +This is an example of a basic configuration file: + + { + "ES_IndexName": "netflow-%Y-%m-%d", + "ES_Type": "ingress_traffic", + "InputFile": "/var/lib/pmacct/ingress_traffic.json", + } + +## Plugins configuration + +### Memory plugins + +For "memory" plugins, a crontab job is needed in order to periodically read +(and clear) the in-memory-table that pmacct uses to store data: + +Example of a command scheduled in crontab: + + pmacct -l -p /var/spool/pmacct/my_mem.pipe -s -O json -e | pmacct-to-elasticsearch my_mem + +In the example above, the pmacct client reads the in-memory-table +referenced by the **/var/spool/pmacct/my_mem.pipe** file and write the JSON +output to stdout, which in turn is redirected to the stdin of +pmacct-to-elasticsearch, that is executed with the **my_mem** argument in order +to let it to load the right configuration from **/etc/p2es/my_mem.conf**. + +### Print plugins + +For "print" plugins, the crontab job is not required but a feature of pmacct +may be used instead: the **print_trigger_exec** config key. +The print_trigger_exec key allows pmacct to directly run +pmacct-to-elasticsearch once the output has been fully written to the output +file. Since pmacct does not allow to pass arguments to programs executed using +the print_trigger_exec key, a trick is needed in order to let +pmacct-to-elasticsearch to understand what configuration to use: a trigger +file must be created for each "print" plugin and it has to execute the +program with the proper argument. + +Example: + + /etc/pmacct/nfacctd.conf: + + ! nfacctd configuration example + plugins: print[my_print] + print_output_file[my_print]: /var/lib/pmacct/my_print.json + print_output[my_print]: json + print_trigger_exec[my_print]: /etc/p2es/triggers/my_print + + /etc/p2es/triggers/my_print: + + #!/bin/sh + /usr/local/bin/pmacct-to-elasticsearch my_print & + + # chmod u+x /etc/p2es/triggers/my_print + + /etc/p2es/my_print.conf: + + { + ... + "InputFile": "/var/lib/pmacct/my_print.json" + ... + } + +In the example, the nfacctd daemon has a plugin named **my_print** that writes +its JSON output to **/var/lib/pmacct/my_print.json** and, when done, executes +the **/etc/p2es/triggers/my_print** program. The trigger program, in turn, runs +pmacct-to-elasticsearch with the **my_print** argument and detaches it. +The **my_print.conf** file contains the "InputFile" configuration key that points +to the aforementioned JSON output file (**/var/lib/pmacct/my_print.json**), where +the program will read data from. + +The trigger program may also be a symbolic link to the **default_trigger** script +provided, which runs pmacct-to-elasticsearch with its own file name as first +argument: + + # cd /etc/p2es/triggers/ + # ln -s default_trigger my_print + + /etc/p2es/triggers/default_trigger: + + #!/bin/sh + PLUGIN_NAME=`basename $0` + /usr/local/bin/pmacct-to-elasticsearch $PLUGIN_NAME & + +Otherwise, remember to use the full path of pmacct-to-elasticsearch in order +to avoid problems with a stripped version of the *PATH* environment variable. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..28e0147 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Pier Carlo Chiodi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c44f9fb --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +pmacct-to-elasticsearch +======================= + +**pmacct-to-elasticsearch** is a python script designed to read JSON output from **pmacct** daemons, to process it and to store it into **ElasticSearch**. It works with both *memory* and *print* plugins and, optionally, it can perform **manipulations on data** (such as to add fields on the basis of other values). + +![Data flow](https://raw.github.com/pierky/pmacct-to-elasticsearch/master/img/data_flow.png) + +1. **pmacct daemons** collect IP accounting data and process them with their plugins; +2. data are stored into **in-memory-tables** (*memory* plugins) or **JSON files** (*print* plugins); +3. **crontab jobs** (*memory* plugins) or **trigger scripts** (*print* plugins) are invoked to execute pmacct-to-elasticsearch; +4. JSON records are finally processed by **pmacct-to-elasticsearch**, which reads them from stdin (*memory* plugins) or directly from JSON file. + +Optionally, some **data transformations** can be configured, to allow pmacct-to-elasticsearch to **add or remove fields** to/from the output documents that are sent to ElasticSearch for indexing. These additional fields may be useful to enhance graphs and reports legibility, or to add a further level of aggregation or filtering. + +## Installation + +Clone the repository and run the ./install script: + + # cd /usr/local/src/ + # git clone https://github.com/pierky/pmacct-to-elasticsearch.git + # cd pmacct-to-elasticsearch/ + # ./install + +## Configuration + +Please refer to the [CONFIGURATION.md](CONFIGURATION.md) file. The [TRANSFORMATIONS.md](TRANSFORMATIONS.md) file contains details about data transformations configuration. + +A simple tutorial on pmacct integration with ElasticSearch/Kibana using pmacct-to-elasticsearch can be found at http://blog.pierky.com/integration-of-pmacct-with-elasticsearch-and-kibana. + +## Author + +Pier Carlo Chiodi - http://pierky.com/aboutme + +Blog: http://blog.pierky.com Twitter: [@pierky](http://twitter.com/pierky) diff --git a/TRANSFORMATIONS.md b/TRANSFORMATIONS.md new file mode 100644 index 0000000..5823e6c --- /dev/null +++ b/TRANSFORMATIONS.md @@ -0,0 +1,219 @@ +BE WARNED: Transformations and Conditions are highly experimental, test them +before using them in a production system. + +Transofrmations allow pmacct-to-elasticsearch to analyze input data and add +additional information to the output that it sends to ElasticSearch. These +additional fields may be useful to enhance graphs and reports legibility, or +to add a further level of aggregation or filtering. + +For example: given an input set of data reporting the ingress router and +interface, a transformation matrix allow to add a new field with the peer's +friendly name: + + pmacct output = pmacct-to-elasticsearch input: + { "peer_ip_src": "10.0.0.1", "iface_in": 10, "packets": 2, "bytes": 100 } + + pmacct-to-elasticsearch output = ElasticSearch indexed document: + { "peer_ip_src": "10.0.0.1", "iface_in": 10, "packets": 2, "bytes": 100, "peer_name": "MyUpstreamProvider" } + +Transofrmations are based on **Conditions** and **Actions**: if a condition is +satisfied then all its actions are performed. + +For each record received from pmacct, pmacct-to-elasticsearch verifies if its +fields match one or more conditions and, in case, performs the related actions. + +## Syntax + +Syntax is JSON based and refers to the "Transformations" key referenced in the +[CONFIGURATION.md](CONFIGURATION.md) file: + + { + ... + "Transformations": [ + { + "Conditions": , + "Actions": + }, + { + "Conditions": , + "Actions": + }, + { + ... + } + ] + ... + } + +### Conditions + +- **Conditions** = `[ ( "AND"|"OR" ), , , ]` + + If omitted, "AND" is used. + +- **Criteria** = ` | { "": (, "__op__": "[ = | < | <= | > | >= | != | in | notin ] " ) }` + + If omitted, operator is "=" (equal). + For "in" and "notin" operators, a list is expected as *value*: + + { "field": [ "a", "b" ], "__op__": "in" } + +Examples: + + Bob, older than 15: + [ { "Name": "Bob" }, { "Age": 16, "__op__": ">=" } ] + + Bob or Tom: + [ "OR", { "Name": "Bob" }, { "Name": "Tom" } ] + + Bob, only if he's older than 15, otherwise Tom or Lisa, only if she's older than 20 + + [ "OR", [ { "Name": "Bob" }, { "Age": 16, "__op__": ">=" } ], { "Name": "Tom" }, [ { "Name": "Lisa" }, { "Age": 20, "__op__": ">=" } ] ] + +### Actions + +- **Actions** = ```[ { "Type": "", }, + { "Type": "", }, + { ... } ]``` + +- **ActionType** == "**AddField**", action's details = + + "Name": "", + "Value": "" + + Sets the "*destination_field_name*" field to "*new_value*"; if + "*destination_field_name*" field does not exist, creates it. + + Macros can be used in "*new_value*". + +- **ActionType** == "**AddFieldLookup**", action's details = + + "Name": "", + "LookupFieldName": "", + "LookupTable": { + "": "", + "": "", + "": "" + } + "LookupTableFile": "" + + If "*key_field*" field is present in the input, searches the lookup + table for "*key_field*" value and, eventually, sets + "*destination_field_name*" field to the "*new_value*" found. + If "*key_field*" is not present in the input dataset but a "*" key is + present in the lookup table then its value is used to set + "*destination_field_name*" field. + + The lookup table can be written directly in the configuration file + (using "LookupTable" key) or referenced as an external file + ("LookupTableFile" key). + + Macros can be used in "*new_value*". + +- **ActionType** == "**DelField**", action's details = + + "Name": "" + + If "*field_name*" is present in the output dataset, it is removed. + +## Macros + +Macros can be used to refer to fields already present in the output dataset; +their syntax is $fieldname. + +## Examples + +1. Add peer's friendly name to ingress traffic: + + { ... + "Transformations": [ + { + "Conditions": [ { "peer_ip_src": "10.0.0.1" }, { "iface_in": 1 } ], + "Actions": [ { "Type": "AddField", "Name": "peer_name", "Value": "MyUpstream1" } ] + }, + { + "Conditions": [ { "peer_ip_src": "192.168.0.1" }, { "iface_in": 10 } ], + "Actions": [ { "Type": "AddField", "Name": "peer_name", "Value": "MyUpstream2" } ] + } + ] + ... } + + If "peer_ip_src" = "10.0.0.1" and "iface_in" = 1, set "peer_name" to + "MyUpstream1". Similar for the second condition. + + input: + { "peer_ip_src": "10.0.0.1", "iface_in": 1, "packets": 2, "bytes": 100 } + { "peer_ip_src": "192.168.0.1", "iface_in": 10, "packets": 4, "bytes": 400 } + output: + { "peer_ip_src": "10.0.0.1", "iface_in": 1, "packets": 2, "bytes": 100, "peer_name": "MyUpstream1" } + { "peer_ip_src": "192.168.0.1", "iface_in": 10, "packets": 4, "bytes": 400, "peer_name": "MyUpstream2" } + +2. Add Autonomous System name to source AS: + + { ... + "Transformations": [ + "Conditions": [ { "as_src": "", "__op__": "!=" } ], + "Actions": [ + { "Type": "AddFieldLookup", "Name": "as_src_name", + "LookupFieldName": "as_src", + "LookupTableFile": "/etc/p2es/AS_map.json" + ] + ] + ... } + + + /etc/p2es/AS_map.json: + + { + "36040": "$as_src - YouTube", + "15169": "$as_src - Google", + "20940": "$as_src - Akamai", + "*": "$as_src" + } + + If "as_src" is not empty, use its value to lookup the table in + **/etc/p2es/AS_map.json**; if a corresponding value is found, use it to fill the + new "as_src_name" field with "ASN - Name" values, otherwise fill if with + only the ASN. + + input: + { "as_src": 36040, "packets": 1, "flows": 1, "bytes": 100 } + { "as_src": 20940, "packets": 5, "flows": 5, "bytes": 500 } + { "as_src": 32934, "packets": 8, "flows": 4, "bytes": 300 } + output: + { "as_src": 36040, "packets": 1, "flows": 1, "bytes": 100, "as_src_name": "36040 - YouTube" } + { "as_src": 20940, "packets": 5, "flows": 5, "bytes": 500, "as_src_name": "20940 - Akamai" } + { "as_src": 32934, "packets": 8, "flows": 4, "bytes": 300, "as_src_name": "32934" } + +3. Another version of example 1: add peer's friendly name to ingress traffic: + + + { ... + "Transformations": [ + { + "Conditions": [ "AND", { "peer_ip_src": "", "__op__": "!=" }, { "iface_in": "", "__op__": "!=" } ], + "Action": [ { "Type": "AddField", "Name": "temporary1", "Value": "$peer_ip_src-$iface_in" } ] + }, + { + "Conditions": [ { "temporary1": "", "__op__": "!=" } ], + "Actions": [ + { + "Type": "AddFieldLookup", + "Name": "peer_name", + "LookupFieldName": "temporary1", + "LookupTable": { + "10.0.0.1-1": "MyUpstream1", + "192.168.0.1-10": "MyUpstream2" + } + }, + { "Type": "DelField", "Name": "temporary1" } + ] + } + ] + ... } + + If "peer_ip_src" and "iface_in" are not empty, add a new temporary field named + "temporary1" with "*peer_ip_src*-*iface_in*". Next, if "temporary1" field has + been filled and it's not empty, use it to lookup the table in order to find the + corresponding peer's friendly name. Finally, remove the temporary field from + the output dataset. diff --git a/distrib/cron b/distrib/cron new file mode 100644 index 0000000..8778016 --- /dev/null +++ b/distrib/cron @@ -0,0 +1,14 @@ +# /etc/cron.d/pmacct-to-elasticsearch: crontab fragment for pmacct-to-elasticsearch + +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin: + +# m h dom mon dow user command + +# Example: dump data from pmacct in-memory-table and process them with pmacct-to-elasticsearch +# every 5 minutes +# +#*/5 * * * * root pmacct -l -p /var/spool/pmacct/my_plugin.pipe -s -O json -e | pmacct-to-elasticsearch my_plugin +# + +#EOF diff --git a/distrib/default_trigger b/distrib/default_trigger new file mode 100755 index 0000000..45f59be --- /dev/null +++ b/distrib/default_trigger @@ -0,0 +1,21 @@ +#!/bin/sh + +# Default trigger to run pmacct-to-elasticsearch from +# pmacct "print_trigger_exec" configuration key. +# +# You can copy and edit this file or create a link to +# it in the format: +# +# -> ./default_trigger +# +# Example: +# +# # cd /etc/p2es/triggers/ +# # ln -s default_trigger plugin2 +# # ls -l +# total 4 +# -rwxr--r-- 1 root root 90 Dec 15 20:20 default_trigger +# lrwxrwxrwx 1 root root 15 Dec 15 20:21 plugin2 -> default_trigger + +PLUGIN_NAME=`basename $0` +/pmacct-to-elasticsearch $PLUGIN_NAME & diff --git a/distrib/new-index-template.json b/distrib/new-index-template.json new file mode 100644 index 0000000..28eef28 --- /dev/null +++ b/distrib/new-index-template.json @@ -0,0 +1,26 @@ +{ + "mappings": { + "_default_": { + "_all" : { "enabled" : false }, + + "_source" : { "enabled" : true }, + + "dynamic_templates": [ + { + "string_template" : { + "match" : "*", + "match_mapping_type" : "string", + "mapping": { "type": "string", "index": "not_analyzed" } + } + } + ], + + "properties": { + "@timestamp": { "type": "date" }, + "bytes": { "type": "long" }, + "packets": { "type": "long" }, + "flows": { "type": "long" } + } + } + } +} diff --git a/img/config_files.png b/img/config_files.png new file mode 100644 index 0000000000000000000000000000000000000000..2a6313f2a91f13f9a559b57a606e5055bba588c5 GIT binary patch literal 43627 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�=$G4(saAqXfc^Xd1dXWE&}Br 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/dev/null + + if [ $? -ne 0 ]; then + echo "ERROR - exit code $?" + exit 1 + else + mkdir $CONF_DIR/triggers &>/dev/null + + if [ $? -ne 0 ]; then + echo "ERROR creating triggers subdirectory - exit code $?" + exit 1 + else + echo "OK" + fi + fi +else + echo "OK (already exists)" +fi + +# ----------------------------------------------------------- + +echo -n "Copying default configuration files in $CONF_DIR... " + +cp -i distrib/new-index-template.json $CONF_DIR + +if [ $? -ne 0 ]; then + echo "ERROR - exit code $?" + exit 1 +else + echo "OK" +fi + +# ----------------------------------------------------------- + +echo -n "Copying default trigger in $CONF_DIR/triggers... " + +if [ -f $CONF_DIR/triggers/default_trigger ]; then + echo "" + read -p "- Default trigger already exists at $CONF_DIR/triggers/default_trigger: overwrite it? [yes|NO] " PROCEED +else + PROCEED="yes" +fi + +if [ "$PROCEED" == "yes" ]; then + BIN_DIR_SED="${BIN_DIR//\//\\/}" + sed -e "s//$BIN_DIR_SED/" distrib/default_trigger > $CONF_DIR/triggers/default_trigger + + if [ $? -ne 0 ]; then + echo "ERROR - exit code $?" + exit 1 + else + echo "OK" + chmod u+x $CONF_DIR/triggers/default_trigger + fi +else + echo "Skipped" +fi + +# ----------------------------------------------------------- + +echo -n "Copying pmacct-to-elasticsearch program to $BIN_DIR... " + +cp pmacct-to-elasticsearch $BIN_DIR/ + +if [ $? -ne 0 ]; then + echo "ERROR - exit code $?" + exit 1 +else + echo "OK" + chmox u+x $BIN_DIR/pmacct-to-elasticsearch +fi + +# ----------------------------------------------------------- + +echo -n "Copying crontab job fragment to /etc/cron.d... " + +if [ -d /etc/cron.d ]; then + if [ ! -f /etc/cron.d/pmacct-to-elasticsearch ]; then + BIN_DIR_SED="${BIN_DIR//\//\\/}" + sed -e "s//$BIN_DIR_SED/" distrib/cron > /etc/cron.d/pmacct-to-elasticsearch + + if [ $? -ne 0 ]; then + echo "ERROR - exit code $?" + exit 1 + else + echo "OK" + fi + else + echo "skipped (/etc/cron.d/pmacct-to-elasticsearch already exists)" + fi +else + echo "ERROR - /etc/cron.d does not exist" + exit 1 +fi + +# ----------------------------------------------------------- + +echo "=====================" +echo "Installation complete" +echo "=====================" +echo "" + +if [ "$CONF_DIR" != "/etc/p2es" ]; then + echo "WARNING: the default configuration directory (CONF_DIR) has been changed from /etc/p2es to $CONF_DIR: please change the CONF_DIR variable in pmacct-to-elasticsearch too." +fi + +echo "Some configurations are needed now. Read the CONFIGURATION.md file for more details." +echo "" diff --git a/pmacct-to-elasticsearch b/pmacct-to-elasticsearch new file mode 100755 index 0000000..021d1e2 --- /dev/null +++ b/pmacct-to-elasticsearch @@ -0,0 +1,665 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 Pier Carlo Chiodi - http://www.pierky.com +# Licensed under The MIT License (MIT) - http://opensource.org/licenses/MIT + +import getopt +import logging +from logging.handlers import RotatingFileHandler +from copy import deepcopy +import fileinput +import select +import sys +import os.path +import json +import datetime +import urllib2 + +APP_NAME = 'pmacct-to-elasticsearch' +CURRENT_RELEASE = 'v0.1.0' + +CONF_DIR = '/etc/p2es' + +EXITCODE_OK = 0 +EXITCODE_OneOrMoreErrors= 1 + +EXITCODE_Program = 2 +EXITCODE_ElasticSearch = 3 + +DEF_CONFIG = { + 'LogFile': '/var/log/%s-$PluginName.log' % APP_NAME, + + 'ES_URL': 'http://localhost:9200', + 'ES_IndexName': '', + 'ES_Type': '', + + 'ES_FlushSize': 5000, + + 'InputFile': None, + + 'Transformations': [] +} + +CONFIG = DEF_CONFIG.copy() + +def ExpandMacro( In ): + if In is None: + return None + + Out = deepcopy( In ) + + Out = Out.replace( '$PluginName', CONFIG.get( 'PluginName' ) or 'default' ) + Out = Out.replace( '$IndexName', datetime.datetime.now().strftime( CONFIG.get( 'ES_IndexName' ) or 'default' ) ) + Out = Out.replace( '$Type', CONFIG.get( 'ES_Type' ) or 'default' ) + return Out + +# returns True or False +def SetupLogging( BaseLogFile=None ): + if BaseLogFile: + LogFilePath = ExpandMacro( BaseLogFile ) + else: + LogFilePath = None + + logger = logging.getLogger(APP_NAME) + + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + + logger.setLevel(logging.INFO) + + logger.handlers = [] + + if LogFilePath: + # Log to stdout also + if sys.stdout.isatty(): + try: + hdlr = logging.StreamHandler( sys.stdout ) + hdlr.setFormatter(formatter) + logger.addHandler(hdlr) + except: + pass + + try: + hdlr = logging.handlers.RotatingFileHandler( LogFilePath, maxBytes=1000000, backupCount=3 ) + hdlr.setFormatter(formatter) + logger.addHandler(hdlr) + except: + Log( logging.ERROR, 'Can\'t setup logging to file %s. Ensure it has write permissions for the current user.' % LogFilePath ) + return False + + else: + try: + hdlr = logging.StreamHandler( sys.stderr ) + hdlr.setFormatter(formatter) + logger.addHandler(hdlr) + except: + sys.stderr.write( 'Can\'t setup logging to stderr.\n' ) + return False + + return True + +def Log( lev, msg, exc_info=False ): + logger = logging.getLogger(APP_NAME) + logger.log( lev, msg, exc_info=exc_info ) + +def Usage(): + print('') + print('%s %s' % ( APP_NAME, CURRENT_RELEASE ) ) + print('by Pier Carlo Chiodi (aka Pierky)') + print('http://www.pierky.com/%s' % APP_NAME ) + print('') + print('Usage: %s [ options ] ' % APP_NAME) + print('') + print('Options:') + print(' -p, --print: only print output to stdout (does not send data to ElasticSearch)') + print(' -t, --test: only tests configuration (does not send data to ElasticSearch)') + print(' -h, --help: show this help') + print('') + print('Copyright (c) 2014 Pier Carlo Chiodi') + print('') + +def ExpandDataMacros( S, Dict ): + if S.find('$') >= 0: + Res = S + + for K in Dict.keys(): + Res = Res.replace( '$%s' % K, str( Dict[K] ) ) + return Res + else: + return S + +# See TRANSFORMATIONS.md file for details +def ParseConditions( C, D, OpField='__op__' ): + if isinstance( C, list ): + if len(C) > 0: + if isinstance( C[0], basestring ): + if C[0] == 'AND': + if len(C) > 2: + for subC in C[1:]: + if not ParseConditions( subC, D ): + return False + return True + else: + return False + + elif C[0] == 'OR': + if len(C) > 2: + for subC in C[1:]: + if ParseConditions( subC, D ): + return True + return False + else: + return True + + else: + raise Exception( 'Logical groups must begin with "AND" or "OR" ("%s" found)' % C[0] ) + else: + # default to "AND" if not specified + + for subC in C: + if not ParseConditions( subC, D ): + return False + return True + + else: + raise Exception( 'Empty list' ) + + elif isinstance( C, dict ): + Op = '=' + N = None + V = None + + for K in C.keys(): + if K == OpField: + Op = C[K] + + if not Op in [ '=', '>', '>=', '<', '<=', '!=', 'in', 'notin' ]: + raise Exception( 'Unexpected operator: "%s"' % Op ) + else: + if N is None: + N = K + V = C[K] + else: + raise Exception( 'Only one name/value pair allowed' ) + + if Op in [ 'in', 'notin' ] and not isinstance( V, list ): + raise Exception( 'The "%s" operator requires a list' % Op ) + + if not N is None: + if not N in D: + return False + + if Op == '=': + return D[N] == V + elif Op == '>': + return D[N] > V + elif Op == '>=': + return D[N] >= V + elif Op == '<': + return D[N] < V + elif Op == '<=': + return D[N] <= V + elif Op == '!=': + return D[N] != V + elif Op == 'in': + return D[N] in V + elif Op == 'notin': + return not D[N] in V + else: + raise Exception( 'Operator not implemented: "%s"' % Op ) + + else: + raise Exception( 'Name/value pair expected' ) + else: + raise Exception( 'Unexpected object type %s from %s' % ( type(C), str(C) ) ) + +# returns True or False +def TestTransformation( Transformation ): + Ret = True + + try: + TransformationDetails = 'Transformations matrix (%s)' % str( Transformation ) + except: + TransformationDetails = 'Transformations matrix' + + if not 'Conditions' in Transformation: + Log( logging.ERROR, '%s, "Conditions" is missing' % TransformationDetails ) + Ret = False + else: + try: + ParseConditions( Transformation['Conditions'], {} ) + except Exception as e: + Log( logging.ERROR, '%s, invalid "Conditions": %s' % ( TransformationDetails, str(e) ) ) + Ret = False + + if not 'Actions' in Transformation: + Log( logging.ERROR, '%s, "Actions is missing' % TransformationDetails ) + Ret = False + else: + for Action in Transformation['Actions']: + if not 'Type' in Action: + Log( logging.ERROR, '%s, "Type" is missing' % TransformationDetails ) + Ret = False + else: + TransformationDetails = TransformationDetails + ', action type = %s' % Action['Type'] + + if not Action['Type'] in [ 'AddField', 'AddFieldLookup', 'DelField' ]: + Log( logging.ERROR, '%s, "Type" unknown' % TransformationDetails ) + Ret = False + else: + if Action['Type'] in [ 'AddField', 'AddFieldLookup', 'DelField' ]: + if not 'Name' in Action: + Log( logging.ERROR, '%s, "Name" is missing' % TransformationDetails ) + Ret = False + + if Action['Type'] in [ 'AddField' ]: + if not 'Value' in Action: + Log( logging.ERROR, '%s, "Value" is missing for new field "%s"' % ( TransformationDetails, Action['Name'] ) ) + Ret = False + + if Action['Type'] in [ 'AddFieldLookup' ]: + if not 'LookupFieldName' in Action: + Log( logging.ERROR, '%s, "LookupFieldName" is missing for new field "%s"' % ( TransformationDetails, Action['Name'] ) ) + Ret = False + if 'LookupTable' in Action and 'LookupTableFile' in Action: + Log( logging.ERROR, '%s, only one from "LookupTable" and "LookupTableFile" allowed' % TransformationDetails ) + Ret = False + if not 'LookupTable' in Action and not 'LookupTableFile' in Action: + Log( logging.ERROR, '%s, "LookupTable" and "LookupTableFile" missing for new field "%s"' % ( TransformationDetails, Action['Name'] ) ) + Ret = False + else: + if 'LookupTableFile' in Action: + try: + LookupTable_File = open( Action['LookupTableFile'] ) + Action['LookupTable'] = json.load( LookupTable_File ) + LookupTable_File.close() + except: + Log( logging.ERROR, '%s, error loading lookup table from %s' % ( TransformationDetails, Action['LookupTableFile'] ) ) + Ret = False + + return Ret + +# returns exit code (EXITCODE_ElasticSearch, EXITCODE_OK or EXITCODE_OneOrMoreErrors) +def SendToElasticSearch( IndexName, Output ): + # HTTP bulk insert toward ES + + URL = '%s/%s/%s/_bulk' % ( CONFIG['ES_URL'], IndexName, CONFIG['ES_Type'] ) + + try: + HTTPResponse = urllib2.urlopen( URL, Output ) + except: + Log( logging.ERROR, 'Error while executing HTTP bulk insert on %s' % IndexName, exc_info=True ) + return EXITCODE_ElasticSearch + + # Interpreting HTTP bulk insert response + + HTTPPlainTextResponse = HTTPResponse.read() + + if( HTTPResponse.getcode() != 200 ): + Log( logging.ERROR, 'Bulk insert on %s failed - HTTP status code = %s - Response %s' % ( IndexName, HTTPResponse.getcode(), HTTPPlainTextResponse ) ) + return EXITCODE_ElasticSearch + + try: + JSONResponse = json.loads( HTTPPlainTextResponse ) + except: + Log( logging.ERROR, 'Error while decoding JSON HTTP response - first 100 characters: %s' % HTTPPlainTextResponse[:100], exc_info=True ) + return EXITCODE_ElasticSearch + + if JSONResponse['errors']: + Log( logging.WARNING, 'Bulk insert on %s failed to process one or more documents' % IndexName ) + return EXITCODE_OneOrMoreErrors + else: + return EXITCODE_OK + +# returns True or False +def CheckConfiguration(): + if not CONFIG['ES_IndexName']: + Log( logging.ERROR, 'ElasticSearch index name not provided') + return False + + if not CONFIG['ES_Type']: + Log( logging.ERROR, 'ElasticSearch type not provided') + return False + + if not CONFIG['ES_URL']: + Log( logging.ERROR, 'ElasticSearch URL not provided') + return False + + if not 'ES_IndexTemplateFileName' in CONFIG: + CONFIG['ES_IndexTemplateFileName'] = 'new-index-template.json' + else: + IndexTemplatePath = '%s/%s' % ( CONF_DIR, CONFIG['ES_IndexTemplateFileName'] ) + + if not os.path.isfile( IndexTemplatePath ): + Log( logging.ERROR, 'Can\'t find index template file %s' % IndexTemplatePath ) + return False + else: + try: + IndexTemplate_File = open( IndexTemplatePath ) + IndexTemplate = json.load( IndexTemplate_File ) + IndexTemplate_File.close() + except: + Log( logging.ERROR, 'Index template from %s is not in valid JSON format' % ( IndexTemplatePath ), exc_info=True ) + return EXITCODE_Program + + if CONFIG['ES_URL'].endswith('/'): + CONFIG['ES_URL'] = CONFIG['ES_URL'][:-1] + + if not 'ES_FlushSize' in CONFIG: + Log( logging.ERROR, 'Flush size not provided' ) + return False + else: + try: + CONFIG['ES_FlushSize'] = int(CONFIG['ES_FlushSize']) + except: + Log( logging.ERROR, 'Flush size must be a positive integer' ) + return False + + if CONFIG['ES_FlushSize'] < 0: + Log( logging.ERROR, 'Flush size must be a positive integer' ) + return False + + TransformationsOK = True + + if 'Transformations' in CONFIG: + for Transformation in CONFIG['Transformations']: + TransformationsOK = TransformationsOK and TestTransformation( Transformation ) + + if not TransformationsOK: + return False + + return True + +# return exit code (EXITCODE_OK, EXITCODE_ElasticSearch ) and True|False +def DoesIndexExist( IndexName ): + URL = '%s/%s' % ( CONFIG['ES_URL'], IndexName ) + + try: + HEADRequest = urllib2.Request(URL) + HEADRequest.get_method = lambda : 'HEAD' + HTTPResponse = urllib2.urlopen( HEADRequest ) + except urllib2.HTTPError, err: + if err.code == 404: + return EXITCODE_OK, False + else: + Log( logging.ERROR, 'Error while checking if %s index exists' % IndexName, exc_info=True ) + return EXITCODE_ElasticSearch, None + + return EXITCODE_OK, ( HTTPResponse.getcode() == 200 ) + +# return exit code +def CreateIndex( IndexName ): + + # index already exists? + + ExitCode, IndexExists = DoesIndexExist( IndexName ) + + if ExitCode != EXITCODE_OK: + return ExitCode + + if IndexExists: + return EXITCODE_OK + + # index does not exist, creating it + + TemplatePath = '%s/%s' % ( CONF_DIR, CONFIG['ES_IndexTemplateFileName'] ) + + try: + TemplateFile = open( TemplatePath, 'r' ) + Template = TemplateFile.read() + TemplateFile.close() + except: + Log( logging.ERROR, 'Error while reading index template from file %s' % TemplatePath, exc_info=True ) + return EXITCODE_Program + + URL = '%s/%s' % ( CONFIG['ES_URL'], IndexName ) + + try: + HTTPResponse = urllib2.urlopen( URL, Template ) + except: + # something went wrong but index now exists anyway? + + ExitCode, IndexExists = DoesIndexExist( IndexName ) + + if IndexExists: + return EXITCODE_OK + else: + Log( logging.ERROR, 'Error while creating index %s from template %s' % ( IndexName, TemplatePath ), exc_info=True ) + return EXITCODE_ElasticSearch + + return EXITCODE_OK + +# return exit code +def Main(): + if not SetupLogging(): + return EXITCODE_Program + + # Parsing command line arguments + + try: + opts, args = getopt.getopt( sys.argv[1:], 'pth', [ 'print', 'test', 'help', 'test-condition=', 'test-condition-data=' ] ) + + except getopt.GetoptError as err: + Log( logging.ERROR, str(err) ) + Usage() + return EXITCODE_Program + + TestOnly = False + PrintOnly = False + TestCondition = None + TestConditionData = {} + + for o, a in opts: + if o in ( '-h', '--help' ): + Usage() + return EXITCODE_OK + + elif o in ( '-t', '--test' ): + TestOnly = True + + elif o in ( '-p', '--print' ): + PrintOnly = True + + elif o in ( '--test-condition' ): + try: + TestCondition = json.loads( a ) + TestOnly = True + except: + Log( logging.ERROR, 'Invalid JSON object for %s option' % o ) + return EXITCODE_Program + + elif o in ( '--test-condition-data' ): + try: + TestConditionData = json.loads( a ) + TestOnly = True + except: + Log( logging.ERROR, 'Invalid JSON object for %s option' % o ) + return EXITCODE_Program + + if TestCondition: + print( "Tested condition evaluated to %s" % ParseConditions( TestCondition, TestConditionData ) ) + return EXITCODE_OK + + if args == []: + Log( logging.ERROR, 'Missing required argument: ' ) + Usage() + return EXITCODE_Program + + if len(args) > 1: + Log( logging.ERROR, 'Unexpected arguments: %s' % ' '.join( args[1:] ) ) + Usage() + return EXITCODE_Program + + CONFIG['PluginName'] = args[0] + + # Loading configuration + + NewConfigFileName = '%s.conf' % CONFIG['PluginName'] + + try: + NewConfig_File = open( '%s/%s' % ( CONF_DIR, NewConfigFileName ) ) + NewConfig = json.load( NewConfig_File ) + NewConfig_File.close() + except: + Log( logging.ERROR, 'Error loading configuration from %s/%s.conf' % ( CONF_DIR, NewConfigFileName ), exc_info=True ) + return EXITCODE_Program + + CONFIG.update( NewConfig ) + + if 'LogFile' in CONFIG: + if not SetupLogging( CONFIG['LogFile'] ): + return EXITCODE_Program + else: + Log( logging.ERROR, 'Missing LogFile' ) + return EXITCODE_Program + + # Checking configuration + + if not CheckConfiguration(): + return EXITCODE_Program + + if TestOnly: + print('Configuration tested successfully') + return EXITCODE_OK + + if not CONFIG['InputFile']: + r, w, x = select.select([sys.stdin], [], [], 0) + if not r: + Log( logging.ERROR, 'Error while reading input data from stdin' ) + return EXITCODE_Program + + # Creating index + + IndexName = datetime.datetime.now().strftime( CONFIG['ES_IndexName'] ) + + ExitCode = CreateIndex( IndexName ) + + if ExitCode != EXITCODE_OK: + return ExitCode + + # Timestamp for ES indexing (UTC) + + TS = datetime.datetime.utcnow().strftime( '%Y-%m-%dT%H:%M:%SZ' ) + + # Read pmacct's JSON output and perform transformations + + Output = '' + Count = 0 + ExitCode = EXITCODE_OK + + if CONFIG['InputFile']: + InputFile = ExpandMacro( CONFIG['InputFile'] ) + else: + InputFile = '-' + + try: + for line in fileinput.input( InputFile, mode='rU' ): + try: + JSONData = json.loads( line ) + except: + Log( logging.ERROR, 'Error while decoding pmacct\'s JSON output: %s' % line ) + break + + JSONData['@timestamp'] = TS + + try: + if 'Transformations' in CONFIG: + for Transformation in CONFIG['Transformations']: + if ParseConditions( Transformation['Conditions'], JSONData ): + for Action in Transformation['Actions']: + Action_Type = Action['Type'] + + if Action_Type == 'AddField': + NewVal = ExpandDataMacros( Action['Value'], JSONData ) + JSONData[ Action['Name'] ] = NewVal + + elif Action_Type == 'AddFieldLookup': + if Action['LookupFieldName'] in JSONData: + NewVal = None + + if str(JSONData[ Action['LookupFieldName'] ]) in Action['LookupTable']: + NewVal = Action['LookupTable'][ str(JSONData[ Action['LookupFieldName'] ]) ] + else: + if "*" in Action['LookupTable']: + NewVal = Action['LookupTable']['*'] + + if NewVal: + JSONData[ Action['Name'] ] = ExpandDataMacros( NewVal, JSONData ) + + elif Action_Type == 'DelField': + if Action['Name'] in JSONData: + del JSONData[ Action['Name'] ] + except: + Log( logging.ERROR, 'Error while applying transformations to pmacct\'s JSON output: %s' % line, exc_info=True ) + break + + Output = Output + '{"index":{}}' + '\n' + Output = Output + json.dumps( JSONData ) + '\n' + + if PrintOnly: + print(Output) + Output = '' + else: + Count = Count + 1 + if CONFIG['ES_FlushSize'] > 0 and Count >= CONFIG['ES_FlushSize']: + Output = Output + '\n' + ES_ExitCode = SendToElasticSearch( IndexName, Output ) + Output = '' + Count = 0 + + if ES_ExitCode == EXITCODE_ElasticSearch: + return ES_ExitCode + if ES_ExitCode == EXITCODE_OneOrMoreErrors: + ExitCode = EXITCODE_OneOrMoreErrors + + if not PrintOnly and Output != '': + Output = Output + '\n' + ES_ExitCode = SendToElasticSearch( IndexName, Output ) + Output = '' + Count = 0 + + if ES_ExitCode == EXITCODE_ElasticSearch: + return ES_ExitCode + if ES_ExitCode == EXITCODE_OneOrMoreErrors: + ExitCode = EXITCODE_OneOrMoreErrors + + except: + Log( logging.ERROR, 'Error while reading and processing input data from %s' % InputFile, exc_info=True ) + return EXITCODE_Program + + return ExitCode + +if __name__ == '__main__': + try: + RetVal = Main() + except: + Log( logging.ERROR, 'Unhandled exception', exc_info=True ) + RetVal = EXITCODE_Program + sys.exit( RetVal ) + + # Test conditions + # ------------------- + # + #C = [ { "Name": "Bob" }, { "Age": 16, "__op__": ">=" } ] + #C = [ "OR", { "Name": "Bob" }, { "Name": "Tom" } ] + #C = [ "OR", [ { "Name": "Bob" }, { "Age": 16, "__op__": ">=" } ], { "Name": "Tom" }, [ { "Name": "Lisa" }, { "Age": 20, "__op__": ">=" } ] ] + #C = [ "Invalid" ] + # + #Data = [ + # { "Name": "Bob", "Age": 15 }, + # { "Name": "Bob", "Age": 16 }, + # { "Name": "Ken", "Age": 14 }, + # { "Name": "Tom", "Age": 14 }, + # { "Name": "Tom", "Age": 20 }, + # { "Name": "Lisa", "Age": 15 }, + # { "Name": "Lisa", "Age": 22 } + #] + # + #for Person in Data: + # try: + # if ParseConditions( C, Person ): + # print( "YES - %s" % Person ) + # else: + # print( "--- - %s" % Person ) + # except Exception as e: + # print( "ParseConditions error: %s" % str(e) ) +