├── .gitignore ├── COPYING ├── Makefile ├── client ├── Makefile ├── embed.html ├── setup.py ├── tryzeek.py └── tryzeek_embed.js ├── containers └── caddy │ ├── Caddyfile.notls │ └── Caddyfile.tls ├── docker-compose.yml ├── manager ├── .dockerignore ├── Dockerfile ├── app.py ├── backend.py ├── bro_ascii_reader.py ├── common.py ├── format.py ├── metrics.py ├── requirements.txt ├── runbro ├── runbro-1.5 ├── static │ ├── examples │ │ ├── basics │ │ │ ├── bro-types │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── composite-types │ │ │ │ ├── comp-type-intro │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── index │ │ │ │ ├── record │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── set │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── table │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ └── vector │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ ├── control-flow │ │ │ │ ├── if │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ └── index │ │ │ ├── event │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── exercise1 │ │ │ │ ├── exercise │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── index │ │ │ │ └── solution │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ ├── exercise2 │ │ │ │ ├── index │ │ │ │ ├── script-exercise-2 │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ └── solution-script-exercise-2 │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ ├── functions │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── hook │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── index │ │ │ ├── loading │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── loops │ │ │ │ ├── for │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── index │ │ │ │ └── while │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ ├── operators │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── primitive-datatypes │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── redefinitions │ │ │ │ ├── index │ │ │ │ ├── redef-records │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ └── redef │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ ├── switches │ │ │ │ ├── index │ │ │ │ ├── solution-switch-exercise │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ ├── switch-exercise │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ │ └── switch │ │ │ │ │ ├── main.zeek │ │ │ │ │ └── readme.markdown │ │ │ └── variables │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ ├── congrats │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ │ ├── hello │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ │ ├── index │ │ ├── intel │ │ │ ├── index │ │ │ └── intel-1 │ │ │ │ ├── intel-1.dat │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ ├── logs │ │ │ ├── filter-logs │ │ │ │ ├── factorial.zeek │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── index │ │ │ └── rename-logs │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ ├── modules │ │ │ ├── export │ │ │ │ ├── factorial.zeek │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ ├── index │ │ │ ├── log-factorial │ │ │ │ ├── factorial.zeek │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ │ └── module │ │ │ │ ├── main.zeek │ │ │ │ └── readme.markdown │ │ ├── new-notice │ │ │ ├── factorial.zeek │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ │ ├── pack.py │ │ └── sumstats │ │ │ ├── index │ │ │ ├── sumstats1 │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ │ │ ├── sumstats2 │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ │ │ └── sumstats3 │ │ │ ├── main.zeek │ │ │ └── readme.markdown │ ├── favicon.ico │ └── pcaps │ │ ├── exercise_traffic.pcap │ │ ├── http.pcap │ │ ├── ssh.pcap │ │ └── sumstat.pcap ├── templates │ ├── base.html │ ├── index.html │ └── metrics.html ├── test_format.py ├── version.py ├── web-ui │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── mode-zeek.js │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── ZeekMode.js │ │ ├── actions.js │ │ ├── babel-plugin-macros.config.js │ │ ├── babel.config.js │ │ ├── broutil.js │ │ ├── index.js │ │ ├── logo.svg │ │ ├── reducers.js │ │ └── tbhistory.js └── worker.py └── tryzeek.service /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | develop-eggs 12 | .installed.cfg 13 | 14 | # Installer logs 15 | pip-log.txt 16 | 17 | # Unit test / coverage reports 18 | .coverage 19 | .tox 20 | 21 | #test databases 22 | *.db 23 | 24 | docs/_build 25 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 1995-2019, The Regents of the University of California 2 | through the Lawrence Berkeley National Laboratory and the 3 | International Computer Science Institute. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | (1) Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | (2) Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | (3) Neither the name of the University of California, Lawrence Berkeley 16 | National Laboratory, U.S. Dept. of Energy, International Computer 17 | Science Institute, nor the names of contributors may be used to endorse 18 | or promote products derived from this software without specific prior 19 | written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 25 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | 33 | Note that some files in the distribution may carry their own copyright 34 | notices. 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker-compose build 3 | 4 | run: 5 | #Use non-redirecting caddy config during development 6 | CADDY=notls docker-compose build proxy 7 | CADDY=notls docker-compose up -d 8 | docker-compose logs -f 9 | 10 | run-prod: build 11 | TRYZEEK_DATA=/srv/trybro_data docker-compose up -d --scale worker=4 12 | docker-compose logs -f 13 | -------------------------------------------------------------------------------- /client/Makefile: -------------------------------------------------------------------------------- 1 | bro.pex: bro.py setup.py 2 | pex --python-shebang='/usr/bin/env python' -o bro.pex . -c bro 3 | -------------------------------------------------------------------------------- /client/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 |

Examples

15 | 16 |

Hello World!

17 | 18 |
19 | event zeek_init() {
20 |     print "Hello, World!";
21 | }
22 | 
23 | 24 |

Hello and goodbye!

25 | 26 |
27 | event zeek_init() { 
28 |     print "Hello, World!"; 
29 | }
30 | 
31 | event zeek_done() { 
32 |     print "Goodbye, World!"; 
33 | }
34 | 
35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /client/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='tryzeek', 4 | version='0.2.0', 5 | zip_safe=True, 6 | py_modules = ["tryzeek"], 7 | install_requires='requests', 8 | entry_points = { 9 | 'console_scripts': [ 10 | 'tryzeek = tryzeek:main', 11 | ] 12 | } 13 | ) 14 | -------------------------------------------------------------------------------- /client/tryzeek.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from optparse import OptionParser 3 | import json 4 | import os 5 | import requests 6 | import sys 7 | import time 8 | import hashlib 9 | 10 | DEFAULT_HOST="https://try.zeek.org/" 11 | HOST = os.getenv("TRYBRO_HOST", DEFAULT_HOST) 12 | 13 | DEFAULT_VERSION="3.0.0" 14 | 15 | def md5(s): 16 | m = hashlib.md5() 17 | m.update(s) 18 | return m.hexdigest() 19 | 20 | def read_file(fn): 21 | with open(fn) as f: 22 | return f.read() 23 | 24 | def run_code(files, version=DEFAULT_VERSION, pcap=None): 25 | main, rest = files[0], files[1:] 26 | sources = [ 27 | {"name": "main.zeek", "content": read_file(main)} 28 | ] 29 | 30 | for fn in rest: 31 | sources.append({"name": fn, "content": read_file(fn)}) 32 | 33 | req = { 34 | "sources": sources, 35 | "version": version, 36 | "pcap": pcap, 37 | } 38 | data = json.dumps(req) 39 | headers = {'Content-type': 'application/json'} 40 | res = requests.post(HOST + "run", data=data, headers=headers).json() 41 | return res 42 | 43 | def maybe_upload_pcap(pcap): 44 | with open(pcap, 'rb') as f: 45 | contents = f.read() 46 | checksum = md5(contents) 47 | 48 | exists = requests.get(HOST + "pcap/" + checksum).json()["status"] 49 | if not exists: 50 | files = {'pcap': ('file.pcap', contents) } 51 | status = requests.post(HOST + "pcap/upload/" + checksum, files=files).json()['status'] 52 | assert status 53 | 54 | return checksum 55 | 56 | def get_files(job): 57 | res = requests.get(HOST + "files/" + job) 58 | file_output = res.json()['files'] 59 | for fn, data in file_output.items(): 60 | if fn == "stdout.log" or not data: continue 61 | if fn == "stderr.log": 62 | sys.stderr.write(data) 63 | continue 64 | with open(fn, 'w') as f: 65 | f.write(data) 66 | 67 | def run(files, version=DEFAULT_VERSION, pcap=None, link=False): 68 | if pcap: 69 | pcap = maybe_upload_pcap(pcap) 70 | res = run_code(files, version, pcap=pcap) 71 | if link: 72 | sys.stdout.write("http://try.zeek.org/#/trybro/saved/%s\n\n" % res['job']) 73 | sys.stdout.write(res['stdout']) 74 | get_files(res["job"]) 75 | 76 | def main(): 77 | parser = OptionParser() 78 | parser.add_option("-r", "--readfile", dest="pcap", help="read from given tcpdump file", action="store") 79 | parser.add_option("-l", "--link", dest="link", help="Output sharable link", action="store_true") 80 | parser.add_option("-V", "--bro-version", dest="version", help="Select bro version", action="store", default=DEFAULT_VERSION) 81 | (options, args) = parser.parse_args() 82 | 83 | files = args 84 | run(files, version=options.version, pcap=options.pcap, link=options.link) 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /client/tryzeek_embed.js: -------------------------------------------------------------------------------- 1 | $.fn.tryzeek = function() { 2 | this.prop("contenteditable", true); 3 | this.wrap("
") 4 | this.after("
");
 5 |     $(".zeek_example button").click(function() {
 6 |         var code = $(this).parent().find(".zeek_source").text()
 7 |         var res = $(this).parent().find(".zeek_result");
 8 |         res.text("running...");
 9 |         $.post("https://try.zeek.org/run_simple", {code: code}, function(data) {
10 |             var out = data.files['stdout.log'];
11 |             var err = data.files['stderr.log'];
12 |             if(err) {
13 |                 res.text(err + "\n" + out);
14 |             } else {
15 |                 res.text(out);
16 |             }
17 |         }, 'json');
18 |     });
19 |     return this;
20 | };
21 | 


--------------------------------------------------------------------------------
/containers/caddy/Caddyfile.notls:
--------------------------------------------------------------------------------
 1 | {
 2 | 	auto_https off
 3 | }
 4 | 
 5 | :80 {
 6 | 	reverse_proxy {
 7 | 		to api:8000
 8 | 	}
 9 | 	header -Server
10 | 
11 | 	log {
12 | 		output stdout
13 | 		format console
14 | 	}
15 | 	encode gzip
16 | }
17 | 


--------------------------------------------------------------------------------
/containers/caddy/Caddyfile.tls:
--------------------------------------------------------------------------------
 1 | {
 2 | 	on_demand_tls {
 3 | 		interval 1d
 4 | 		burst 5
 5 | 	}
 6 | }
 7 | 
 8 | {$SITE_ADDRESS} {
 9 | 	reverse_proxy {
10 | 		to api:8000
11 | 	}
12 | 	header -Server
13 | 
14 | 	log {
15 | 		output stdout
16 | 		format console
17 | 	}
18 | 	encode gzip
19 | }
20 | 


--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
 1 | version: '3'
 2 | services:
 3 |   redis:
 4 |     image: redis
 5 |     restart: always
 6 |     networks:
 7 |       - backend
 8 |     volumes:
 9 |       - ${TRYZEEK_DATA:-./tryzeek_data}:/data
10 | 
11 |   worker:
12 |     build: manager
13 |     command: rq worker --with-scheduler --url redis://redis
14 |     volumes:
15 |       - /var/run/docker.sock:/var/run/docker.sock
16 |       - /brostuff:/brostuff
17 |     restart: always
18 |     deploy:
19 |       replicas: 5
20 |     depends_on:
21 |       - redis
22 |     networks:
23 |       - backend
24 | 
25 |   version-updater:
26 |     build: manager
27 |     command: python3 version.py -i 4020
28 |     init: true
29 |     volumes:
30 |       - /var/run/docker.sock:/var/run/docker.sock
31 |     restart: always
32 |     depends_on:
33 |       - redis
34 |     networks:
35 |       - backend
36 | 
37 |   api:
38 |     build: manager
39 |     command: gunicorn -w 4 -b 0.0.0.0 app:app --log-file - --max-requests 500 --timeout 20
40 |     restart: always
41 |     depends_on:
42 |       - redis
43 |     networks:
44 |       - backend
45 |       - frontend
46 | 
47 |   proxy:
48 |     image: caddy
49 |     restart: always
50 |     environment:
51 |       - ACME_AGREE=true
52 |       - ENABLE_TELEMETRY=false
53 |       - SITE_ADDRESS=${SITE_ADDRESS:-try.zeek.org:443, try.bro.org:443}
54 |     volumes:
55 |       - $HOME/.caddy:/data
56 |       - ./containers/caddy/Caddyfile.${CADDY-tls}:/etc/caddy/Caddyfile
57 |     networks:
58 |       - frontend
59 |     ports:
60 |       - "80:80/tcp"
61 |       - "443:443/tcp"
62 | 
63 | networks:
64 |   backend:
65 |   frontend:
66 | 


--------------------------------------------------------------------------------
/manager/.dockerignore:
--------------------------------------------------------------------------------
1 | web-ui/node_modules
2 | 


--------------------------------------------------------------------------------
/manager/Dockerfile:
--------------------------------------------------------------------------------
 1 | # tryzeek
 2 | #
 3 | # VERSION               0.3
 4 | 
 5 | FROM node:22.12 AS web-builder
 6 | ADD web-ui/package.json web-ui/
 7 | ADD web-ui/package-lock.json web-ui/
 8 | RUN cd web-ui && npm install
 9 | ADD web-ui web-ui
10 | RUN make -C web-ui
11 | 
12 | FROM  debian:bookworm-slim
13 | LABEL org.opencontainers.image.authors="Justin Azoff "
14 | 
15 | RUN apt-get update && \
16 |     apt-get dist-upgrade -yq && \
17 |     apt-get install -yq python3-pip python3-dev rsync && \
18 |     rm /var/lib/apt/lists/*list -vf && \
19 |     true
20 | 
21 | RUN mkdir /brostuff
22 | 
23 | ADD requirements.txt requirements.txt
24 | RUN pip3 install --break-system-packages -r requirements.txt
25 | 
26 | ADD . /src
27 | WORKDIR /src
28 | 
29 | # Add the webpack output to the web root
30 | COPY --from=web-builder web-ui/build/ web-ui-build
31 | RUN cp web-ui-build/*.* static/
32 | RUN rsync -avP web-ui-build/static/ static/
33 | 
34 | RUN cd static/examples && ./pack.py
35 | 


--------------------------------------------------------------------------------
/manager/app.py:
--------------------------------------------------------------------------------
  1 | from flask import Flask, render_template, request, redirect, make_response
  2 | from flask_jsonpify import jsonify
  3 | import glob
  4 | import hashlib
  5 | import os
  6 | 
  7 | import backend
  8 | import metrics
  9 | from format import fmt
 10 | 
 11 | class FlaskStaticCors(Flask):
 12 |     def send_static_file(self, filename):
 13 |         response = super(FlaskStaticCors, self).send_static_file(filename)
 14 |         response.headers['Access-Control-Allow-Origin'] = '*'
 15 |         return response
 16 | 
 17 | app = FlaskStaticCors(__name__)
 18 | 
 19 | @app.route('/favicon.ico')
 20 | def favicon():
 21 |     return app.send_static_file("favicon.ico")
 22 | 
 23 | @app.route('/mode-zeek.js')
 24 | def zeek_mode():
 25 |     return app.send_static_file("mode-zeek.js")
 26 | 
 27 | @app.route("/")
 28 | def index():
 29 |     return app.send_static_file("index.html")
 30 | 
 31 | @app.route("/metrics")
 32 | def bro_metrics():
 33 |     return render_template('metrics.html', metrics=metrics.get())
 34 | 
 35 | @app.route("/metrics.json")
 36 | def bro_metrics_json():
 37 |     return cors_jsonify(**metrics.get())
 38 | 
 39 | @app.route("/saved/")
 40 | def saved(job):
 41 |     return corsify(make_response(backend.get_saved(job)))
 42 | 
 43 | @app.route("/run", methods=['OPTIONS'])
 44 | def run_options_for_cors():
 45 |     return cors_jsonify(ok="ok")
 46 | 
 47 | @app.route("/run", methods=['POST'])
 48 | def run():
 49 |     sources = request.json.get('sources', '')
 50 |     pcap = request.json.get('pcap', '')
 51 |     version = request.json.get('version', '')
 52 |     if pcap and '--' in pcap:
 53 |         pcap = None
 54 | 
 55 |     job_id, stdout = backend.run_code(sources, pcap=pcap, version=version)
 56 |     return cors_jsonify(job=job_id, stdout=stdout)
 57 | 
 58 | @app.route("/run_simple", methods=['GET', 'POST'])
 59 | def run_simple():
 60 |     stdin = request.args.get("code") or request.form.get("code") or 'print "Huh?";'
 61 |     version = request.args.get("version") or request.form.get("version")
 62 | 
 63 |     files = backend.run_code_simple(stdin, version=version)
 64 |     return cors_jsonify(files=files)
 65 | 
 66 | @app.route("/stdout/")
 67 | def stdout(job):
 68 |     txt = backend.get_stdout(job)
 69 |     if txt is None:
 70 |         return 'wait', 202
 71 |     return cors_jsonify(txt=txt)
 72 | 
 73 | @app.route("/files/")
 74 | def files(job):
 75 |     files = backend.get_files(job)
 76 |     return cors_jsonify(files=files)
 77 | 
 78 | @app.route("/files/.json")
 79 | def files_json(job):
 80 |     files = backend.get_files_json(job)
 81 |     return cors_jsonify(files=files)
 82 | 
 83 | @app.route("/versions.json")
 84 | def versions():
 85 |     default_version, versions = backend.zeek_versions_from_redis()
 86 |     return cors_jsonify(versions=versions, default=default_version)
 87 | 
 88 | @app.route("/pcap/upload/", methods=['OPTIONS'])
 89 | def pcap_upload_options_for_cors(checksum):
 90 |     return cors_jsonify(ok="ok")
 91 | 
 92 | @app.route("/pcap/upload/", methods=['POST'])
 93 | def pcap_upload(checksum):
 94 |     contents = request.files['pcap'].read()
 95 |     actual = md5(contents)
 96 |     ok = actual == checksum
 97 |     if ok:
 98 |         backend.save_pcap(checksum, contents)
 99 |     return cors_jsonify(status=ok, checksum=actual)
100 | 
101 | @app.route("/pcap/")
102 | def has_pcap(checksum):
103 |     status = backend.check_pcap(checksum)
104 |     return cors_jsonify(status=status)
105 | 
106 | @app.route("/example/")
107 | def example(name):
108 |     return redirect("/#/trybro?example=%s" % name)
109 | 
110 | @app.route("/pcaps.json")
111 | def pcap_list():
112 |     paths = glob.glob(os.path.join(app.static_folder, "pcaps/*.pcap"))
113 |     pcaps = list(map(os.path.basename, paths))
114 |     return cors_jsonify(available=pcaps)
115 | 
116 | @app.route("/format", methods=['POST'])
117 | def format():
118 |     sources = request.json.get('sources', [])
119 |     errors = {}
120 |     for src in sources:
121 |         # If anything goes wrong, just leave it unchanged.
122 |         try:
123 |             src['content'], error = fmt(src['content'])
124 |             if error:
125 |                 errors[src['name']] = [error]
126 |         except Exception as err:
127 |             pass
128 | 
129 |     return cors_jsonify(sources=sources, errors=errors)
130 | 
131 | def md5(s):
132 |     m = hashlib.md5()
133 |     m.update(s)
134 |     return m.hexdigest()
135 | 
136 | def cors_jsonify(**kwargs):
137 |     response = jsonify(**kwargs)
138 |     return corsify(response)
139 | 
140 | def corsify(response):
141 |     response.headers['Access-Control-Allow-Origin'] = '*'
142 |     response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
143 |     return response
144 | 
145 | app.debug = False
146 | 
147 | if __name__ == "__main__":
148 |     app.run(debug=True)
149 | 


--------------------------------------------------------------------------------
/manager/backend.py:
--------------------------------------------------------------------------------
  1 | import time
  2 | import json
  3 | import hashlib
  4 | import traceback
  5 | import uuid
  6 | 
  7 | import redis
  8 | 
  9 | import bro_ascii_reader
 10 | import rq
 11 | import metrics
 12 | 
 13 | from common import get_cache_key, get_redis, get_redis_raw, get_rq
 14 | from version import zeek_versions_from_redis
 15 | 
 16 | r = get_redis()
 17 | r_raw = get_redis_raw()
 18 | 
 19 | q = get_rq()
 20 | 
 21 | CACHE_EXPIRE = 60*10
 22 | SOURCES_EXPIRE = 60*60*24*30
 23 | 
 24 | def wait_for_result(r):
 25 |     while r.result is None:
 26 |         time.sleep(0.1)
 27 |     return r.result
 28 | 
 29 | def get_job_id():
 30 |     return uuid.uuid4().hex
 31 | 
 32 | def run_code(sources, pcap, version=None):
 33 |     """Try to find a cached result for this submission
 34 |     If not found, submit a new job to the worker"""
 35 |     default_version, versions = zeek_versions_from_redis()
 36 |     if version not in versions:
 37 |         version = default_version
 38 | 
 39 |     metrics.log_execution(version)
 40 |     cache_key = get_cache_key(sources, pcap, version)
 41 |     job_id = r.get(cache_key)
 42 |     if job_id:
 43 |         metrics.log_cache_hit()
 44 |         r.expire(cache_key, CACHE_EXPIRE)
 45 |         r.expire('stdout:%s' % job_id, CACHE_EXPIRE + 5)
 46 |         r.expire('files:%s' % job_id, CACHE_EXPIRE + 5)
 47 |         r.expire('sources:%s' % job_id, SOURCES_EXPIRE)
 48 |         return job_id, get_stdout(job_id)
 49 |     job_id = get_job_id()
 50 |     job_data = {
 51 |         "job_id": job_id,
 52 |         "sources": sources,
 53 |         "pcap": pcap,
 54 |         "version": version
 55 |     }
 56 |     result = wait_for_result(q.enqueue("worker.run_code", sources=sources,
 57 |                                        pcap=pcap, version=version, job_id=job_id))
 58 |     return job_id, result
 59 | 
 60 | def run_code_simple(stdin, version=None):
 61 |     sources = [
 62 |         {"name": "main.zeek", "content": stdin}
 63 |     ]
 64 |     job_id, stdout = run_code(sources, pcap=None, version=version)
 65 |     files = get_files_json(job_id)
 66 |     return files
 67 | 
 68 | def get_stdout(job):
 69 |     stdout_key = 'stdout:%s' % job
 70 |     stdout = r.get(stdout_key)
 71 |     if stdout:
 72 |         r.expire(stdout_key, CACHE_EXPIRE+5)
 73 |     return stdout
 74 | 
 75 | def get_files(job):
 76 |     files_key = 'files:%s' % job
 77 |     files = r.hgetall(files_key)
 78 |     return files
 79 | 
 80 | def get_files_json(job):
 81 |     files = get_files(job)
 82 |     return parse_tables(files)
 83 | 
 84 | def get_saved(job):
 85 |     sources_key = 'sources:%s' % job
 86 |     return r.get(sources_key)
 87 | 
 88 | def parse_tables(files):
 89 |     for fn, contents in files.items():
 90 |         if contents.startswith("#sep"):
 91 |             files[fn] = bro_ascii_reader.reader(contents.splitlines(), max_rows=200)
 92 |     return files
 93 | 
 94 | def save_pcap(checksum, contents):
 95 |     pcap_key = "pcaps:%s" % checksum
 96 |     r_raw.set(pcap_key, contents)
 97 |     r_raw.expire(pcap_key, 60*60)
 98 |     return True
 99 | 
100 | def check_pcap(checksum):
101 |     pcap_key = "pcaps:%s" % checksum
102 |     exists = r_raw.exists(pcap_key)
103 | 
104 |     if exists:
105 |         r_raw.expire(pcap_key, 60*60)
106 |     return exists
107 | 


--------------------------------------------------------------------------------
/manager/bro_ascii_reader.py:
--------------------------------------------------------------------------------
 1 | def reader(f, max_rows=None):
 2 |     line = ''
 3 |     headers = {}
 4 |     it = iter(f)
 5 |     while not line.startswith("#types"):
 6 |         line = next(it).rstrip()
 7 |         k,v = line[1:].split(None, 1)
 8 |         headers[k] = v
 9 | 
10 |     sep = headers['separator'].encode().decode("unicode_escape")
11 | 
12 |     for k,v in headers.items():
13 |         if sep in v or k in ('fields', 'types'):
14 |             headers[k] = v.split(sep)
15 | 
16 |     headers['separator'] = sep
17 |     fields = headers['fields']
18 |     types = headers['types']
19 |     set_sep = headers['set_separator']
20 | 
21 |     rows = []
22 |     for row in it:
23 |         if row.startswith("#close"):
24 |             break
25 |         parts = row.rstrip().split(sep)
26 |         rows.append(parts)
27 | 
28 |     if max_rows and len(rows) > max_rows:
29 |         mid = max_rows // 2
30 |         rows = rows[:mid] + rows[-mid:]
31 | 
32 |     return {
33 |         "header": fields,
34 |         "types": types,
35 |         "rows": rows,
36 |     }
37 | 


--------------------------------------------------------------------------------
/manager/common.py:
--------------------------------------------------------------------------------
 1 | import hashlib
 2 | import json
 3 | import os
 4 | 
 5 | import redis
 6 | import rq
 7 | 
 8 | REDIS_HOST = os.environ.get("REDIS_HOST", "redis")
 9 | 
10 | def get_cache_key(sources, pcap, version):
11 |     key = [sources, pcap, version]
12 |     h = hashlib.sha1(json.dumps(key).encode())
13 |     h = h.hexdigest()
14 |     cache_key = f"cache:{h}"
15 |     return cache_key
16 | 
17 | 
18 | def get_redis() -> redis.StrictRedis:
19 |     return redis.StrictRedis(REDIS_HOST, charset="utf-8", decode_responses=True)
20 | 
21 | 
22 | def get_redis_raw():
23 |     return redis.Redis(REDIS_HOST)
24 | 
25 | 
26 | def get_rq():
27 |     return rq.Queue(connection=get_redis_raw())
28 | 


--------------------------------------------------------------------------------
/manager/format.py:
--------------------------------------------------------------------------------
 1 | import io
 2 | import re
 3 | 
 4 | import zeekscript
 5 | 
 6 | 
 7 | ERROR_REGEX = r'line (\d+), col (\d+)'
 8 | 
 9 | def parse_error(error):
10 |     # This is a tuple of... (string, some number, actual error)
11 |     txt = error[2]
12 |     # 'cannot parse line 0, col 0: "function() {"'
13 | 
14 |     match = re.search(ERROR_REGEX, txt)
15 |     if not match:
16 |         return None
17 | 
18 |     line = int(match.group(1))
19 |     col = int(match.group(2))
20 | 
21 |     return {
22 |         'row': line,
23 |         'column': col,
24 |         'type': "error",
25 |         'text': txt,
26 |     }
27 | 
28 | def fmt(txt):
29 |     s = zeekscript.Script(io.StringIO(txt))
30 |     s.parse()
31 |     if s.has_error():
32 |         return txt, parse_error(s.get_error())
33 |     buf = io.BytesIO()
34 |     s.format(buf)
35 |     return buf.getvalue().decode(), None
36 | 


--------------------------------------------------------------------------------
/manager/metrics.py:
--------------------------------------------------------------------------------
 1 | import datetime
 2 | from redis import Redis
 3 | r = Redis(host="redis")
 4 | 
 5 | def log_execution(version):
 6 |     date = datetime.datetime.today().strftime("%Y-%m-%d")
 7 |     r.incr("trybro:metrics:executions")
 8 |     r.hincrby("trybro:metrics:executions_bydate", date, 1)
 9 |     r.hincrby("trybro:metrics:executions_byversion", version, 1)
10 | 
11 | def log_cache_hit():
12 |     r.incr("trybro:metrics:cache_hits")
13 | 
14 | def get():
15 |     return {
16 |         "executions":    r.get("trybro:metrics:executions"),
17 |         "cache_hits":    r.get("trybro:metrics:cache_hits"),
18 |         "bydate":    r.hgetall("trybro:metrics:executions_bydate"),
19 |         "byversion": r.hgetall("trybro:metrics:executions_byversion"),
20 |     }
21 | 


--------------------------------------------------------------------------------
/manager/requirements.txt:
--------------------------------------------------------------------------------
 1 | async-timeout==4.0.2
 2 | certifi==2024.7.4
 3 | chardet==5.0.0
 4 | charset-normalizer==2.1.1
 5 | click==8.1.3
 6 | colorama==0.4.6
 7 | coverage==6.5.0
 8 | Deprecated==1.2.13
 9 | docker-py==1.10.6
10 | docker-pycreds==0.4.0
11 | Flask==2.3.2
12 | Flask-Jsonpify==1.5.0
13 | gunicorn==22.0.0
14 | html5lib==1.1
15 | idna==3.7
16 | importlib-metadata==5.0.0
17 | itsdangerous==2.1.2
18 | Jinja2==3.1.4
19 | Markdown==3.4.1
20 | MarkupSafe==2.1.1
21 | mock==4.0.3
22 | packaging==21.3
23 | pyparsing==3.0.9
24 | redis==4.4.4
25 | requests==2.32.0
26 | rq==1.11.1
27 | six==1.16.0
28 | supervisor==4.2.4
29 | urllib3==1.26.19
30 | webencodings==0.5.1
31 | websocket-client==1.4.2
32 | Werkzeug==3.0.6
33 | wrapt==1.14.1
34 | zipp==3.19.1
35 | zeekscript==1.2.9
36 | 


--------------------------------------------------------------------------------
/manager/runbro:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | 
 3 | export ZEEK_DNS_FAKE=1
 4 | export BRO_DNS_FAKE=1
 5 | 
 6 | for prog in /bro/bin/bro /bro/bin/zeek /usr/local/zeek/bin/zeek ; do
 7 |     if [ -x $prog ] ; then
 8 |         PROG=$prog
 9 |     fi
10 | done
11 | 
12 | cd /brostuff/*/ || exit 1
13 | opts=""
14 | 
15 | if [ -e file.pcap ]; then
16 |     opts="-r file.pcap"
17 | fi
18 | 
19 | 
20 | chown nobody . ./*
21 | #Try both, just in case
22 | mv main.zeek try.zeek || mv main.bro try.zeek
23 | 
24 | cat < __pre.zeek
25 | redef Site::local_nets += { 0.0.0.0/0 };
26 | @ifndef(zeek_init)
27 | global zeek_init: event();
28 | global zeek_done: event();
29 | event bro_init() { event zeek_init() ; }
30 | event bro_done() { event zeek_done() ; }
31 | @endif
32 | EOF
33 | su -s /bin/sh nobody -c "/usr/bin/timeout -k 2 15 $PROG -C $opts local  __pre.zeek ./try.zeek > stdout.log 2> stderr.log"
34 | 
35 | #remove harmless message
36 | grep -v 'NB-DNS' stderr.log | grep -v 'issue DNS request' > tmp.log
37 | mv tmp.log stderr.log
38 | 
39 | #remove useless output files
40 | rm -f loaded_scripts.log packet_filter.log notice_policy.log
41 | 


--------------------------------------------------------------------------------
/manager/runbro-1.5:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | 
 3 | export BRO_DNS_FAKE=1
 4 | 
 5 | cd /brostuff/*/ || exit 1
 6 | opts=""
 7 | 
 8 | if [ -e file.pcap ]; then
 9 |     opts="-r file.pcap"
10 | fi
11 | 
12 | 
13 | mv main.zeek try.bro
14 | 
15 | cat < __pre.bro
16 | const use_dpd = T;
17 | @load brolite
18 | redef log_rotate_interval = 0secs;
19 | redef local_nets += { 0.0.0.0/0 };
20 | 
21 | @ifndef(zeek_init)
22 | global zeek_init: event();
23 | global zeek_done: event();
24 | event bro_init() { event zeek_init() ; }
25 | event bro_done() { event zeek_done() ; }
26 | @endif
27 | EOF
28 | 
29 | chown nobody . *
30 | 
31 | su -s /bin/sh nobody -c \
32 |     "/usr/bin/timeout -k 2 15 /bro/bin/bro -f 'ip or not ip' -C $opts __pre.bro ./try.bro > stdout.log 2> stderr.log"
33 | 
34 | #remove harmless message
35 | grep -v 'NB-DNS' stderr.log > tmp.log
36 | mv tmp.log stderr.log
37 | 
38 | #remove useless output files
39 | rm -f loaded_scripts.log packet_filter.log
40 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/bro-types/main.zeek:
--------------------------------------------------------------------------------
1 | event zeek_init()
2 | 	{
3 | 	print "Time to figure out why Zeek is special";
4 | 	}
5 | 
6 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/bro-types/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Zeek Datatypes
 2 | pcaps: 
 3 | 
 4 | Zeek Datatypes
 5 | ===================
 6 | 
 7 | As a network monitoring system Zeek has its focus on networks and includes some data types 
 8 | specifically helpful when working with networks.
 9 | 
10 | * `time` - an absolute point in time. The built-in function
11 |   `network_time` returns Zeek's notion of *now* (which is derived from
12 |   the packets it analyzes). The only way to create an
13 |   arbitrary time value is via the `double_to_time(d)`, with `d`
14 |   being a variable of type `double` representing seconds since the
15 |   UNIX epoch..
16 | * `interval` - a relative unit of time. Known units are `usec`,
17 |   `msec`, `sec`, `min`, `hr`, or `day` (any may be pluralized by
18 |   adding "s" to the end).  Examples: `3secs`, `-1min`.
19 | * `port` - a transport-level port number.  Examples: `80/tcp`,
20 |   `53/udp`.
21 | * `addr` - an IP address.  Examples: `1.2.3.4`, `[2001:db8::1]`.
22 | * `subnet` - a set of IP addresses with a common prefix, using CDIR notation.  Example:
23 |   `192.168.0.0/16`.  Note that the `/` operator used on an address as
24 |    the left operand produces a subnet mask of bit-width equal to the value
25 |    of the right operand.
26 |  
27 | 
28 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/comp-type-intro/main.zeek:
--------------------------------------------------------------------------------
1 | event zeek_init() 
2 | 	{
3 | 	print "Go to the next page to learn about sets";     
4 | 	}
5 | 
6 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/comp-type-intro/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Composite Datatypes
 2 | pcaps: 
 3 | 
 4 | Composite Datatypes
 5 | ===================
 6 | 
 7 | We have already used simple data types like `string` and `int`. 
 8 | We now introduce the complex datatypes
 9 | `set`, `table`, `vector`, and `record`. This will allow to solve more
