added Malcolm
This commit is contained in:
		
							
								
								
									
										3
									
								
								Vagrant/resources/malcolm/filebeat/certs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Vagrant/resources/malcolm/filebeat/certs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| * | ||||
| !.gitignore | ||||
|  | ||||
							
								
								
									
										40
									
								
								Vagrant/resources/malcolm/filebeat/filebeat-nginx.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Vagrant/resources/malcolm/filebeat/filebeat-nginx.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| #================================ Modules ====================================== | ||||
| filebeat.modules: | ||||
| - module: nginx | ||||
|   access: | ||||
|     enabled: true | ||||
|     var.paths: ["${FILEBEAT_NGINX_LOG_PATH:/data/nginx}/access.log*"] | ||||
|   error: | ||||
|     enabled: true | ||||
|     var.paths: ["${FILEBEAT_NGINX_LOG_PATH:/data/nginx}/error.log*"] | ||||
|  | ||||
| #================================ Outputs ====================================== | ||||
|  | ||||
| #-------------------------- Elasticsearch output ------------------------------- | ||||
| output.elasticsearch: | ||||
|   enabled: true | ||||
|   hosts: ["elasticsearch:9200"] | ||||
|   indices: | ||||
|     - index: "filebeat-%{[agent.version]}-nginx-%{+yyyy.MM.dd}" | ||||
|       when.equals: | ||||
|         event.module: "nginx" | ||||
|  | ||||
| setup.template.enabled: true | ||||
| setup.template.overwrite: false | ||||
| setup.template.settings: | ||||
|   index.number_of_shards: 1 | ||||
|   index.number_of_replicas: 0 | ||||
|  | ||||
| #============================== Dashboards ===================================== | ||||
| setup.dashboards.enabled: true | ||||
| setup.dashboards.directory: "/usr/share/filebeat/kibana" | ||||
|  | ||||
| #============================== Kibana ===================================== | ||||
| setup.kibana: | ||||
|   host: "kibana:5601" | ||||
|   path: "/kibana" | ||||
|  | ||||
| #================================ Logging ====================================== | ||||
| logging.metrics.enabled: false | ||||
							
								
								
									
										60
									
								
								Vagrant/resources/malcolm/filebeat/filebeat.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Vagrant/resources/malcolm/filebeat/filebeat.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| logging.metrics.enabled: false | ||||
