diff --git a/.dockerignore b/.dockerignore
index 8ffb737e2c..c3aefc7e58 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,3 +1,49 @@
+# Files to ignore by Docker when transfering context
+
**/.git
/docs/
/notebooks/
+
+**/.DS_Store
+
+# Back-up files
+**/*~
+**/*.swp
+
+# Generic auto-generated build files
+**/*.pyc
+**/*.pyo
+**/.ipynb_checkpoints/
+
+# Specific auto-generated build files
+/.eggs
+/.tox
+**/__pycache__
+/api_client/python/build/
+/cli_client/python/build/
+/importer_client/python/build/
+**/dependencies/
+/*.egg-info
+
+# Ignore frontend build related files
+**/node_modules
+/timesketch/static/dist
+**/package-lock.json
+**/yarn.lock
+
+# Test files
+**/.coverage
+**/tests-coverage.txt
+
+# Exclude Vagrant runtime files
+/vagrant/.vagrant/
+/vagrant/*.log
+
+# Exclude .venv folder
+/.venv/
+
+# Exclude Visual Studio Code files
+/.vscode/*
+
+# Exclude JetBrains IDE files
+/.idea/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..8c3938ddca
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+* text=auto
+**/docker/** text=auto eol=lf
+requirements.txt text=auto eol=lf
+requirements-dev.txt text=auto eol=lf
+
diff --git a/.gitignore b/.gitignore
index 45ed349e37..100d9312bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,6 @@ vagrant/*.log
# Exclude debugging profiles
profiles/
+
+# Exclude Docker Compose .env file
+.env
diff --git a/contrib/docker/dev/.env.template b/contrib/docker/dev/.env.template
new file mode 100644
index 0000000000..4a16a750e6
--- /dev/null
+++ b/contrib/docker/dev/.env.template
@@ -0,0 +1,22 @@
+GIFT_PPA_TRACK="stable"
+GIFT_PPA_URL="https://ppa.launchpadcontent.net/gift/${GIFT_PPA_TRACK}/ubuntu"
+NODE_VERSION="20.x"
+NODE_PPA_URL="https://deb.nodesource.com/node_${NODE_VERSION}"
+NODE_NPMRC=""
+YARN_YARNRC=""
+PYTHON_PIP_CONF=""
+
+TIMESKETCH_BASE_IMAGE="ubuntu:24.04"
+TIMESKETCH_CONF_DIR="/etc/timesketch"
+TIMESKETCH_SECRET_KEY="L4np0jV3yAdAFdbVzWRMaBqiFMV8FKYd+Je1WKE40o8="
+TIMESKETCH_USER="dev"
+TIMESKETCH_PASSWORD="dev"
+TIMESKETCH_USER_NAME="ubuntu"
+TIMESKETCH_USER_UID="1000"
+TIMESKETCH_USER_GID="1000"
+
+POSTGRES_USER="timesketch"
+POSTGRES_PASSWORD="password"
+POSTGRES_DB="timesketch"
+
+FRONTEND_VERSION="frontend-ng"
diff --git a/contrib/docker/dev/README.md b/contrib/docker/dev/README.md
new file mode 100644
index 0000000000..7d7eabac62
--- /dev/null
+++ b/contrib/docker/dev/README.md
@@ -0,0 +1,63 @@
+# Docker Compose for development
+
+## Prepare a .env file
+
+Compose requires a `.env` file with top level environment variables to be set.
+To create it, just copy the `.env.template` file as a base.
+
+```bash
+cp contrib/docker/dev/.env.template contrib/docker/dev/.env
+```
+
+Note the `.env` is ignored by Git: You can safely write sensitive data in it.
+
+You can optionally edit the `.env` file.
+This is useful if you need to build images with some company restrictions
+(accessing remote Ubuntu, PyPI or Node repositories).
+
+The default Timesketch user is `dev` and its password is `dev`.
+
+## Build or rebuild images
+
+To build images:
+
+```bash
+docker compose -f contrib/docker/dev/compose.yaml build
+```
+
+## Start services
+
+To start all services:
+
+```bash
+docker compose -f contrib/docker/dev/compose.yaml up -d
+```
+
+On the first run, the _setup_ service will:
+
+- Create the Timesketch user (defaults `dev` with password `dev`),
+- Clone (fetch on subsequent runs) the
+ [SigmaHQ/sigma](https://github.com/SigmaHQ/sigma) repository,
+- Import the sigma rules set in [sigma_rules.txt](timesketch/sigma_rules.txt)
+
+Go to your browser:
+
+- : Frozen frontend (_dist_ compiled files), backend
+ with latest sources and live reload,
+- : Frontend and backend with latest sources and live
+ reload (slower to be available).
+- : Jupyter notebook with (password: `timesketch`).
+
+## Stop services
+
+To stop services and remove related containers:
+
+```bash
+docker compose -f contrib/docker/dev/compose.yaml down
+```
+
+To delete service data at once:
+
+```bash
+docker compose -f contrib/docker/dev/compose.yaml down -v
+```
diff --git a/contrib/docker/dev/compose.yaml b/contrib/docker/dev/compose.yaml
new file mode 100644
index 0000000000..c06cac9a6e
--- /dev/null
+++ b/contrib/docker/dev/compose.yaml
@@ -0,0 +1,277 @@
+name: timesketch-dev
+
+networks:
+ timesketch-dev:
+
+volumes:
+ setup-data:
+ timesketch-data:
+ celery-worker-data:
+ opensearch-data:
+ postgresql-data:
+ redis-data:
+ prometheus-data:
+ notebook-data:
+
+services:
+ setup:
+ image: timesketch-setup:latest
+ build:
+ context: ../../..
+ dockerfile: contrib/docker/dev/timesketch/Dockerfile
+ target: setup
+ args:
+ BASE_IMAGE: "${TIMESKETCH_BASE_IMAGE:?}"
+ TIMESKETCH_USER_NAME: "${TIMESKETCH_USER_NAME:?}"
+ TIMESKETCH_USER_UID: "${TIMESKETCH_USER_UID:?}"
+ TIMESKETCH_USER_GID: "${TIMESKETCH_USER_GID:?}"
+ TIMESKETCH_CONF_DIR: "${TIMESKETCH_CONF_DIR:?}"
+ GIFT_PPA_TRACK: "${GIFT_PPA_TRACK:?}"
+ GIFT_PPA_URL: "${GIFT_PPA_URL:?}"
+ NODE_VERSION: "${NODE_VERSION:?}"
+ NODE_PPA_URL: "${NODE_PPA_URL:?}"
+ NODE_NPMRC: "${NODE_NPMRC?}"
+ YARN_YARNRC: "${YARN_YARNRC?}"
+ PYTHON_PIP_CONF: "${PYTHON_PIP_CONF?}"
+ command: timesketch
+ env_file:
+ - timesketch/timesketch.env
+ volumes:
+ - "setup-data:/usr/local/src/sigma"
+ - "../../../:/usr/local/src/timesketch"
+ - "./timesketch/timesketch.conf:${TIMESKETCH_CONF_DIR:?}/timesketch.conf:ro"
+ - "./timesketch/sigma_rules.txt:${TIMESKETCH_CONF_DIR:?}/sigma_rules.txt:ro"
+ - "../../../data/sigma_config.yaml:${TIMESKETCH_CONF_DIR:?}/sigma_config.yaml:ro"
+ - "../../../data/sigma:${TIMESKETCH_CONF_DIR:?}/sigma:ro"
+ depends_on:
+ postgresql:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+ networks:
+ - timesketch-dev
+
+ celery-worker:
+ image: timesketch-celery-worker:latest
+ build:
+ context: ../../..
+ dockerfile: contrib/docker/dev/timesketch/Dockerfile
+ target: celery-worker
+ args:
+ BASE_IMAGE: "${TIMESKETCH_BASE_IMAGE:?}"
+ TIMESKETCH_USER_NAME: "${TIMESKETCH_USER_NAME:?}"
+ TIMESKETCH_USER_UID: "${TIMESKETCH_USER_UID:?}"
+ TIMESKETCH_USER_GID: "${TIMESKETCH_USER_GID:?}"
+ TIMESKETCH_CONF_DIR: "${TIMESKETCH_CONF_DIR:?}"
+ GIFT_PPA_TRACK: "${GIFT_PPA_TRACK:?}"
+ GIFT_PPA_URL: "${GIFT_PPA_URL:?}"
+ NODE_VERSION: "${NODE_VERSION:?}"
+ NODE_PPA_URL: "${NODE_PPA_URL:?}"
+ NODE_NPMRC: "${NODE_NPMRC?}"
+ YARN_YARNRC: "${YARN_YARNRC?}"
+ PYTHON_PIP_CONF: "${PYTHON_PIP_CONF?}"
+ env_file:
+ - timesketch/timesketch.env
+ volumes:
+ - "../../../:/usr/local/src/timesketch/"
+ - "./timesketch/timesketch.conf:${TIMESKETCH_CONF_DIR:?}/timesketch.conf:ro"
+ - "../../../data/regex_features.yaml:${TIMESKETCH_CONF_DIR:?}/regex_features.yaml:ro"
+ - "../../../data/winevt_features.yaml:${TIMESKETCH_CONF_DIR:?}/winevt_features.yaml:ro"
+ - "../../../data/tags.yaml:${TIMESKETCH_CONF_DIR:?}/tags.yaml:ro"
+ - "../../../data/intelligence_tag_metadata.yaml:${TIMESKETCH_CONF_DIR:?}/intelligence_tag_metadata.yaml:ro"
+ - "../../../data/plaso.mappings:${TIMESKETCH_CONF_DIR:?}/plaso.mappings:ro"
+ - "../../../data/generic.mappings:${TIMESKETCH_CONF_DIR:?}/generic.mappings:ro"
+ - "../../../data/ontology.yaml:${TIMESKETCH_CONF_DIR:?}/ontology.yaml:ro"
+ - "../../../data/data_finder.yaml:${TIMESKETCH_CONF_DIR:?}/data_finder.yaml:ro"
+ - "../../../data/bigquery_matcher.yaml:${TIMESKETCH_CONF_DIR:?}/bigquery_matcher.yaml:ro"
+ - "../../../data/sigma_config.yaml:${TIMESKETCH_CONF_DIR:?}/sigma_config.yaml:ro"
+ - "../../../data/sigma:${TIMESKETCH_CONF_DIR:?}/sigma:ro"
+ - "../../../data/dfiq:${TIMESKETCH_CONF_DIR:?}/dfiq:ro"
+ - "../../../data/context_links.yaml:${TIMESKETCH_CONF_DIR:?}/context_links.yaml:ro"
+ - "../../../data/plaso_formatters.yaml:${TIMESKETCH_CONF_DIR:?}/plaso_formatters.yaml:ro"
+ - "../../../data/nl2q:${TIMESKETCH_CONF_DIR:?}/nl2q:ro"
+ - "../../../data/llm_summarize:${TIMESKETCH_CONF_DIR:?}/llm_summarize:ro"
+ - "timesketch-data:/tmp"
+ - "celery-worker-data:/var/log/timesketch/psort"
+ depends_on:
+ setup:
+ condition: service_completed_successfully
+ opensearch:
+ condition: service_started
+ networks:
+ - timesketch-dev
+
+ gunicorn:
+ image: timesketch-gunicorn:latest
+ build:
+ context: ../../..
+ dockerfile: contrib/docker/dev/timesketch/Dockerfile
+ target: gunicorn
+ args:
+ BASE_IMAGE: "${TIMESKETCH_BASE_IMAGE:?}"
+ TIMESKETCH_USER_NAME: "${TIMESKETCH_USER_NAME:?}"
+ TIMESKETCH_USER_UID: "${TIMESKETCH_USER_UID:?}"
+ TIMESKETCH_USER_GID: "${TIMESKETCH_USER_GID:?}"
+ TIMESKETCH_CONF_DIR: "${TIMESKETCH_CONF_DIR:?}"
+ GIFT_PPA_TRACK: "${GIFT_PPA_TRACK:?}"
+ GIFT_PPA_URL: "${GIFT_PPA_URL:?}"
+ NODE_VERSION: "${NODE_VERSION:?}"
+ NODE_PPA_URL: "${NODE_PPA_URL:?}"
+ NODE_NPMRC: "${NODE_NPMRC?}"
+ YARN_YARNRC: "${YARN_YARNRC?}"
+ PYTHON_PIP_CONF: "${PYTHON_PIP_CONF?}"
+ ports:
+ - name: gunicorn
+ published: "5000"
+ target: 5000
+ - name: metrics
+ published: "8080"
+ target: 8080
+ env_file:
+ - timesketch/timesketch.env
+ volumes:
+ - "../../../:/usr/local/src/timesketch/"
+ - "./timesketch/timesketch.conf:${TIMESKETCH_CONF_DIR:?}/timesketch.conf:ro"
+ - "../../../data/regex_features.yaml:${TIMESKETCH_CONF_DIR:?}/regex_features.yaml:ro"
+ - "../../../data/winevt_features.yaml:${TIMESKETCH_CONF_DIR:?}/winevt_features.yaml:ro"
+ - "../../../data/tags.yaml:${TIMESKETCH_CONF_DIR:?}/tags.yaml:ro"
+ - "../../../data/intelligence_tag_metadata.yaml:${TIMESKETCH_CONF_DIR:?}/intelligence_tag_metadata.yaml:ro"
+ - "../../../data/plaso.mappings:${TIMESKETCH_CONF_DIR:?}/plaso.mappings:ro"
+ - "../../../data/generic.mappings:${TIMESKETCH_CONF_DIR:?}/generic.mappings:ro"
+ - "../../../data/ontology.yaml:${TIMESKETCH_CONF_DIR:?}/ontology.yaml:ro"
+ - "../../../data/data_finder.yaml:${TIMESKETCH_CONF_DIR:?}/data_finder.yaml:ro"
+ - "../../../data/bigquery_matcher.yaml:${TIMESKETCH_CONF_DIR:?}/bigquery_matcher.yaml:ro"
+ - "../../../data/sigma_config.yaml:${TIMESKETCH_CONF_DIR:?}/sigma_config.yaml:ro"
+ - "../../../data/sigma:${TIMESKETCH_CONF_DIR:?}/sigma:ro"
+ - "../../../data/dfiq:${TIMESKETCH_CONF_DIR:?}/dfiq:ro"
+ - "../../../data/context_links.yaml:${TIMESKETCH_CONF_DIR:?}/context_links.yaml:ro"
+ - "../../../data/plaso_formatters.yaml:${TIMESKETCH_CONF_DIR:?}/plaso_formatters.yaml:ro"
+ - "../../../data/nl2q:${TIMESKETCH_CONF_DIR:?}/nl2q:ro"
+ - "../../../data/llm_summarize:${TIMESKETCH_CONF_DIR:?}/llm_summarize:ro"
+ - "timesketch-data:/tmp"
+ depends_on:
+ setup:
+ condition: service_completed_successfully
+ networks:
+ - timesketch-dev
+
+ vue-cli-service:
+ image: timesketch-vue-cli-service:latest
+ build:
+ context: ../../..
+ dockerfile: contrib/docker/dev/timesketch/Dockerfile
+ target: vue-cli-service
+ args:
+ BASE_IMAGE: "${TIMESKETCH_BASE_IMAGE:?}"
+ TIMESKETCH_USER_NAME: "${TIMESKETCH_USER_NAME:?}"
+ TIMESKETCH_USER_UID: "${TIMESKETCH_USER_UID:?}"
+ TIMESKETCH_USER_GID: "${TIMESKETCH_USER_GID:?}"
+ TIMESKETCH_CONF_DIR: "${TIMESKETCH_CONF_DIR:?}"
+ GIFT_PPA_TRACK: "${GIFT_PPA_TRACK:?}"
+ GIFT_PPA_URL: "${GIFT_PPA_URL:?}"
+ NODE_VERSION: "${NODE_VERSION:?}"
+ NODE_PPA_URL: "${NODE_PPA_URL:?}"
+ NODE_NPMRC: "${NODE_NPMRC?}"
+ YARN_YARNRC: "${YARN_YARNRC?}"
+ PYTHON_PIP_CONF: "${PYTHON_PIP_CONF?}"
+ FRONTEND_VERSION: "${FRONTEND_VERSION?}"
+ ports:
+ - name: vue-cli-service
+ published: "5001"
+ target: 5001
+ env_file:
+ - timesketch/timesketch.env
+ volumes:
+ - "../../../:/usr/local/src/timesketch/"
+ depends_on:
+ gunicorn:
+ condition: service_healthy
+ networks:
+ - timesketch-dev
+
+ opensearch:
+ image: timesketch-opensearch:latest
+ build:
+ context: opensearch
+ env_file:
+ - opensearch/opensearch.env
+ ports:
+ - name: api
+ published: "9200"
+ target: 9200
+ volumes:
+ - "opensearch-data:/usr/share/opensearch/data"
+ networks:
+ - timesketch-dev
+ ulimits:
+ memlock:
+ soft: -1
+ hard: -1
+ nofile:
+ soft: 65536
+ hard: 65536
+
+ postgresql:
+ image: timesketch-postgresql:latest
+ build:
+ context: postgresql
+ env_file:
+ - postgresql/postgresql.env
+ ports:
+ - name: database
+ published: "5432"
+ target: 5432
+ volumes:
+ - "postgresql-data:/var/lib/postgresql/data"
+ networks:
+ - timesketch-dev
+
+ redis:
+ image: timesketch-redis
+ build:
+ context: redis
+ ports:
+ - name: database
+ published: "6379"
+ target: 6379
+ volumes:
+ - "redis-data:/data"
+ networks:
+ - timesketch-dev
+
+ prometheus:
+ image: prom/prometheus:v2.24.1
+ volumes:
+ - "./prometheus:/etc/prometheus:ro"
+ - "prometheus-data:/prometheus"
+ ports:
+ - name: database
+ published: "9090"
+ target: 9090
+
+ command: --config.file=/etc/prometheus/prometheus.yml
+ depends_on:
+ gunicorn:
+ condition: service_healthy
+ networks:
+ - timesketch-dev
+
+ notebook:
+ image: timesketch-notebook:latest
+ build:
+ context: ../../..
+ dockerfile: contrib/docker/dev/notebook/Dockerfile
+ args:
+ PYTHON_PIP_CONF: "${PYTHON_PIP_CONF?}"
+ ports:
+ - name: web
+ published: "8844"
+ target: 8844
+ volumes:
+ - "../../../:/usr/local/src/timesketch/:ro"
+ - "notebook-data:/usr/local/src/picadata"
+ depends_on:
+ opensearch:
+ condition: service_started
+ networks:
+ - timesketch-dev
diff --git a/contrib/docker/dev/notebook/10-widgets.py b/contrib/docker/dev/notebook/10-widgets.py
new file mode 100644
index 0000000000..baf47efd43
--- /dev/null
+++ b/contrib/docker/dev/notebook/10-widgets.py
@@ -0,0 +1,108 @@
+# Copyright 2020 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Few functions that take advantage of ipywidgets for jupyter.
+
+These are temporary functions until they have been generalized
+and implemented in picatrix.
+"""
+
+# pylint: disable=undefined-variable
+# pylint: disable=import-error
+from picatrix.lib import utils
+
+import ipydatetime
+import ipywidgets as widgets
+import pytz
+
+from IPython.display import Markdown
+
+from timesketch_api_client import search
+
+
+# TODO: Generalize and move to picatrix.
+def generate_connect_button(click_function = None):
+ """Creates a button and an int form to connect to timesketch."""
+ button = widgets.Button(description='Connect to sketch')
+ output = widgets.Output()
+
+ sketch_field = widgets.IntText(
+ value=1,
+ description='Sketch ID:',
+ disabled=False
+ )
+
+ display(Markdown('## Connect to a sketch'))
+ display(Markdown('Select a sketch to connect to.'))
+ display(sketch_field, button, output)
+
+ def _click_function(_):
+ with output:
+ timesketch_set_active_sketch_func(str(sketch_field.value))
+ sketch = timesketch_get_sketch_func()
+ try:
+ display(Markdown(
+ f'Connected to sketch: {sketch.id}: **{sketch.name}**'))
+ valid = widgets.Valid(value=True, description='Connected')
+ display(valid)
+ utils.ipython_bind_global('sketch', sketch)
+ display(Markdown('Sketch object saved to **sketch**'))
+ except KeyError:
+ display(Markdown('**Unable to connect to sketch.**'))
+ invalid = widgets.Valid(value=False, description='Connected')
+ display(invalid)
+
+ if click_function:
+ button.on_click(click_function)
+ else:
+ button.on_click(_click_function)
+
+
+def generate_query_button():
+ """Generates a button and form to query Timesketch data."""
+ button = widgets.Button(description='Query Timesketch')
+ query_string_form = widgets.Text(
+ value='*',
+ placeholder='Type something',
+ description='Query String:',
+ disabled=False
+ )
+
+ start_time_form = ipydatetime.DatetimePicker(tzinfo=pytz.utc)
+ end_time_form = ipydatetime.DatetimePicker(tzinfo=pytz.utc)
+
+ display(Markdown('## Query A Sketch'))
+ display(query_string_form)
+ display(Markdown('Start time: '), start_time_form)
+ display(Markdown('End time: '), end_time_form)
+ display(button)
+
+ def _click_function(_):
+ sketch = timesketch_get_sketch_func()
+ search_obj = search.Search(sketch)
+ if start_time_form.value and end_time_form.value:
+ date_chip = search.DateRangeChip()
+ date_chip.start_time = start_time_form.value.strftime(
+ '%Y-%m-%dT%H:%M:%S')
+ date_chip.end_time = end_time_form.value.strftime(
+ '%Y-%m-%dT%H:%M:%S')
+ search_obj.add_chip(date_chip)
+ search_obj.query_string = query_string_form.value
+ display(Markdown(
+ f'Query **executed** - returned: {len(search_obj.table)} '
+ 'records'))
+ utils.ipython_bind_global('search_obj', search_obj)
+ display(Markdown(
+ 'Results are stored in the **search_obj**'))
+
+ button.on_click(_click_function)
diff --git a/contrib/docker/dev/notebook/Dockerfile b/contrib/docker/dev/notebook/Dockerfile
new file mode 100644
index 0000000000..028f534074
--- /dev/null
+++ b/contrib/docker/dev/notebook/Dockerfile
@@ -0,0 +1,42 @@
+FROM us-docker.pkg.dev/osdfir-registry/picatrix/picatrix:latest
+
+USER picatrix
+
+ENV VIRTUAL_ENV="/home/picatrix/picenv"
+ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
+ENV JUPYTER_PORT="8844"
+
+RUN mkdir -p \
+ "/home/picatrix/.local/share/jupyter/nbextensions/snippets/" \
+ "/home/picatrix/.jupyter/custom"
+
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/timesketchrc", "/home/picatrix/.timesketchrc"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/timesketch_token", "/home/picatrix/.timesketch.token"]
+COPY --chown=1000:1000 [".", "/home/picatrix/code"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/snippets.json", "/home/picatrix/.local/share/jupyter/nbextensions/snippets/snippets.json"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/10-widgets.py", "/home/picatrix/.ipython/profile_default/startup/10-widgets.py"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/logo.png", "/home/picatrix/.jupyter/custom/logo.png"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/custom.css", "/home/picatrix/.jupyter/custom/custom.css"]
+COPY --chown=1000:1000 ["contrib/docker/dev/notebook/timesketch", "/home/picatrix/picenv/share/jupyter/nbextensions/timesketch"]
+
+ARG PYTHON_PIP_CONF=""
+RUN if [ -n "${PYTHON_PIP_CONF}" ]; then \
+ mkdir -p ~/.config/pip; \
+ env echo -e "${PYTHON_PIP_CONF}" > ~/.config/pip/pip.conf; \
+ fi
+
+RUN sed -i -e "s/c.NotebookApp.token = 'picatrix'/c.NotebookApp.token = 'timesketch'/g" /home/picatrix/.jupyter/jupyter_notebook_config.py \
+ && sed -i -e "s/c.NotebookApp.port = 8899/c.NotebookApp.port = ${JUPYTER_PORT}/g" /home/picatrix/.jupyter/jupyter_notebook_config.py \
+ && pip install -e /home/picatrix/code/api_client/python \
+ && pip install -e /home/picatrix/code/importer_client/python/ \
+ && jupyter nbextension enable snippets/main \
+ && jupyter nbextension enable timesketch/main \
+ && pip install ipydatetime \
+ && jupyter nbextension install --user --py ipydatetime \
+ && jupyter nbextension enable --user --py ipydatetime
+
+WORKDIR /usr/local/src/picadata
+EXPOSE 8844
+
+# Run jupyter.
+ENTRYPOINT ["jupyter", "notebook"]
diff --git a/contrib/docker/dev/notebook/custom.css b/contrib/docker/dev/notebook/custom.css
new file mode 100644
index 0000000000..5b2a19ae6c
--- /dev/null
+++ b/contrib/docker/dev/notebook/custom.css
@@ -0,0 +1,11 @@
+#ipython_notebook img{
+ display:block;
+ background: url("logo.png") no-repeat;
+ background-size: contain;
+ width: 83px;
+ height: 35px;
+ padding-left: 90px;
+ padding-bottom: 50px;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
diff --git a/contrib/docker/dev/notebook/logo.png b/contrib/docker/dev/notebook/logo.png
new file mode 100644
index 0000000000..2775b207c3
Binary files /dev/null and b/contrib/docker/dev/notebook/logo.png differ
diff --git a/contrib/docker/dev/notebook/snippets.json b/contrib/docker/dev/notebook/snippets.json
new file mode 100644
index 0000000000..8b95e175bf
--- /dev/null
+++ b/contrib/docker/dev/notebook/snippets.json
@@ -0,0 +1,81 @@
+{
+ "snippets": [
+ {
+ "name": "Example initial import",
+ "code" : [
+ "from timesketch_api_client import search",
+ "from picatrix.lib import state as state_lib",
+ "import altair as alt",
+ "import numpy as np",
+ "import pandas as pd",
+ "state = state_lib.state()"
+ ]
+ },
+ {
+ "name": "Select a sketch using magics",
+ "code" : [
+ "%timesketch_set_active_sketch 1"
+ ]
+ },
+ {
+ "name" : "Select a sketch using a button and a form",
+ "code" : [
+ "# Execute cell in order to generate button.",
+ "generate_connect_button()"
+ ]
+ },
+ {
+ "name" : "Query a sketch using a button and a form",
+ "code" : [
+ "# Execute cell in order to generate button.",
+ "generate_query_button()"
+ ]
+ },
+ {
+ "name": "Search with date range filter",
+ "code" : [
+ "sketch = %timesketch_get_sketch",
+ "search_obj = search.Search(sketch)",
+ "date_chip = search.DateRangeChip()",
+ "date_chip.start_time = '2020-12-24T18:00:00'",
+ "date_chip.end_time = '2020-12-31T23:59:59'",
+ "search_obj.add_chip(date_chip)",
+ "search_obj.query_string = '*'",
+ "df = search_obj.table"
+ ]
+ },
+ {
+ "name": "Run an example aggregation.",
+ "code" : [
+ "sketch = %timesketch_get_sketch",
+ "params = {",
+ " 'field': 'domain',",
+ " 'limit': 10,",
+ " 'supported_charts': 'hbarchart',",
+ " 'chart_title': 'Top 10 Domains'",
+ "}",
+ " ",
+ "agg = sketch.run_aggregator(",
+ " aggregator_name='field_bucket', aggregator_parameters=params)",
+ " ",
+ "# You can get the aggregation data as a chart or a dataframe",
+ "agg.chart",
+ " ",
+ "# Enable this to get the results as a table",
+ "# agg.table",
+ " ",
+ "# To save the aggregation you need to create a name and a title.",
+ "# agg.name = 'Top 10 Domains'",
+ "# agg.title = 'Top 10 Domains'",
+ "# agg.save()"
+ ]
+ },
+ {
+ "name" : "Get Timesketch client object",
+ "code" : [
+ "# Execute cell in order to get a Timesketch API client instance.",
+ "client = state.get_from_cache('timesketch_client')"
+ ]
+ },
+ ]
+}
diff --git a/contrib/docker/dev/notebook/timesketch/README.md b/contrib/docker/dev/notebook/timesketch/README.md
new file mode 100644
index 0000000000..69f3fa3c04
--- /dev/null
+++ b/contrib/docker/dev/notebook/timesketch/README.md
@@ -0,0 +1,7 @@
+timesketch
+=========
+
+This extension adds few default cells to each new notebook created in this
+Timesketch container.
+
+This is a simple extension with a simple purpose.
diff --git a/contrib/docker/dev/notebook/timesketch/main.js b/contrib/docker/dev/notebook/timesketch/main.js
new file mode 100644
index 0000000000..a72a44aac5
--- /dev/null
+++ b/contrib/docker/dev/notebook/timesketch/main.js
@@ -0,0 +1,46 @@
+// Code cell snippets
+
+define([
+ 'base/js/namespace',
+], function(
+ Jupyter,
+) {
+ "use strict";
+
+ // will be called when the nbextension is loaded
+ function load_extension() {
+ var ncells = Jupyter.notebook.ncells();
+ if (ncells > 1) {
+ return true;
+ }
+
+ var new_cell = Jupyter.notebook.insert_cell_above('markdown', 0);
+ new_cell.set_text('# Timesketch Notebook\nThis is a base notebook for connecting to a dev instance of Timesketch.\n**Remember to rename the notebook**.');
+ new_cell.render();
+ new_cell.focus_cell();
+
+ var new_cell = Jupyter.notebook.insert_cell_below('markdown');
+ new_cell.set_text('*If you want to query data you can use the snippets menu, or create a search obj, and to display a table use `display_table(search_obj.table)` or `display_table(data_frame)`*\n\nTo see a list of available helper functions run `%picatrixhelpers` in a cell, or to see a list of functions/magics use `%picatrixmagics`.');
+ new_cell.render();
+
+ var select_cell = Jupyter.notebook.insert_cell_below('code');
+ select_cell.set_text('generate_connect_button()');
+
+ var text_cell = Jupyter.notebook.insert_cell_below('markdown');
+ text_cell.set_text('## Select a Sketch.\nNow it is time to select a sketch to use, first execute the cell and then change the ID of the sketch to the one you want, and press the button.');
+ text_cell.render();
+
+ var import_cell = Jupyter.notebook.insert_cell_below('code');
+ import_cell.set_text('from timesketch_api_client import search\nfrom picatrix.lib import state as state_lib\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\nstate = state_lib.state()');
+
+ var new_cell = Jupyter.notebook.insert_cell_below('markdown');
+ new_cell.set_text('## Import\nTo start a notebook we import few base libraries.\nExecute the cell below by pressing the play button or using "shift + enter"');
+ new_cell.render();
+ import_cell.focus_cell();
+ };
+
+ // return public methods
+ return {
+ load_ipython_extension : load_extension
+ };
+});
diff --git a/contrib/docker/dev/notebook/timesketch/timesketch.yaml b/contrib/docker/dev/notebook/timesketch/timesketch.yaml
new file mode 100644
index 0000000000..619fd93499
--- /dev/null
+++ b/contrib/docker/dev/notebook/timesketch/timesketch.yaml
@@ -0,0 +1,6 @@
+Type: IPython Notebook Extension
+Compatibility: 4.x, 5.x, 6.x
+Name: Timesketch
+Main: main.js
+Link: README.md
+Description: Add few default cells to a new notebook.
diff --git a/contrib/docker/dev/notebook/timesketch_token b/contrib/docker/dev/notebook/timesketch_token
new file mode 100644
index 0000000000..43a2dff189
--- /dev/null
+++ b/contrib/docker/dev/notebook/timesketch_token
@@ -0,0 +1,2 @@
+^_~&lę/ŕ
+słgAAAAABfy0n4zomCWiuIuz9XGnC5Co9x1omFCNbZmpOowgqdhCzzJrzvqM6A0qhYcK4k9iILVW9CWvmn-SS0IBBnsfv-un1Amh9Ip9j4zNZNST-DBOc8N9piJ0BCXck6bVy48J_g4BfI8idmTBzXi6ehKfX4oD4Bbg==
\ No newline at end of file
diff --git a/contrib/docker/dev/notebook/timesketchrc b/contrib/docker/dev/notebook/timesketchrc
new file mode 100644
index 0000000000..b5aa567b76
--- /dev/null
+++ b/contrib/docker/dev/notebook/timesketchrc
@@ -0,0 +1,8 @@
+[timesketch]
+host_uri = http://timesketch:5000
+username = dev
+verify = True
+client_id =
+client_secret =
+auth_mode = userpass
+cred_key = oqyymP-6FS9IY-Id-5nieXMzIajJ7NWx9Ndm_r7K0xg=
diff --git a/contrib/docker/dev/opensearch.env b/contrib/docker/dev/opensearch.env
new file mode 100644
index 0000000000..3176c6728d
--- /dev/null
+++ b/contrib/docker/dev/opensearch.env
@@ -0,0 +1,6 @@
+discovery.type="single-node"
+bootstrap.memory_lock="true"
+network.host="0.0.0.0"
+OPENSEARCH_JAVA_OPTS="-Xms2g -Xmx2g"
+DISABLE_INSTALL_DEMO_CONFIG="true"
+DISABLE_SECURITY_PLUGIN="true" # TODO: Enable when we have migrated the python client to Opensearch as well.
diff --git a/contrib/docker/dev/opensearch/Dockerfile b/contrib/docker/dev/opensearch/Dockerfile
new file mode 100644
index 0000000000..18ccc0ddf9
--- /dev/null
+++ b/contrib/docker/dev/opensearch/Dockerfile
@@ -0,0 +1,5 @@
+FROM opensearchproject/opensearch:2.15.0
+
+HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --start-interval=5s --retries=1 \
+ CMD ["curl", "-f", "-s", "http://localhost:9200/_cluster/health"]
+
diff --git a/contrib/docker/dev/opensearch/opensearch.env b/contrib/docker/dev/opensearch/opensearch.env
new file mode 100644
index 0000000000..3176c6728d
--- /dev/null
+++ b/contrib/docker/dev/opensearch/opensearch.env
@@ -0,0 +1,6 @@
+discovery.type="single-node"
+bootstrap.memory_lock="true"
+network.host="0.0.0.0"
+OPENSEARCH_JAVA_OPTS="-Xms2g -Xmx2g"
+DISABLE_INSTALL_DEMO_CONFIG="true"
+DISABLE_SECURITY_PLUGIN="true" # TODO: Enable when we have migrated the python client to Opensearch as well.
diff --git a/contrib/docker/dev/postgresql/Dockerfile b/contrib/docker/dev/postgresql/Dockerfile
new file mode 100644
index 0000000000..c582354842
--- /dev/null
+++ b/contrib/docker/dev/postgresql/Dockerfile
@@ -0,0 +1,6 @@
+FROM postgres:13.1-alpine
+
+COPY --chown=root:root --chmod=755 ["docker-healthcheck.sh", "/usr/local/bin/"]
+
+HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --start-interval=2s --retries=1 \
+ CMD ["/usr/local/bin/docker-healthcheck.sh"]
diff --git a/contrib/docker/dev/postgresql/docker-healthcheck.sh b/contrib/docker/dev/postgresql/docker-healthcheck.sh
new file mode 100755
index 0000000000..23da820457
--- /dev/null
+++ b/contrib/docker/dev/postgresql/docker-healthcheck.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+exec pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}"
diff --git a/contrib/docker/dev/postgresql/postgresql.env b/contrib/docker/dev/postgresql/postgresql.env
new file mode 100644
index 0000000000..40cc20cbfb
--- /dev/null
+++ b/contrib/docker/dev/postgresql/postgresql.env
@@ -0,0 +1,3 @@
+POSTGRES_USER="${POSTGRES_USER?-}"
+POSTGRES_PASSWORD="${POSTGRES_PASSWORD?-}"
+POSTGRES_DB="${POSTGRES_DB?-}"
diff --git a/contrib/docker/dev/prometheus/prometheus.yml b/contrib/docker/dev/prometheus/prometheus.yml
new file mode 100644
index 0000000000..8f92f67fe9
--- /dev/null
+++ b/contrib/docker/dev/prometheus/prometheus.yml
@@ -0,0 +1,8 @@
+global:
+ scrape_interval: 10s
+ external_labels:
+ monitor: "timesketch"
+scrape_configs:
+ - job_name: "timesketch"
+ static_configs:
+ - targets: ["timesketch:8080"]
diff --git a/contrib/docker/dev/redis/Dockerfile b/contrib/docker/dev/redis/Dockerfile
new file mode 100644
index 0000000000..f981d3ff8c
--- /dev/null
+++ b/contrib/docker/dev/redis/Dockerfile
@@ -0,0 +1,4 @@
+FROM redis:7.2.11-alpine
+
+HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --start-interval=1s --retries=1 \
+ CMD ["redis-cli", "ping"]
diff --git a/contrib/docker/dev/redis/redis.env b/contrib/docker/dev/redis/redis.env
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contrib/docker/dev/timesketch/Dockerfile b/contrib/docker/dev/timesketch/Dockerfile
new file mode 100644
index 0000000000..e0a4400919
--- /dev/null
+++ b/contrib/docker/dev/timesketch/Dockerfile
@@ -0,0 +1,183 @@
+# Use the official Docker Hub Ubuntu base image
+ARG BASE_IMAGE="ubuntu:24.04"
+FROM ${BASE_IMAGE} AS common
+
+USER root
+
+ARG TIMESKETCH_USER_NAME="ubuntu"
+ARG TIMESKETCH_USER_UID="1000"
+ARG TIMESKETCH_USER_GID="1000"
+ARG TIMESKETCH_CONF_DIR="/etc/timesketch"
+RUN if id -u "${TIMESKETCH_USER_UID}" &>/dev/null; then \
+ echo "User with UID ${TIMESKETCH_USER_UID} already exists."; \
+ else \
+ echo "Creating user ${USER_NAME} (${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID})..."; \
+ if getent group "${TIMESKETCH_USER_GID}" >/dev/null; then \
+ echo "Group with GID ${TIMESKETCH_USER_GID} already exists."; \
+ else \
+ echo "Creating group ${TIMESKETCH_USER_NAME} with GID ${TIMESKETCH_USER_GID}..."; \
+ groupadd -g "${TIMESKETCH_USER_GID}" "${TIMESKETCH_USER_NAME}"; \
+ fi; \
+ useradd -m -u "${TIMESKETCH_USER_UID}" -g "${TIMESKETCH_USER_GID}" -s /bin/bash "${TIMESKETCH_USER_NAME}"; \
+ fi \
+ && for d in "${TIMESKETCH_CONF_DIR}" "/usr/local/src/sigma"; do \
+ mkdir -p "${d}" \
+ && chown "${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" "${d}"; \
+ done \
+ && echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+ software-properties-common \
+ apt-transport-https \
+ apt-utils \
+ ca-certificates \
+ curl \
+ git \
+ gpg-agent \
+ python3-dev \
+ python3-pip \
+ python3-wheel \
+ python3-setuptools \
+ python3-psycopg2 \
+ python3-venv \
+ tzdata \
+ nano \
+ vim \
+ python3-venv \
+ && rm -rf /var/lib/apt/lists/*
+
+FROM common AS common-python
+
+# Install Plaso
+ARG GIFT_PPA_TRACK="stable"
+ARG GIFT_PPA_URL="http://ppa.launchpad.net/gift/${GIFT_PPA_TRACK}/ubuntu"
+RUN set -eux \
+ && DIST="$(lsb_release -cs)" \
+ && KEY_ID="$(curl -sS "${GIFT_PPA_URL}/dists/${DIST}/Release.gpg" | gpg --list-packets | grep -oE 'keyid [0-9A-F]+' | cut -d ' ' -f 2)" \
+ && curl -sSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x${KEY_ID}" | \
+ gpg --dearmor -o /usr/share/keyrings/gift.gpg \
+ && echo "deb [signed-by=/usr/share/keyrings/gift.gpg] ${GIFT_PPA_URL} ${DIST} main" > /etc/apt/sources.list.d/gift.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+ plaso-tools \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /root/.gnupg
+
+# Fix for broken PPA dependency in Ubuntu 24.04: Plaso needs the 'events'
+# library for its opensearch output module.
+RUN pip3 install --break-system-packages events
+
+ARG PYTHON_PIP_CONF=""
+RUN if [ -n "${PYTHON_PIP_CONF}" ]; then \
+ mkdir -p /root/.config/pip /home/${TIMESKETCH_USER_NAME}/.config/pip; \
+ env echo -e "${PYTHON_PIP_CONF}" > /root/.config/pip/pip.conf; \
+ cp /root/.config/pip/pip.conf /home/${TIMESKETCH_USER_NAME}/.config/pip/pip.conf; \
+ chown -R "${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" /home/${TIMESKETCH_USER_NAME}/.config; \
+ fi
+
+USER "${TIMESKETCH_USER_NAME}"
+
+# Install dependencies for Timesketch in a virtual environment
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["api_client", "/usr/local/src/timesketch/api_client/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["cli_client", "/usr/local/src/timesketch/cli_client/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["end_to_end_tests", "/usr/local/src/timesketch/end_to_end_tests/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["importer_client", "/usr/local/src/timesketch/importer_client/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["timesketch", "/usr/local/src/timesketch/timesketch/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["tests", "/usr/local/src/timesketch/tests/"]
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" [ \
+ "requirements.txt", \
+ "setup.py", \
+ "test_requirements.txt", \
+ "/usr/local/src/timesketch/" \
+]
+
+RUN python3 -m venv --upgrade-deps --system-site-packages "${HOME}/venv" \
+ && . "${HOME}/venv/bin/activate" \
+ && pip install --no-cache-dir \
+ -r /usr/local/src/timesketch/requirements.txt \
+ -r /usr/local/src/timesketch/test_requirements.txt \
+ && pip install -e /usr/local/src/timesketch
+
+# Update the PATH to include the virtual environment
+ENV PATH="/home/${TIMESKETCH_USER_NAME}/venv/bin:${PATH}"
+ENV TIMESKETCH_CONF_DIR="${TIMESKETCH_CONF_DIR}"
+
+FROM common-python AS setup
+
+COPY --chown=root:root --chmod=755 ["contrib/docker/dev/timesketch/setup-docker-entrypoint.sh", "/usr/local/bin/docker-entrypoint.sh"]
+ENTRYPOINT ["docker-entrypoint.sh"]
+
+FROM common-python AS celery-worker
+
+USER root
+
+ARG PLASO_LOG_DIR="/var/log/timesketch/psort"
+RUN mkdir -p "${PLASO_LOG_DIR}" \
+ && chown "${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" "${PLASO_LOG_DIR}"
+
+USER "${TIMESKETCH_USER_NAME}"
+
+COPY --chown=root:root --chmod=755 ["contrib/docker/dev/timesketch/celery-worker-docker-entrypoint.sh", "/usr/local/bin/docker-entrypoint.sh"]
+ENTRYPOINT ["docker-entrypoint.sh"]
+
+FROM common-python AS gunicorn
+
+COPY --chown=root:root --chmod=755 ["contrib/docker/dev/timesketch/gunicorn-docker-entrypoint.sh", "/usr/local/bin/docker-entrypoint.sh"]
+
+HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --start-interval=2s --retries=1 \
+ CMD ["curl", "-f", "-s", "http://localhost:5000/"]
+
+ENTRYPOINT ["docker-entrypoint.sh"]
+
+FROM common AS vue-cli-service
+
+USER root
+
+# Install NodeJS for frontend development
+ARG NODE_VERSION="20.x"
+ARG NODE_PPA_URL="https://deb.nodesource.com/node_${NODE_VERSION}"
+RUN set -eux \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
+ gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \
+ && echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] ${NODE_PPA_URL} nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+ nodejs \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /root/.gnupg
+
+ARG TIMESKETCH_USER_NAME="timesketch"
+ARG TIMESKETCH_USER_UID="1000"
+ARG TIMESKETCH_USER_GID="1000"
+ARG NODE_NPMRC=""
+RUN if [ -n "${NODE_NPMRC}" ]; then \
+ env echo -e "${NODE_NPMRC}" > /root/.npmrc; \
+ cp /root/.npmrc /home/${TIMESKETCH_USER_NAME}/.npmrc; \
+ chown "${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" /home/${TIMESKETCH_USER_NAME}/.npmrc; \
+ fi
+
+ARG YARN_YARNRC=""
+RUN if [ -n "${YARN_YARNRC}" ]; then \
+ env echo -e "${YARN_YARNRC}" > /root/.yarnrc; \
+ cp /root/.yarnrc /home/${TIMESKETCH_USER_NAME}/.yarnrc; \
+ chown "${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" /home/${TIMESKETCH_USER_NAME}/.yarnrc; \
+ fi
+
+# Install Yarn for frontend development
+RUN npm install --global yarn
+
+USER "${TIMESKETCH_USER_NAME}"
+
+COPY --chown="${TIMESKETCH_USER_UID}:${TIMESKETCH_USER_GID}" ["timesketch", "/usr/local/src/timesketch/timesketch/"]
+
+ARG FRONTEND_VERSION="frontend-ng"
+ENV FRONTEND_VERSION="${FRONTEND_VERSION}"
+RUN if ! yarn --cwd="/usr/local/src/timesketch/timesketch/${FRONTEND_VERSION}" install; then \
+ yarn --cwd="/usr/local/src/timesketch/timesketch/${FRONTEND_VERSION}" install --no-lockfile; \
+fi
+
+COPY --chown=root:root --chmod=755 [ \
+ "contrib/docker/dev/timesketch/vue-cli-service-docker-entrypoint.sh", \
+ "/usr/local/bin/docker-entrypoint.sh" \
+]
+ENTRYPOINT ["docker-entrypoint.sh"]
diff --git a/contrib/docker/dev/timesketch/celery-worker-docker-entrypoint.sh b/contrib/docker/dev/timesketch/celery-worker-docker-entrypoint.sh
new file mode 100644
index 0000000000..2799317407
--- /dev/null
+++ b/contrib/docker/dev/timesketch/celery-worker-docker-entrypoint.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+exec celery \
+ -A timesketch.lib.tasks \
+ worker \
+ --loglevel debug
diff --git a/contrib/docker/dev/timesketch/gunicorn-docker-entrypoint.sh b/contrib/docker/dev/timesketch/gunicorn-docker-entrypoint.sh
new file mode 100644
index 0000000000..bc5b49c85a
--- /dev/null
+++ b/contrib/docker/dev/timesketch/gunicorn-docker-entrypoint.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+
+exec gunicorn \
+ --reload \
+ -b 0.0.0.0:5000 \
+ --log-file - \
+ --timeout 600 \
+ -c /usr/local/src/timesketch/data/gunicorn_config.py \
+ timesketch.wsgi:application
diff --git a/contrib/docker/dev/timesketch/setup-docker-entrypoint.sh b/contrib/docker/dev/timesketch/setup-docker-entrypoint.sh
new file mode 100644
index 0000000000..a71c94a1c2
--- /dev/null
+++ b/contrib/docker/dev/timesketch/setup-docker-entrypoint.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Add web user
+if ! tsctl list-users | grep -q "${TIMESKETCH_USER}"; then
+ tsctl create-user --password "${TIMESKETCH_PASSWORD}" "${TIMESKETCH_USER}"
+fi
+
+# Add Sigma rules
+if [ -d "/usr/local/src/sigma/.git" ]; then
+ git -C /usr/local/src/sigma fetch --depth=1
+ git -C /usr/local/src/sigma reset --hard "$(git -C /usr/local/src/sigma rev-parse --abbrev-ref --symbolic-full-name @{u})"
+else
+ git clone --depth 1 https://github.com/SigmaHQ/sigma /usr/local/src/sigma
+fi
+
+aggregated_rules_dir="$(mktemp -d)"
+
+# Create symbolic links to the rule files specified in sigma_rules.txt
+while IFS= read -r rule_file_path; do
+ if [ -f "${rule_file_path}" ]; then
+ ln -s "${rule_file_path}" "${aggregated_rules_dir}/"
+ else
+ echo "Skipping non existing Sigma rule: ${rule_file_path}"
+ fi
+done < "${TIMESKETCH_CONF_DIR}/sigma_rules.txt"
+
+# Loading all sigma rules at once
+tsctl import-sigma-rules "${aggregated_rules_dir}"
+rm -rf "${aggregated_rules_dir}"
diff --git a/contrib/docker/dev/timesketch/sigma_rules.txt b/contrib/docker/dev/timesketch/sigma_rules.txt
new file mode 100644
index 0000000000..699bc0fff3
--- /dev/null
+++ b/contrib/docker/dev/timesketch/sigma_rules.txt
@@ -0,0 +1,25 @@
+/usr/local/src/sigma/rules/application/rpc_firewall/rpc_firewall_sharphound_recon_sessions.yml
+/usr/local/src/sigma/rules/application/sql/app_sqlinjection_errors.yml
+/usr/local/src/sigma/other/godmode_sigma_rule.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_icmp_exfiltration.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_clip.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_obfuscated_iex.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_stdin.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_var.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_compress.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_rundll.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_stdin.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_use_clip.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_use_mhsta.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_use_rundll32.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_invoke_obfuscation_via_var.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_nishang_malicious_commandlets.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_powerview_malicious_commandlets.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_shellintel_malicious_commandlets.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_malicious_keywords.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_susp_keywords.yml
+/usr/local/src/sigma/rules/windows/powershell/powershell_script/posh_ps_susp_ssl_keyword.yml
+/usr/local/src/sigma/deprecated/windows/win_defender_disabled.yml
+/usr/local/src/sigma/rules/windows/builtin/windefend/win_defender_tamper_protection_trigger.yml
+/usr/local/src/sigma/rules/windows/builtin/windefend/win_defender_history_delete.yml
+/usr/local/src/sigma/rules/windows/builtin/windefend/win_defender_threat.yml
diff --git a/contrib/docker/dev/timesketch/timesketch.conf b/contrib/docker/dev/timesketch/timesketch.conf
new file mode 100644
index 0000000000..4e84fde138
--- /dev/null
+++ b/contrib/docker/dev/timesketch/timesketch.conf
@@ -0,0 +1,492 @@
+# Timesketch configuration
+import os
+
+# Show debug information.
+# Note: It is a security risk to have this enabled in production.
+DEBUG = False
+
+# Enable performance profiling for all backend requests.
+# Profiling data will be written as .prof files to a `profiles/` directory
+# in either the project root (dev) or /var/log/timesketch/ (release).
+ENABLE_PROFILING = False
+
+# Key for signing cookies and for CSRF protection.
+#
+# This should be a unique random string. Don't share this with anyone.
+# To generate a key, you can for example use openssl:
+# $ openssl rand -base64 32
+SECRET_KEY = os.environ["SECRET_KEY"]
+
+# Setup the database.
+#
+# For more options, see the official documentation:
+# https://pythonhosted.org/Flask-SQLAlchemy/config.html
+# By default sqlite is used.
+#
+# NOTE: SQLite should only be used in development. Use PostgreSQL or MySQL in
+# production.
+SQLALCHEMY_DATABASE_URI = "postgresql://{USER}:{PASSWORD}@postgresql/{DATABASE}".format(
+ USER=os.environ['POSTGRES_USER'],
+ PASSWORD=os.environ['POSTGRES_PASSWORD'],
+ DATABASE=os.environ['POSTGRES_DB']
+)
+SQLALCHEMY_ENGINE_OPTIONS = {}
+
+# Configure where your OpenSearch server is located.
+#
+# Make sure that the OpenSearch server is properly secured and not accessible
+# from the internet. See the following link for more information:
+# https://opensearch.org/docs/latest/getting-started/security/
+OPENSEARCH_HOSTS = [{"host": "opensearch", "port": 9200}]
+OPENSEARCH_USER = None
+OPENSEARCH_PASSWORD = None
+OPENSEARCH_SSL = False
+OPENSEARCH_VERIFY_CERTS = True
+OPENSEARCH_CA_CERTS = None
+OPENSEARCH_TIMEOUT = 10
+OPENSEARCH_FLUSH_INTERVAL = 5000
+OPENSEARCH_INDEX_WAIT_TIMEOUT = 10
+OPENSEARCH_MINIMUM_HEALTH = 'yellow'
+# Be careful when increasing the upper limit since this will impact your
+# OpenSearch clusters performance and storage requirements!
+OPENSEARCH_MAPPING_BUFFER = 0.1
+OPENSEARCH_MAPPING_UPPER_LIMIT = 1000
+
+# Define what labels should be defined that make it so that a sketch and
+# timelines will not be deleted. This can be used to add a list of different
+# labels that ensure that a sketch and it's associated timelines cannot be
+# deleted.
+LABELS_TO_PREVENT_DELETION = ['protected', 'preserved']
+
+# Number of seconds before a timeout occurs in bulk operations in the
+# OpenSearch client.
+TIMEOUT_FOR_EVENT_IMPORT = 180
+
+# Location for the configuration file of the data finder.
+DATA_FINDER_PATH = f'{os.environ["TIMESKETCH_CONF_DIR"]}/data_finder.yaml'
+
+#-------------------------------------------------------------------------------
+# Single Sign On (SSO) configuration.
+
+# Your web server can handle authentication for you by setting a environment
+# variable when the user is successfully authenticated. The standard environment
+# variable is REMOTE_USER and this is the default, but if your SSO system uses
+# another name you can configure that here.
+
+SSO_ENABLED = False
+SSO_USER_ENV_VARIABLE = 'REMOTE_USER'
+
+# Some SSO systems provides group information as environment variable.
+# Timesketch can automatically create groups and add users as members.
+# To enable this feature just provide the environment variable used in the SSO
+# system of use.
+SSO_GROUP_ENV_VARIABLE = None
+
+# Different systems use different separators in the string returned in the
+# environment variable.
+SSO_GROUP_SEPARATOR = ';'
+
+# Some SSO systems uses a special prefix for the group name to indicate that
+# the user is not a member of that group. Set this if that is the case, i.e.
+# '-'.
+SSO_GROUP_NOT_MEMBER_SIGN = None
+
+#-------------------------------------------------------------------------------
+# Google Cloud Identity-Aware Proxy (Cloud IAP) authentication configuration.
+
+# Cloud IAP controls access to your Timesketch server running on Google Cloud
+# Platform. Cloud IAP works by verifying a userâs identity and determining if
+# that user should be allowed to access the server.
+#
+# For this feature you will need to configure your Cloud IAP and HTTPS load-
+# balancer. Follow the official documentation to get everything ready:
+# https://cloud.google.com/iap/docs/enabling-compute-howto
+
+# Enable Cloud IAP authentication support.
+GOOGLE_IAP_ENABLED = False
+
+# This information is available via the Google Cloud console:
+# https://cloud.google.com/iap/docs/signed-headers-howto
+GOOGLE_IAP_PROJECT_NUMBER = ''
+GOOGLE_IAP_BACKEND_ID = ''
+
+# DON'T EDIT: Google IAP expected audience is based on Cloud project number and
+# backend ID.
+GOOGLE_IAP_AUDIENCE = '/projects/{}/global/backendServices/{}'.format(
+ GOOGLE_IAP_PROJECT_NUMBER,
+ GOOGLE_IAP_BACKEND_ID
+)
+
+GOOGLE_IAP_ALGORITHM = 'ES256'
+GOOGLE_IAP_ISSUER = 'https://cloud.google.com/iap'
+GOOGLE_IAP_PUBLIC_KEY_URL = 'https://www.gstatic.com/iap/verify/public_key'
+
+#-------------------------------------------------------------------------------
+# Google Cloud OpenID Connect (OIDC) authentication configuration.
+
+# Cloud OIDC controls access to your Timesketch server running on Google Cloud
+# Platform. Cloud OIDC works by verifying a userâs identity and determining if
+# that user should be allowed to access the server.
+
+# Enable Cloud OIDC authentication support.
+# For Google's federated identity, leave AUTH_URI and DICOVERY_URL to None.
+# For others, refer to your OIDC provider configuration. Configuration can be
+# obtain from the discovery url. eg. https://accounts.google.com/.well-known/openid-configuration
+
+# Some OIDC providers expects a specific Algorithm. If so, specify in ALGORITHM.
+# Eg. HS256, HS384, HS512, RS256, RS384, RS512.
+# For Google, leave it to None
+
+GOOGLE_OIDC_ENABLED = False
+
+GOOGLE_OIDC_AUTH_URL = None
+GOOGLE_OIDC_DISCOVERY_URL = None
+GOOGLE_OIDC_ALGORITHM = None
+
+GOOGLE_OIDC_CLIENT_ID = None
+GOOGLE_OIDC_CLIENT_SECRET = None
+
+# If you need to authenticate an API client using OIDC you need to create
+# an OAUTH client for "other", or for native applications.
+# https://developers.google.com/identity/protocols/OAuth2ForDevices
+GOOGLE_OIDC_API_CLIENT_ID = None
+
+# List of additional allowed GOOGLE OIDC clients that can authenticate to the APIs
+GOOGLE_OIDC_API_CLIENT_IDS = []
+
+# Limit access to a specific Google GSuite domain.
+GOOGLE_OIDC_HOSTED_DOMAIN = None
+
+# Additional Google GSuite domains allowed API access.
+GOOGLE_OIDC_API_ALLOWED_DOMAINS = []
+
+# If populated only these users (email addresses) will be able to login to
+# this server. This can be used when access should be limited to a specific
+# set of users.
+GOOGLE_OIDC_ALLOWED_USERS = []
+
+#-------------------------------------------------------------------------------
+# Upload and processing of Plaso storage files.
+
+# To enable this feature you need to configure an upload directory and
+# how to reach the Redis database used by the distributed task queue.
+UPLOAD_ENABLED = True
+
+# Folder for temporarily storage of Plaso dump files before being processed and
+# inserted into the datastore.
+UPLOAD_FOLDER = '/tmp'
+
+# Celery broker configuration. You need to change ip/port to where your Redis
+# server is running.
+CELERY_BROKER_URL = 'redis://redis:6379'
+CELERY_RESULT_BACKEND = 'redis://redis:6379'
+
+# File location to store the mappings used when OpenSearch indices are created
+# for plaso files.
+PLASO_MAPPING_FILE = f'{os.environ["TIMESKETCH_CONF_DIR"]}/plaso.mappings'
+GENERIC_MAPPING_FILE = f'{os.environ["TIMESKETCH_CONF_DIR"]}/generic.mappings'
+
+# Override/extend Plaso default message string formatters.
+PLASO_FORMATTERS = f'{os.environ["TIMESKETCH_CONF_DIR"]}/plaso_formatters.yaml'
+
+# Upper limits for the process memory that psort.py is allocated when ingesting
+# plaso files. The size is in bytes, with the default value of
+# 4294967296 or 4 GiB.
+PLASO_UPPER_MEMORY_LIMIT = None
+
+# Directory to store Plaso (psort) log files.
+# If this is set, psort will write execution logs here.
+# If this is NOT set, logs will be discarded to /dev/null.
+# WARNING: These logs can be large. Ensure you have log rotation configured!
+PLASO_LOG_FOLDER = '/var/log/timesketch/psort/'
+
+#-------------------------------------------------------------------------------
+# Analyzers.
+
+# Which analyzers to run automatically.
+AUTO_SKETCH_ANALYZERS = []
+
+# Optional specify any default arguments to pass to analyzers.
+# The format is:
+# {'analyzer1_name': {
+# 'param1': 'value'
+# },
+# {'analyzer2_name': {
+# 'param1': 'value'
+# }
+# }
+# }
+AUTO_SKETCH_ANALYZERS_KWARGS = {}
+ANALYZERS_DEFAULT_KWARGS = {}
+
+# Add all domains that are relevant to your enterprise here.
+# All domains in this list are added to the list of watched
+# domains and compared to other domains in the timeline to
+# attempt to spot "phishy" domains.
+DOMAIN_ANALYZER_WATCHED_DOMAINS = []
+
+# Defines how deep into the most frequently visited top
+# level domains the analyzer should include in its watch list.
+DOMAIN_ANALYZER_WATCHED_DOMAINS_THRESHOLD = 10
+
+# The minimum Jaccard distance for a domain to be considered
+# similar to the domains in the watch list. The lower this number
+# is the more domains will be included in the "phishy" domain
+# category.
+DOMAIN_ANALYZER_WATCHED_DOMAINS_SCORE_THRESHOLD = 0.75
+
+# A list of domains that are frequent source of false positives
+# in the "phishy" domain comparison, mostly CDNs and similar.
+DOMAIN_ANALYZER_EXCLUDE_DOMAINS = ['ytimg.com', 'gstatic.com', 'yimg.com', 'akamaized.net', 'akamaihd.net', 's-microsoft.com', 'images-amazon.com', 'ssl-images-amazon.com', 'wikimedia.org', 'redditmedia.com', 'googleusercontent.com', 'googleapis.com', 'wikipedia.org', 'github.io', 'github.com']
+
+# The threshold in minutes which the difference in timestamps has to cross in order to be
+# detected as 'timestomping'.
+NTFS_TIMESTOMP_ANALYZER_THRESHOLD = 10
+
+# Safe Browsing API key for the URL analyzer.
+SAFEBROWSING_API_KEY = ''
+
+# For the other possible values of the two settings below, please refer to
+# the Safe Browsing API reference at:
+# https://developers.google.com/safe-browsing/v4/reference/rest
+
+# Platforms to be looked at in Safe Browsing (PlatformType).
+SAFEBROWSING_PLATFORMS = ['ANY_PLATFORM']
+
+# Types to be looked at in Safe Browsing (ThreatType).
+SAFEBROWSING_THREATTYPES = ['MALWARE']
+
+#-- hashR integration --#
+# https://github.com/google/hashr
+# Uncomment and fill this section if you want to use the hashR lookup analyzer.
+# Provide hashR postgres database connection information below:
+# HASHR_DB_USER = 'hashRuser'
+# HASHR_DB_PW = 'hashRpass'
+# HASHR_DB_ADDR = '127.0.0.1'
+# HASHR_DB_PORT = '5432'
+# HASHR_DB_NAME = 'hashRdb'
+
+# The total number of unique hashes that are checked against the database is
+# split into multiple batches. This number defines how many unique hashes are
+# checked per query. 50000 is the default value.
+# HASHR_QUERY_BATCH_SIZE = '50000'
+
+# Set as True if you want to add the source of the hash ([repo:imagename]) as
+# an attribute to the event. WARNING: This will increase the processing time
+# of the analyzer!
+# HASHR_ADD_SOURCE_ATTRIBUTE = True
+
+# Threatintel Yeti analyzer-specific configuration
+# https://yeti-platform.io/
+# URI root to Yeti's API, e.g. 'https://localhost:8000'
+YETI_API_ROOT = ''
+
+# API key to authenticate requests
+YETI_API_KEY = ''
+
+# Path to a TLS certificate that can be used to authenticate servers
+# using self-signed certificates. Provide the full path to the .crt file.
+YETI_TLS_CERTIFICATE = None
+
+# Labels to narrow down indicator selection
+YETI_INDICATOR_LABELS = ['domain']
+
+# Enable loading DFIQ templates from a Yeti instance.
+# This requires DFIQ_ENABLED to be True and YETI_API_ROOT/YETI_API_KEY to be set.
+# Yeti DFIQ entries will overwrite local entries if they share the same UUID!
+YETI_DFIQ_ENABLED = False
+
+# Yeti instance web base URL used for links in the UI.
+# (Falls back to YETI_API_ROOT if not set.)
+YETI_WEB_ROOT = ''
+
+# Url to MISP instance
+MISP_URL = ''
+
+# API key to authenticate requests
+MISP_API_KEY = ''
+
+# Url to Hashlookup instance
+HASHLOOKUP_URL = ''
+
+# GeoIP Analyzer Settings
+#
+# Disclaimer: Please note that the geolocation results obtained from this analyzer
+# are indicative and based upon the accuracy of the configured datasource.
+# This analyzer uses GeoLite2 data created by MaxMind, available from
+# https://maxmind.com.
+
+# The path to a MaxMind GeoIP database
+MAXMIND_DB_PATH = ''
+
+# The Account ID to access a MaxMind GeoIP web service
+MAXMIND_WEB_ACCOUNT_ID = ''
+
+# The license key to access a MaxMind GeoIP web service
+MAXMIND_WEB_LICENSE_KEY = ''
+
+# The host URL of a MaxMind GeoIP web service
+MAXMIND_WEB_HOST = ''
+
+#-------------------------------------------------------------------------------
+# Enable experimental UI features.
+
+ENABLE_EXPERIMENTAL_UI = False
+
+#-------------------------------------------------------------------------------
+# Email notifications.
+
+ENABLE_EMAIL_NOTIFICATIONS = False
+EMAIL_DOMAIN = 'localhost'
+EMAIL_FROM_USER = 'nobody'
+EMAIL_SMTP_SERVER = 'localhost'
+
+# Only send emails to these users.
+EMAIL_RECIPIENTS = []
+
+# Configuration to construct URLs for resources.
+EXTERNAL_HOST_URL = 'https://localhost'
+
+# SSL/TLS support for emails
+EMAIL_TLS = False
+EMAIL_SSL = False
+
+# Email support for authentication
+EMAIL_AUTH_USERNAME = ""
+EMAIL_AUTH_PASSWORD = ""
+
+#-------------------------------------------------------------------------------
+# Sigma Settings
+
+SIGMA_CONFIG = f'{os.environ["TIMESKETCH_CONF_DIR"]}/sigma_config.yaml'
+SIGMA_TAG_DELAY = 5
+
+#-------------------------------------------------------------------------------
+# Flask Settings
+# Everything mentioned in https://flask-wtf.readthedocs.io/en/latest/config/ can be used.
+# Max age in seconds for CSRF tokens. Default is 3600. If set to None, the CSRF token is valid for the life of the session.
+# WTF_CSRF_TIME_LIMIT = 7200
+WTF_CSRF_ENABLED = False # Set this to False for UI-development purposes
+
+# The number of proxies that are chained in front of the application.
+# This is used by the ProxyFix middleware to trust the X-Forwarded-* headers.
+# See: https://werkzeug.palletsprojects.com/en/stable/middleware/proxy_fix/
+REVERSE_PROXY_COUNT = 1
+
+# Increase memory limit for form fields to support large legacy client uploads.
+# Default: 200MB chunks
+MAX_FORM_MEMORY_SIZE = 209715200
+
+#-------------------------------------------------------------------------------
+# DFIQ - Digital Forensics Investigation Questions
+# How to set-up DFIQ: https://timesketch.org/guides/admin/load-dfiq/
+DFIQ_ENABLED = False
+DFIQ_PATH = f'{os.environ["TIMESKETCH_CONF_DIR"]}/dfiq/'
+
+# Intelligence tag metadata configuration
+INTELLIGENCE_TAG_METADATA = f'{os.environ["TIMESKETCH_CONF_DIR"]}/intelligence_tag_metadata.yaml'
+
+# Context links configuration
+CONTEXT_LINKS_CONFIG_PATH = f'{os.environ["TIMESKETCH_CONF_DIR"]}/context_links.yaml'
+
+# LLM provider configs
+LLM_PROVIDER_CONFIGS = {
+ # Configure a LLM provider for a specific LLM enabled feature, or the
+ # default provider will be used.
+ # Supported LLM Providers:
+ # - ollama: Self-hosted, open-source.
+ # To use the Ollama provider you need to download and run an Ollama server.
+ # See instructions at: https://ollama.ai/
+ # - vertexai: Google Cloud Vertex AI. Requires Google Cloud Project.
+ # To use the Vertex AI provider you need to:
+ # 1. Create and export a Service Account Key from the Google Cloud Console.
+ # 2. Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the full path
+ # to your service account private key file by adding it to the docker-compose.yml
+ # under environment:
+ # GOOGLE_APPLICATION_CREDENTIALS=/usr/local/src/timesketch/.json
+ # 3. Install the python libraries: $ pip3 install google-cloud-aiplatform
+ # - secgemini_log_analyzer_agent: SecGemini Log Analyzer Agent.
+ # To use the SecGemini Log Analyzer Agent you need to:
+ # 1. Obtain an API key from https://secgemini.com/
+ # 2. pip3 install sec-gemini
+ # IMPORTANT: Private keys must be kept secret. If you expose your private key it is
+ # recommended to revoke it immediately from the Google Cloud Console.
+ # - aistudio: Google AI Studio (API key). Get API key from Google AI Studio website.
+ # To use Google's AI Studio simply obtain an API key from https://aistudio.google.com/
+ # $ pip3 install google-generativeai
+ 'nl2q': {
+ 'vertexai': {
+ 'model': 'gemini-2.0-flash',
+ 'project_id': '',
+ },
+ },
+ 'llm_summarize': {
+ 'aistudio': {
+ 'model': 'gemini-2.0-flash',
+ 'api_key': '',
+ },
+ },
+ 'llm_synthesize': {
+ 'aistudio': {
+ 'model': 'gemini-2.0-flash',
+ 'api_key': '',
+ },
+ },
+ 'log_analyzer':
+ {
+ # NOTE: This feature will not work with default LLM providers and requires
+ # a dedicated log analyzer agent service!
+ # Read more: https://timesketch.org/developers/log-analyzer-agent/
+ # Did you install `pip3 install sec-gemini` in your Timesketch containers?
+ 'secgemini_log_analyzer_agent': {
+ 'logs_processor_api_url': '',
+ 'api_key': '',
+ 'model': 'logs_analysis_agent-1.1',
+ 'base_url': '',
+ 'wss_url': '',
+ # Configuration for individual agents. This is a dictionary where the
+ # key is the agent name and the value is another dictionary with
+ # agent-specific configuration parameters.
+ # Example:
+ # 'agents_config': {
+ # 'logs_analysis_loop_agent': {
+ # 'max_iterations': 10,
+ # }
+ 'agents_config': {},
+ }
+ },
+ 'default': {
+ 'ollama': {
+ 'server_url': '',
+ 'model': '',
+ },
+ }
+}
+
+
+# LLM nl2q configuration
+DATA_TYPES_PATH = f'{os.environ["TIMESKETCH_CONF_DIR"]}/nl2q/data_types.csv'
+PROMPT_NL2Q = f'{os.environ["TIMESKETCH_CONF_DIR"]}/nl2q/prompt_nl2q'
+EXAMPLES_NL2Q = f'{os.environ["TIMESKETCH_CONF_DIR"]}/nl2q/examples_nl2q'
+
+# LLM event summarization configuration
+PROMPT_LLM_SUMMARIZATION = f'{os.environ["TIMESKETCH_CONF_DIR"]}/llm_summarize/prompt.txt'
+PROMPT_LLM_SYNTHESIZE = f'{os.environ["TIMESKETCH_CONF_DIR"]}/llm_summarize/prompt_llm_synthesize.txt'
+
+# LLM log_analyzer default prompt
+LLM_LOG_ANALYZER_DEFAULT_PROMPT = (
+ "Perform a forensics investigation on the provided logs. Determine if the "
+ "host has been compromised, and if so, reconstruct the complete attacker "
+ "timeline, from initial compromise to actions on objectives."
+)
+
+#-------------------------------------------------------------------------------
+# Timesketch UI Option
+
+# Get the search processing timelines setting.
+# If set to True, the search processing timelines options will be displayed in the UI.
+SEARCH_PROCESSING_TIMELINES = True
+
+# Enable the investigation view running on the v3 frontend.
+# Set-up instructions: https://timesketch.org/guides/admin/investigation-view-setup/
+ENABLE_V3_INVESTIGATION_VIEW = False
diff --git a/contrib/docker/dev/timesketch/timesketch.env b/contrib/docker/dev/timesketch/timesketch.env
new file mode 100644
index 0000000000..126ca4d9cd
--- /dev/null
+++ b/contrib/docker/dev/timesketch/timesketch.env
@@ -0,0 +1,12 @@
+TIMESKETCH_USER="${TIMESKETCH_USER?-}"
+TIMESKETCH_PASSWORD="${TIMESKETCH_PASSWORD?-}"
+
+SECRET_KEY="${TIMESKETCH_SECRET_KEY?-}"
+POSTGRES_USER="${POSTGRES_USER?-}"
+POSTGRES_PASSWORD="${POSTGRES_PASSWORD?-}"
+POSTGRES_DB="${POSTGRES_DB?-}"
+
+CHOKIDAR_USEPOLLING="true"
+prometheus_multiproc_dir="/tmp/"
+
+BACKEND_URL="http://gunicorn:5000/"
diff --git a/contrib/docker/dev/timesketch/vue-cli-service-docker-entrypoint.sh b/contrib/docker/dev/timesketch/vue-cli-service-docker-entrypoint.sh
new file mode 100644
index 0000000000..41d2185498
--- /dev/null
+++ b/contrib/docker/dev/timesketch/vue-cli-service-docker-entrypoint.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "/usr/local/src/timesketch/timesketch/${FRONTEND_VERSION:?}"
+
+yarn install
+
+if [[ "${FRONTEND_VERSION}" = "frontend" || "${FRONTEND_VERSION}" = "frontend-ng" ]]; then
+ exec yarn run serve
+elif [[ "${FRONTEND_VERSION}" = "frontend-v3" ]]; then
+ exec yarn dev
+else
+ echo "Unknown FRONTEND_VERSION value: \"${FRONTEND_VERSION}\"." >&2
+ exit 1
+fi
diff --git a/timesketch/frontend-ng/vue.config.js b/timesketch/frontend-ng/vue.config.js
index 8a9310b853..94fb09e85e 100644
--- a/timesketch/frontend-ng/vue.config.js
+++ b/timesketch/frontend-ng/vue.config.js
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+const backendUrl = process.env.BACKEND_URL || 'http://localhost:5000/';
+
module.exports = {
lintOnSave: false,
publicPath: process.env.NODE_ENV === 'development' ? '/' : '/dist/',
@@ -30,15 +32,15 @@ module.exports = {
proxy: {
'^/api': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
'^/dist': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
'^/login|logout': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
},
},
diff --git a/timesketch/frontend-v3/vite.config.mjs b/timesketch/frontend-v3/vite.config.mjs
index 6a789afd9f..f92ea15676 100644
--- a/timesketch/frontend-v3/vite.config.mjs
+++ b/timesketch/frontend-v3/vite.config.mjs
@@ -11,6 +11,8 @@ import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url";
+const backendUrl = process.env.BACKEND_URL || "http://localhost:5000/";
+
// https://vitejs.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === "development" ? "/" : "/v3/",
@@ -58,12 +60,12 @@ export default defineConfig({
port: 5001,
proxy: {
"^/api": {
- target: "http://127.0.0.1:5000/",
+ target: backendUrl,
changeOrigin: true,
secure: false,
},
'^/login|logout': {
- target: 'http://127.0.0.1:5000/',
+ target: backendUrl,
changeOrigin: true,
secure: false,
},
diff --git a/timesketch/frontend/vue.config.js b/timesketch/frontend/vue.config.js
index 601ffdfb99..c79e514427 100644
--- a/timesketch/frontend/vue.config.js
+++ b/timesketch/frontend/vue.config.js
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+const backendUrl = process.env.BACKEND_URL || 'http://localhost:5000/';
+
module.exports = {
lintOnSave: false,
publicPath: process.env.NODE_ENV === 'development' ? '/' : '/legacy/dist/',
@@ -30,15 +32,15 @@ module.exports = {
proxy: {
'^/api': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
'^/dist': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
'^/login|logout': {
autoRewrite: true,
- target: 'http://localhost:5000/',
+ target: backendUrl,
},
},
},