10 | complex script exercises.
11 | 
12 | On the following pages we introduce each of the complex types on its own page.
13 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/index:
--------------------------------------------------------------------------------
1 | set
2 | table
3 | vector
4 | record
5 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/record/main.zeek:
--------------------------------------------------------------------------------
 1 | type MyRecord: record {
 2 | 	a: string;
 3 | 	b: count;
 4 | 	c: bool &default = T;
 5 | 	d: int &optional;
 6 | };
 7 | 
 8 | event zeek_init() 
 9 | 	{ 
10 | 	local x = MyRecord($a = "vvvvvv", $b = 6, $c = F, $d = -13);
11 | 	if ( x?$d )
12 | 		{
13 | 		print x$d;
14 | 		}
15 | 	
16 | 	x = MyRecord($a = "abc", $b = 3);
17 | 	print x$c;  # T (default value of the field)
18 | 	print x?$d; # F (optional field was not set)
19 | 	}
20 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/record/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: record
 2 | pcaps: 
 3 | 
 4 | Record
 5 | =======
 6 | 
 7 | A `record` is a user-defined collection of named values of
 8 | heterogeneous types, similar to a struct in C.  Fields are dereferenced via the `$` operator
 9 | (`.`, as used in other languages, would be ambiguous in Zeek because of
10 | IPv4 address literals).  Optional field existence is checked via the
11 | `?$` operator.
12 | 
13 | 
14 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/set/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 |     {
 3 |     local x: set[string] = { "one", "two", "three" };
 4 |     add x["four"];
 5 |     print "four" in x; # T
 6 |     delete x["two"];
 7 |     print "two" !in x; # T
 8 |     add x["one"]; # x is unmodified since 1 is already a member.
 9 | 
10 |     for ( e in x )
11 |         {
12 |         print e;
13 |         }
14 |     }
15 | 
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/set/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Set	
 2 | pcaps: 
 3 | 
 4 | Set
 5 | ===================
 6 | 
 7 | A `set` is a collection of unique values.  Sets use the `add` and
 8 | `delete` operators to add and remove elements, and the `in`
 9 | operator to test for membership. 
10 | 
11 | Run the example. 
12 | 
13 | In this example we first define a set of strings, containing the words "one", "two", and "three".
14 | The we add the string "four" to it. Thus the test for membership of "four" will result in "T" for
15 | true. The same way we can delete "two" from the set, testing if "two is not a member will result 
16 | in "T" again. Adding the string "one" has no effect since it is already in the set.
17 | We can also use a for loop to print each member of a set.
18 | 
19 | 
20 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/table/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{ 
 3 | 	local x: table[count] of string = { [1] = "one", 
 4 | 	                                    [3] = "three",
 5 | 	                                    [5] = "five" };
 6 | 	x[7] = "seven";
 7 | 	print 7 in x; # T
 8 | 	print x[7]; # seven
 9 | 	delete x[3];
10 | 	print 3 !in x; # T
11 | 	x[1] = "1"; # changed the value at index 1
12 | 
13 | 	for ( key in x ) 
14 | 		{
15 | 		print key;
16 | 		}
17 | 	}
18 | 
19 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/table/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Table
 2 | pcaps: 
 3 | 
 4 | Table
 5 | ===================
 6 | 
 7 | A `table` is an associative collection that maps a set of unique indices
 8 | to other values. The same way as for sets, the `delete` operator is used to remove
 9 | elements, however, adding elements is done just by assigning to an index as shown in this code example.
10 | 
11 | Tables are comparable to arrays, hashes, or maps in other languages.
12 | 
13 | Run the example. Most of it is the same as for sets. You can experiment with searching in sets and tables for example.
14 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/vector/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{ 
 3 | 	local x: vector of string = { "one", "two", "three" };
 4 | 	print x; # [one, two, three]
 5 | 	print x[1]; # two
 6 | 	x[|x|] = "one";
 7 | 	print x; # [one, two, three, one]
 8 | 
 9 | 	for ( i in x ) 
10 | 		{
11 | 		print i;  # Iterates over indices.
12 | 		}
13 | 	}
14 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/composite-types/vector/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Vector	
 2 | pcaps: 
 3 | 
 4 | Vector
 5 | =======
 6 | 
 7 | A `vector` is a collection of values with 0-based indexing.
 8 | In comparison to sets this allows to store the same value twice.
 9 | 
10 | Line 6 shows an example of the length operator. 
11 | This line adds a new element at the end of the list.
12 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/control-flow/if/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{ 
 3 | 	local x = "3";
 4 | 
 5 | 	for ( c in "12345" )
 6 | 		{
 7 | 		if ( c == x )
 8 | 			{
 9 | 			print "Found it.";
10 | 			# A preview of functions: fmt() does substitutions, outputs result.
11 | 			print fmt("And by 'it', I mean %s.", x);
12 | 			}
13 | 		else
14 | 			{
15 | 			# A quick way to print multiple things on one line.
16 | 			print "I'm looking for", x, "not", c;
17 | 			}
18 | 		}
19 | 	}
20 | 
21 | 
22 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/control-flow/if/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: If
 2 | pcaps:
 3 | pred: while
 4 | succ: script-exercise-1
 5 | 
 6 | If Statement
 7 | ================
 8 | 
 9 | If statements conditionally execute another statement or block of statements.
10 | Play with the given example.
11 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/control-flow/index:
--------------------------------------------------------------------------------
1 | if
2 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/event/main.zeek:
--------------------------------------------------------------------------------
 1 | global myevent: event(s: string);
 2 | 
 3 | global n = 0;
 4 | 
 5 | event myevent(s: string) &priority = -10
 6 | 	{
 7 | 	++n;
 8 | 	}
 9 | 
10 | event myevent(s: string) &priority = 10
11 | 	{
12 | 	print "myevent", s, n;
13 | 	}
14 | 
15 | event zeek_init()
16 | 	{
17 | 	print "zeek_init()";
18 | 	event myevent("hi");
19 | 	schedule 5 sec { myevent("bye") };
20 | 	}
21 | 
22 | event zeek_done()
23 | 	{
24 | 	print "zeek_done()";
25 | 	}
26 | 
27 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/event/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Event	
 2 | pcaps: 
 3 | 
 4 | Event
 5 | ===================
 6 | 
 7 | We introduced events briefly in the first example to be able to do anything 
 8 | with the Zeek language. Events are actually a special flavor of functions 
 9 | but are essential to how Zeek works.