|  | ||||
| filebeat.inputs: | ||||
| - type: log | ||||
|   paths: | ||||
|     - ${FILEBEAT_LOG_PATH:/data/zeek/current}/*.log | ||||
|   # see comment below for signatures(_carved).log | ||||
|   exclude_files: ['signatures\(_carved.*\)\.log$'] | ||||
|   symlinks: true | ||||
|   fields_under_root: true | ||||
|   # tags: ["foo"] | ||||
|   fields: | ||||
|     type: "session" | ||||
|   compression_level: 0 | ||||
|   exclude_lines: ['^\s*#'] | ||||
|   scan_frequency: ${FILEBEAT_SCAN_FREQUENCY:10s} | ||||
|   clean_inactive: ${FILEBEAT_CLEAN_INACTIVE:45m} | ||||
|   ignore_older: ${FILEBEAT_IGNORE_OLDER:30m} | ||||
|   close_inactive: ${FILEBEAT_CLOSE_INACTIVE:30s} | ||||
|   close_renamed: ${FILEBEAT_CLOSE_RENAMED:true} | ||||
|   close_removed: ${FILEBEAT_CLOSE_REMOVED:true} | ||||
|   close_eof: ${FILEBEAT_CLOSE_EOF:true} | ||||
|   clean_removed: ${FILEBEAT_CLEAN_REMOVED:true} | ||||
|  | ||||
| # signatures(_carved).log is different, as it comes from file carving and is | ||||
| # "live" regardless of whether the other *.log files that may be processed | ||||
| # after the fact. The most important difference is close_eof, as | ||||
| # we don't want to close signatures(_carved).log when we get to the end of the | ||||
| # file as it will likely be written to again shortly. For these reasons we | ||||
| # exclude it in the main filebeat log input (see above) and handle it with | ||||
| # custom settings here. | ||||
| - type: log | ||||
|   paths: | ||||
|     - ${FILEBEAT_LOG_PATH:/data/zeek/current}/signatures(_carved*).log | ||||
|   symlinks: true | ||||
|   fields_under_root: true | ||||
|   # tags: ["foo"] | ||||
|   fields: | ||||
|     type: "session" | ||||
|   compression_level: 0 | ||||
|   exclude_lines: ['^\s*#'] | ||||
|   scan_frequency: ${FILEBEAT_SCAN_FREQUENCY:10s} | ||||
|   clean_inactive: 200m | ||||
|   ignore_older: 180m | ||||
|   close_inactive: 120m | ||||
|   close_renamed: false | ||||
|   close_removed: true | ||||
|   close_eof: false | ||||
|   clean_removed: true | ||||
|  | ||||
| output.logstash: | ||||
|   hosts: ["logstash:5044"] | ||||
|   ssl.enabled: ${BEATS_SSL:false} | ||||
|   ssl.certificate_authorities: ["/certs/ca.crt"] | ||||
|   ssl.certificate: "/certs/client.crt" | ||||
|   ssl.key: "/certs/client.key" | ||||
|   ssl.supported_protocols: "TLSv1.2" | ||||
|   ssl.verification_mode: "none" | ||||
| @@ -0,0 +1,139 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
|  | ||||
| import os | ||||
| from os.path import splitext | ||||
| from tempfile import gettempdir | ||||
| import errno | ||||
| import time | ||||
| import fcntl | ||||
| import fnmatch | ||||
| import magic | ||||
| import json | ||||
| import pprint | ||||
| import re | ||||
| from subprocess import Popen, PIPE | ||||
|  | ||||
| lockFilename = os.path.join(gettempdir(), '{}.lock'.format(os.path.basename(__file__))) | ||||
| broDir = os.path.join(os.getenv('FILEBEAT_ZEEK_DIR', "/data/zeek/"), '') | ||||
| cleanLogSeconds = int(os.getenv('FILEBEAT_LOG_CLEANUP_MINUTES', "30")) * 60 | ||||
| cleanZipSeconds = int(os.getenv('FILEBEAT_ZIP_CLEANUP_MINUTES', "120")) * 60 | ||||
| fbRegFilename = os.getenv('FILEBEAT_REGISTRY_FILE', "/usr/share/filebeat/data/registry/filebeat/data.json") | ||||
| currentDir = broDir + "current/" | ||||
| processedDir = broDir + "processed/" | ||||
|  | ||||
| import os, errno | ||||
|  | ||||
| def silentRemove(filename): | ||||
|   try: | ||||
|     if os.path.isfile(filename) or os.path.islink(filename): | ||||
|       os.remove(filename) | ||||
|     elif os.path.isdir(filename): | ||||
|       os.rmdir(filename) | ||||
|   except OSError: | ||||
|     pass | ||||
|  | ||||
| def pruneFiles(): | ||||
|  | ||||
|   if (cleanLogSeconds <= 0) and (cleanZipSeconds <= 0): | ||||
|     # disabled, don't do anything | ||||
|     return | ||||
|  | ||||
|   nowTime = time.time() | ||||
|  | ||||
|   logMimeType = "text/plain" | ||||
|   archiveMimeTypeRegex = re.compile(r"(application/gzip|application/x-gzip|application/x-7z-compressed|application/x-bzip2|application/x-cpio|application/x-lzip|application/x-lzma|application/x-rar-compressed|application/x-tar|application/x-xz|application/zip)") | ||||
|  | ||||
|   # look for regular files in the processed/ directory | ||||
|   foundFiles = [(os.path.join(root, filename)) for root, dirnames, filenames in os.walk(processedDir) for filename in filenames] | ||||
|  | ||||
|   # look up the filebeat registry file and try to read it | ||||
|   fbReg = None | ||||
|   if os.path.isfile(fbRegFilename): | ||||
|     with open(fbRegFilename) as f: | ||||
|       fbReg = json.load(f) | ||||
|  | ||||
|   # see if the files we found are in use and old enough to be pruned | ||||
|   for file in foundFiles: | ||||
|  | ||||
|     # first check to see if it's in the filebeat registry | ||||
|     if fbReg is not None: | ||||
|       fileStatInfo = os.stat(file) | ||||
|       if (fileStatInfo): | ||||
|         fileFound = any(((entry['FileStateOS']) and | ||||
|                          (entry['FileStateOS']['device'] == fileStatInfo.st_dev) and | ||||
|                          (entry['FileStateOS']['inode'] == fileStatInfo.st_ino)) for entry in fbReg) | ||||
|         if fileFound: | ||||
|           # found a file in the filebeat registry, so leave it alone! | ||||
|           # we only want to delete files that filebeat has forgotten | ||||
|           #print "{} is found in registry!".format(file) | ||||
|           continue | ||||
|         #else: | ||||
|           #print "{} is NOT found in registry!".format(file) | ||||
|  | ||||
|     # now see if the file is in use by any other process in the system | ||||
|     fuserProcess = Popen(["fuser", "-s", file], stdout=PIPE) | ||||
|     fuserProcess.communicate() | ||||
|     fuserExitCode = fuserProcess.wait() | ||||
|     if (fuserExitCode != 0): | ||||
|  | ||||
|       # the file is not in use, let's check it's mtime/ctime | ||||
|       logTime = max(os.path.getctime(file), os.path.getmtime(file)) | ||||
|       lastUseTime = nowTime - logTime | ||||
|  | ||||
|       # get the file type | ||||
|       fileType = magic.from_file(file, mime=True) | ||||
|       if (cleanLogSeconds > 0) and (fileType == logMimeType): | ||||
|         cleanSeconds = cleanLogSeconds | ||||
|       elif (cleanZipSeconds > 0) and archiveMimeTypeRegex.match(fileType) is not None: | ||||
|         cleanSeconds = cleanZipSeconds | ||||
|       else: | ||||
|         # not a file we're going to be messing with | ||||
|         cleanSeconds = 0 | ||||
|  | ||||
|       if (cleanSeconds > 0) and (lastUseTime >= cleanSeconds): | ||||
|         # this is a closed file that is old, so delete it | ||||
|         print('removing old file "{}" ({}, used {} seconds ago)'.format(file, fileType, lastUseTime)) | ||||
|         silentRemove(file) | ||||
|  | ||||
|   # clean up any broken symlinks in the current/ directory | ||||
|   for current in os.listdir(currentDir): | ||||
|     currentFileSpec = os.path.join(currentDir, current) | ||||
|     if os.path.islink(currentFileSpec) and not os.path.exists(currentFileSpec): | ||||
|       print('removing dead symlink "{}"'.format(currentFileSpec)) | ||||
|       silentRemove(currentFileSpec) | ||||
|  | ||||
|   # clean up any old and empty directories in processed/ directory | ||||
|   cleanDirSeconds = min(i for i in (cleanLogSeconds, cleanZipSeconds) if i > 0) | ||||
|   candidateDirs = [] | ||||
|   for root, dirs, files in os.walk(processedDir, topdown=False): | ||||
|     if (root and dirs): | ||||
|       candidateDirs += [os.path.join(root, tmpDir) for tmpDir in dirs] | ||||
|   candidateDirs = list(set(candidateDirs)) | ||||
|   candidateDirs.sort(reverse=True) | ||||
|   candidateDirs.sort(key=len, reverse=True) | ||||
|   candidateDirsAndTimes = zip(candidateDirs, [os.path.getmtime(dirToRm) for dirToRm in candidateDirs]) | ||||
|   for (dirToRm, dirTime) in candidateDirsAndTimes: | ||||
|     dirAge = (nowTime - dirTime) | ||||
|     if (dirAge >= cleanDirSeconds): | ||||
|       try: | ||||
|         os.rmdir(dirToRm) | ||||
|         print('removed empty directory "{}" (used {} seconds ago)'.format(dirToRm, dirAge)) | ||||
|       except OSError: | ||||
|         pass | ||||
|  | ||||
| def main(): | ||||
|   with open(lockFilename, 'w') as lock_file: | ||||
|     try: | ||||
|       fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||||
|     except IOError: | ||||
|         return | ||||
|     else: | ||||
|       pruneFiles() | ||||
|     finally: | ||||
|       os.remove(lockFilename) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   main() | ||||
| @@ -0,0 +1,12 @@ | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| function in_array() { | ||||
|   local haystack="${1}[@]" | ||||
|   local needle="${2}" | ||||
|   for i in "${!haystack}"; do | ||||
|     if [[ "${i}" == "${needle}" ]]; then | ||||
|       return 0 | ||||
|     fi | ||||
|   done | ||||
|   return 1 | ||||
| } | ||||
							
								
								
									
										92
									
								
								Vagrant/resources/malcolm/filebeat/scripts/filebeat-process-zeek-folder.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										92
									
								
								Vagrant/resources/malcolm/filebeat/scripts/filebeat-process-zeek-folder.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
|  | ||||
| # for files (sort -V (natural)) under /data/zeek that: | ||||
| #   - are not in processed/ or current/ or upload/ or extract_files/ (-prune) | ||||
| #   - are archive files | ||||
| #   - are not in use (fuser -s) | ||||
| # 1. move file to processed/ (preserving original subdirectory heirarchy, if any) | ||||
| # 2. calculate tags based on splitting the file path and filename (splitting on | ||||
| #    on [, -/_]) | ||||
|  | ||||
| FILEBEAT_PREPARE_PROCESS_COUNT=1 | ||||
|  | ||||
| # ensure only one instance of this script can run at a time | ||||
| LOCKDIR="/tmp/zeek-beats-process-folder" | ||||
|  | ||||
| export SCRIPT_DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | ||||
|  | ||||
| export ZEEK_LOG_FIELD_BITMAP_SCRIPT="$SCRIPT_DIR/zeek-log-field-bitmap.py" | ||||
|  | ||||
| export ZEEK_LOG_AUTO_TAG=${AUTO_TAG:-"true"} | ||||
|  | ||||
| ZEEK_LOGS_DIR=${FILEBEAT_ZEEK_DIR:-/data/zeek/} | ||||
|  | ||||
| # remove the lock directory on exit | ||||
| function cleanup { | ||||
|   if ! rmdir $LOCKDIR; then | ||||
|     echo "Failed to remove lock directory '$LOCKDIR'" | ||||
|     exit 1 | ||||
|   fi | ||||
| } | ||||
|  | ||||
| if mkdir $LOCKDIR; then | ||||
|   # ensure that if we "grabbed a lock", we release it (works for clean exit, SIGTERM, and SIGINT/Ctrl-C) | ||||
|   trap "cleanup" EXIT | ||||
|  | ||||
|   # get new zeek logs ready for processing | ||||
|   cd "$ZEEK_LOGS_DIR" | ||||
|   find . -path ./processed -prune -o -path ./current -prune -o -path ./upload -prune -o -path ./extract_files -prune -o -type f -exec file --mime-type "{}" \; | grep -P "(application/gzip|application/x-gzip|application/x-7z-compressed|application/x-bzip2|application/x-cpio|application/x-lzip|application/x-lzma|application/x-rar-compressed|application/x-tar|application/x-xz|application/zip)" | awk -F: '{print $1}' | sort -V | \ | ||||
|     xargs -n 1 -P $FILEBEAT_PREPARE_PROCESS_COUNT -I '{}' bash -c ' | ||||
|  | ||||
|     fuser -s "{}" 2>/dev/null | ||||
|     if [[ $? -ne 0 ]] | ||||
|     then | ||||
|       . $SCRIPT_DIR/filebeat-process-zeek-folder-functions.sh | ||||
|  | ||||
|       PROCESS_TIME=$(date +%s%N) | ||||
|       SOURCEDIR="$(dirname "{}")" | ||||
|       DESTDIR="./processed/$SOURCEDIR" | ||||
|       DESTNAME="$DESTDIR/$(basename "{}")" | ||||
|       DESTDIR_EXTRACTED="${DESTNAME}_${PROCESS_TIME}" | ||||
|       LINKDIR="./current" | ||||
|  | ||||
|       TAGS=() | ||||
|       if [[ "$ZEEK_LOG_AUTO_TAG" = "true" ]]; then | ||||
|         IFS=",-/_." read -r -a SOURCESPLIT <<< $(echo "{}" | sed "s/\.[^.]*$//") | ||||
|         echo "\"{}\" -> \"${DESTNAME}\"" | ||||
|         for index in "${!SOURCESPLIT[@]}" | ||||
|         do | ||||
|           TAG_CANDIDATE="${SOURCESPLIT[index]}" | ||||
|           if ! in_array TAGS "$TAG_CANDIDATE"; then | ||||
|             if [[ -n $TAG_CANDIDATE && ! $TAG_CANDIDATE =~ ^[0-9-]+$ && $TAG_CANDIDATE != "tar" && $TAG_CANDIDATE != "AUTOZEEK" && ! $TAG_CANDIDATE =~ ^AUTOCARVE ]]; then | ||||
|               TAGS+=("${TAG_CANDIDATE}") | ||||
|             fi | ||||
|           fi | ||||
|         done | ||||
|       fi | ||||
|  | ||||
|       mkdir -p "$DESTDIR" | ||||
|       mkdir -p "$DESTDIR_EXTRACTED" | ||||
|       mv -v "{}" "$DESTNAME" | ||||
|       python3 -m pyunpack.cli "$DESTNAME" "$DESTDIR_EXTRACTED" | ||||
|       find "$DESTDIR_EXTRACTED" -type f -name "*.log" | while read LOGFILE | ||||
|       do | ||||
|         PROCESS_TIME=$(date +%s%N) | ||||
|         TAGS_JOINED=$(printf "%s," "${TAGS[@]}")${PROCESS_TIME} | ||||
|         FIELDS_BITMAP="$($ZEEK_LOG_FIELD_BITMAP_SCRIPT "$LOGFILE" | head -n 1)" | ||||
|         LINKNAME_BASE="$(basename "$LOGFILE" .log)" | ||||
|         if [[ -n $FIELDS_BITMAP ]]; then | ||||
|           LINKNAME="${LINKNAME_BASE}(${TAGS_JOINED},${FIELDS_BITMAP}).log" | ||||
|         else | ||||
|           LINKNAME="${LINKNAME_BASE}(${TAGS_JOINED}).log" | ||||
|         fi | ||||
|         touch "$LOGFILE" | ||||
|         ln -sfr "$LOGFILE" "$LINKDIR/$LINKNAME" | ||||
|       done | ||||
|     fi | ||||
|   ' | ||||
|  | ||||
| fi | ||||
| @@ -0,0 +1,22 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
|  | ||||
| PROCESS_DIR=${FILEBEAT_ZEEK_DIR:-/data/zeek/} | ||||
| UPLOAD_DIR="${PROCESS_DIR}/upload" | ||||
| mkdir -p "$UPLOAD_DIR" | ||||
|  | ||||
| # as new zeek log archives are closed for writing in /data/zeek/upload, move them to /data/zeek for processing | ||||
| inotifywait -m -e close_write --format '%w%f' "${UPLOAD_DIR}" | while read NEWFILE | ||||
| do | ||||
|   FILEMIME=$(file -b --mime-type "$NEWFILE") | ||||
|   if ( echo "$FILEMIME" | grep --quiet -P "(application/gzip|application/x-gzip|application/x-7z-compressed|application/x-bzip2|application/x-cpio|application/x-lzip|application/x-lzma|application/x-rar-compressed|application/x-tar|application/x-xz|application/zip)" ); then | ||||
|     # looks like this is a compressed file, we're assuming it's a zeek log archive to be processed by filebeat | ||||
|     sleep 0.1 && chown ${PUID:-${DEFAULT_UID}}:${PGID:-${DEFAULT_GID}} "$NEWFILE" && (>&2 mv -v "$NEWFILE" "$PROCESS_DIR/") | ||||
|   else | ||||
|     # unhandled file type uploaded, delete it | ||||
|     sleep 0.1 && chown ${PUID:-${DEFAULT_UID}}:${PGID:-${DEFAULT_GID}} && (>&2 rm "$NEWFILE") && echo "Removed \"$NEWFILE\", unhandled file type \"$FILEMIME\"" | ||||
|   fi | ||||
| done | ||||
|  | ||||
							
								
								
									
										152
									
								
								Vagrant/resources/malcolm/filebeat/scripts/zeek-log-field-bitmap.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										152
									
								
								Vagrant/resources/malcolm/filebeat/scripts/zeek-log-field-bitmap.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| ################################################################################################### | ||||
| # parse the fields names from the header of of the log file and compare them to the | ||||
| # known list of total fields. if this zeek log has is a subset of the known fields, | ||||
| # create a bitmap of the included fields to be included as a special tag | ||||
| # which can help the logstash parser know on a line-by-line basis which fields are included. | ||||
| # when logstash-filter-dissect gets this implemented, we may not have to do this: | ||||
| #   - https://github.com/logstash-plugins/logstash-filter-dissect/issues/56 | ||||
| #   - https://github.com/logstash-plugins/logstash-filter-dissect/issues/62 | ||||
| # | ||||
| # arguments: accepts one argument, the name of a zeek log file | ||||
| # output:    returns a string suitable for use as a tag indicating the field bitset., eg., ZEEKFLDx00x01FFFFFF | ||||
| # | ||||
| #            ZEEKFLDx00x01FFFFFF | ||||
| #                   |  └ bitmap of included fields within field list | ||||
| #                   └ index into zeekLogFields list indicating (to support legacy field configurations, see below) | ||||
| # | ||||
| # example: | ||||
| #            $ ./zeek-log-field-bitmap.py /path/to/conn.log | ||||
| #            ZEEKFLDx00x01FFFFFF | ||||
| # | ||||
| # there are two cases we're trying to cover here by indicating the field types: | ||||
| #   1. certain fields can be turned on/off in config (for example, enabling/disabling MACs or VLANs for conn.log) | ||||
| #   2. a Zeek version upgrade changed the field list (see notes about DHCP.log in | ||||
| #      https://docs.zeek.org/en/latest/install/release-notes.html#bro-2-6) | ||||
| # | ||||
| # The first case is pretty simple, because in that case the fields in the zeek log will be some subset of | ||||
| # the list of all known fields for that type. | ||||
| # | ||||
| # The second case is more complicated because the field list could be completely different. Because of this case | ||||
| # each of the entries in zeekLogFields is itself a list, with older configurations occuring earlier in the list | ||||
| # | ||||
| #     $ zeek-log-field-bitmap.py ./bro2.5/dhcp.log | ||||
| #     ZEEKFLDx00x000003FF | ||||
| # | ||||
| #     $ zeek-log-field-bitmap.py ./bro2.6/dhcp.log | ||||
| #     ZEEKFLDx01x00007FFF | ||||
| # | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import json | ||||
| from collections import defaultdict | ||||
| from ordered_set import OrderedSet | ||||
|  | ||||
| # lists of all known fields for each type of zeek log we're concerned with mapping (ordered as in the .log file header) | ||||
| # are stored in zeek-log-fields.json | ||||
| FIELDS_JSON_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "zeek-log-fields.json") | ||||
|  | ||||
| ZEEK_LOG_DELIMITER = '\t'            # zeek log file field delimiter | ||||
| ZEEK_LOG_HEADER_LOGTYPE = 'path'     # header value for zeek log type (conn, weird, etc.) | ||||
| ZEEK_LOG_HEADER_FIELDS = 'fields'    # header value for zeek log fields list | ||||
|  | ||||
| # file prefix for bitmap to stdout, eg., ZEEKFLDx00x01FFFFFF | ||||
| ZEEK_LOG_BITMAP_PREFIX = 'ZEEKFLD' | ||||
|  | ||||
|  | ||||
| ################################################################################################### | ||||
| # print to stderr | ||||
| def eprint(*args, **kwargs): | ||||
|   print(*args, file=sys.stderr, **kwargs) | ||||
|  | ||||
| ################################################################################################### | ||||
| # Set the index'th bit of v to 1 if x is truthy, else to 0, and return the new value | ||||
| def set_bit(v, index, x): | ||||
|   mask = 1 << index   # Compute mask, an integer with just bit 'index' set. | ||||
|   v &= ~mask          # Clear the bit indicated by the mask (if x is False) | ||||
|   if x: | ||||
|     v |= mask         # If x was True, set the bit indicated by the mask. | ||||
|   return v | ||||
|  | ||||
| ################################################################################################### | ||||
| # main | ||||
| def main(): | ||||
|   errCode = os.EX_DATAERR | ||||
|  | ||||
|  | ||||
|   dataError = False | ||||
|   zeekLogFields = defaultdict(list) | ||||
|  | ||||
|   # load from json canonical list of known zeek log fields we're concerned with mapping | ||||
|   zeekLogFieldsTmp = json.load(open(FIELDS_JSON_FILE, 'r')) | ||||
|   if isinstance(zeekLogFieldsTmp, dict): | ||||
|     for logType, listOfFieldLists in zeekLogFieldsTmp.items(): | ||||
|       if isinstance(logType, str) and isinstance(listOfFieldLists, list): | ||||
|         zeekLogFields[str(logType)] = [OrderedSet(fieldList) for fieldList in listOfFieldLists] | ||||
|       else: | ||||
|         dataError = True | ||||
|         break | ||||
|   else: | ||||
|     dataError = True | ||||
|  | ||||
|  | ||||
|   if dataError: | ||||
|     # something is wrong with the json file | ||||
|     eprint("Error loading {} (not found or incorrectly formatted)".format(FIELDS_JSON_FILE)) | ||||
|  | ||||
|   else: | ||||
|     if (len(sys.argv) == 2) and os.path.isfile(sys.argv[1]): | ||||
|  | ||||
|       fieldsBitmap = 0 | ||||
|  | ||||
|       # loop over header lines in zeek log file (beginning with '#') and extract the header values | ||||
|       # into a dictionary containing, among other things: | ||||
|       #   - the "path" which is the zeek log type (eg., conn, weird, etc.) | ||||
|       #   - the "fields" list of field names | ||||
|       headers = {} | ||||
|       with open(sys.argv[1], "r") as zeekLogFile: | ||||
|         for line in zeekLogFile: | ||||
|           if line.startswith('#'): | ||||
|             values = line.strip().split(ZEEK_LOG_DELIMITER) | ||||
|             key = values.pop(0)[1:] | ||||
|             if (len(values) == 1): | ||||
|               headers[key] = values[0] | ||||
|             else: | ||||
|               headers[key] = values | ||||
|           else: | ||||
|             break | ||||
|  | ||||
|       if ((ZEEK_LOG_HEADER_LOGTYPE in headers) and                 # the "path" header exists | ||||
|           (ZEEK_LOG_HEADER_FIELDS in headers) and                  # the "fields" header exists | ||||
|           (headers[ZEEK_LOG_HEADER_LOGTYPE] in zeekLogFields)):    # this zeek log type is one we're concerned with mapping | ||||
|  | ||||
|         # the set of field names in *this* log file | ||||
|         logFieldNames = OrderedSet(headers[ZEEK_LOG_HEADER_FIELDS]) | ||||
|  | ||||
|         for versionIdx, allFieldNames in reversed(list(enumerate(zeekLogFields[headers[ZEEK_LOG_HEADER_LOGTYPE]]))): | ||||
|  | ||||
|           # are this logfile's fields a subset of the complete list? | ||||
|           if logFieldNames.issubset(allFieldNames): | ||||
|  | ||||
|             # determine which fields in the complete list are included in this log file | ||||
|             for i, fName in enumerate(allFieldNames): | ||||
|               fieldsBitmap = set_bit(fieldsBitmap, i, fName in logFieldNames) | ||||
|  | ||||
|             # eprint(fieldsBitmap) | ||||
|             print('{0}x{1:02X}x{2:08X}'.format(ZEEK_LOG_BITMAP_PREFIX, versionIdx, fieldsBitmap)) | ||||
|             errCode = os.EX_OK | ||||
|  | ||||
|     else: | ||||
|       # invalid command-line arguments | ||||
|       eprint("{} <Zeek log file>".format(sys.argv[0])) | ||||
|       errCode = os.EX_USAGE | ||||
|  | ||||
|   return errCode | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   sys.exit(main()) | ||||
							
								
								
									
										273
									
								
								Vagrant/resources/malcolm/filebeat/scripts/zeek-log-fields.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								Vagrant/resources/malcolm/filebeat/scripts/zeek-log-fields.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,273 @@ | ||||
| { | ||||
|     "conn": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "proto", | ||||
|             "service", | ||||
|             "duration", | ||||
|             "orig_bytes", | ||||
|             "resp_bytes", | ||||
|             "conn_state", | ||||
|             "local_orig", | ||||
|             "local_resp", | ||||
|             "missed_bytes", | ||||
|             "history", | ||||
|             "orig_pkts", | ||||
|             "orig_ip_bytes", | ||||
|             "resp_pkts", | ||||
|             "resp_ip_bytes", | ||||
|             "tunnel_parents", | ||||
|             "vlan", | ||||
|             "inner_vlan", | ||||
|             "orig_l2_addr", | ||||
|             "resp_l2_addr", | ||||
|             "community_id" | ||||
|         ] | ||||
|     ], | ||||
|     "dhcp": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "mac", | ||||
|             "assigned_ip", | ||||
|             "lease_time", | ||||
|             "trans_id" | ||||
|         ], | ||||
|         [ | ||||
|             "ts", | ||||
|             "uids", | ||||
|             "client_addr", | ||||
|             "server_addr", | ||||
|             "mac", | ||||
|             "host_name", | ||||
|             "client_fqdn", | ||||
|             "domain", | ||||
|             "requested_addr", | ||||
|             "assigned_addr", | ||||
|             "lease_time", | ||||
|             "client_message", | ||||
|             "server_message", | ||||
|             "msg_types", | ||||
|             "duration", | ||||
|             "client_software", | ||||
|             "server_software" | ||||
|         ] | ||||
|     ], | ||||
|     "files": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "fuid", | ||||
|             "tx_hosts", | ||||
|             "rx_hosts", | ||||
|             "conn_uids", | ||||
|             "source", | ||||
|             "depth", | ||||
|             "analyzers", | ||||
|             "mime_type", | ||||
|             "filename", | ||||
|             "duration", | ||||
|             "local_orig", | ||||
|             "is_orig", | ||||
|             "seen_bytes", | ||||
|             "total_bytes", | ||||
|             "missing_bytes", | ||||
|             "overflow_bytes", | ||||
|             "timedout", | ||||
|             "parent_fuid", | ||||
|             "md5", | ||||
|             "sha1", | ||||
|             "sha256", | ||||
|             "extracted", | ||||
|             "extracted_cutoff", | ||||
|             "extracted_size" | ||||
|         ] | ||||
|     ], | ||||
|     "http": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "trans_depth", | ||||
|             "method", | ||||
|             "host", | ||||
|             "uri", | ||||
|             "referrer", | ||||
|             "version", | ||||
|             "user_agent", | ||||
|             "origin", | ||||
|             "request_body_len", | ||||
|             "response_body_len", | ||||
|             "status_code", | ||||
|             "status_msg", | ||||
|             "info_code", | ||||
|             "info_msg", | ||||
|             "tags", | ||||
|             "username", | ||||
|             "password", | ||||
|             "proxied", | ||||
|             "orig_fuids", | ||||
|             "orig_filenames", | ||||
|             "orig_mime_types", | ||||
|             "resp_fuids", | ||||
|             "resp_filenames", | ||||
|             "resp_mime_types", | ||||
|             "post_username", | ||||
|             "post_password_plain", | ||||
|             "post_password_md5", | ||||
|             "post_password_sha1", | ||||
|             "post_password_sha256" | ||||
|         ] | ||||
|     ], | ||||
|     "ntlm": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "username", | ||||
|             "hostname", | ||||
|             "domainname", | ||||
|             "success", | ||||
|             "status" | ||||
|         ], | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "username", | ||||
|             "hostname", | ||||
|             "domainname", | ||||
|             "server_nb_computer_name", | ||||
|             "server_dns_computer_name", | ||||
|             "server_tree_name", | ||||
|             "success" | ||||
|         ] | ||||
|     ], | ||||
|     "rdp": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "cookie", | ||||
|             "result", | ||||
|             "security_protocol", | ||||
|             "client_channels", | ||||
|             "keyboard_layout", | ||||
|             "client_build", | ||||
|             "client_name", | ||||
|             "client_dig_product_id", | ||||
|             "desktop_width", | ||||
|             "desktop_height", | ||||
|             "requested_color_depth", | ||||
|             "cert_type", | ||||
|             "cert_count", | ||||
|             "cert_permanent", | ||||
|             "encryption_level", | ||||
|             "encryption_method" | ||||
|         ] | ||||
|     ], | ||||
|     "smb_files": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "fuid", | ||||
|             "action", | ||||
|             "path", | ||||
|             "name", | ||||
|             "size", | ||||
|             "prev_name", | ||||
|             "times.modified", | ||||
|             "times.accessed", | ||||
|             "times.created", | ||||
|             "times.changed", | ||||
|             "data_offset_req", | ||||
|             "data_len_req", | ||||
|             "data_len_rsp" | ||||
|         ] | ||||
|     ], | ||||
|     "ssh": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "version", | ||||
|             "auth_success", | ||||
|             "auth_attempts", | ||||
|             "direction", | ||||
|             "client", | ||||
|             "server", | ||||
|             "cipher_alg", | ||||
|             "mac_alg", | ||||
|             "compression_alg", | ||||
|             "kex_alg", | ||||
|             "host_key_alg", | ||||
|             "host_key", | ||||
|             "remote_location.country_code", | ||||
|             "remote_location.region", | ||||
|             "remote_location.city", | ||||
|             "remote_location.latitude", | ||||
|             "remote_location.longitude", | ||||
|             "hasshVersion", | ||||
|             "hassh", | ||||
|             "hasshServer", | ||||
|             "cshka", | ||||
|             "hasshAlgorithms", | ||||
|             "sshka", | ||||
|             "hasshServerAlgorithms" | ||||
|         ] | ||||
|     ], | ||||
|     "ssl": [ | ||||
|         [ | ||||
|             "ts", | ||||
|             "uid", | ||||
|             "id.orig_h", | ||||
|             "id.orig_p", | ||||
|             "id.resp_h", | ||||
|             "id.resp_p", | ||||
|             "version", | ||||
|             "cipher", | ||||
|             "curve", | ||||
|             "server_name", | ||||
|             "resumed", | ||||
|             "last_alert", | ||||
|             "next_protocol", | ||||
|             "established", | ||||
|             "cert_chain_fuids", | ||||
|             "client_cert_chain_fuids", | ||||
|             "subject", | ||||
|             "issuer", | ||||
|             "client_subject", | ||||
|             "client_issuer", | ||||
|             "validation_status", | ||||
|             "ja3", | ||||
|             "ja3s" | ||||
|         ] | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										69
									
								
								Vagrant/resources/malcolm/filebeat/supervisord.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Vagrant/resources/malcolm/filebeat/supervisord.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| ; Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| [unix_http_server] | ||||
| file=/tmp/supervisor.sock   ; (the path to the socket file) | ||||
| chmod=0700 | ||||
|  | ||||
| [supervisord] | ||||
| nodaemon=true | ||||
| logfile=/dev/null | ||||
| logfile_maxbytes=0 | ||||
| pidfile=/tmp/supervisord.pid | ||||
|  | ||||
| [rpcinterface:supervisor] | ||||
| supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface | ||||
|  | ||||
| [supervisorctl] | ||||
| serverurl=unix:///tmp/supervisor.sock | ||||
|  | ||||
| [program:filebeat] | ||||
| command=/usr/local/bin/docker-entrypoint -e --strict.perms=false | ||||
| user=%(ENV_PUSER)s | ||||
| startsecs=0 | ||||
| startretries=0 | ||||
| stopasgroup=true | ||||
| killasgroup=true | ||||
| directory=/usr/share/filebeat | ||||
| stdout_logfile=/dev/fd/1 | ||||
| stdout_logfile_maxbytes=0 | ||||
| redirect_stderr=true | ||||
|  | ||||
| [program:filebeat-nginx] | ||||
| command=bash -c "/data/elastic_search_status.sh && /usr/local/bin/docker-entrypoint -e --strict.perms=false \ | ||||
|   --path.home /usr/share/filebeat-nginx \ | ||||
|   --path.config /usr/share/filebeat-nginx \ | ||||
|   --path.data /usr/share/filebeat-nginx/data \ | ||||
|   -c /usr/share/filebeat-nginx/filebeat-nginx.yml \ | ||||
|   --modules nginx" | ||||
| user=%(ENV_PUSER)s | ||||
| autostart=%(ENV_NGINX_LOG_ACCESS_AND_ERRORS)s | ||||
| startsecs=30 | ||||
| startretries=2000000000 | ||||
| stopasgroup=true | ||||
| killasgroup=true | ||||
| directory=/usr/share/filebeat-nginx | ||||
| stdout_logfile=/dev/fd/1 | ||||
| stdout_logfile_maxbytes=0 | ||||
| redirect_stderr=true | ||||
|  | ||||
| [program:watch-upload] | ||||
| command=/bin/bash -c "sleep 30 && /data/filebeat-watch-zeeklogs-uploads-folder.sh" | ||||
| user=root | ||||
| startsecs=35 | ||||
| startretries=1 | ||||
| stopasgroup=true | ||||
| killasgroup=true | ||||
| directory=/data | ||||
| stdout_logfile=/dev/fd/1 | ||||
| stdout_logfile_maxbytes=0 | ||||
| redirect_stderr=true | ||||
|  | ||||
| [program:cron] | ||||
| autorestart=true | ||||
| command=/usr/local/bin/supercronic -json "%(ENV_SUPERCRONIC_CRONTAB)s" | ||||
| user=%(ENV_PUSER)s | ||||
| stopasgroup=true | ||||
| killasgroup=true | ||||
| stdout_logfile=/dev/fd/1 | ||||
| stdout_logfile_maxbytes=0 | ||||
| redirect_stderr=true | ||||
		Reference in New Issue
	
	Block a user