├── .gitignore ├── image ├── etc │ ├── supervisord.d │ │ ├── crond.ini │ │ ├── nginx.ini │ │ ├── logstash.ini │ │ ├── suricata.ini │ │ └── elasticsearch.ini │ ├── logrotate.d │ │ └── suricata │ ├── cron.daily │ │ └── elasticsearch-curator │ ├── suricata │ │ └── suricata.yaml │ └── logstash │ │ └── conf.d │ │ └── suricata.conf ├── start.sh ├── srv │ └── index.html └── Dockerfile ├── README.md └── launcher /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | cid 3 | data 4 | -------------------------------------------------------------------------------- /image/etc/supervisord.d/crond.ini: -------------------------------------------------------------------------------- 1 | [program:crond] 2 | command=/sbin/crond -n 3 | redirect_stderr=true 4 | stdout_logfile_maxbytes=1MB 5 | stdout_logfile_backups=3 6 | -------------------------------------------------------------------------------- /image/etc/supervisord.d/nginx.ini: -------------------------------------------------------------------------------- 1 | [program:nginx] 2 | command=/sbin/nginx -g "daemon off;" 3 | redirect_stderr=true 4 | stdout_logfile_maxbytes=1MB 5 | stdout_logfile_backups=3 6 | -------------------------------------------------------------------------------- /image/etc/supervisord.d/logstash.ini: -------------------------------------------------------------------------------- 1 | [program:logstash] 2 | command=/opt/logstash/bin/logstash --config /etc/logstash/conf.d 3 | redirect_stderr=true 4 | autostart=yes 5 | stdout_logfile_maxbytes=1MB 6 | stdout_logfile_backups=3 7 | user=user 8 | -------------------------------------------------------------------------------- /image/etc/logrotate.d/suricata: -------------------------------------------------------------------------------- 1 | /var/log/suricata/stats.log /var/log/suricata/eve.json 2 | { 3 | rotate 3 4 | hourly 5 | missingok 6 | nocompress 7 | sharedscripts 8 | postrotate 9 | /bin/kill -HUP `cat /var/run/suricata.pid` || true 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /image/etc/supervisord.d/suricata.ini: -------------------------------------------------------------------------------- 1 | [program:suricata] 2 | command=/usr/sbin/suricata 3 | -c /etc/suricata/suricata.yaml 4 | --pidfile /var/run/suricata.pid 5 | -k none 6 | %(ENV_SURICATA_ARGS)s 7 | stdout_logfile=/var/log/suricata/suricata.log 8 | redirect_stderr=true 9 | stdout_logfile_maxbytes=1MB 10 | stdout_logfile_backups=3 11 | -------------------------------------------------------------------------------- /image/etc/supervisord.d/elasticsearch.ini: -------------------------------------------------------------------------------- 1 | [program:elasticsearch] 2 | command=/usr/share/elasticsearch/bin/elasticsearch 3 | -Des.default.path.data=/data/elasticsearch 4 | -Des.cluster.name=docker-suricata-elk 5 | -Des.default.path.logs=/var/log/elasticsearch 6 | -Des.default.config=/etc/elasticsearch/elasticsearch.yml 7 | user=user 8 | redirect_stderr=true 9 | stdout_logfile_maxbytes=1MB 10 | stdout_logfile_backups=3 11 | -------------------------------------------------------------------------------- /image/etc/cron.daily/elasticsearch-curator: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | HOST="127.0.0.1" 4 | TIMESTRING="%Y.%m.%d" 5 | PREFIX="logstash-" 6 | TIME_UNIT="days" 7 | 8 | DELETE_OLDER_THAN=7 9 | OPTIMIZE_OLDER_THAN=1 10 | 11 | curator --host ${HOST} delete indices \ 12 | --older-than ${DELETE_OLDER_THAN} \ 13 | --timestring ${TIMESTRING} --time-unit ${TIME_UNIT} 14 | curator --host ${HOST} optimize indices \ 15 | --older-than ${OPTIMIZE_OLDER_THAN} \ 16 | --timestring ${TIMESTRING} --time-unit ${TIME_UNIT} 17 | 18 | 19 | -------------------------------------------------------------------------------- /image/etc/suricata/suricata.yaml: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | --- 3 | 4 | # Include the default configuration file. 5 | include: /etc/suricata/suricata.yaml-default 6 | 7 | # Overrides for this Docker container. 8 | 9 | outputs: 10 | - eve-log: 11 | enabled: yes 12 | filetype: regular 13 | filename: eve.json 14 | types: 15 | - alert: 16 | payload: yes 17 | packet: yes 18 | http: yes 19 | - http: 20 | extended: yes 21 | - dns 22 | - tls: 23 | extended: yes 24 | - files: 25 | force-magic: yes 26 | force-md5: yes 27 | - ssh 28 | - flow 29 | - netflow 30 | - stats: 31 | enabled: yes 32 | filename: stats.log 33 | interval: 8 34 | 35 | af-packet: 36 | # Just define the default as we don't know what interface we will be 37 | # run on. 38 | - interface: default 39 | threads: auto 40 | use-mmap: yes 41 | cluster-id: 99 42 | cluster-type: cluster_flow 43 | -------------------------------------------------------------------------------- /image/etc/logstash/conf.d/suricata.conf: -------------------------------------------------------------------------------- 1 | input { 2 | file { 3 | path => ["/var/log/suricata/eve.json"] 4 | codec => json 5 | type => "eve-json" 6 | add_field => ["engine", "suricata"] 7 | sincedb_path => "/tmp/.sincedb_eve" 8 | } 9 | } 10 | 11 | filter { 12 | 13 | if [type] == "eve-json" { 14 | date { 15 | match => [ "timestamp", "ISO8601" ] 16 | } 17 | } 18 | 19 | if [event_type] == "alert" { 20 | mutate { 21 | add_tag => ["inbox"] 22 | } 23 | } 24 | 25 | if [src_ip] { 26 | geoip { 27 | source => "src_ip" 28 | target => "geoip" 29 | add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ] 30 | add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ] 31 | } 32 | mutate { 33 | convert => [ "[geoip][coordinates]", "float" ] 34 | } 35 | } 36 | 37 | } 38 | 39 | output { 40 | stdout { 41 | codec => rubydebug 42 | } 43 | 44 | elasticsearch { 45 | host => localhost 46 | protocol => http 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | **NOTE: I have stopped maintaining this Docker image. For 4 | Suricata + ELK check out the [Amsterdam](https://github.com/StamusNetworks/Amsterdam) 5 | project which uses more recent versions of Elastic Search and Kibana, 6 | as well as docker-compose, a better way to run multiple related 7 | apps in containers.** 8 | 9 | A Docker image with Suricata and the ELK (Elastic Search, Logstash, 10 | Kibana). 11 | 12 | ## NOTE 13 | 14 | Unlike most Docker containers, this one uses host networking. At this 15 | time it will attempt to bind the following ports: 16 | 17 | - 7777: The web interface to expose Kibana and EveBox 18 | - 9200: Elastic Search 19 | 20 | This is to allow Suricata access to your physical interfaces while 21 | running inside the Docker container. A more "Docker" approach would 22 | probably be to break this one container into two, one for Suricata, 23 | and one for ELK. 24 | 25 | ## Running 26 | 27 | As this is a Docker container you need to be running Docker on Linux. 28 | Please refer to the Docker documentation at https://docs.docker.com/ 29 | for installation help. Note that if running in a virtual machine you 30 | should allocate at least 2GB of memory. 31 | 32 | - git clone https://github.com/jasonish/docker-suricata-elk.git 33 | - cd docker-suricata-elk 34 | - ./launcher start [-i INTERFACE] 35 | 36 | Then assuming your running on your localhost, point your browser at 37 | http://localhost:7200. 38 | 39 | The container is completely stateless with all persistent data stored 40 | in ./data. This includes the Elastic Search database and all log 41 | files. 42 | 43 | To get a shell into the running container (may require sudo): 44 | 45 | - ./launcher enter 46 | 47 | ## Building 48 | 49 | If you wish to rebuild the image yourself simply run: 50 | 51 | - ./launcher build 52 | -------------------------------------------------------------------------------- /image/start.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | CONTAINER_USER=user 4 | 5 | ARGS=$(getopt -o "i:" -- "$@") 6 | if [ $? -ne 0 ]; then 7 | exit 1 8 | fi 9 | eval set -- "${ARGS}" 10 | 11 | while true; do 12 | case "$1" in 13 | -i) 14 | SURICATA_INTERFACE=$2 15 | shift 2 16 | ;; 17 | --) 18 | shift 19 | break 20 | ;; 21 | esac 22 | done 23 | 24 | # Return the first interface that is not the loopback or the docker interface. 25 | function find_interface() { 26 | ifconfig -a | egrep -o '^[a-z0-9]+' | grep -v docker | grep -v lo | head -n1 27 | } 28 | 29 | fix_permissions() { 30 | echo "Fixing permissions." 31 | 32 | usermod --non-unique --uid ${HOST_UID} ${CONTAINER_USER} > /dev/null 2>&1 33 | 34 | chown -R ${CONTAINER_USER}:${CONTAINER_USER} /data/elasticsearch 35 | chown -R ${CONTAINER_USER}:${CONTAINER_USER} /var/log/elasticsearch 36 | } 37 | 38 | if [ -z "${SURICATA_INTERFACE}" ]; then 39 | SURICATA_INTERFACE=$(find_interface) 40 | if [ -z "${SURICATA_INTERFACE}" ]; then 41 | echo "Failed to find interface to run Suricata on. Exiting." 42 | exit 1 43 | fi 44 | echo "No interface specified, will try ${SURICATA_INTERFACE}" 45 | fi 46 | SURICATA_ARGS="--af-packet=${SURICATA_INTERFACE}" 47 | export SURICATA_ARGS 48 | 49 | if [ ! -e /data ]; then 50 | echo "WARNING: /data is not a host volume. No data will be persisted." 51 | fi 52 | 53 | test -e /data/elasticsearch || \ 54 | install -d -o ${CONTAINER_USER} -g ${CONTAINER_USER} /data/elasticsearch 55 | 56 | if [ "${HOST_UID}" != "" ]; then 57 | fix_permissions 58 | fi 59 | 60 | echo -e "\e[36m" 61 | echo "If all goes well, point your browser at http://localhost:7777." 62 | echo "If not running on localhost, its up to you to figure it out." 63 | echo -e "\e[0m" 64 | 65 | exec /usr/bin/supervisord -c /etc/supervisord.conf --nodaemon 66 | -------------------------------------------------------------------------------- /launcher: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | TAG="jasonish/suricata-elk" 6 | 7 | VOLUMES="-v $(pwd)/data:/data" 8 | VOLUMES="${VOLUMES} -v $(pwd)/data/var/log/suricata:/var/log/suricata" 9 | 10 | use_privileged() { 11 | if [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then 12 | printf -- "--privileged" 13 | fi 14 | } 15 | 16 | enter() { 17 | if [ ! -e cid ]; then 18 | echo "error: container does not appear to be running." 19 | exit 1 20 | fi 21 | docker exec -it $(cat cid) /bin/bash 22 | } 23 | 24 | start() { 25 | 26 | if [ -e cid ]; then 27 | if ! docker ps --no-trunc -q | grep -q $(cat cid); then 28 | echo "Removing stale cid." 29 | rm -f cid 30 | else 31 | echo "error: container appears to be running." 32 | exit 1 33 | fi 34 | fi 35 | 36 | test -e data || mkdir data 37 | docker run \ 38 | $(use_privileged) --rm --cidfile=cid --net=host -i -t ${PORTS} \ 39 | ${VOLUMES} \ 40 | -e HOST_UID=$(id -u) \ 41 | ${TAG} "$@" 42 | rm -f cid 43 | } 44 | 45 | usage() { 46 | echo "Usage: launcher [args]" 47 | echo 48 | echo "Commands:" 49 | echo 50 | echo " start [-i interface] Start the container" 51 | echo " enter Get a shell in the running container" 52 | echo " bash Start the container with a shell" 53 | echo " pull Update the image" 54 | echo " build (Re)build the image" 55 | echo 56 | } 57 | 58 | case "$1" in 59 | 60 | start) 61 | shift 62 | start "$@" 63 | ;; 64 | 65 | bash) 66 | docker run --rm --entrypoint=/bin/bash \ 67 | --net=host -i -t ${VOLUMES} ${TAG} 68 | ;; 69 | 70 | build) 71 | docker build --rm -t ${TAG} image 72 | ;; 73 | 74 | enter) 75 | enter 76 | ;; 77 | 78 | supervisorctl) 79 | enter supervisorctl 80 | ;; 81 | 82 | pull) 83 | docker pull ${TAG} 84 | ;; 85 | 86 | *) 87 | usage 88 | ;; 89 | 90 | esac 91 | -------------------------------------------------------------------------------- /image/srv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 | 19 | Kibana 20 | 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 | 104 | 105 |
106 | 107 |
108 | 109 | 116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:23 2 | 3 | RUN dnf -y install https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.4.noarch.rpm && \ 4 | dnf -y install https://download.elastic.co/logstash/logstash/packages/centos/logstash-1.5.6-1.noarch.rpm 5 | 6 | RUN dnf -y install \ 7 | cronie \ 8 | logrotate \ 9 | ed \ 10 | tar \ 11 | tcpdump \ 12 | python-pip \ 13 | nginx \ 14 | python-simplejson \ 15 | wget \ 16 | supervisor \ 17 | which \ 18 | tcpdump \ 19 | net-tools \ 20 | procps-ng \ 21 | hostname \ 22 | java-1.8.0-openjdk-headless \ 23 | findutils \ 24 | dnf-plugins-core && \ 25 | dnf -y copr enable jasonish/suricata-stable && \ 26 | dnf -y install suricata 27 | 28 | # Create a user to run non-root applications. 29 | RUN useradd user 30 | 31 | # Install Kibana 3. 32 | RUN mkdir -p /srv/kibana && \ 33 | cd /srv/kibana && \ 34 | curl -o - -L -s http://download.elasticsearch.org/kibana/kibana/kibana-3.1.2.tar.gz | tar zxvf - --strip-components=1 35 | 36 | # Pevma's Kibana Dashboards. 37 | RUN cd /var/tmp && \ 38 | curl -o - -L http://github.com/pevma/Suricata-Logstash-Templates/archive/master.tar.gz | tar zxvf - && \ 39 | cd Suricata-Logstash-Templates-master/Templates && \ 40 | for template in *; do \ 41 | cp $template /srv/kibana/app/dashboards/$template.json; \ 42 | done 43 | 44 | # EveBox. 45 | ENV EVEBOX_COMMIT be8389d4ad119a1ce984718297b94daa3b0c814d 46 | RUN mkdir -p /usr/local/src/evebox && \ 47 | cd /usr/local/src/evebox && \ 48 | curl -L -o - http://github.com/jasonish/evebox/archive/${EVEBOX_COMMIT}.tar.gz | tar zxf - --strip-components=1 && \ 49 | cp -a app /srv/evebox 50 | 51 | # Setup minimal web site. 52 | RUN cd /srv && \ 53 | curl -O -L -# \ 54 | http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css 55 | 56 | # Install idstools for rule updates. 57 | RUN pip install --upgrade \ 58 | http://github.com/jasonish/py-idstools/archive/master.zip 59 | 60 | # Update the rules. 61 | RUN idstools-rulecat --rules-dir=/etc/suricata/rules 62 | 63 | # Install Elastic Search curator for optimizing and purging events. 64 | RUN pip install elasticsearch-curator 65 | 66 | # Fixup Nginx to list on port 7777. 67 | RUN printf "\ 68 | /listen\n\ 69 | s/80/7777\n\ 70 | /listen\n\ 71 | s/80/7777\n\ 72 | /root\n\ 73 | d\n\ 74 | i\n\ 75 | \troot\t/srv;\n\ 76 | .\n\ 77 | w\n" | ed /etc/nginx/nginx.conf 78 | 79 | # Enable CORS and dynamic scripting in Elastic Search. 80 | RUN echo "http.cors.enabled: true" >> /etc/elasticsearch/elasticsearch.yml && \ 81 | echo "script.disable_dynamic: false" >> /etc/elasticsearch/elasticsearch.yml 82 | 83 | # Copy in files. 84 | COPY /etc/supervisord.d /etc/supervisord.d 85 | COPY /etc/logstash/conf.d /etc/logstash/conf.d 86 | COPY /etc/logrotate.d /etc/logrotate.d 87 | COPY /etc/cron.daily /etc/cron.daily 88 | COPY /srv /srv 89 | COPY /start.sh /start.sh 90 | RUN mv /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml-default 91 | COPY /etc/suricata /etc/suricata 92 | 93 | # Fix permissions. 94 | RUN chmod 644 /etc/logrotate.d/* 95 | 96 | # Cleanup. 97 | RUN dnf clean all && \ 98 | rm -rf /var/tmp/* && \ 99 | find /var/log -type f -exec rm -f {} \; && \ 100 | rm -rf /tmp/* /tmp/.[A-Za-z]* 101 | 102 | ENTRYPOINT ["/start.sh"] 103 | --------------------------------------------------------------------------------