10 | They differ from functions in the following ways:
11 | 
12 | * They may be scheduled and executed at a later time, so that their
13 |   effects may not be realized directly after they are invoked.
14 | * They return no value -- they can't since they're not called directly
15 |   but rather scheduled for later execution.
16 | * Multiple bodies can be defined for the same event, each one is
17 |   deemed an "event handler". When it comes time to execute an
18 |   event, all handler bodies for that event are executed in order of
19 |   `&priority`.
20 | 
21 | In the Zeek documentation, there is a detailed chapter about Zeek's event engine, how Zeek and the scripts
22 | interact, and what role the `event` plays in a Zeek script. Please [read](https://docs.zeek.org/en/current/scripting/index.html#the-event-queue-and-event-handlers).
23 | A reference for predefined events not related to protocol or file analysis is [here](https://docs.zeek.org/en/current/scripts/base/bif/event.bif.zeek.html).
24 | 
25 | This example shows how to define and trigger a custom event.
26 | 
27 | * We first see an event declaration of "myevent" that takes the string "s".
28 | * The the event handler implementation follows. The `&priority` attribute is optional and
29 | may be used to influence the order in which event handler bodies execute.
30 | If omitted, &priority is implicitly 0. In the example the priority is `-10` and thus very low. 
31 | When this handler is called it will increment `n` from `0` to `1`.
32 | * The next handler for the same event sets the priority to 10. This handler will print the string "myevent"
33 | and the current values of the variables `s` and `n`.
34 | * Next we see the already familiar `zeek_init` event that is executed
35 | once when Zeek starts. It schedules the event twice.
36 | The first execution is a 'a soon as possible"
37 | schedule, the `schedule 5 sec {}` executes either in 5 seconds or upon Zeek shutting down, whichever 
38 | happens first.
39 | 
40 | Run the code and follow the order in which the events are executed. 
41 | 
42 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise1/exercise/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{ 
 3 | 	print "find meeeeeee!";
 4 | 	}
 5 | 
 6 | event zeek_done() 
 7 | 	{
 8 | 	print "fizz buzz";
 9 | 	}
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise1/exercise/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Exercise
 2 | pcaps:
 3 | 
 4 | Exercise
 5 | ==========================
 6 | 
 7 | Now that we have types, operators, functions, loops and logic control we can 
 8 | go into a first exercise.
 9 | 
10 | As you have seen, Zeek supports pattern matching. 
11 | Pattern matching can be a powerful tool especially in the area of network debugging and security.
12 | Finding specific things in logs is an efficient way to make a change in many situations.
13 | 
14 | *  Write a program to remove every letter "e" from an arbitrary string
15 |    of your choosing (does not have to be done in-place).
16 | 
17 | *  Write a program that prints the numbers from 1 to 100. But for
18 |    multiples of three, print "Fizz" instead of the number; and for 
19 |    multiples of five, print "Buzz". For numbers which are multiples of
20 |    both three and five print "FizzBuzz".
21 | 
22 | The solution to this exercise follows on the next page.
23 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise1/index:
--------------------------------------------------------------------------------
1 | exercise
2 | solution
3 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise1/solution/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 |     { 
 3 |     local result = "";
 4 | 
 5 |     for ( c in "testing" )
 6 |         {
 7 |         if ( c != "e" )
 8 | 	    {
 9 |             result = result + c;
10 |             # Compound assignment, ``result += c``, also works.
11 |             }
12 |         }
13 |     print result;
14 |     }
15 | 
16 | #Recursive approach w/ string concatenation.   
17 | function fizzbuzz(i: count)
18 |     {
19 |     # Modulo, string concatenation approach.
20 |     local s = "";
21 |     
22 |     if ( i % 3 == 0 )
23 |         s += "Fizz";
24 |     
25 |     if ( i % 5 == 0 )
26 |         s += "Buzz";
27 |     
28 |     if ( s == "" )
29 |         print i;
30 |     else
31 |         print s;
32 |     
33 |     if ( i < 100 )
34 |         fizzbuzz(i + 1);
35 |     }
36 | 
37 | event zeek_done() 
38 |     {
39 |     fizzbuzz(1);
40 |     }
41 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise1/solution/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Exercise 1: Solution
 2 | pcaps:
 3 | 
 4 | Exercise 1 Solution
 5 | =====================================
 6 | 
 7 | Here is the solution for the first exercise.
 8 | 
 9 | In the zeek\_init event we have a simple for-loop that iterates over
10 | the string "testing". Every character is tested if it is not an "e".
11 | Every other character is added to the end of the string in the variable "result".
12 | The resulting string is the printed and should contain no more "e"s.
13 | 
14 | The second example shows recursive usage of a function.
15 | The recursion counts to 100 and replaces every 3rd number by "Fizz", every
16 | fifth by "Buzz". To do this the modulo operation is used.
17 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise2/index:
--------------------------------------------------------------------------------
1 | script-exercise-2
2 | solution-script-exercise-2
3 | 
4 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise2/script-exercise-2/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 | 	{
 3 | 	print "use all the Zeek types";
 4 | 	}
 5 | 
 6 | event zeek_done()
 7 | 	{
 8 | 	print "and tell me what you learned";
 9 | 	}
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise2/script-exercise-2/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Exercise
 2 | pcaps: exercise_traffic.pcap
 3 | order:
 4 | 
 5 | Exercise
 6 | ==========================
 7 | 
 8 | By now we have all basic concepts of the Zeek scripting language.
 9 | To finish the first part of your journey into the Zeek language solve the following exercise.
10 | 
11 | Consider the following list of subnets as your given local subnets:
12 | 
13 | 192.168.1.0/24, 192.68.2.0/24, 172.16.0.0/20, 172.16.16.0/20, 172.16.32.0/20, 172.16.48.0/20.
14 | 
15 | Write a script that:
16 | 
17 | * tells for the first 10  new connections source IP and port, destinations IP and port, connection ID, time when the connection started.
18 | * counts all connections seen and prints them in the end.
19 | * prints out for each unique IP address if its is local or external.
20 | 
21 | To solve this exercise please load the traffic sample exercise_traffic.pcap.
22 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise2/solution-script-exercise-2/main.zeek:
--------------------------------------------------------------------------------
 1 | global local_subnets: set[subnet] = { 192.168.1.0/24, 192.68.2.0/24, 172.16.0.0/20, 172.16.16.0/20, 172.16.32.0/20, 172.16.48.0/20 };
 2 | global my_count = 0;
 3 | global inside_networks: set[addr];
 4 | global outside_networks: set[addr];
 5 | 
 6 | event new_connection(c: connection)
 7 |     {
 8 |     ++my_count;
 9 |     if ( my_count <= 10 )
10 | 	{
11 |         print fmt("The connection %s from %s on port %s to %s on port %s started at %s.", c$uid, c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p, strftime("%D %H:%M", c$start_time)); 
12 |     }
13 |     if ( c$id$orig_h in local_subnets)
14 |     	{
15 | 	add inside_networks[c$id$orig_h];
16 |         }
17 |     else
18 |         add outside_networks[c$id$orig_h];
19 | 	    
20 |     if ( c$id$resp_h in local_subnets)
21 |         {
22 |         add inside_networks[c$id$resp_h];
23 |         }
24 |     else
25 |         add outside_networks[c$id$resp_h];
26 |     }
27 | 
28 | event connection_state_remove(c: connection)
29 |     {
30 |     if ( my_count <= 10 )
31 |     	{
32 |     	print fmt("Connection %s took %s seconds", c$uid, c$duration);	
33 |     	}
34 |     }
35 | 
36 | event zeek_done() 
37 |     {
38 |     print fmt("Saw %d new connections", my_count);
39 |     print "These IPs are considered local";
40 |     for (a in inside_networks)
41 |         {
42 |         print a;
43 |         }
44 |     print "These IPs are considered external";
45 |     for (a in outside_networks)
46 |         {
47 |         print a;
48 |         }
49 |     }
50 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/exercise2/solution-script-exercise-2/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Exercise 2
 2 | pcaps: exercise_traffic.pcap
 3 | 
 4 | Exercise: Solution
 5 | ==========================
 6 | 
 7 | The solution is one possible way to solve this exercise.
 8 | 
 9 | * First we write local subnets into a set.
10 | * To count all connections we declare the global counter my\_count.
11 | * To learn about every new connection we simply use the event [new\_connection](https://docs.zeek.org/en/current/scripts/base/bif/event.bif.zeek.html#id-new_connection). 
12 |   Every time this
13 |   event is triggered we increase the counter. For the first 10 connections we print source IP and port and 
14 |   destination IP and port, plus connection ID and time. To get the connection ID we need the filed uid of the connection.
15 |   To print the start time of the connection in human readable form we use the Zeek bif [strftime](https://docs.zeek.org/en/current/scripts/base/bif/bro.bif.zeek.html?highlight=strftime#id-strftime).
16 | * The duration of a connection - expressed as an interval - can be retrieved when the connection ends. 
17 |   The event [connection\_state\_remove](https://docs.zeek.org/en/current/scripts/base/bif/event.bif.zeek.html?highlight=connection_state_remove#id-connection_state_remove)
18 |   is triggered when a connection is about to be removed from memory. Then we can simply ask for the duration.
19 | * At the very end inside the zeek\_done event we compute the rest. Print the number of connections stored in my\_count
20 |   and use a for-loop to print out a list of all unique IPs and if they are local or external IPs.
21 |   In this example we simply define which subnets are considered local. This does not mean that the list is complete. In a real
22 |   world example this should be verified and documented in [networks.cfg](https://github.com/zeek/zeekctl/blob/master/doc/zeekctl.rst).
23 | 
24 | 
25 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/functions/main.zeek:
--------------------------------------------------------------------------------
 1 | # Function implementation.
 2 | function emphasize(s: string, p: string &default = "*"): string
 3 | 	{
 4 | 	return p + s + p;
 5 | 	}
 6 | 
 7 | 
 8 | event zeek_init() 
 9 | 	{ 
10 | 	# Function calls.
11 | 	print emphasize("yes");
12 | 	print emphasize("no", "_");
13 | 	}
14 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/functions/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Functions
 2 | pcaps: 
 3 | 
 4 | Functions
 5 | ==========
 6 | 
 7 | Introducing a programming language often encounters mutual dependencies on different pieces of knowledge. 
 8 | As a basic part we introduce functions now. To show you a working example we need to use some
 9 | elements that are explained later. 
10 | 
11 | This example function takes one string argument and another optional string argument.
12 | It returns a string. 
13 | The function is declared and implemented at the same time. The function is then called 
14 | within the zeek\_init event.
15 | 
16 | What do we see here?
17 | 
18 | Input parameters are specified within parentheses in a comma separated list. The return value follows after the colon.
19 | All parameters in this function are of type 'string'. We will see more about types in Zeek in the next 
20 | [lesson](http://try.zeek.org/example/primitive_datatypes).
21 | 
22 | The second argument in this example is optional. This is because of the [attribute](https://docs.zeek.org/en/current/script-reference/attributes.html) 
23 | &default. In the example here the default value would be '\*' in case the second parameter is missing.
24 | 
25 | Another element seen here is the '+'-operator that concatenates the strings in this case.
26 | 
27 | At last when the function is used, the resulting values are simply printed to STDOUT.
28 | 
29 | Another side note on the relation between functions and events: Events
30 | are a kind of function as well, and both can only be declared at the global
31 | level; one cannot nest them.
32 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/hook/main.zeek:
--------------------------------------------------------------------------------
 1 | global myhook: hook(s: string);
 2 | 
 3 | hook myhook(s: string) &priority = 10
 4 | 	{
 5 | 	print "priority 10 myhook handler", s;
 6 | 	s = "bye";
 7 | 	}
 8 | 
 9 | hook myhook(s: string)
10 | 	{
11 | 	print "break out of myhook handling", s;
12 | 	break;
13 | 	}
14 | 
15 | hook myhook(s: string) &priority = -5
16 | 	{
17 | 	print "not going to happen", s;
18 | 	}
19 | 
20 | event zeek_init() 
21 | 	{
22 | 	local ret: bool = hook myhook("hi");
23 | 	if ( ret )
24 | 		{
25 | 		print "all handlers ran";
26 | 		}
27 | 	}
28 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/hook/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: hook
 2 | pcaps: 
 3 | 
 4 | Hook
 5 | =======
 6 | 
 7 | Hooks are yet another flavor of function.  They are similar to events
 8 | in that they can also have multiple bodies. However they are different
 9 | in two regards:
10 | 
11 | * They do execute immediately when invoked (i.e. they're not scheduled
12 |   like events).
13 | * The way the body of a hook handler terminates determines if further
14 |   handlers get executed. If the end of the
15 |   body, or a `return` statement, is reached, the next hook handler
16 |   will be executed.  If, however, a hook handler body terminates with a `break`
17 |   statement, no remaining hook handlers will execute.
18 | 
19 | Hooks are useful to provide customization points for modules, as they
20 | allow to outsource decisions to site-specific code.
21 | 
22 | In this example we included the mentioned break statement, so the hook
23 | with priority `-5` is never executed. Try to play with this statement and 
24 | the priorities to change the behavior of this example code.
25 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/index:
--------------------------------------------------------------------------------
 1 | loading
 2 | functions
 3 | variables
 4 | primitive-datatypes
 5 | operators
 6 | control-flow
 7 | loops
 8 | exercise1
 9 | switches
10 | event
11 | hook
12 | composite-types
13 | redefinitions
14 | bro-types
15 | exercise2
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loading/main.zeek:
--------------------------------------------------------------------------------
1 | @load misc/dump-events
2 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loading/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Loading Scripts
 2 | pcaps: 
 3 | 
 4 | Loading Scripts
 5 | ===============
 6 | 
 7 | Like most programming languages, Zeek has the ability to load in script code 
 8 | from other files.  There is a directive, `@load` which provides the capability.
 9 | 
10 | The code here shows a simple script that does nothing but loading a script. The script [misc/dump-events](https://docs.zeek.org/en/current/scripts/policy/misc/dump-events.zeek.html) prints the events that Zeek generates out to standard output in a readable form. This is for debugging only and can be used to help understand events and their parameters. Note that it will show only events for which a handler is defined.
11 | 
12 | A small note needs to be made here because there are some default paths defined by Zeek automatically which make it easier to load many of the scripts that are included with Zeek. The default paths are as follows (based on the installed prefix directory): 
13 | 
14 |   - `/share/bro`
15 |   - `/share/bro/policy`
16 |   - `/share/bro/site`
17 | 
18 | The most common use case of the load statement is in [local.zeek](https://github.com/zeek/zeekctl/blob/master/doc/zeekctl.rst#site-specific-customization).
19 | This file is part of Zeek's configuration files and adds further scripts that are not loaded by default. A reference of all scripts that can be loaded is found [here](https://docs.zeek.org/en/current/script-reference/scripts.html).
20 | Everything you see there in `base/` is loaded by default, e.g., policies have to be loaded via the load statement.
21 | 
22 | 
23 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loops/for/main.zeek:
--------------------------------------------------------------------------------
1 | event zeek_init() 
2 | 	{ 
3 | 	for ( character in "abc" )
4 | 		{
5 | 		print character;
6 | 		}
7 | 	}
8 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loops/for/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: For Loops
 2 | pcaps:
 3 | 
 4 | For Loops
 5 | ================
 6 | 
 7 | Zeek provides a "foreach" style loop.
 8 | In the given example we simply iterate through the string "abc"
 9 | and print the current character.
10 | 
11 | Note: Iterating over any collection other than a vector won't provide any guarantee of the order
12 | Zeek iterates over the collection. If the order is important the collection should be a vector.
13 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loops/index:
--------------------------------------------------------------------------------
1 | for
2 | while
3 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loops/while/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 |     {
 3 |     local i = 0;
 4 | 
 5 |     while ( i < 5 )
 6 |     print ++i;
 7 | 
 8 |     while ( i % 2 != 0 )
 9 |     {
10 |     local finish_up = F;
11 | 
12 |     if ( finish_up == F )
13 |     	print "nope";
14 |         ++i;
15 |         next;
16 | 
17 |     if ( finish_up )
18 |         break;
19 |     }
20 |     print i;
21 |     }
22 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/loops/while/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Loops: While
 2 | pcaps:
 3 | 
 4 | Loops: While
 5 | ================
 6 | 
 7 | A "while" loop iterates over a body statement as long as a given condition remains true.
 8 | 
 9 | A [break](https://docs.zeek.org/en/current/script-reference/statements.html#keyword-break) 
10 | statement can be used at any time to immediately terminate 
11 | the "while" loop, and a 
12 | [next](https://docs.zeek.org/en/current/script-reference/statements.html#keyword-next) 
13 | statement can be used to skip to the next loop iteration.
14 | 
15 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/operators/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{ 
 3 | 	print "Try to figure out what happens if ";
 4 | 	print "you use numbers as strings and compare them with 'ints'";
 5 | 	print "get to know Zeek's types and operators so you don't ";
 6 | 	print "need to look them up all them time in later exercises.";
 7 | 	}
 8 | 
 9 | event zeek_done() 
10 | 	{ 
11 | 	print "Well done!"; 
12 | 	}
13 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/operators/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Operators
 2 | pcaps:
 3 | 
 4 | Operators
 5 | ==========
 6 | 
 7 | So far we have functions, variables, and we can even type them. 
 8 | We still can't connect two (or more) values to build a new one. 
 9 | So now we can talk about operators that are used to manipulate, inspect, or compare data.
10 | 
11 | Explore the operators below to play with the Zeek elements we have so
12 | far. In the next two steps, we introduce loops 
13 | and if-statements so that we can solve more complex exercises.
14 | 
15 | Arithmetic Operators
16 | -----------------------
17 | 
18 |  Name          | Syntax    | Example Usage
19 |  ------------- | --------- | --------------------------------------------------
20 | Addition       | ``a + b`` | ``print 2 + 2;   # 4``
21 | Subtraction    | ``a - b`` | ``print 2 - 2;   # 0``
22 | Multiplication | ``a * b`` | ``print 4 * 4;   # 16``
23 | Division       | ``a / b`` | ``print 15 / 3;  # 5``
24 | Modulo         | ``a % b`` | ``print 18 % 15; # 3``
25 | Unary Plus     | ``+a``    | ``local a = +1;  # Force use of a signed integer``
26 | Unary Minus    | ``-a``    | ``local a = 5; print -a; # -5``
27 | Increment      | ``++a``   | ``local a = 1; print ++a, a; # 2, 2``
28 | Decrement      | ``--a``   | ``local a = 2; print --a, a; # 1, 1``
29 | 
30 | Assignment Operators
31 | --------------------
32 | 
33 | Name                   | Syntax     |  Example Usage
34 | -----------------------| ---------- | ------------------------ 
35 | Assignment             | ``a = b``  |  ``local a = 7;``
36 | Addition assignment    | ``a += b`` |  ``local a = 7; a += 2; # 9``
37 | Subtraction assignment | ``a -= b`` |  ``local a = 7; a -= 2; # 5``
38 | 
39 | Relational Operators
40 | ---------------------
41 | 
42 |  Name            | Syntax     | Example Usage
43 |  --------------- | ---------- | ----------------------
44 | Equality         | ``a == b`` | ``print 2 == 2; # T``
45 | Inequality       | ``a != b`` | ``print 2 != 2; # F``
46 | Less             | ``a < b``  | ``print 2 < 3;  # T``
47 | Less or Equal    | ``a <= b`` | ``print 2 <= 2; # T``
48 | Greater          | ``a > b``  | ``print 2 > 3;  # F``
49 | Greater or Equal | ``a >= b`` | ``print 2 >= 2; # T``
50 | 
51 | Logical Operators
52 | ------------------
53 | 
54 | Name             | Syntax     | Example Usage
55 | ---------------- | ---------- | ----------------------
56 | Logical NOT      | ``! a``    | ``print !T;     # F``
57 | Logical AND      | ``a && b`` | ``print T && F; # F``
58 | Logical OR       | ``a || b`` | ``print F || T; # T``
59 | 
60 | Other Operators
61 | ----------------
62 | 
63 | Name             | Syntax                    | Example Usage
64 | ---------------- | ------------------------  | -----------------------------
65 | Member Inclusion | ``a in b``                | ``print "z" in "test";  # F``
66 | Member Exclusion | ``a !in b``               | ``print "z" !in "test"; # T``
67 | Size/Length      | ``|a|``         | ``print |"test"|; # 4``
68 | Absolute Value   | ``|a|``         | ``print |-5|;     # 5``
69 | Index            | ``a[i]``                  | ``print "test"[2];      # s``
70 | String Slicing   | ``a[i:j], a[i:], a[:j]``  | ``print "testing"[2:4]; # st``
71 | 
72 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/primitive-datatypes/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 | 	{
 3 | 	local x : string = "two";
 4 | 	local y : int = 10000000000000000000;
 5 | 	print "y is a large int:", y;
 6 | 	print "x is a short string:", x;
 7 | 	
 8 | 	#pattern matching 
 9 | 	print /one|two|three/ == "two";  # T
10 | 	print /one|two|three/ == "ones"; # F (exact matching)
11 | 	print /one|two|three/ in "ones"; # T (embedded matching)
12 | 	print /[123].*/ == "2 two";  # T
13 | 	print /[123].*/ == "4 four"; # F
14 | 	}
15 | 
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/primitive-datatypes/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Primitive Datatypes
 2 | pcaps: 
 3 | 
 4 | Primitive Datatypes
 5 | ===================
 6 | 
 7 | Now that we have variables we can talk about which data types we can use and assign to variables.
 8 | In this lesson we introduce the simpler types.
 9 | 
10 | Zeek has a static type system (i.e., the type of data a variable holds is
11 | fixed) with type inference, e.g., `local x = 0` is equivalent to
12 | `local x: count = 0`. It also implicitly promotes/coerces types in
13 | certain situations.
14 | 
15 | The full reference on types in Zeek can be found [here](https://docs.zeek.org/en/current/script-reference/types.html).
16 | For now, look through the simple types. Most of the types should be familiar from other programming languages,
17 | e.g., `bool`, `double`, `int`, `count`, `string`, `pattern` (a regular expression using [flex's syntax](http://westes.github.io/flex/manual/Patterns.html)).
18 | But Zeek as a network monitoring system introduces also a set of domain-specific types that are explained 
19 | in the [reference](https://docs.zeek.org/en/current/script-reference/types.html).
20 | Examples are `time`, `interval`, `port`, `addr`, and `subnet`.
21 | 
22 | These custom Zeek types and the more complex types will be discussed in detailed examples in later lessons. 
23 | 
24 | Run the code in this example. Try to play with the given code example,
25 | e.g. change the given types. Does that work?
26 | 
27 | 
28 | 
29 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/redefinitions/index:
--------------------------------------------------------------------------------
1 | redef-records
2 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/redefinitions/redef-records/main.zeek:
--------------------------------------------------------------------------------
 1 | type MyRecord: record {
 2 | 	a: string &default="hi";
 3 | 	b: count  &default=7;
 4 | } &redef;
 5 | 
 6 | redef record MyRecord += {
 7 | 	c: bool &optional;
 8 | 	d: bool &default=F;
 9 | 	#e: bool; # Not allowed, must be &optional or &default.
10 | };
11 | 
12 | event zeek_init() 
13 | 	{
14 | 	print MyRecord();
15 | 	print MyRecord($c=T);
16 | 	}
17 | 
18 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/redefinitions/redef-records/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Redefinitions
 2 | pcaps: 
 3 | 
 4 | Redefinitions for records
 5 | ============================
 6 | 
 7 | `redef` not only works with values, but also
 8 | certain *types*.  Namely `record` (and `enum`) may be extended, which is shown in this
 9 | code example.
10 | 
11 | Redefining records is especially helpful when working with given modules. 
12 | 
13 | Run this code example. Play with the the printing command, change the `redef` and see what effects
14 | happen. 
15 | 
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/redefinitions/redef/main.zeek:
--------------------------------------------------------------------------------
 1 | const pi = 3.14 &redef;
 2 | redef pi = 3.1415;
 3 | 
 4 | event zeek_init() 
 5 | 	{
 6 | 	const two = 2;
 7 | 	#redef two = 1; # not allowed
 8 | 	#pi = 5.5;      # not allowed
 9 | 	print pi;
10 | 	print two;
11 | 	}
12 | 
13 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/redefinitions/redef/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Redefinitions
 2 | pcaps: 
 3 | 
 4 | Redefinitions
 5 | ============================
 6 | 
 7 | Zeek supports redefining constants, but only at parse-time, not at
 8 | run-time.  This feature may not be that useful when writing your own
 9 | scripts for private usage, but it's the suggested way for script authors
10 | to advertise "knobs and switches" that one may choose to configure.
11 | These are usually values that one doesn't want to accidentally modify
12 | while Zeek is running, but that the author either can't know ahead of
13 | time (e.g. local IP addresses of interest), may differ across
14 | environments (e.g. trusted SSL certificates), or may evolve over
15 | time (e.g. a list of known cipher suites).
16 | 
17 | Normally, the declaration and the `redef` would live in different
18 | scripts (e.g. the declaration in a script from the "standard library"
19 | that comes with Zeek and the `redef` in the script you write), but
20 | this is just an example.
21 | 
22 | Run the code and try to uncomment the line 
23 |     
24 |     redef two = 1;
25 | 
26 | Then uncomment the next line.
27 | 
28 | 
29 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/index:
--------------------------------------------------------------------------------
1 | switch
2 | switch-exercise
3 | solution-switch-exercise
4 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/solution-switch-exercise/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 |     { 
 3 |     local result = 0;
 4 |     local input = "The Zeek Network Security Monitor";
 5 |     for ( c in input )
 6 |         {
 7 |         switch ( c ) 
 8 |             {
 9 |             case "a", "e", "i", "o", "u":
10 |                 ++result;
11 |                 break;
12 |             }
13 |         }
14 |     print result;
15 |     }
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/solution-switch-exercise/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Switch Exercise: Solution
 2 | pcaps:
 3 | 
 4 | Switch Exercise: Solution
 5 | ==========================================
 6 | 
 7 | Write a program that relies on a switch statement to count the
 8 | number of vowels (a, e, i, o, u) in an arbitrary string of your
 9 | choosing.
10 | 
11 | Here is an example that works.
12 | We loop through the string that is given in the variable `input`.
13 | Inside the for loop every character is sent to the switch to test if it is a vowel. If so, the 
14 | variable `result` is incremented.
15 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/switch-exercise/main.zeek:
--------------------------------------------------------------------------------
1 | event zeek_init() 
2 | 	{ 
3 | 	print "write a switch to count vowels!";
4 | 	}
5 | 
6 | 
7 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/switch-exercise/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Switch Exercise
 2 | pcaps:
 3 | 
 4 | Switch Exercise
 5 | ================================
 6 | 
 7 | Write a program that relies on a switch statement to count the
 8 | number of vowels (a, e, i, o, u) in an arbitrary string of your
 9 | choosing.
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/switch/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init() 
 2 |     { 
 3 |     local x = 4;
 4 | 
 5 |     switch ( x ) 
 6 |         {
 7 |         case 0:
 8 |             # This block only executes if x is 0.
 9 |             print "case 0";
10 |             break;
11 |         case 1, 2, 3:
12 |             # This block executes if any of the case labels match.
13 |             print "case 1, 2, 3";
14 |             break;
15 |         case 4:
16 |             print "case 4 and ...";
17 |             # Block ending in the "fallthrough" also execute subsequent case.
18 |             fallthrough;
19 |         case 5:
20 |             # This block may execute if x is 4 or 5.
21 |             print "case 5";
22 |             break;
23 |         default:
24 |             # This block executed if no other case matches.
25 |             print "default case";
26 |             break;
27 |         }
28 |     }    
29 | 
30 | 
31 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/switches/switch/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: The switch statement
 2 | pcaps:
 3 | 
 4 | The Switch Statement
 5 | =====================
 6 | 
 7 | Sometimes a switch statement is a more convenient way to organize code.
 8 | For example, consider a switch instead of large chains of "else if"
 9 | blocks if there's a large chain of OR'd conditions.
10 | 
11 | The syntax is similar to other common languages, "switch - variable - label".
12 | In Zeek it is possible to collect two or more label values to execute the same block of code.
13 | Also you can declare a default case if the input value does not match any of the cases.
14 | You must finish each case block with either "break" statement (to
15 | continue after the switch), or an explicit "fallthrough" to proceed
16 | into the subsequent case.
17 | 
18 | Now click "next" to solve an exercise using a switch.
19 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/variables/main.zeek:
--------------------------------------------------------------------------------
 1 | global x = "Hello";
 2 | 
 3 | event zeek_init()
 4 | 	{
 5 | 	print x;
 6 | 	
 7 | 	const y = "Guten Tag";
 8 | 	# Changing value of 'y' is not allowed.
 9 | 	#y = "Nope";
10 | 
11 | 	local z = "What does that mean?";
12 | 	print z;
13 | 	}
14 | 
15 | event zeek_done()
16 | 	{
17 | 	x = "Bye";
18 | 	print x;
19 | 	}
20 | 
21 | 


--------------------------------------------------------------------------------
/manager/static/examples/basics/variables/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Variables
 2 | pcaps:
 3 | 
 4 | Variables
 5 | ===========
 6 | 
 7 | You can assign arbitrary data to a variable in order to store it for later use.
 8 | A `local` variable differs from a `global` in that its 
 9 | scope is restricted to the body of a function 
10 | and will be assigned its initial 
11 | value each time the function body is executed. 
12 | 
13 | The reference on declaring variables, constants, functions etc is found 
14 | [here](https://docs.zeek.org/en/current/script-reference/statements.html).
15 | More on types and all things that can be declared will follow in later lessons of this
16 | tutorial.
17 | 
18 | Run the example for this exercise. Try to print 'z' in the second event. Does that work?
19 | 
20 | 


--------------------------------------------------------------------------------
/manager/static/examples/congrats/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 | 	{
 3 | 	print "Congratulations on your first lesson in Zeek Scripting!";
 4 | 	}
 5 | 
 6 | event zeek_done()
 7 | 	{
 8 | 	print "Well done!";
 9 | 	}
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/congrats/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Congrats
 2 | pcaps:
 3 | 
 4 | Congratulations
 5 | ==========================
 6 | 
 7 | You have finished all lessons in Zeek Script we have provided so far.
 8 | Welcome in the world of Zeek. We hope you enjoyed this little journey.
 9 | 
10 | Feedback is more than welcome, please write to [info@zeek.org](info@zeek.org).
11 | 
12 | To find more resources to learn Zeek visit the [Zeek web site](https://www.zeek.org).
13 | Or go back to the [Tutorial Index](https://old.zeek.org/documentation/tutorials/index.html).
14 | 


--------------------------------------------------------------------------------
/manager/static/examples/hello/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 | 	{
 3 | 	print "Hello, World!";
 4 | 	}
 5 | 
 6 | event zeek_done()
 7 | 	{
 8 | 	print "Goodbye, World!";
 9 | 	}
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/hello/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Hello World
 2 | pcaps: 
 3 | 
 4 | Hello World
 5 | ============
 6 | 
 7 | Welcome to our interactive Zeek tutorial.  (Note that "Zeek" is the
 8 | new name of what used to be known as the "Bro" network monitoring system.
 9 | The old "Bro" name still frequently appears in the system's documentation
10 | and workings, including in the names of events and the suffix used for
11 | script files.)
12 | 
13 | Click run and see the Zeek magic happen. You may need to scroll
14 | down a bit to get to the output.
15 | 
16 | In this simple example you can see already a specialty of Zeek, the "event". Zeek is event-driven.
17 | This means you can control any execution by making it dependent on an event trigger. 
18 | Our example here would not work without an event to be triggered so we use the two events that are always raised,
19 | 	zeek_init()
20 | and 
21 | 	zeek_done()
22 |  
23 | The first is executed when Zeek is started, the second when Zeek terminates, so we can use these for example
24 | when no traffic is actually analyzed as we do for our basic examples
25 | (see [here](https://docs.zeek.org/en/current/scripts/base/bif/event.bif.zeek.html) for more on these basic events).
26 | In this tutorial we will come back to events in the lesson about [complex data types](http://try.zeek.org/example/events).
27 | 
28 | Other than that, all this script does is sending warm greetings to new Zeek users by printing to STDOUT.
29 | 
30 | Try.Zeek allows you to hide the text if you want to script console to be full width. Find the button "Hide Text" and give it a try.
31 | 
32 | Every example can be run with a pcap file, you can select one below the script area. You can also
33 | upload your own pcap-examples. Select a pcap and click run again. Below the print-output you will find tabs
34 | with the familar log-file names. You can click on each row inside a log file and get more details. If Zeek logs are not yet
35 | familiar to you please go to the documentation on [log files](https://docs.zeek.org/en/current/script-reference/log-files.html).
36 | 
37 | When you are ready you can just click on next above to start the next example, or jump directly to a topic in the following list
38 | 
39 | Basics
40 | ======
41 |  * [Loading Scripts](#/?example=basics-loading)
42 |  * [Functions](#/?example=basics-functions)
43 |  * [Variables](#/?example=basics-variables)
44 |  * [Primitive Datatypes](#/?example=basics-primitive-datatypes)
45 |  * [Operators](#/?example=basics-operators)
46 |  * [If](#/?example=basics-control-flow-if)
47 |  * [For Loops](#/?example=basics-loops-for)
48 |  * [Loops: While](#/?example=basics-loops-while)
49 |  * [Exercise](#/?example=basics-exercise1-exercise)
50 |  * [Exercise 1: Solution](#/?example=basics-exercise1-solution)
51 |  * [The switch statement](#/?example=basics-switches-switch)
52 |  * [Switch Exercise](#/?example=basics-switches-switch-exercise)
53 |  * [Switch Exercise: Solution](#/?example=basics-switches-solution-switch-exercise)
54 |  * [Event](#/?example=basics-event)
55 |  * [hook](#/?example=basics-hook)
56 |  * [Set](#/?example=basics-composite-types-set)
57 |  * [Table](#/?example=basics-composite-types-table)
58 |  * [Vector](#/?example=basics-composite-types-vector)
59 |  * [record](#/?example=basics-composite-types-record)
60 |  * [Redefinitions](#/?example=basics-redefinitions-redef-records)
61 |  * [Zeek Datatypes](#/?example=basics-bro-types)
62 |  * [Exercise](#/?example=basics-exercise2-script-exercise-2)
63 |  * [Exercise 2](#/?example=basics-exercise2-solution-script-exercise-2)
64 | 
65 | Modules
66 | =======
67 |  * [Writing a Module](#/?example=modules-module)
68 |  * [Writing a Module: Export](#/?example=modules-export)
69 |  * [Writing a Module: Logging](#/?example=modules-log-factorial)
70 | 
71 | Logs
72 | ====
73 |  * [Filtering Logs](#/?example=logs-filter-logs)
74 |  * [Rename Logs](#/?example=logs-rename-logs)
75 |  * [Raising a Notice](#/?example=new-notice)
76 | 
77 | Sumstats
78 | ========
79 |  * [The Summary Statistics Framework](#/?example=sumstats-sumstats1)
80 |  * [SumStats Exercise Solution Part 1](#/?example=sumstats-sumstats2)
81 |  * [SumStats Exercise Solution Part 2](#/?example=sumstats-sumstats3)
82 | 
83 | Intel Framework
84 | ===============
85 |  * [Intel](#/?example=intel-intel-1)
86 | 


--------------------------------------------------------------------------------
/manager/static/examples/index:
--------------------------------------------------------------------------------
1 | hello
2 | basics
3 | modules
4 | logs
5 | new-notice
6 | sumstats
7 | intel
8 | congrats
9 | 


--------------------------------------------------------------------------------
/manager/static/examples/intel/index:
--------------------------------------------------------------------------------
1 | intel-1
2 | 


--------------------------------------------------------------------------------
/manager/static/examples/intel/intel-1/intel-1.dat:
--------------------------------------------------------------------------------
1 | #fields	indicator	indicator_type	meta.source
2 | www.reddit.com	Intel::DOMAIN	my_special_source
3 | 


--------------------------------------------------------------------------------
/manager/static/examples/intel/intel-1/main.zeek:
--------------------------------------------------------------------------------
1 | @load frameworks/intel/seen
2 | 
3 | redef Intel::read_files += {
4 | 	fmt("%s/intel-1.dat", @DIR)
5 | };
6 | 


--------------------------------------------------------------------------------
/manager/static/examples/intel/intel-1/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Intel
 2 | pcaps: http.pcap
 3 | 
 4 | The Intel Framework
 5 | ====================
 6 | 
 7 | The Intel Framework provides an interface to feed your own intelligence data into Zeek.
 8 | 
 9 | "Intelligence data is critical to the process of monitoring for security purposes. There is always data which will be discovered through the incident response process and data which is shared through private communities. The goals of Zeek's Intelligence Framework are to consume that data, make it available for matching, and provide infrastructure around improving performance, memory utilization, and generally making all of this easier." 
10 | 
11 | Find the whole documentation on the Intelligence Framework on 
12 | our [website](https://docs.zeek.org/en/current/frameworks/intel.html). 
13 | You will also find details on input formats etc.
14 | 
15 | Run the example given. Have a look at the extra file "intel-1.dat".
16 | This the source intel data that you feed into Zeek. 
17 | 
18 | As you can see the script to read an intel data file and make Zeek
19 | notify you if it sees a match is very simple at the start.
20 | The "seen"-policy needs to be loaded. In a real setup, this would be done in the local.zeek file.
21 | The second step is to tell Zeek where to find the file and its name. 
22 | 
23 | Please have a look at the results. You can notice a new log-file called intel.log which contains
24 | all seen matches. As always the uid allows you to investigate further which connections were involved in this match and in what way.
25 | 
26 | It is also possible to raise notices based on intel data. To see how this works have a look at the 
27 | documentation mentioned above.
28 | 
29 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/filter-logs/factorial.zeek:
--------------------------------------------------------------------------------
 1 | module Factor;
 2 | 
 3 | export {
 4 |     # Append the value LOG to the Log::ID enumerable.
 5 |     redef enum Log::ID += { LOG };
 6 | 
 7 |     # Define a new type called Factor::Info.
 8 |     type Info: record {
 9 |         num:           count &log;
10 |         factorial_num: count &log;
11 |         };
12 |     global factorial: function(n: count): count;
13 |     global mod5: function(id: Log::ID, path: string, rec: Factor::Info) : string;
14 |     }
15 |     
16 | function factorial(n: count): count
17 |     {
18 |     if ( n == 0 )
19 |         return 1;
20 |     
21 |     else
22 |         return ( n * factorial(n - 1) );
23 |     }
24 |     
25 | function mod5(id: Log::ID, path: string, rec: Factor::Info) : string    
26 |     {
27 |     if ( rec$factorial_num % 5 == 0 )
28 |         return "factor-mod5";
29 |     
30 |     else
31 |         return "factor-non5";
32 |     }
33 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/filter-logs/main.zeek:
--------------------------------------------------------------------------------
 1 | @load factorial
 2 | 
 3 | event zeek_init()
 4 |     {
 5 |     Log::create_stream(Factor::LOG, [$columns=Factor::Info, $path="factor"]);
 6 |     
 7 |     local filter: Log::Filter = [$name="split-mod5s", $path_func=Factor::mod5];
 8 |     Log::add_filter(Factor::LOG, filter);
 9 |     Log::remove_filter(Factor::LOG, "default");
10 |     }
11 | 
12 | event zeek_done()
13 |     {
14 |     local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);    
15 |     for ( n in numbers )
16 |         Log::write( Factor::LOG, [$num=numbers[n],
17 |                                   $factorial_num=Factor::factorial(numbers[n])]);
18 |     }
19 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/filter-logs/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Filtering Logs
 2 | pcaps:  
 3 | pred: rename-logs
 4 | succ: new-notice
 5 | 
 6 | Filtering Logs
 7 | =========================
 8 | 
 9 | As said in the last lesson, filtering allows to manipulate Zeek's logging in several ways. Renaming the log-file is one.
10 | Splitting the stream into two log files is the one we explore in this lesson.
11 | 
12 | For this we go back to the factorial module. The goal now is to split the logging stream in two. We send all results to 
13 | one log-file that are divisible evenly by 5, the others to a second log-file. 
14 | Which record is sent to which log-file is decided dynamically based on the 
15 | modulo function. Find more details on dynamically determining log paths 
16 | [here](https://docs.zeek.org/en/current/frameworks/logging.html#determine-log-path-dynamically).
17 | 
18 | Now run the code example. You see that there are now two log-files, one called `num` and `factorial_num`.
19 | Lets look at the code. In the module *factorial.zeek* we add a new function, the `path_func` we
20 | are going to use. Again, the function is also added to the export section. The function returns whether a number is in
21 | our modulo-5 category or not and then returns a string accordingly. The strings returned are the names of the two new log files. 
22 | In *main.zeek* you will find that we added more lines to `zeek_init`. We create a
23 | filter called *split-mod5s*, add that new filter and remove the `default` filter.
24 | Comment out the line 9 that removes the dafault and see what happens.
25 | 
26 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/index:
--------------------------------------------------------------------------------
1 | filter-logs
2 | rename-logs
3 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/rename-logs/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 |     {
 3 |     # Replace default filter for the Conn::LOG stream in order to
 4 |     # change the log filename.
 5 | 
 6 |     local f = Log::get_filter(Conn::LOG, "default");
 7 |     f$path = "myconn";
 8 |     Log::add_filter(Conn::LOG, f);
 9 |     }
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/logs/rename-logs/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Rename Logs
 2 | pcaps: exercise_traffic.pcap
 3 | 
 4 | Rename Logs
 5 | =============
 6 | 
 7 | Creating your own Logs is already very useful. Another feature in Zeek allows you to modify existing logging, too.
 8 | This feature is called filtering, although it allows more than to simply filter out certain fields.
 9 | A stream has one or more filters attached to it (a stream without any filters will not produce any log output). 
10 | When a stream is created, it automatically gets a default filter attached to it. 
11 | This default filter can be removed or replaced, or other filters can be added to the stream. 
12 | This is accomplished by using either the 
13 | [Log::add_filter](https://docs.zeek.org/en/current/scripts/base/frameworks/logging/main.zeek.html#id-Log::add_filter) 
14 | or [Log::remove_filter](https://docs.zeek.org/en/current/scripts/base/frameworks/logging/main.zeek.html#id-Log::remove_filter) 
15 | function. The default filter writes all fields to the logfile that carry the `&log` attribute.
16 | In this tutorial we will show you how to use filters to do such tasks as rename a log file, 
17 | split the output into multiple files, control which records are written, and set a custom rotation interval.
18 | 
19 | We start with a very simple case. In the code example we simply replace the default log 
20 | filter with a new filter that specifies a value for the “path” field. We'll come back to the factorial example later.
21 | 
22 | Step by step:
23 | First the function *get_filter* assoziates the new filter *f* with the logging stream of the
24 | [connection analyzer log strteam](https://docs.zeek.org/en/current/scripts/base/protocols/conn/).
25 | After that the new name `myconn` is set. This new filter has to be added to the logging stream.
26 | 
27 | 
28 | 
29 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/export/factorial.zeek:
--------------------------------------------------------------------------------
 1 | module Factor;
 2 | 
 3 | export {
 4 |     global factorial: function(n: count): count;
 5 |     }
 6 |     
 7 | function factorial(n: count): count
 8 |     {
 9 |     if ( n == 0 )
10 |         return 1;
11 |     
12 |     else
13 |         return ( n * factorial(n - 1) );
14 |     }
15 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/export/main.zeek:
--------------------------------------------------------------------------------
1 | @load factorial
2 | 
3 | event zeek_done()
4 |     {
5 |     local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);    
6 |     for ( n in numbers )
7 |     	print fmt("%d", Factor::factorial(numbers[n]));
8 |     }
9 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/export/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Writing a Module: Export
 2 | pcaps: 
 3 | 
 4 | Writing a Module: Export
 5 | =========================
 6 | 
 7 | In this tutorial you will create a module that computes the factorial function (n!)
 8 | and writes the result to a log file.
 9 | 
10 | As said before a module is a semantic entity. This means also that all variables and
11 | functions that you want to use outside of that entity need to be made available.
12 | In Zeek this is done through an `export` block.
13 | 
14 | In the example here you see two Zeek files. One is the module-script factorial.zeek,
15 | one is main.zeek, the script that uses the module the resulting values. You can simply click on the 
16 | tabs with the file names to switch between the Zeek-files.
17 | 
18 | Let's have a closer look on the code. 
19 | The first line declares that this is a module named Factor.
20 | We will come back to this later. 
21 | The next thing is the export environment. Every record, variable, and function
22 | that needs to be accessed from other scripts has to go here.
23 | In this case the export environment contains a function declaration, 
24 | expecting one parameter, returning one result value, both of type count.
25 | Note that export values always have to be `global`. Otherwise they can't be used 
26 | later.
27 | 
28 | The second part is the function implementation that simply computes the factorial of 
29 | a given *n*.
30 | 
31 | Now switch tabs and looks at main.zeek. The first line is the already 
32 | known `load`-statement. This time it loads factorial.zeek. 
33 | Inside the event we define a vector of length 9, our *n*s that we will
34 | give to the function as parameters.
35 | Then we call iterate over the vector, calling the function and printing the result.
36 | Note the syntax for calling the function. Before the function name we have to give the 
37 | module name (Factor not factorial). Every time a parameter or function from the export-section needs
38 | to be used the module name has to be given, too.
39 |  
40 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/index:
--------------------------------------------------------------------------------
1 | module
2 | export
3 | log-factorial
4 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/log-factorial/factorial.zeek:
--------------------------------------------------------------------------------
 1 | module Factor;
 2 | 
 3 | export {
 4 |     # Append the value LOG to the Log::ID enumerable.
 5 |     redef enum Log::ID += { LOG };
 6 | 
 7 |     # Define a new type called Factor::Info.
 8 |     type Info: record {
 9 |         num:           count &log;
10 |         factorial_num: count &log;
11 |         };
12 |     global factorial: function(n: count): count;
13 |     }
14 |     
15 | function factorial(n: count): count
16 |     {
17 |     if ( n == 0 )
18 |         return 1;
19 |     
20 |     else
21 |         return ( n * factorial(n - 1) );
22 |     }
23 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/log-factorial/main.zeek:
--------------------------------------------------------------------------------
 1 | @load factorial
 2 | 
 3 | event zeek_init()
 4 |     {
 5 |     # Create the logging stream.
 6 |     Log::create_stream(Factor::LOG, [$columns=Factor::Info, $path="factor"]);
 7 |     }
 8 | 
 9 | event zeek_done()
10 |     {
11 |     local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);    
12 |     for ( n in numbers )
13 |         Log::write( Factor::LOG, [$num=numbers[n],
14 |                                   $factorial_num=Factor::factorial(numbers[n])]);
15 |     }
16 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/log-factorial/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Writing a Module: Logging
 2 | pcaps: 
 3 | 
 4 | Writing a Module: Logging
 5 | =========================
 6 | 
 7 | Often a new module creates new data that you may want to collect in a new log file, too.
 8 | Manipulating Logs in Zeek is a useful tool and is more than just adding more log-files or fields.
 9 | We use the case of the factorial module to introduce you to the [logging framework](https://docs.zeek.org/en/current/frameworks/logging.html)
10 | 
11 | Again, we first look into factorial.zeek.
12 | In the export section you find a new line that uses `redef` to add a new value named `LOG`
13 | to the `Log::ID` enumerable. This enumerable is part of Zeek's logging framework. You can find details
14 | [here](https://docs.zeek.org/en/current/scripts/base/frameworks/logging/main.zeek.html).
15 | 
16 | The next step is to create a record that contains the columns of the future log file. The record
17 | is named `Info`. We create two columns named `num` and `factorial_num`. `num` is there to list the current value
18 | of `n`, `factorial_num` logs the factorial of `n`. Both variables have the attribute `&log` which
19 | tells Zeek that a field of the given name has to be added to the logging stream.
20 | 
21 | During this tutorial you have already seen some attributes. Attributes are used to add certain properties to functions and
22 | variables. For example the `&redef` attribute allows to redefine a global constant or extend a type. `&optional` allows a 
23 | value in a record field to be missing. The list of all attributes is found [here](https://docs.zeek.org/en/current/script-reference/attributes.html?highlight=attributes).
24 | 
25 | Now please switch to the file main.zeek. At the beginning of our script we need to create the new logging
26 | stream. [Log::create\_stream](https://docs.zeek.org/en/current/scripts/base/frameworks/logging/main.zeek.html?highlight=log%3A%3Acreate_stream#id-Log::create_stream) does exactly this. 
27 | The necessary parameters are of course the module's `LOG` value, and the record that holds the logging fields. 
28 | The variable `$path` tells Zeek how it should name the new log-file. Note that the Log stream needs to be initialized within the zeek\_init event.
29 | 
30 | The next step looks very similar to the one before, but instead of printing the results to Stdout we now 
31 | write to the new log, using the [Log::write](https://docs.zeek.org/en/current/scripts/base/frameworks/logging/main.zeek.html?highlight=log%3A%3Awrite#id-Log::write) function.
32 | 
33 | One more note on writing to logs: In this example we wrote all results within zeek\_done, in a real world example this
34 | is usually done inside an event handler that has to do with the log-file.
35 | 
36 | 
37 | 
38 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/module/main.zeek:
--------------------------------------------------------------------------------
 1 | event zeek_init()
 2 | 	{
 3 | 	print "Welcome back!";
 4 | 	}
 5 | 
 6 | event zeek_done()
 7 | 	{
 8 | 	print "Ready to write a module?";
 9 | 	}
10 | 


--------------------------------------------------------------------------------
/manager/static/examples/modules/module/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Writing a Module
 2 | pcaps:
 3 | 
 4 | Writing a Module
 5 | =====================
 6 | 
 7 | A module in Zeek is a semantic entity that defines a new name space.
 8 | The work flow usually is that solving a problem results in a new module.
 9 | A module can be a file or a bundle of files, a package. See below on this page.
10 | 
11 | It is important to know
12 | that you cannot write new protocol events, you can only react in a different way on an event that is already implemented
13 | in Zeek. Extending Zeek with [new analyzers](https://old.zeek.org/development/howtos/dpd.html) and
14 | creating new events is a topic that is beyond the scope of Try.Zeek.
15 | Usually a module reacts on already existing internal events coming from the Zeek event engine.
16 | Events that are not working on traffic can created within a Zeek script, though.
17 | 
18 | Before you dive into Zeek Modules we would like to point you on the
19 | [Zeek Scripting Conventions](https://old.zeek.org/development/howtos/script-conventions.html).
20 | Apart from naming conventions the Zeek scripts sets use a convention for file-names and
21 | the setup of Modules. We've already talked about
22 | [loading scripts](http://try.zeek.org/examples/loading). If you have a look into the
23 | [Zeek Script Packages](https://docs.zeek.org/en/current/script-reference/packages.html)
24 | you will find that each Module consists at least of two files, `__load__.zeek` and
25 | `main.zeek`. The first one should list all files that are part of the module (also `main.zeek`)
26 | The directory name in which all those files are collected gives the name to the module.
27 | When loading a module Zeek will look into this directory expecting the `__load__.zeek` script.
28 | In this tutorial we will not use this convention most of the time since we keep all examples in Try.Zeek.
29 | If you want to convert the final Zeek Module that we create in this tutorial to test it with your Zeek installation you can also
30 | try the naming conventions for Zeek Modules.
31 | 
32 | For the next lessons you will need the knowledge from the previous lessons.
33 | 


--------------------------------------------------------------------------------
/manager/static/examples/new-notice/factorial.zeek:
--------------------------------------------------------------------------------
 1 | module Factor;
 2 | 
 3 | export {
 4 |     redef enum Log::ID += { LOG };
 5 |     #Append a new notice value to the Notice::Type enumerable.
 6 |     redef enum Notice::Type += { Interesting_Result };
 7 |     
 8 |     const interesting_result = 120 &redef;
 9 | 
10 |     type Info: record {
11 |         num:           count &log;
12 |         factorial_num: count &log;
13 |         };
14 |     global factorial: function(n: count): count;
15 |     global mod5: function(id: Log::ID, path: string, rec: Factor::Info) : string;
16 |     global result : count  = 0;
17 |     }
18 | 
19 | function factorial(n: count): count
20 |     {
21 |     if ( n == 0 )
22 |         {
23 |     	result = 1;
24 |         return 1;
25 |         }
26 |     
27 |     else
28 |         {
29 |     	result = n * factorial(n - 1);
30 |         return result;
31 |         }
32 |     }
33 |     
34 | function mod5(id: Log::ID, path: string, rec: Factor::Info) : string    
35 |     {
36 |     if ( rec$factorial_num % 5 == 0 )
37 |         return "factor-mod5";
38 |     
39 |     else
40 |         return "factor-non5";
41 |     }
42 | 


--------------------------------------------------------------------------------
/manager/static/examples/new-notice/main.zeek:
--------------------------------------------------------------------------------
 1 | @load factorial
 2 | 
 3 | event zeek_init()
 4 |     {
 5 |     Log::create_stream(Factor::LOG, [$columns=Factor::Info, $path="factor"]);
 6 |     
 7 |     local filter: Log::Filter = [$name="split-mod5s", $path_func=Factor::mod5];
 8 |     Log::add_filter(Factor::LOG, filter);
 9 |     Log::remove_filter(Factor::LOG, "default");
10 |     }
11 | 
12 | event zeek_done()
13 |     {
14 |     local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);    
15 |     for ( i in numbers )
16 |     	{
17 |     	local result = Factor::factorial(numbers[i]);
18 |         Log::write( Factor::LOG, [$num=numbers[i],
19 |                                   $factorial_num=result]);
20 |     	if ( result == Factor::interesting_result)
21 |     		{
22 | 	    	NOTICE([$note=Factor::Interesting_Result,
23 |     	 	$msg = "Something happened!",
24 |         	$sub = fmt("result = %d", result)]);
25 |     		}
26 |     	}
27 |     }
28 |     
29 | 
30 | 


--------------------------------------------------------------------------------
/manager/static/examples/new-notice/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: Raising a Notice
 2 | pcaps: 
 3 | 
 4 | Raising a Notice
 5 | =====================
 6 | 
 7 | One of the easiest ways to customize Zeek is writing a local notice policy.
 8 | Apart from looking into log files you can ask Zeek to send you emails, either for a 
 9 | certain situation or aggregated summary emails about warnings etc.
10 | This feature is given through the [Notice Framework](https://docs.zeek.org/en/current/frameworks/notice.html).
11 | 
12 | In this lesson we start by creating a new notice. In Try.Zeek the notice can only be made into a log file.
13 | It can't send an email. Please run the code example and have a look at the new log-file.
14 | Apart from the message itself it tells you the name of the notice. This is especially useful for aggregated summaries.
15 | 
16 | Lets have a look on the code. Start with factorial.zeek. We append a new notice type value in the export section.
17 | For this example we say that *120* is an interesting value we want to be notified about. So we make it a constant that
18 | can be changed to something else later.
19 | Now the Factor moudle can be asked to rais a notice.
20 | 
21 | In main.zeek, every time the factorial is computed we ask if it is an interesting result.
22 | If so, the notice is raised. The fields `msg` and `sub` are given. You can put any text there that will later help you
23 | to find out what you need. The Notice Framework can be a little confusing. It is easier to handle if you
24 | remind yourself that it is simply a function.
25 | 
26 | Sending emails through the Notice Frameworks requires a working sendmail config on your system. This lesson should give you a start with this topic. Please go ahead and try it out within your local Zeek installation.
27 | 
28 | 


--------------------------------------------------------------------------------
/manager/static/examples/pack.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env python3
  2 | # encoding=utf8  
  3 | import sys  
  4 | 
  5 | import os
  6 | import glob
  7 | import json
  8 | import markdown
  9 | 
 10 | 
 11 | HELP_FILE = "readme.markdown"
 12 | MULTI_VALUE_FIELDS = ['pcaps']
 13 | 
 14 | def main_first_sort_key(f):
 15 |     if f['name'] == 'main.zeek':
 16 |         return (0, '')
 17 |     else:
 18 |         return (1, f['name'])
 19 | 
 20 | def redirect_example_links(source):
 21 |     return source.replace("http://try.zeek.org/example/", "#/trybro?example=")
 22 | 
 23 | def clean_path(path):
 24 |     if path.startswith("./"):
 25 |         path = path[2:]
 26 |     if path == '.':
 27 |         path = ''
 28 |     return path
 29 | 
 30 | def fix_path(path):
 31 |     return clean_path(path).replace("/", "-")
 32 | 
 33 | def pack(example):
 34 |     sources = []
 35 |     for fn in os.listdir(example):
 36 |         if fn == HELP_FILE: continue
 37 |         full = os.path.join(example, fn)
 38 |         with open(full) as f:
 39 |             sources.append({
 40 |                 "name": fn,
 41 |                 "content": f.read(),
 42 |             })
 43 | 
 44 |     sources.sort(key=main_first_sort_key)
 45 | 
 46 |     packed_example = {
 47 |         'sources': sources,
 48 |         'path': fix_path(example),
 49 |         'parent': clean_path(os.path.dirname(example))
 50 |     }
 51 | 
 52 |     full_help_filename = os.path.join(example, HELP_FILE)
 53 |     if os.path.exists(full_help_filename):
 54 |         md = markdown.Markdown(extensions = ['markdown.extensions.meta', 'markdown.extensions.tables'])
 55 |         with open(full_help_filename) as f:
 56 |             source = f.read()
 57 |         source = redirect_example_links(source)
 58 |         html = md.convert(source)
 59 |         #HACK, FIXME: We are having issues with | chars inside tables
 60 |         #The python parser doesn't seem to support \|, at least not inside code blocks
 61 |         #This hack lets ``|`` be rendered as a | in a code block.
 62 |         html = html.replace("&#124;", "|")
 63 |         packed_example['html'] = html
 64 |         for k, vs in md.Meta.items():
 65 |             if k not in MULTI_VALUE_FIELDS:
 66 |                 packed_example[k] = vs[0]
 67 |             else:
 68 |                 packed_example[k] = vs
 69 | 
 70 |     return packed_example
 71 | 
 72 | def read_index(fn):
 73 |     with open(fn) as f:
 74 |         return f.read().split()
 75 | 
 76 | def pack_recursive(e):
 77 | 
 78 |     directory_children = os.listdir(e)
 79 |     if 'index' in directory_children:
 80 |         index = read_index(os.path.join(e, "index"))
 81 |         children = { f: pack_recursive(os.path.join(e, f)) for f in index }
 82 |         example = { "path": fix_path(e), "index": index, "children": children, "child_count": len(children)}
 83 |     else:
 84 |         example = pack(e)
 85 | 
 86 |     return example
 87 | 
 88 |     add_next(examples)
 89 | 
 90 | def flatten(examples, flat=None):
 91 |     if flat is None:
 92 |         flat = []
 93 |     if 'index' in examples:
 94 |         for i in examples['index']:
 95 |             flatten(examples['children'][i], flat)
 96 |     else:
 97 |         flat.append(examples)
 98 | 
 99 |     return flat
100 | 
101 | def add_next_and_prev(flattened):
102 |     prev = None
103 |     for f in flattened:
104 |         f['prev'] = prev
105 |         prev = {'path': f['path'], 'title': f['title']}
106 | 
107 |     next = None
108 |     for f in reversed(flattened):
109 |         f['next'] = next
110 |         next = {'path': f['path'], 'title': f['title']}
111 | 
112 | 
113 | def main():
114 |     examples = pack_recursive(".")
115 |     flat = flatten(examples)
116 | 
117 |     add_next_and_prev(flat)
118 | 
119 |     with open("examples.json", 'w') as f:
120 |         json.dump(flat, f)
121 | 
122 | if __name__ == "__main__":
123 |     main()
124 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/index:
--------------------------------------------------------------------------------
1 | sumstats1
2 | sumstats2
3 | sumstats3
4 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats1/main.zeek:
--------------------------------------------------------------------------------
 1 | @load base/frameworks/sumstats
 2 | event zeek_init()
 3 |     {
 4 |     local r1 = SumStats::Reducer($stream="dns.lookup", $apply=set(SumStats::UNIQUE));
 5 |     SumStats::create([$name="dns.requests.unique",
 6 |                       $epoch=6hrs,
 7 |                       $reducers=set(r1),
 8 |                       $epoch_result(ts: time, key: SumStats::Key, result: SumStats::Result) =
 9 |                         {
10 |                         local r = result["dns.lookup"];
11 |                         print fmt("%s did %d total and %d unique DNS requests in the last 6 hours.", 
12 |                         			key$host, r$num, r$unique);
13 |                         }]);
14 |     }
15 | 
16 | event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
17 |     {
18 |     if ( c$id$resp_p == 53/udp && query != "" )
19 |         SumStats::observe("dns.lookup", [$host=c$id$orig_h], [$str=query]);
20 |     }
21 | 
22 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats1/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: The Summary Statistics Framework
 2 | pcaps: exercise_traffic.pcap
 3 | 
 4 | The Summary Statistics Framework
 5 | ==================================
 6 | 
 7 | The Summary Statistics framework (short sumstats) is a very useful tool to get a deep understanding
 8 | about what 
 9 | is going on in your network. A good example is the scan-detector that correlates many short, 
10 | maybe even failed connections that seem unrelated, together and gives a warning about
11 | a scanner activity. To many users this framework appears difficult to use. This tutorial 
12 | is meant to change that.
13 | 
14 | Alongside this tutorial you can read the 
15 | [documentation](https://docs.zeek.org/en/current/frameworks/sumstats.html) 
16 | explaining the terminology used for the Sumstats framework. This tutorial follows (in parts)
17 | the live tutorial Seth Hall gave at BroCon 2014, which is available as Youtube 
18 | [video](https://youtu.be/9YsenekNaSI)
19 | 
20 | 
21 | Run the code to the left and have a look on the output. Now lets go through the code.
22 | 
23 | First sumstats needs something to count, i.e. a Zeek event. For this example we chose the event 
24 | "dns\_request", we will count unique DNS requests, that is unique requests per host. 
25 | The "if" line is there to ensure real DNS requests (via port 53) that are also not empty.
26 | This specific case would quickly kill the memory of your Zeek system, because it collects
27 | every unique DNS request for every host it can see, so one option would be to limit this to port
28 | 53, non-empty requests and only local hosts. 
29 | If it is a real request where we want to observe something, that is where the sumstats observer is used. 
30 | The string dns.lookup is an arbitrary name, it could be something else. The principle is that a stream
31 | of information is observed (counted), the stream gets a name to later be addressed by.
32 | The next part is the key, in this case the host which sends the request. To know the actual unique request
33 | also the query string needs to be part of the key.
34 | 
35 | Now there is a stream that is observed, the next step is to reduce (i.e. summarize) the stream and then do 
36 | something with it. The reducer gets a variable name, r1 in this case, is attached to the stream named
37 | dns.lookup and also needs at least one reducing function that is applied on the stream.
38 | In this example the method used is "UNIQUE".
39 | More than one calculation method can be applied, they are all listed in the 
40 | [sumstats reference](https://docs.zeek.org/en/current/scripts/base/frameworks/sumstats/main.zeek.html#type-SumStats::Calculation).
41 | There can also be more than one reducer, an example for this is explained in the documentation for
42 | [MIME type statistics](https://docs.zeek.org/en/current/mimestats/index.html).
43 | 
44 | The third step is to link the reducer to a SumStats to finally do something with it.
45 | The SumStats also gets a name to reference later. An epoch is assigned, in this case 6 hours. 
46 | After 6 hours or the end of the trace it will come back with the results. 
47 | The reducer (or set of reducers) is attached. 
48 | The call back function is epoch result which is the key given in the observer before, 
49 | and it will give you the result. 
50 | Now you can access the results for the stream named dns.lookup. In this case we print the host, 
51 | the total number of DNS requests and the number of unique requests.
52 | 
53 | Comment out the two lines within the epoch\_result and directly print result. You can see
54 | all fields available to print out.
55 | 
56 | Exercise: Use the heuristic version of the unique calculation, HLL\_UNIQUE, you find it 
57 | in the [documentation](https://docs.zeek.org/en/current/scripts/base/frameworks/sumstats/main.zeek.html#type-SumStats::Calculation). Then take a sample of size 5. For the solution go to the next page.
58 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats2/main.zeek:
--------------------------------------------------------------------------------
 1 | @load base/frameworks/sumstats
 2 | event zeek_init()
 3 |     {
 4 |     local r1 = SumStats::Reducer($stream="dns.lookup", $apply=set(SumStats::HLL_UNIQUE, SumStats::SAMPLE), $num_samples=5);
 5 |     SumStats::create([$name="dns.requests.unique",
 6 |                       $epoch=6hrs,
 7 |                       $reducers=set(r1),
 8 |                       $epoch_result(ts: time, key: SumStats::Key, result: SumStats::Result) =
 9 |                         {
10 |                         local r = result["dns.lookup"];
11 |                         print fmt("%s did %d total and %d unique DNS requests in the last 6 hours.", 
12 |                         			key$host, r$num, r$hll_unique);
13 |                         }]);
14 |     }
15 | 
16 | event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
17 |     {
18 |     if ( c$id$resp_p == 53/udp && query != "" )
19 |         SumStats::observe("dns.lookup", [$host=c$id$orig_h], [$str=query]);
20 |     }
21 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats2/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: SumStats Exercise Solution
 2 | pcaps: exercise_traffic.pcap
 3 | 
 4 | SumStats Exercise Solution
 5 | ==============================
 6 | 
 7 | On the left is the solution to the exercise. Note that you not only need to change the reducer but also
 8 | the field names in the print statement. Try again to print the whole result to see all the options.
 9 | Also for this traffic example the probabilistic calculation is too good and you won't see a difference.
10 | You can print out the fields hll\_error\_margin  hll\_confidence to see the error that is made by using 
11 | this option. Both parameters can also be set using the same syntax as for the sample size. 
12 | 
13 | Next Exercise
14 | -------------
15 | 
16 | Sumstats becomes especially useful if used with thresholds. Write a sumstats script 
17 | that counts the DNS lookups and writes a notice if a host does more than 10 DNS lookups.
18 | Have a look at the available SumStat 
19 | [functions](https://docs.zeek.org/en/current/scripts/base/frameworks/sumstats/main.zeek.html?highlight=sumstats#type-SumStats::SumStat)
20 | to find out how to work with thresholds.
21 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats3/main.zeek:
--------------------------------------------------------------------------------
 1 | @load base/frameworks/sumstats
 2 | 
 3 | redef enum Notice::Type += 
 4 | 	{
 5 |     ExcessiveRequests
 6 | 	};
 7 | 
 8 | const excessive_limit: double = 10  &redef;
 9 | 
10 | event zeek_init()
11 |     {
12 |     local r1 = SumStats::Reducer($stream="dns.lookup", $apply=set(SumStats::SUM));
13 |     SumStats::create([$name="dns.requests",
14 |                       $epoch=6hrs,
15 |                       $threshold = excessive_limit,
16 |                       $reducers=set(r1),
17 |                       $threshold_val(key: SumStats::Key, result: SumStats::Result) = 
18 |                       	{
19 |                         return result["dns.lookup"]$sum;
20 |                       	},
21 |                       $threshold_crossed(key: SumStats::Key, result: SumStats::Result) = 
22 |                       	{
23 |                         local r = result["dns.lookup"];
24 |                         NOTICE([
25 |                             $note=ExcessiveRequests,
26 |                             $src=key$host,
27 |                             $msg=fmt("%s has made more than %.0f DNS requests.", key$host, r$sum),
28 |                             $sub=cat(r$sum),
29 |                             $identifier=cat(key$host)
30 |                           ]);
31 |                       	}
32 |                     ]);
33 |     }
34 | 
35 | event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
36 |     {
37 |     if ( c$id$resp_p == 53/udp && query != "" )
38 |         SumStats::observe("dns.lookup", [$host=c$id$orig_h], [$str=query]);
39 |     }
40 | 
41 | 


--------------------------------------------------------------------------------
/manager/static/examples/sumstats/sumstats3/readme.markdown:
--------------------------------------------------------------------------------
 1 | title: SumStats Exercise Solution
 2 | pcaps: sumstat.pcap
 3 | 
 4 | SumStats Exercise Solution
 5 | ==================================
 6 | 
 7 | On the left is the solution to the exercise.
 8 | As seen before in this tutorial to raise a notice we need to add a new type of notice. 
 9 | Instead of UNIQUE this time we calculate the sum of all requests. 10 is a very low number but serves the
10 | purpose of an exercise. This time we don't use the epoch result but react to a crossed threshold. 
11 | In order to do this we need to evaluate the threshold first, once it is crossed we send out the notice. 
12 | Within this platform we can only write it to the notice.log but in reality it can of course be sent as
13 | an email to warn the security admin about suspicious behavior.
14 | 


--------------------------------------------------------------------------------
/manager/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/static/favicon.ico


--------------------------------------------------------------------------------
/manager/static/pcaps/exercise_traffic.pcap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/static/pcaps/exercise_traffic.pcap


--------------------------------------------------------------------------------
/manager/static/pcaps/http.pcap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/static/pcaps/http.pcap


--------------------------------------------------------------------------------
/manager/static/pcaps/ssh.pcap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/static/pcaps/ssh.pcap


--------------------------------------------------------------------------------
/manager/static/pcaps/sumstat.pcap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/static/pcaps/sumstat.pcap


--------------------------------------------------------------------------------
/manager/templates/base.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 | 
10 |     
11 |     Try Zeek
12 |     {% block title %}
13 |     {% endblock %}
14 |     
15 | 
16 |     
17 |     
18 | 
19 |     
20 |     
21 | 
22 |     
23 |     
24 | 
25 |     
26 |     
30 |   
31 | 
32 |   
33 | 
34 |     
36 | 
37 |     
38 | 39 | {% block content %} 40 | {% endblock %} 41 | 42 |
43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /manager/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 | 7 | 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /manager/templates/metrics.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |

{{ metrics['executions'] }} Total Bro Executions ({{metrics['cache_hits']}} from cache)

6 | 7 |

By Version

8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for v, execs in metrics['byversion']|dictsort %} 18 | 19 | 20 | 21 | 22 | {% endfor %} 23 | 24 |
VersionExecutions
{{ v }} {{ execs }}
25 |
26 | 27 |

By Date

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for d, execs in metrics['bydate']|dictsort %} 38 | 39 | 40 | 41 | 42 | {% endfor %} 43 | 44 |
DateExecutions
{{ d }} {{ execs }}
45 |
46 | 47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /manager/test_format.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from format import fmt 3 | 4 | class TestFormat(unittest.TestCase): 5 | 6 | def test_cases(self): 7 | cases = [ 8 | ('event zeek_init(){}', None), 9 | ('event zeek_init(){', {'row': 0, 'column': 18, 'type': 'error', 'text': 'missing grammar node "}" on line 0, col 18'}), 10 | ('function foo(){}', None), 11 | ('function foo({}', {'row': 0, 'column': 13, 'type': 'error', 'text': 'missing grammar node ")" on line 0, col 13'}), 12 | ] 13 | for case, expected in cases: 14 | with self.subTest(txt=case): 15 | _, error = fmt(case) 16 | self.assertEqual(error, expected) 17 | 18 | if __name__ == '__main__': 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /manager/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | Zeek version helper. 3 | 4 | All available Zeek versions are stored within the zeek:versions set 5 | in Redis. This script polls the Docker Hub API once in a while and 6 | fetches all new tags using docker-py. 7 | """ 8 | import argparse 9 | import logging 10 | import packaging.version 11 | import re 12 | import time 13 | import typing 14 | 15 | import docker 16 | import requests 17 | 18 | from common import get_redis 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | NAMESPACE = "zeek" 23 | REPO = "zeek" 24 | TAGS_URL = f"https://hub.docker.com/v2/namespaces/{NAMESPACE}/repositories/{REPO}/tags" 25 | 26 | REDIS_VERSION_KEY = "zeek:versions" 27 | 28 | 29 | def is_acceptable_zeek_version(version: str): 30 | """ 31 | For now, just the three digit version tags, avoiding the 6.0, lts 32 | and amd64 / arm64 architecture ones. 33 | """ 34 | return re.match(r"^[0-9]+\.[0-9]+\.[0-9]+$", version) is not None 35 | 36 | 37 | def zeek_versions_from_redis() -> typing.Tuple[str, list[str]]: 38 | """ 39 | Return versions found in Redis as tuple (default, all). 40 | """ 41 | redis = get_redis() 42 | versions = sorted(redis.smembers(REDIS_VERSION_KEY), key=packaging.version.parse) 43 | default = versions[-1] if len(versions) > 0 else None 44 | if versions == "master" and len(versions) > 1: 45 | return versions[-2] 46 | 47 | return default, versions 48 | 49 | 50 | def pull_new_tags(): 51 | """ 52 | Fetch all tags from Docker Hub and pull those we do no know about yet. 53 | 54 | https://docs.docker.com/docker-hub/api/latest/#tag/repositories/paths/%7E1v2%7E1namespaces%7E1%7Bnamespace%7D%7E1repositories%7E1%7Brepository%7D%7E1tags/get 55 | """ 56 | # We only fetch the first page to avoid unnecessarily using up space. 57 | max_page = 1 58 | 59 | session = requests.Session() 60 | redis = get_redis() 61 | docker_client = docker.Client() 62 | 63 | _, known_versions = zeek_versions_from_redis() 64 | logger.info("known_versions: %s", known_versions) 65 | 66 | for page in range(1, max_page + 1): 67 | logger.debug("Fetching tags, page %s", page) 68 | response = session.get(TAGS_URL, params={"page": page}, timeout=30) 69 | 70 | if response.status_code == 404: 71 | logger.debug("No more pages") 72 | break 73 | 74 | response.raise_for_status() 75 | 76 | for result in response.json()["results"]: 77 | version = result["name"] 78 | 79 | if not is_acceptable_zeek_version(version): 80 | logger.debug("Ignoring image tag %r", version) 81 | continue 82 | 83 | if version in known_versions: 84 | logger.debug("Ignoring known image tag %r", version) 85 | continue 86 | 87 | logger.info("Pulling new image %s", version) 88 | docker_client.pull(f"{NAMESPACE}/{REPO}", tag=version) 89 | 90 | redis.sadd(REDIS_VERSION_KEY, version) 91 | 92 | 93 | def sync_docker_versions_to_redis(): 94 | """ 95 | List locally available container images and store their tags in Redis. 96 | """ 97 | redis = get_redis() 98 | docker_client = docker.Client() 99 | 100 | images = docker_client.images(name="zeek/zeek") 101 | tags = [] 102 | for i in images: 103 | tags.extend(i["RepoTags"]) 104 | 105 | versions = [t.replace("zeek/zeek:", "") for t in tags if "_" not in t] 106 | versions = [v for v in versions if is_acceptable_zeek_version(v)] 107 | 108 | if versions: 109 | redis.sadd(REDIS_VERSION_KEY, *versions) 110 | 111 | # Now, also fetch all versions from Redis and remove those 112 | # not available to Docker anymore. 113 | _, redis_versions = zeek_versions_from_redis() 114 | for rv in redis_versions: 115 | if rv not in versions: 116 | logger.info("Deleting %r from Redis", rv) 117 | redis.srem(REDIS_VERSION_KEY, rv) 118 | 119 | 120 | def main(): 121 | """ 122 | Entry point. 123 | """ 124 | parser = argparse.ArgumentParser() 125 | parser.add_argument("-i", "--interval", type=int, default=67 * 60) 126 | parser.add_argument("-l", "--log-level", type=str, default="info") 127 | args = parser.parse_args() 128 | 129 | logging.basicConfig(level=getattr(logging, args.log_level.upper())) 130 | while True: 131 | sync_docker_versions_to_redis() 132 | pull_new_tags() 133 | try: 134 | time.sleep(args.interval) 135 | except KeyboardInterrupt: 136 | logger.info("Interrupt") 137 | break 138 | 139 | 140 | if __name__ == "__main__": 141 | main() 142 | -------------------------------------------------------------------------------- /manager/web-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /manager/web-ui/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | npm install 3 | npm run build 4 | 5 | dev: 6 | REACT_APP_TRY_BRO_API_HOST=http://192.168.59.103 npm start 7 | -------------------------------------------------------------------------------- /manager/web-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "try-bro", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^6.2.0", 7 | "@fortawesome/free-regular-svg-icons": "^6.2.0", 8 | "@fortawesome/free-solid-svg-icons": "^6.2.0", 9 | "@fortawesome/react-fontawesome": "^0.2.0", 10 | "ajv-keywords": "^5.1.0", 11 | "babel-plugin-macros": "^3.1.0", 12 | "bootstrap": "^5.2.2", 13 | "brace": "^0.11.1", 14 | "history": "^5.3.0", 15 | "isomorphic-fetch": "^3.0.0", 16 | "md5": "^2.3.0", 17 | "query-string": "^7.1.1", 18 | "react": "^18.2.0", 19 | "react-ace": "^10.1.0", 20 | "react-bootstrap": "^2.6.0", 21 | "react-dom": "^18.2.0", 22 | "react-overlays": "^5.2.1", 23 | "react-redux": "^8.0.5", 24 | "react-router-dom": "^6.4.3", 25 | "react-scripts": "^5.0.1", 26 | "redux": "^4.2.0", 27 | "redux-logger": "^2.7.0", 28 | "redux-thunk": "^2.4.2" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test --env=jsdom", 34 | "eject": "react-scripts eject" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /manager/web-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/try-zeek/764c096477c823348b20dc5bf4b2b2586deba6ce/manager/web-ui/public/favicon.ico -------------------------------------------------------------------------------- /manager/web-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | Try Zeek 17 | 18 | 19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /manager/web-ui/public/mode-zeek.js: -------------------------------------------------------------------------------- 1 | ace.define('ace/mode/zeek_highlight_rules', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function(acequire, exports, module) { 2 | 3 | var oop = acequire("../lib/oop"); 4 | var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules; 5 | 6 | var ZeekHighlightRules = function() { 7 | // regexp must not have capturing parentheses. Use (?:) instead. 8 | // regexps are ordered -> the first match is used 9 | 10 | this.$rules = { 11 | "start": [ 12 | { 13 | token: "comment.line", 14 | regex: "#.*$" 15 | }, 16 | { 17 | token: "string.double", 18 | regex: /"/, 19 | next: "string-state" 20 | }, 21 | { 22 | token: "string.regexp", 23 | regex: "(/)(?=.*/)", 24 | next: "pattern-state" 25 | }, 26 | { 27 | token: ["keyword.other", "meta.preprocessor"], 28 | regex: /(@(?:load-plugin|load-sigs|load|unload))(.*$)/ 29 | }, 30 | { 31 | token: "keyword.other", 32 | regex: /@(?:DEBUG|DIR|FILENAME|deprecated|if|ifdef|ifndef|else|endif)/ 33 | }, 34 | { 35 | token: [ 36 | "keyword.other", 37 | "meta.preprocessor", 38 | "keyword.operator", 39 | "meta.preprocessor" 40 | ], 41 | regex: /(@prefixes)(\s*)(\+?=)(.*$)/ 42 | }, 43 | { 44 | token: "storage.modifier.attribute", 45 | regex: /\&\b(?:redef|priority|log|optional|default|add_func|delete_func|expire_func|read_expire|write_expire|create_expire|synchronized|persistent|rotate_interval|rotate_size|encrypt|raw_output|mergeable|error_handler|type_column|deprecated)\b/ 46 | }, 47 | { 48 | token: "constant.language", 49 | regex: /\b(?:T|F)\b/ 50 | }, 51 | { 52 | token: "constant.numeric.port", 53 | regex: /\b\d{1,5}\/(?:udp|tcp|icmp|unknown)\b/ 54 | }, 55 | { 56 | token: "constant.numeric.addr", 57 | regex: /\b(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\b/, 58 | comment: "IPv4 address" 59 | }, 60 | { 61 | token: "constant.numeric.addr", 62 | regex: /\[(?:[0-9a-fA-F]{0,4}:){2,7}(?:[0-9a-fA-F]{0,4})?(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2}))?\]/, 63 | comment: "IPv6 address" 64 | }, 65 | { 66 | token: "constant.numeric.float.decimal.interval", 67 | regex: /(?:(?:\d*\.\d*(?:[eE][+-]?\d+)?|\d*[eE][+-]?\d+|\d*\.\d*)|\d+)\s*(?:day|hr|min|msec|usec|sec)s?/ 68 | }, 69 | { 70 | token: "constant.numeric.float.decimal", 71 | regex: /\d*\.\d*(?:[eE][+-]?\d+)?|\d*[eE][+-]?\d+|\d*\.\d*/ 72 | }, 73 | { 74 | token: "constant.numeric.hostname", 75 | regex: /\b[A-Za-z0-9][A-Za-z0-9\-]*(?:\.[A-Za-z0-9][A-Za-z0-9\-]*)+\b/ 76 | }, 77 | { 78 | token: "constant.numeric.integer.hexadecimal", 79 | regex: /\b0x[0-9a-fA-F]+\b/ 80 | }, 81 | { 82 | token: "constant.numeric.integer.decimal", 83 | regex: /\b\d+\b/ 84 | }, 85 | { 86 | token: "keyword.operator", 87 | regex: /==|!=|<=|<|>=|>/ 88 | }, 89 | { 90 | token: "keyword.operator", 91 | regex: /(&&)|(\|\|)|(!)/ 92 | }, 93 | { 94 | token: "keyword.operator", 95 | regex: /=|\+=|-=/ 96 | }, 97 | { 98 | token: "keyword.operator", 99 | regex: /\+\+|\+|--|-|\*|\/|%/ 100 | }, 101 | { 102 | token: "keyword.operator", 103 | regex: /&|\||\^|~/ 104 | }, 105 | { 106 | token: "keyword.operator", 107 | regex: /\b(?:in|as|is)\b/ 108 | }, 109 | { 110 | token: "punctuation.terminator", 111 | regex: /;/ 112 | }, 113 | { 114 | token: "punctuation.accessor", 115 | regex: /\??\$/ 116 | }, 117 | { 118 | token: "punctuation.accessor", 119 | regex: /::/ 120 | }, 121 | { 122 | token: "keyword.operator", 123 | regex: /\?/ 124 | }, 125 | // Unsure how to tell if colon is used as operator vs. separator. 126 | // { 127 | // token: "keyword.operator", 128 | // regex: /:/ 129 | // }, 130 | { 131 | token: "punctuation.separator", 132 | regex: /:/ 133 | }, 134 | { 135 | token: "punctuation.separator", 136 | regex: /,/ 137 | }, 138 | { 139 | token: [ 140 | "keyword.other", 141 | "meta.namespace", 142 | "entity.name.namespace" 143 | ], 144 | regex: /(module)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)/ 145 | }, 146 | { 147 | token: "keyword.other", 148 | regex: /\bexport\b/ 149 | }, 150 | { 151 | token: "keyword.control.conditional", 152 | regex: /\b(?:if|else)\b/ 153 | }, 154 | { 155 | token: "keyword.control", 156 | regex: /\b(?:for|while)\b/ 157 | }, 158 | { 159 | token: "keyword.control", 160 | regex: /\b(?:return|break|next|continue|fallthrough)\b/ 161 | }, 162 | { 163 | token: "keyword.control", 164 | regex: /\b(?:switch|default|case)\b/ 165 | }, 166 | { 167 | token: "keyword.other", 168 | regex: /\b(?:add|delete)\b/ 169 | }, 170 | { 171 | token: "keyword.other", 172 | regex: /\bprint\b/ 173 | }, 174 | { 175 | token: "keyword.control", 176 | regex: /\b(?:when|timeout|schedule)\b/ 177 | }, 178 | { 179 | token: [ 180 | "keyword.other", 181 | "meta.struct.record", 182 | "entity.name.struct.record", 183 | "meta.struct.record", 184 | "punctuation.separator", 185 | "meta.struct.record", 186 | "storage.type.struct.record" 187 | ], 188 | regex: /\b(type)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(\s*)(:)(\s*\b)(record)\b/ 189 | }, 190 | { 191 | token: [ 192 | "keyword.other", 193 | "meta.enum", 194 | "entity.name.enum", 195 | "meta.enum", 196 | "punctuation.separator", 197 | "meta.enum", 198 | "storage.type.enum" 199 | ], 200 | regex: /\b(type)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(\s*)(:)(\s*\b)(enum)\b/ 201 | }, 202 | { 203 | token: [ 204 | "keyword.other", 205 | "meta.type", 206 | "entity.name.type", 207 | "meta.type", 208 | "punctuation.separator" 209 | ], 210 | regex: /\b(type)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(\s*)(:)/ 211 | }, 212 | { 213 | token: [ 214 | "keyword.other", 215 | "meta.struct.record", 216 | "storage.type.struct.record", 217 | "meta.struct.record", 218 | "entity.name.struct.record" 219 | ], 220 | regex: /\b(redef)(\s+)(record)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)\b/ 221 | }, 222 | { 223 | token: [ 224 | "keyword.other", 225 | "meta.enum", 226 | "storage.type.enum", 227 | "meta.enum", 228 | "entity.name.enum" 229 | ], 230 | regex: /\b(redef)(\s+)(enum)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)\b/ 231 | }, 232 | { 233 | token: [ 234 | "storage.type", 235 | "text", 236 | "entity.name.function.event" 237 | ], 238 | regex: /\b(event)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(?=s*\()/ 239 | }, 240 | { 241 | token: [ 242 | "storage.type", 243 | "text", 244 | "entity.name.function.hook" 245 | ], 246 | regex: /\b(hook)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(?=s*\()/ 247 | }, 248 | { 249 | token: [ 250 | "storage.type", 251 | "text", 252 | "entity.name.function" 253 | ], 254 | regex: /\b(function)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)(?=s*\()/ 255 | }, 256 | { 257 | token: "keyword.other", 258 | regex: /\bredef\b/ 259 | }, 260 | { 261 | token: "storage.type", 262 | regex: /\bany\b/ 263 | }, 264 | { 265 | token: "storage.type", 266 | regex: /\b(?:enum|record|set|table|vector)\b/ 267 | }, 268 | { 269 | token: [ 270 | "storage.type", 271 | "text", 272 | "keyword.operator", 273 | "text", 274 | "storage.type" 275 | ], 276 | regex: /\b(opaque)(\s+)(of)(\s+)([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)\b/ 277 | }, 278 | { 279 | token: "keyword.operator", 280 | regex: /\bof\b/ 281 | }, 282 | { 283 | token: "storage.type", 284 | regex: /\b(?:addr|bool|count|double|file|int|interval|pattern|port|string|subnet|time)\b/ 285 | }, 286 | { 287 | token: "storage.type", 288 | regex: /\b(?:function|hook|event)\b/ 289 | }, 290 | { 291 | token: "storage.modifier", 292 | regex: /\b(?:global|local|const|option)\b/ 293 | }, 294 | { 295 | token: "entity.name.function.call", 296 | regex: /\b[A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*(?=s*\()/ 297 | }, 298 | { 299 | token: "punctuation.section.block.begin", 300 | regex: /\{/ 301 | }, 302 | { 303 | token: "punctuation.section.block.end", 304 | regex: /\}/ 305 | }, 306 | { 307 | token: "punctuation.section.brackets.begin", 308 | regex: /\[/ 309 | }, 310 | { 311 | token: "punctuation.section.brackets.end", 312 | regex: /\]/ 313 | }, 314 | { 315 | token: "punctuation.section.parens.begin", 316 | regex: /\(/ 317 | }, 318 | { 319 | token: "punctuation.section.parens.end", 320 | regex: /\)/ 321 | } 322 | 323 | ], // state: start 324 | 325 | "string-state": [ 326 | { 327 | token: "constant.character.escape", 328 | regex: /\\./ 329 | }, 330 | { 331 | token: "string.double", 332 | regex: /"/, 333 | next: "start" 334 | }, 335 | { 336 | token: "constant.other.placeholder", 337 | regex: /%-?[0-9]*(\.[0-9]+)?[DTdxsefg]/ 338 | }, 339 | { 340 | token: "string.double", 341 | regex: "." 342 | } 343 | ], // state: string-state 344 | 345 | "pattern-state": [ 346 | { 347 | token: "constant.character.escape", 348 | regex: /\\./ 349 | }, 350 | { 351 | token: "string.regexp", 352 | regex: "/", 353 | next: "start" 354 | }, 355 | { 356 | token: "string.regexp", 357 | regex: "." 358 | } 359 | ] // state: pattern-state 360 | 361 | }; 362 | 363 | this.normalizeRules(); 364 | }; 365 | 366 | ZeekHighlightRules.metaData = { 367 | fileTypes: ["bro", "zeek"], 368 | name: "Zeek", 369 | scopeName: "source.zeek" 370 | }; 371 | 372 | oop.inherits(ZeekHighlightRules, TextHighlightRules); 373 | 374 | exports.ZeekHighlightRules = ZeekHighlightRules; 375 | }); 376 | 377 | ace.define('ace/mode/zeek', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/zeek_highlight_rules'], function(acequire, exports, module) { 378 | 379 | var oop = acequire("../lib/oop"); 380 | var TextMode = acequire("./text").Mode; 381 | var ZeekHighlightRules = acequire("./zeek_highlight_rules").ZeekHighlightRules; 382 | var FoldMode = acequire("./folding/cstyle").FoldMode; 383 | 384 | var Mode = function() { 385 | this.HighlightRules = ZeekHighlightRules; 386 | this.foldingRules = new FoldMode(); 387 | }; 388 | oop.inherits(Mode, TextMode); 389 | 390 | (function() { 391 | this.lineCommentStart = "#"; 392 | // Extra logic goes here. 393 | this.$id = "ace/mode/zeek"; 394 | }).call(Mode.prototype); 395 | 396 | exports.Mode = Mode; 397 | }); 398 | -------------------------------------------------------------------------------- /manager/web-ui/src/App.css: -------------------------------------------------------------------------------- 1 | .Pointer { 2 | cursor: pointer; 3 | } 4 | 5 | .overflow{ 6 | max-width:200px; 7 | white-space: nowrap; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | } 11 | -------------------------------------------------------------------------------- /manager/web-ui/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | 4 | import {connect} from 'react-redux'; 5 | 6 | import brace from 'brace'; 7 | import 'brace/theme/tomorrow'; 8 | 9 | import AceEditor from 'react-ace'; 10 | import ZeekMode from './ZeekMode.js' 11 | 12 | import { fetchVersions, setVersion } from './actions'; 13 | import { fetchExamples, hideExample, showExample } from './actions'; 14 | import { codeAddFile, codeSelectFile, codeRenameFile, codeEditFile } from './actions'; 15 | import { execReset, execSubmit } from './actions'; 16 | import { fetchPcaps, pcapSelected, pcapFileChanged } from './actions'; 17 | import { formatSubmit } from './actions'; 18 | 19 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 20 | import { solid, regular } from '@fortawesome/fontawesome-svg-core/import.macro' // <-- import styles to be used 21 | 22 | import {Tab, Tabs, Button, ButtonGroup} from 'react-bootstrap'; 23 | import Pagination from 'react-bootstrap/Pagination'; 24 | import Container from 'react-bootstrap/Container'; 25 | import Row from 'react-bootstrap/Row'; 26 | import Col from 'react-bootstrap/Col'; 27 | import {Table} from 'react-bootstrap'; 28 | import {Modal} from 'react-bootstrap'; 29 | 30 | import { setHistoryToExample } from './tbhistory'; 31 | 32 | var DropDown = ({options, includeBlank, selected, onChange}) => { 33 | if(includeBlank) { 34 | options = ['', ...options]; 35 | } 36 | return ( 37 | 42 | ); 43 | }; 44 | 45 | 46 | class BroVersions extends Component { 47 | change = v => this.props.onVersionChanged(v); 48 | render() { 49 | const { version, versions } = this.props.versions; 50 | return ( 51 | 52 | Zeek Version 53 | { ' ' } 54 | 55 | 56 | ); 57 | } 58 | }; 59 | 60 | //TODO: simpler way of doing this? 61 | var ExampleDropDown = ({examples, includeBlank, selected, onChange}) => { 62 | var cur_selected = selected ? selected.path: ''; 63 | return ( 64 | 69 | ); 70 | } 71 | 72 | class BroEditor extends Component { 73 | handleCodeChanged(file, c) { 74 | this.props.onCodeChanged(file, c); 75 | } 76 | handleSelect(key) { 77 | if(key === 'ADD') { 78 | this.props.onAddFile(); 79 | } else if(this.props.code.current === key && this.props.code.current !== 'main.zeek') { 80 | var new_name = prompt(`Rename ${this.props.code.current} to`, this.props.code.current); 81 | if (new_name && new_name !== '' && new_name !== this.props.code.current) 82 | this.props.onRenameFile(new_name); 83 | } else if(this.props.code.current !== key) { 84 | this.props.onSelectFile(key); 85 | } 86 | } 87 | 88 | render() { 89 | var add_button = Add File 90 | return ( 91 |
92 | this.handleSelect(e)} id="Editor"> 93 | {this.props.code.sources.map(c => 94 | 95 | { 104 | this.handleCodeChanged(c.name, contents) 105 | }} 106 | editorProps={{$blockScrolling: true}} 107 | setOptions={{useSoftTabs: false}} 108 | /> 109 | 110 | )} 111 | Add 112 | 113 |
114 | )} 115 | } 116 | 117 | class BroExampleReadme extends Component { 118 | render() { 119 | const { example, onChange } = this.props; 120 | if (!example || !example.html) { 121 | return null; 122 | } 123 | var markup = {__html: example.html } 124 | var prev = example.prev ? onChange(example.prev.path)}>Previous : null; 125 | var next = example.next ? onChange(example.next.path)}>Next : null; 126 | return ( 127 |
128 | 129 | {prev} 130 | {next} 131 | 132 |
133 |
134 | ) 135 | } 136 | } 137 | 138 | var BroFileViewerDetailTable = ({record}) => { 139 | if (record == null) 140 | return null; 141 | return ( 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | {record.map((r, id) => 152 | 153 | 154 | 155 | 156 | 157 | )} 158 | 159 |
FieldTypeValue
{r[0]}{r[1]}{r[2]}
160 | ) 161 | } 162 | 163 | class BroFileViewerModal extends Component { 164 | 165 | render() { 166 | const { file, recordNum, onPrev, onNext } = this.props; 167 | var record = null; 168 | var rowStatus = null; 169 | if (file && file.rows && file.rows.length && recordNum !== null) { 170 | var row = file.rows[recordNum] 171 | record = zip([file.header, file.types, row]); 172 | rowStatus = Viewing record {recordNum+1} of {file.rows.length}; 173 | } 174 | return ( 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | {rowStatus} 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | ); 193 | } 194 | } 195 | 196 | class BroFileViewerTableTable extends Component { 197 | shouldComponentUpdate () { 198 | return false; 199 | } 200 | 201 | render() { 202 | const { file, onRowSelected } = this.props; 203 | return ( 204 | 205 | 206 | 207 | {file.header.map(h => 208 | 209 | )} 210 | 211 | 212 | 213 | {file.rows.map((r, id) => 214 | onRowSelected(file, r, id)}> 215 | {r.map((c, cid) => 216 | 217 | )} 218 | 219 | )} 220 | 221 |
{h}
{c}
222 | ) 223 | } 224 | } 225 | 226 | class BroFileViewerTable extends Component { 227 | constructor(props) { 228 | super(props); 229 | this.state = {showDetail: false, detailRecordNum: null}; 230 | } 231 | showDetails = (file, record, row) => { 232 | this.setState({showDetail: true, detailRecordNum: row}); 233 | } 234 | hideDetails = () => { 235 | this.setState({showDetail: false}); 236 | } 237 | showPrev = () => { 238 | this.setState({detailRecordNum: this.state.detailRecordNum-1}); 239 | } 240 | showNext = () => { 241 | this.setState({detailRecordNum: this.state.detailRecordNum+1}); 242 | } 243 | render() { 244 | const { file } = this.props; 245 | return ( 246 |
247 | 255 | 256 |
257 | ) 258 | } 259 | } 260 | 261 | var BroFileViewerText = ({file}) => ( 262 |
{file}
263 | ) 264 | 265 | var BroFileViewerDetail = ({file}) => { 266 | if(file.header) { 267 | return 268 | } else { 269 | return 270 | } 271 | } 272 | 273 | class BroFileViewer extends Component { 274 | shouldComponentUpdate(nextProps) { 275 | return nextProps.files !== this.props.files; 276 | } 277 | render() { 278 | const { files } = this.props; 279 | if(!files || Object.keys(files).length === 0) { 280 | return
; 281 | } 282 | let tab = files.hasOwnProperty("conn.log") ? "conn.log" : Object.keys(files)[0]; 283 | return ( 284 |
285 |

Output Logs

286 | 287 | {Object.keys(files).map( (f) => 288 | 289 | 290 | 291 | )} 292 | 293 |
294 | ) 295 | } 296 | } 297 | 298 | 299 | var RunButton = ({status, pcap, onClick}) => { 300 | if (pcap.too_large) 301 | return ; 302 | 303 | if (pcap.uploading) 304 | return ; 305 | 306 | if (status) 307 | return ; 308 | 309 | return ; 310 | } 311 | 312 | var TextMessage = ({header, text, className}) => { 313 | if(!text) { 314 | return
; 315 | } 316 | return ( 317 |
318 |

{ header }

319 |
{ text }
320 |
321 | ); 322 | } 323 | 324 | export class App extends Component { 325 | componentDidMount() { 326 | console.log('App mounted!'); 327 | this.props.dispatch(fetchExamples()); 328 | this.props.dispatch(fetchVersions()); 329 | this.props.dispatch(fetchPcaps()); 330 | } 331 | versionSelected = (version) => { 332 | this.props.dispatch(setVersion(version)) 333 | } 334 | exampleSelected = (example) => { 335 | setHistoryToExample(example); 336 | this.props.dispatch(execReset()) 337 | } 338 | hideExample = () => { 339 | this.props.dispatch(hideExample()) 340 | } 341 | showExample = () => { 342 | this.props.dispatch(showExample()) 343 | } 344 | addFile = () => { 345 | this.props.dispatch(codeAddFile()); 346 | } 347 | renameFile = (new_name) => { 348 | this.props.dispatch(codeRenameFile(new_name)); 349 | } 350 | selectFile = (name) => { 351 | this.props.dispatch(codeSelectFile(name)); 352 | } 353 | codeChanged = (name, contents) => { 354 | this.props.dispatch(codeEditFile(name, contents)); 355 | } 356 | pcapChanged = (pcap) => { 357 | this.refs.file.value=null; 358 | this.props.dispatch(pcapSelected(pcap)); 359 | } 360 | runCode = () => { 361 | this.props.dispatch(execSubmit()); 362 | } 363 | formatCode = () => { 364 | this.props.dispatch(formatSubmit()); 365 | } 366 | fileChanged = () => { 367 | var f = this.refs.file.files[0]; 368 | this.props.dispatch(pcapFileChanged(f)); 369 | } 370 | 371 | renderLoadLine() { 372 | const { examples } = this.props; 373 | var showHide = null; 374 | if ( examples.example && examples.example.html && examples.hidden) 375 | showHide = ; 376 | else 377 | showHide = ; 378 | 379 | return ( 380 | 381 | Example: 382 | { ' ' } 383 | {showHide} 384 | 385 | ); 386 | } 387 | 388 | renderCodeRow() { 389 | const { versions, examples, code, pcap, exec } = this.props; 390 | var editor = 397 | 398 | 399 | var editorBox = 400 | 401 | 402 | {editor} 403 | 404 | 405 | 406 | 407 | 408 | { ' ' } 409 | Use PCAP 410 | { ' ' } 411 | Or { ' ' } 412 | 415 | 416 | { ' ' } 417 | 418 | 419 | 420 | 421 | ; 422 | 423 | 424 | if(examples.example && examples.example.html && !examples.hidden) { 425 | return ( 426 | 427 | 428 | 429 | 430 | 431 | {editorBox} 432 | 433 | 434 | ); 435 | } else { 436 | return ( 437 | 438 | 439 | {editorBox} 440 | 441 | 442 | ); 443 | } 444 | } 445 | render() { 446 | const { exec } = this.props; 447 | return ( 448 | 449 | {this.renderLoadLine()} 450 |
451 | {this.renderCodeRow()} 452 | 453 | 454 | 455 |
456 | ); 457 | } 458 | } 459 | 460 | function zip(arrays) { 461 | return arrays[0].map(function(_,i){ 462 | return arrays.map(function(array){return array[i]}) 463 | }); 464 | } 465 | 466 | function mapStateToProps(state) { 467 | return { 468 | versions: state.versions, 469 | examples: state.examples, 470 | code: state.code, 471 | pcap: state.pcap, 472 | exec: state.exec 473 | }; 474 | } 475 | 476 | export default connect(mapStateToProps)(App); 477 | -------------------------------------------------------------------------------- /manager/web-ui/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {App} from './App'; 4 | 5 | import tryBroApp from './reducers'; 6 | 7 | it('renders without crashing', () => { 8 | const div = document.createElement('div'); 9 | var props = tryBroApp(undefined, {type:null}) 10 | ReactDOM.render(, div); 11 | }); 12 | -------------------------------------------------------------------------------- /manager/web-ui/src/ZeekMode.js: -------------------------------------------------------------------------------- 1 | import 'brace/mode/java'; 2 | 3 | export class ZeekHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules { 4 | } 5 | 6 | export default class ZeekMode extends window.ace.acequire('ace/mode/java').Mode { 7 | } 8 | -------------------------------------------------------------------------------- /manager/web-ui/src/actions.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | import { tbhistory, setHistoryToExample } from './tbhistory'; 4 | import md5 from 'md5'; 5 | import queryString from 'query-string' 6 | 7 | const API_HOST = process.env.REACT_APP_TRY_BRO_API_HOST ? process.env.REACT_APP_TRY_BRO_API_HOST : ''; 8 | 9 | 10 | export const VERSIONS_FETCHING = 'VERSIONS_FETCHING'; 11 | export const VERSIONS_FETCHED = 'VERSIONS_FETCHED'; 12 | export const VERSIONS_SET = 'VERSIONS_SET'; 13 | 14 | 15 | export function fetchingVersions() { 16 | return { 17 | type: VERSIONS_FETCHING, 18 | }; 19 | } 20 | 21 | export function fetchedVersions(version_data) { 22 | return { 23 | type: VERSIONS_FETCHED, 24 | versions: version_data.versions, 25 | version: version_data['default'] 26 | }; 27 | } 28 | export function setVersion(version) { 29 | return { 30 | type: VERSIONS_SET, 31 | version: version 32 | }; 33 | } 34 | 35 | export function fetchVersions() { 36 | return dispatch => { 37 | dispatch(fetchingVersions()); 38 | return fetch(`${API_HOST}/versions.json`) 39 | .then(response => response.json()) 40 | .then(json => dispatch(fetchedVersions(json))); 41 | }; 42 | } 43 | 44 | export const EXAMPLES_FETCHING = 'EXAMPLES_FETCHING'; 45 | export const EXAMPLES_FETCHED = 'EXAMPLES_FETCHED'; 46 | export const EXAMPLES_SET = 'EXAMPLES_SET'; 47 | 48 | export const EXAMPLE_SELECTED = 'EXAMPLE_SELECTED'; 49 | export const EXAMPLE_HIDE = 'EXAMPLE_HIDE'; 50 | export const EXAMPLE_SHOW = 'EXAMPLE_SHOW'; 51 | 52 | 53 | export function fetchingExamples() { 54 | return { 55 | type: EXAMPLES_FETCHING, 56 | }; 57 | } 58 | 59 | export function fetchExamples() { 60 | return dispatch => { 61 | dispatch(fetchingExamples()); 62 | return fetch(`${API_HOST}/static/examples/examples.json`) 63 | .then(response => response.json()) 64 | .then(json => dispatch(fetchedExamples(json))); 65 | }; 66 | } 67 | 68 | export function fetchedExamples(examples) { 69 | return { 70 | type: EXAMPLES_FETCHED, 71 | examples: examples 72 | }; 73 | } 74 | 75 | export function loadExample(example, run=false) { 76 | return (dispatch, getState) => { 77 | const state = getState(); 78 | if (!state.examples.fetched) { 79 | dispatch(fetchExamples()).then(() => { 80 | dispatch(selectExample(example, run)); 81 | }); 82 | } else { 83 | dispatch(selectExample(example, run)); 84 | } 85 | } 86 | } 87 | 88 | export function selectExample(example, run=false) { 89 | return (dispatch, getState) => { 90 | dispatch({ type: EXAMPLE_SELECTED, path: example}) 91 | const state = getState(); 92 | example = state.examples.example; 93 | dispatch(setCode(example.sources)); 94 | if (example.pcaps.length) 95 | dispatch(pcapSelected(example.pcaps[0])) 96 | if(run) 97 | dispatch(execSubmit()); 98 | }; 99 | }; 100 | 101 | export function hideExample() { 102 | return { 103 | type: EXAMPLE_HIDE, 104 | }; 105 | } 106 | export function showExample() { 107 | return { 108 | type: EXAMPLE_SHOW, 109 | }; 110 | } 111 | 112 | export const CODE_SET = 'CODE_SET'; 113 | export const CODE_SELECT_FILE = 'CODE_SELECT_FILE'; 114 | export const CODE_ADD_FILE = 'CODE_ADD_FILE'; 115 | export const CODE_RENAME_FILE = 'CODE_RENAME_FILE'; 116 | export const CODE_EDIT_FILE = 'CODE_EDIT_FILE'; 117 | 118 | export function setCode(sources) { 119 | return { 120 | type: CODE_SET, 121 | sources 122 | } 123 | } 124 | 125 | export function codeSelectFile(name) { 126 | return { 127 | type: CODE_SELECT_FILE, 128 | name 129 | } 130 | } 131 | 132 | export function codeAddFile() { 133 | return { 134 | type: CODE_ADD_FILE, 135 | } 136 | } 137 | export function codeRenameFile(new_name) { 138 | return { 139 | type: CODE_RENAME_FILE, 140 | new_name, 141 | } 142 | } 143 | 144 | export function codeEditFile(name, contents) { 145 | return { 146 | type: CODE_EDIT_FILE, 147 | name, 148 | contents 149 | } 150 | } 151 | 152 | export const EXEC_RUNNING = 'EXEC_RUNNING' 153 | export const EXEC_COMPLETE = 'EXEC_COMPLETE' 154 | 155 | export const EXEC_FETCHING_FILES = 'EXEC_FETCHING_FILES' 156 | export const EXEC_FETCHED_FILES = 'EXEC_FETCHED_FILES' 157 | 158 | export const EXEC_RESET = 'EXEC_RESET' 159 | 160 | export const EXEC_SET_ERRORS = 'EXEC_SET_ERRORS'; 161 | 162 | export function execReset(){ 163 | return { 164 | type: EXEC_RESET 165 | } 166 | } 167 | 168 | export function execSubmit(pcap_uploaded) { 169 | return (dispatch, getState) => { 170 | const state = getState(); 171 | const sources = state.code.sources; 172 | const version = state.versions.version; 173 | const pcap = state.pcap.pcap; 174 | const pcapfile = state.pcap.file; 175 | if (pcapfile !== null && pcap_uploaded !== true) { 176 | return upload_and_reexec(dispatch, pcapfile); 177 | } 178 | var opts = { 179 | method: 'post', 180 | headers: { 181 | 'Accept': 'application/json', 182 | 'Content-Type': 'application/json' 183 | }, 184 | body: JSON.stringify({ 185 | sources: sources, 186 | version: version, 187 | pcap: pcap 188 | }) 189 | }; 190 | dispatch(execRunning()); 191 | return fetch(`${API_HOST}/run`, opts) 192 | .then(response => response.json()) 193 | .then(json => { 194 | dispatch(execComplete(json)); 195 | dispatch(execFetchFiles(json.job)); 196 | tbhistory.push({ pathname: '/tryzeek/saved/' + json.job}); 197 | }); 198 | }; 199 | } 200 | 201 | function upload_and_reexec(dispatch, file) { 202 | var reader = new FileReader(); 203 | reader.onloadend = function () { 204 | var checksum = md5(reader.result, {encoding: 'binary'}); 205 | check_or_upload_pcap(dispatch, file, checksum); 206 | } 207 | reader.readAsBinaryString(file); 208 | } 209 | 210 | function check_or_upload_pcap(dispatch, file, checksum){ 211 | fetch(`${API_HOST}/pcap/${checksum}`) 212 | .then(response => response.json()) 213 | .then(response => { 214 | if (response.status) { 215 | //pcap already exists on server 216 | dispatch(pcapSelected(checksum)); 217 | dispatch(execSubmit(true)); 218 | } else { 219 | upload_pcap(dispatch, file, checksum); 220 | } 221 | }); 222 | } 223 | 224 | function upload_pcap(dispatch, file, checksum) { 225 | var xhr = new XMLHttpRequest(); 226 | xhr.upload.addEventListener('progress', function(e) { 227 | if (e.lengthComputable) { 228 | var percentage = Math.round((e.loaded * 100) / e.total); 229 | dispatch(pcapProgress(percentage)); 230 | } 231 | }, false); 232 | xhr.onreadystatechange = function() { 233 | if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { 234 | dispatch(pcapUploaded(checksum)); 235 | dispatch(execSubmit(true)); 236 | } 237 | }; 238 | 239 | var fd = new FormData(); 240 | fd.append('pcap', file); 241 | 242 | xhr.open('POST', `${API_HOST}/pcap/upload/${checksum}`, true); 243 | xhr.send(fd); 244 | } 245 | 246 | export function execFetchFiles(job) { 247 | return dispatch => { 248 | dispatch(execFetchingFiles()) 249 | return fetch(`${API_HOST}/files/${job}.json`) 250 | .then(response => response.json()) 251 | .then(json => dispatch(execFetchedFiles(json.files))); 252 | } 253 | } 254 | export function execFetchingFiles() { 255 | return { 256 | type: EXEC_FETCHING_FILES 257 | } 258 | } 259 | 260 | export function execFetchedFiles(files) { 261 | return { 262 | type: EXEC_FETCHED_FILES, 263 | files 264 | } 265 | } 266 | 267 | export function execRunning() { 268 | return { 269 | type: EXEC_RUNNING 270 | } 271 | } 272 | 273 | export function execComplete(response) { 274 | return { 275 | type: EXEC_COMPLETE, 276 | response 277 | } 278 | } 279 | 280 | export function setExecErrors(errors) { 281 | return { 282 | type: EXEC_SET_ERRORS, 283 | errors 284 | } 285 | } 286 | 287 | export const PCAP_FETCHING = 'PCAP_FETCHING' 288 | export const PCAP_FETCHED = 'PCAP_FETCHED' 289 | export const PCAP_SELECTED = 'PCAP_SELECTED' 290 | export const PCAP_FILE_CHANGED = 'PCAP_FILE_CHANGED' 291 | export const PCAP_UPLOAD_PROGRESS = 'PCAP_UPLOAD_PROGRESS' 292 | export const PCAP_UPLOADED = 'PCAP_UPLOADED' 293 | 294 | export function fetchPcaps() { 295 | return dispatch => { 296 | dispatch(fetchingPcaps()); 297 | return fetch(`${API_HOST}/pcaps.json`) 298 | .then(response => response.json()) 299 | .then(json => dispatch(fetchedPcaps(json))); 300 | }; 301 | } 302 | 303 | export function fetchingPcaps() { 304 | return { 305 | type: PCAP_FETCHING, 306 | }; 307 | } 308 | 309 | export function fetchedPcaps(pcap_data) { 310 | return { 311 | type: PCAP_FETCHED, 312 | available: pcap_data.available, 313 | }; 314 | } 315 | 316 | export function pcapSelected(pcap) { 317 | return { 318 | type: PCAP_SELECTED, 319 | pcap 320 | } 321 | } 322 | 323 | export function pcapFileChanged(file) { 324 | return { 325 | type: PCAP_FILE_CHANGED, 326 | file 327 | } 328 | } 329 | 330 | export function pcapProgress(pct) { 331 | return { 332 | type: PCAP_UPLOAD_PROGRESS, 333 | pct 334 | } 335 | } 336 | export function pcapUploaded(checksum) { 337 | return { 338 | type: PCAP_UPLOADED, 339 | checksum 340 | } 341 | } 342 | 343 | export function loadSaved(job, autorun) { 344 | return dispatch => { 345 | return fetch(`${API_HOST}/saved/${job}`) 346 | .then(response => response.json()) 347 | .then(json => { 348 | dispatch(setCode(json.sources)); 349 | dispatch(setVersion(json.version)); 350 | dispatch(pcapSelected(json.pcap)); 351 | if (autorun) 352 | dispatch(execSubmit()); 353 | }); 354 | } 355 | } 356 | 357 | export function formatSubmit() { 358 | return (dispatch, getState) => { 359 | const state = getState(); 360 | const sources = state.code.sources; 361 | var opts = { 362 | method: 'post', 363 | headers: { 364 | 'Accept': 'application/json', 365 | 'Content-Type': 'application/json' 366 | }, 367 | body: JSON.stringify({ 368 | sources: sources, 369 | }) 370 | }; 371 | return fetch(`${API_HOST}/format`, opts) 372 | .then(response => response.json()) 373 | .then(json => { 374 | dispatch(setCode(json.sources)); 375 | dispatch(setExecErrors(json.errors)); 376 | }); 377 | }; 378 | } 379 | 380 | 381 | export function handleLocationChange(dispatch, location, initial=false) { 382 | console.log('Location is now', location); 383 | var match = /\/(?:trybro|tryzeek)\/saved\/([0-9a-f]+)/.exec(location.pathname); 384 | if (match) { 385 | var job = match[1]; 386 | return dispatch(loadSaved(job, initial)); 387 | } 388 | const query = queryString.parse(location.search); 389 | if(query) { 390 | var q = query; 391 | if (q.pcap) 392 | dispatch(pcapSelected(q.pcap)); 393 | if (q.version) 394 | dispatch(setVersion(q.version)); 395 | if (q.example) 396 | dispatch(loadExample(q.example, q.run)); 397 | } 398 | if(location.pathname === '/' && Object.keys(query).length === 0 && location.hash === '') { 399 | setHistoryToExample('hello'); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /manager/web-ui/src/babel-plugin-macros.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'fontawesome-svg-core': { 3 | 'license': 'free' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /manager/web-ui/src/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | return { 3 | plugins: ['macros'], 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /manager/web-ui/src/broutil.js: -------------------------------------------------------------------------------- 1 | var BroErrorRegex = /(error|warning) in (.+), line ([0-9]+): (.+)/g; 2 | 3 | export function parse_errors(stderr) 4 | { 5 | var errors = {}; 6 | var match = {}; 7 | // eslint-disable-next-line 8 | while ((match = BroErrorRegex.exec(stderr)) !== null) { 9 | var type = match[1]; 10 | var filename = match[2]; 11 | var line = parseInt(match[3], 10); 12 | var text = match[4]; 13 | 14 | filename = filename.replace(/^[/.]+/,'') 15 | filename = filename.replace('try.zeek', 'main.zeek'); 16 | 17 | var errors_for_this_file = errors[filename] || []; 18 | errors_for_this_file.push({ 19 | row: line-1, 20 | column: 0, 21 | type: type, 22 | text: text, 23 | }); 24 | errors[filename] = errors_for_this_file; 25 | } 26 | return errors; 27 | } 28 | -------------------------------------------------------------------------------- /manager/web-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App'; 4 | 5 | import thunk from 'redux-thunk'; 6 | import { compose, createStore, applyMiddleware } from 'redux'; 7 | import { Provider } from 'react-redux'; 8 | 9 | import { tbhistory } from './tbhistory'; 10 | import { handleLocationChange } from './actions'; 11 | 12 | import tryBroApp from './reducers'; 13 | 14 | import 'bootstrap/dist/css/bootstrap.css'; 15 | 16 | 17 | const middlewares = [thunk]; 18 | if (process.env.NODE_ENV === `development`) { 19 | const createLogger = require(`redux-logger`); 20 | const logger = createLogger(); 21 | middlewares.push(logger); 22 | } 23 | 24 | const store = compose(applyMiddleware(...middlewares))(createStore)(tryBroApp); 25 | 26 | tbhistory.listen(function (location) { 27 | console.log('tbhistory event handler:', location.action, location.location); 28 | if(location.action !== 'REPLACE') 29 | handleLocationChange(store.dispatch, location.location, false) 30 | }) 31 | handleLocationChange(store.dispatch, tbhistory.location, true) 32 | 33 | let rootElement = document.getElementById('root'); 34 | render( 35 | 36 | 37 | , 38 | rootElement 39 | ); 40 | 41 | -------------------------------------------------------------------------------- /manager/web-ui/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /manager/web-ui/src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { VERSIONS_FETCHING, VERSIONS_FETCHED, VERSIONS_SET } from './actions'; 3 | import { EXAMPLES_FETCHING, EXAMPLES_FETCHED, EXAMPLE_SELECTED, EXAMPLE_HIDE, EXAMPLE_SHOW } from './actions'; 4 | import { CODE_SET, CODE_ADD_FILE, CODE_SELECT_FILE, CODE_RENAME_FILE, CODE_EDIT_FILE } from './actions'; 5 | import { EXEC_RUNNING, EXEC_COMPLETE, EXEC_FETCHING_FILES, EXEC_FETCHED_FILES, EXEC_RESET, EXEC_SET_ERRORS } from './actions'; 6 | import { PCAP_FETCHING, PCAP_FETCHED, PCAP_SELECTED, PCAP_FILE_CHANGED, PCAP_UPLOAD_PROGRESS, PCAP_UPLOADED } from './actions'; 7 | import { parse_errors } from './broutil'; 8 | 9 | const initialVersionState = { 10 | fetching: false, 11 | versions: [], 12 | version: null 13 | }; 14 | 15 | const initialExampleState = { 16 | fetching: false, 17 | fetched: false, 18 | examples: [], 19 | example: null, 20 | hidden: false 21 | } 22 | const initialCodeState = { 23 | sources: [{name:'main.zeek', content:''}], 24 | current: 'main.zeek', 25 | newCounter: 1 26 | } 27 | 28 | const initialExecState = { 29 | status: null, 30 | mode: 'text', 31 | stdout: null, 32 | stderr: null, 33 | files: null, 34 | visible: null, 35 | job: null, 36 | errors: {}, 37 | } 38 | 39 | const initialPcapState = { 40 | pcap: null, 41 | file: null, 42 | fetching: null, 43 | fetched: false, 44 | available: [], 45 | uploaded: false, 46 | uploading: false, 47 | upload_progress: null, 48 | } 49 | 50 | function versions(state = initialVersionState, action) { 51 | switch (action.type) { 52 | case VERSIONS_SET: 53 | return Object.assign({}, state, { 54 | version: action.version 55 | }); 56 | case VERSIONS_FETCHING: 57 | return Object.assign({}, state, { 58 | fetching: true, 59 | }); 60 | case VERSIONS_FETCHED: 61 | return Object.assign({}, state, { 62 | fetching: false, 63 | versions: action.versions, 64 | version: action.version 65 | }); 66 | default: 67 | return state; 68 | } 69 | } 70 | 71 | function examples(state = initialExampleState, action) { 72 | switch (action.type) { 73 | case EXAMPLES_FETCHING: 74 | return Object.assign({}, state, { 75 | fetching: true, 76 | }); 77 | case EXAMPLES_FETCHED: 78 | return Object.assign({}, state, { 79 | fetching: false, 80 | examples: action.examples, 81 | fetched: true, 82 | }); 83 | case EXAMPLE_SELECTED: 84 | var ex = state.examples.filter((e) => { 85 | return e.path === action.path; 86 | })[0]; 87 | if (ex === undefined) { 88 | return state; 89 | } 90 | return { ...state, 91 | example: ex, 92 | } 93 | case EXAMPLE_HIDE: 94 | return Object.assign({}, state, { 95 | hidden: true, 96 | }); 97 | case EXAMPLE_SHOW: 98 | return Object.assign({}, state, { 99 | hidden: false, 100 | }); 101 | default: 102 | return state; 103 | } 104 | } 105 | 106 | function code(state = initialCodeState, action) { 107 | console.log(state, action); 108 | switch (action.type) { 109 | case CODE_SET: 110 | return Object.assign({}, state, { 111 | sources: action.sources, 112 | current: action.sources[0].name, 113 | newCounter: action.sources.length 114 | }); 115 | case CODE_ADD_FILE: 116 | var new_filename = `new-${state.newCounter}.zeek`; 117 | return Object.assign({}, state, { 118 | sources: [...state.sources, {name: new_filename, content: '#'}], 119 | current: new_filename, 120 | newCounter: state.newCounter + 1 121 | }); 122 | case CODE_RENAME_FILE: 123 | var new_sources = []; 124 | state.sources.map( s => { 125 | if(s.name !== state.current) { 126 | new_sources.push(s); 127 | } else { 128 | new_sources.push({ 129 | name: action.new_name, 130 | content: s.content 131 | }) 132 | } 133 | }) 134 | return Object.assign({}, state, { 135 | sources: new_sources, 136 | current: action.new_name 137 | }) 138 | case CODE_SELECT_FILE: 139 | return Object.assign({}, state, { 140 | current: action.name 141 | }); 142 | case CODE_EDIT_FILE: 143 | var new_sources = []; 144 | state.sources.map( s => { 145 | if(s.name !== action.name) { 146 | new_sources.push(s); 147 | } else { 148 | new_sources.push({ 149 | name: action.name, 150 | content: action.contents 151 | }) 152 | } 153 | }) 154 | return Object.assign({}, state, { 155 | sources: new_sources 156 | }) 157 | default: 158 | return state; 159 | } 160 | } 161 | 162 | function exec(state = initialExecState, action) { 163 | switch (action.type) { 164 | case EXEC_RESET: 165 | return initialExecState; 166 | case EXEC_RUNNING: 167 | return Object.assign({}, state, { 168 | status: 'Running...', 169 | files: null, 170 | stdout: null, 171 | stderr: null 172 | }); 173 | case EXEC_COMPLETE: 174 | return Object.assign({}, state, { 175 | stdout: action.response.stdout, 176 | job: action.response.job, 177 | status: null, 178 | }); 179 | case EXEC_FETCHING_FILES: 180 | return Object.assign({}, state, { 181 | status: 'Fetching files...' 182 | }); 183 | case EXEC_FETCHED_FILES: 184 | var files = action.files; 185 | var stderr = files['stderr.log']; 186 | delete(files['stderr.log']); 187 | delete(files['stdout.log']); 188 | return Object.assign({}, state, { 189 | status: null, 190 | files: files, 191 | stderr: stderr, 192 | errors: parse_errors(stderr) 193 | }); 194 | case EXEC_SET_ERRORS: 195 | return Object.assign({}, state, { 196 | errors: action.errors 197 | }); 198 | default: 199 | return state; 200 | } 201 | } 202 | 203 | function pcap(state = initialPcapState, action) { 204 | switch (action.type) { 205 | case PCAP_FETCHING: 206 | return Object.assign({}, state, { 207 | fetching: true, 208 | }); 209 | case PCAP_FETCHED: 210 | return Object.assign({}, state, { 211 | fetching: false, 212 | fetched: true, 213 | available: action.available, 214 | }); 215 | case PCAP_SELECTED: 216 | return { ...state, 217 | pcap: action.pcap, 218 | file: null, 219 | uploaded: false 220 | } 221 | case PCAP_FILE_CHANGED: 222 | return { ...state, 223 | pcap: null, 224 | file: action.file, 225 | too_large: action.file.size > 10*1024*1024, 226 | uploaded: false, 227 | uploading: false, 228 | upload_progress: null, 229 | } 230 | case PCAP_UPLOAD_PROGRESS: 231 | return { ...state, 232 | uploaded: false, 233 | uploading: true, 234 | upload_progress: action.pct, 235 | } 236 | case PCAP_UPLOADED: 237 | return { ...state, 238 | pcap: action.checksum, 239 | uploaded: true, 240 | uploading: false, 241 | upload_progress: null, 242 | } 243 | default: 244 | return state; 245 | } 246 | } 247 | 248 | const tryBroApp = combineReducers({ 249 | versions, 250 | examples, 251 | code, 252 | pcap, 253 | exec 254 | }); 255 | 256 | export default tryBroApp; 257 | 258 | -------------------------------------------------------------------------------- /manager/web-ui/src/tbhistory.js: -------------------------------------------------------------------------------- 1 | import { createHashHistory } from 'history' 2 | import queryString from 'query-string' 3 | 4 | export const tbhistory = createHashHistory(); 5 | 6 | export function setHistoryToExample(example) { 7 | var q = queryString.stringify({example: example}); 8 | tbhistory.push({pathname: '/', search: q}); 9 | } 10 | -------------------------------------------------------------------------------- /manager/worker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -u 2 | import asyncio 3 | import codecs 4 | import docker 5 | import hashlib 6 | import json 7 | import os 8 | import shutil 9 | import time 10 | import tempfile 11 | import traceback 12 | import rq 13 | 14 | from common import get_cache_key, get_redis, get_redis_raw 15 | from version import zeek_versions_from_redis 16 | 17 | CACHE_EXPIRE = 60*10 18 | SOURCES_EXPIRE = 60*60*24*30 19 | 20 | r = get_redis() 21 | r_raw = get_redis_raw() 22 | 23 | background_tasks = set() 24 | 25 | 26 | def read_fn(fn): 27 | with open(fn) as f: 28 | return f.read() 29 | 30 | 31 | def get_bro_versions(): 32 | c = docker.Client() 33 | images = c.images(name='zeek/zeek') 34 | tags = [i['RepoTags'][0] for i in images] 35 | versions = [t.replace("zeek/zeek:", "") for t in tags if '_' not in t] 36 | return sorted(versions) 37 | 38 | def get_pcap(checksum): 39 | pcap_key = "pcaps:%s" % checksum 40 | r_raw.expire(pcap_key, 60*60) 41 | return r_raw.get(pcap_key) 42 | 43 | def get_pcap_with_retry(checksum): 44 | """There is an annoying race condition where POST /pcap/upload is returning as soon as the pcap is sent, but before 45 | the request is complete and redis has actually saved it. then run_code 46 | doesn't find it. so for now, retry here and see if it shows up""" 47 | 48 | for x in range(10): 49 | contents = get_pcap(checksum) 50 | if contents: 51 | return contents 52 | time.sleep(0.1) 53 | return None 54 | 55 | 56 | def run_code(sources, pcap=None, version=None): 57 | if not version: # Assume backend.py set this properly. 58 | raise RuntimeError("version is missing") 59 | 60 | job = rq.get_current_job() 61 | if type(job) is bytes: 62 | job = job.decode() 63 | return really_run_code(job.id, sources, pcap=pcap, version=version) 64 | 65 | 66 | def really_run_code(job_id, sources, pcap, version): 67 | cache_key = get_cache_key(sources, pcap, version) 68 | stdout_key = 'stdout:%s' % job_id 69 | files_key = 'files:%s' % job_id 70 | sources_key = 'sources:%s' % job_id 71 | 72 | file_output = run_code_docker(sources, pcap, version) 73 | 74 | stdout="" 75 | for f, txt in file_output.items(): 76 | if not f.endswith(".log"): continue 77 | r.hset(files_key, f, txt) 78 | if f == 'stdout.log': 79 | stdout = txt 80 | r.expire(files_key, CACHE_EXPIRE+5) 81 | 82 | r.set(stdout_key, stdout) 83 | r.expire(stdout_key, CACHE_EXPIRE+5) 84 | 85 | r.set(sources_key, json.dumps(dict(sources=sources, pcap=pcap, version=version))) 86 | r.expire(sources_key, SOURCES_EXPIRE) 87 | 88 | r.set(cache_key, job_id) 89 | r.expire(cache_key, CACHE_EXPIRE) 90 | return stdout 91 | 92 | def run_code_docker(sources, pcap=None, version=None): 93 | with tempfile.TemporaryDirectory(dir="/brostuff") as work_dir: 94 | return _run_code_docker(sources, pcap=pcap, version=version, work_dir=work_dir) 95 | def _run_code_docker(sources, pcap, version, work_dir): 96 | default_version, versions = zeek_versions_from_redis() 97 | if version not in versions: 98 | print(f"Warning: {version!r} not available, using {default_version!r}") 99 | version = default_version 100 | 101 | for s in sources: 102 | s['content'] = s['content'].replace("\r\n", "\n") 103 | s['content'] = s['content'].rstrip() + "\n" 104 | 105 | runbro_path = os.path.join(work_dir, "runbro") 106 | for s in sources: 107 | code_fn = os.path.join(work_dir, s['name']) 108 | with codecs.open(code_fn, 'w', encoding="utf-8") as f: 109 | f.write(s['content']) 110 | 111 | runbro_src = "./runbro" 112 | runbro_src_version_specific = "%s-%s" % (runbro_src, version) 113 | if os.path.exists(runbro_src_version_specific): 114 | runbro_src = runbro_src_version_specific 115 | 116 | shutil.copy(runbro_src, runbro_path) 117 | os.chmod(runbro_path, 0o755) 118 | 119 | binds = {work_dir: {"bind": work_dir, "mode": "rw"}} 120 | 121 | if pcap: 122 | dst = os.path.join(work_dir, "file.pcap") 123 | if '.' in pcap: 124 | src = os.path.join(os.getcwd(), "static/pcaps", pcap) 125 | #FIXME: Work out a better way to share pcaps around 126 | #binds[src]={"bind": dst, "ro": True} 127 | shutil.copy(src, dst) 128 | else: 129 | contents = get_pcap_with_retry(pcap) 130 | if contents: 131 | with open(dst, 'wb') as f: 132 | f.write(contents) 133 | 134 | #docker run -v /brostuff/tmpWh0k1x:/brostuff/ -n --rm -t -i bro_worker /bro/bin/bro /brostuff/code.bro 135 | 136 | print("Connecting to docker....") 137 | c = docker.Client() 138 | 139 | print("Creating Bro %s container.." % version) 140 | host_config = docker.utils.create_host_config( 141 | binds=binds, 142 | dns=["127.0.0.1"], 143 | mem_limit="128m", 144 | network_mode="none", 145 | read_only=True, 146 | ) 147 | container = c.create_container('zeek/zeek:' + version, 148 | working_dir=work_dir, 149 | command=runbro_path, 150 | host_config=host_config, 151 | volumes=[work_dir], 152 | ) 153 | print("Starting container..") 154 | try: 155 | c.start(container) 156 | 157 | print("Waiting..") 158 | c.wait(container) 159 | 160 | finally: 161 | print("Removing Container") 162 | c.remove_container(container) 163 | 164 | files = {} 165 | for f in os.listdir(work_dir): 166 | if not f.endswith(".log"): continue 167 | full = os.path.join(work_dir, f) 168 | txt = read_fn(full) 169 | if txt.strip() or 'stdout' in f: 170 | files[f] = txt 171 | return files 172 | -------------------------------------------------------------------------------- /tryzeek.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=TryZeek 3 | After=docker.service 4 | Requires=docker.service 5 | 6 | [Service] 7 | TimeoutStartSec=0 8 | #ExecStartPre=-/usr/bin/docker kill trybro 9 | #ExecStartPre=-/usr/bin/docker rm trybro 10 | WorkingDirectory=/root/try-zeek 11 | RemainAfterExit=yes 12 | Type=oneshot 13 | Environment=TRYZEEK_DATA=/srv/tryzeek_data 14 | # Set this if you need a different SITE_ADDRESS that is used by caddy: 15 | # Environment=SITE_ADDRESS=try.zeek.example.com:443 16 | ExecStart=docker-compose up -d --scale worker=4 17 | ExecStop=docker-compose down 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | --------------------------------------------------------------------------------