feat: add timesketch

This commit is contained in:
Tommy 2024-05-12 11:50:54 +02:00
parent 46de9b3040
commit efb22e4133
No known key found for this signature in database
22 changed files with 2242 additions and 0 deletions

View file

@ -0,0 +1 @@
*/configs/*

View file

@ -0,0 +1,17 @@
apiVersion: v2
name: timesketch
description: |
A toolset of DFIR tools
appVersion: "20240508"
type: application
version: 0.1.2
maintainers:
- name: Tommy Skaug
email: tommy@skaug.me
keywords:
- timesketch
- forensics
- google
- scale
sources:
- https://github.com/google/osdfir-infrastructure

View file

@ -0,0 +1,46 @@
hardcoded_modules:
### format xml dialog
xml_formatter:
short_name: 'Prettify XML'
match_fields:
- xml
- xml_string
### unfurl dialog
unfurl_graph:
short_name: 'Unfurl URL'
match_fields:
- url
- uri
- original_url
### Add Threat Intel
threat_intel:
short_name: 'Add to Threat Intel'
match_fields:
- url
- uri
- original_url
- ip
- ip_address
- domain
- domain_name
- host
- hostname
- email
- email_address
- hash
- sha256_hash
- sha256
- sha1_hash
- sha1
- md5_hash
- md5
## External Services
linked_services:
### Virustotal Example:
virustotal_hash_lookup:
short_name: 'VirusTotal'
match_fields: ['hash', 'sha256_hash', 'sha256', 'sha1_hash', 'sha1', 'md5_hash', 'md5']
context_link: 'https://www.virustotal.com/gui/search/<ATTR_VALUE>'
redirect_warning: TRUE

View file

@ -0,0 +1,11 @@
{
"date_detection": false,
"properties": {
"datetime": {
"type": "date"
},
"timesketch_label": {
"type": "nested"
}
}
}

View file

@ -0,0 +1,30 @@
# Configuration file for tags that need highlighting in the intelligence page
#
# For each tag name, the following attributes are defined:
# * weight: Used for ordering, tags with larger weight will appear first in the list of tags.
# * class: Used in the UI to defined a Buefy class to color the tag with (common classes are danger, warning, success, and info)
#
# The `regex` attribute is used to specify regexes to match against the tags. 1:1 tag matches take precedence over regex matches.
#
# With the configuration below, an IOC with the 'malware' tag will have that tag displayed first (heavy weight), in red (danger)
malware:
weight: 100
class: 'danger'
suspicious:
weight: 50
class: 'warning'
legit:
weight: 10
class: 'success'
default:
weight: 0
class: 'info'
regexes:
'^GROUPNAME':
weight: 100
class: 'danger'

View file

@ -0,0 +1,38 @@
# Define the ontology that is available for Timesketch.
# An ontology needs to define the name, which is what will
# be used to identify the ontology.
# + cast_as is used to define how the value should be
# interpreted in code.
# + description is a short text describing the ontology.
text:
cast_as: str
description: "Free form text."
int:
cast_as: int
description: "Integer"
url.safe:
cast_as: str
description: "Safe URL that can be visited."
url.bad:
cast_as: str
description: "URL related to a sketch, can morph into a search."
domain:
cast_as: str
description: "Domain related to a sketch, can morp into a search."
float:
cast_as: float
description: "Float."
bool:
cast_as: bool
description: "Boolean, True or False values."
intelligence:
cast_as: dict
description: "Set of key/value pairs to summarize an intelligence report."

View file

@ -0,0 +1,87 @@
{
"properties": {
"application": {
"type": "text",
"fields": {
"keyword": {"type": "keyword"}}
},
"timesketch_label": {
"type": "nested"
},
"datetime": {
"type": "date"
},
"data": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"doc_type": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"event_type": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"exit_status": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"facility": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"file_reference": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"file_size": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"flags": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"identifier": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"message_status": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"message_type": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"offset": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"sequence_number": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"severity": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"source_port": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"user_identifier": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"version": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
},
"http_response_bytes": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}}
}
}
}

View file

@ -0,0 +1,23 @@
# Plaso uses formatter definitions to format events into a human readable format.
# The formatter definitions are defined in YAML and loaded by Plaso at runtime.
# This file overrides or extend the default formatter definitions.
# For more information about the formatter definitions see: https://plaso.readthedocs.io/en/latest/sources/user/Output-and-formatting.html#formatter-configuration-file-format
# Windows Event Log (EVTX) formatter definitions.
type: 'conditional'
data_type: 'windows:evtx:record'
custom_helpers:
- identifier: 'windows_eventlog_message'
output_attribute: 'message_string'
message:
- '[{event_identifier}]'
- '{message_string}'
- 'Source Name: {source_name}'
- 'Strings: {strings}'
short_message:
- '[{event_identifier} / 0x{event_identifier:04x}]'
- 'Source Name: {source_name}'
- 'Strings: {strings}'
- 'Provider identifier: {provider_identifier}'
short_source: 'EVTX'
source: 'WinEVTX'

View file

@ -0,0 +1,182 @@
# Config file for the feature extraction analyzer.
# A feature extraction definition looks like this:
# name:
# # Define either a query_string or query_dsl.
# query_string: *
# query_dsl:
# # Mandatory fields.
# attribute:
# store_as:
# re:
# # Optional fields.
# re_flags: []
# emojis: []
# tags: []
# overwrite_store_as: True
# overwrite_and_merge_store_as: False
# store_type_list: False
# keep_multimatch: False
#
# Each definition needs to define either a query_string or a query_dsl.
#
# re_flags is a list of flags as strings from the re module. These include:
# - DEBUG
# - DOTALL
# - IGNORECASE
# - LOCALE
# - MULTILINE
# - TEMPLATE
# - UNICODE
# - VERBOSE
#
# The fields tags and emojis are optional.
#
# The field store_as defines the name of the attribute the feature is
# stored as.
#
# The overwrite_store_as is an optional boolean that determines if
# we want to overwrite the field store_as if it already exists.
#
# The overwrite_and_merge_store_as is an optional boolean that determines
# if we want to overwrite the field store_as and merge the existing values.
#
# The store_type_list is an optional boolean that determines if we want to
# store the extracted data in List type (default is text).
#
# The keep_multimatch is an optional boolean that determines if we want to
# store all matching results (default store first result).
#
# The feature extraction works in the way that the query is run, and
# the regular expression is run against the attribute to extract a value.
# The first value extracted is then stored inside the "store_as" attribute.
# If there are emojis or tags defined they are also applied to that event.
# ------------------------------------------------------------------------
email_addresses:
query_string: 'source_short:"WEBHIST"'
attribute: 'message'
store_as: 'email_address'
re: '([a-zA-Z0-9_\.+\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+)'
re_flags: []
tags: ['email-address']
gmail_accounts:
query_string: 'source_short:"WEBHIST" AND url:"mail.google.com"'
attribute: 'message'
store_as: 'found_account'
re: '[a-zA-Z0-9_\.+\-]+@(gmail|googlemail)\.com'
re_flags: []
tags: ['gmail-account']
emojis: ['ID_BUTTON']
github_accounts:
query_string: 'source_short:"WEBHIST" AND
url:"https://github.com/users" AND title:"Your Profile"'
attribute: 'url'
store_as: 'found_account'
re: 'https://github.com/users/([A-z-\d]{1,39})'
re_flags: []
tags: ['github-account']
emojis: ['ID_BUTTON']
# Linkedin account extraction from profile edit url
linkedin_accounts:
query_string: 'source_short:"WEBHIST" AND
url:"https://www.linkedin.com/in/" AND url:"/edit/"'
attribute: 'url'
store_as: 'found_account'
re: 'https://www.linkedin.com/in/([A-z-\d]{5,32})/edit/'
tags: ['linkedin-account']
emojis: ['ID_BUTTON']
rdp_ts_ipv4_addresses:
query_string: 'data_type:"windows:evtx:record" AND
source_name:"Microsoft-Windows-TerminalServices-LocalSessionManager"'
attribute: 'strings'
store_as: 'ip_address'
re: '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
rdp_rds_ipv4_addresses:
query_string: 'data_type:"windows:evtx:record" AND
source_name:"Microsoft-Windows-RemoteDesktopServices-RdpCoreTS"'
attribute: 'strings'
store_as: 'client_ip'
re: '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
ssh_client_ipv4_addresses:
query_string: 'reporter:"sshd"'
attribute: 'message'
store_as: 'client_ip'
re: 'Connection from ((?:[0-9]{1,3}\.){3}[0-9]{1,3}) port \d+ on (?:[0-9]{1,3}\.){3}[0-9]{1,3} port \d+(?: rdomain ? .*)?$'
ssh_client_ipv4_addresses_2:
query_string: 'reporter:"sshd"'
attribute: 'message'
store_as: 'client_ip'
re: 'Connection [a-z]+ by ((?:[0-9]{1,3}\.){3}[0-9]{1,3}) port \d+'
ssh_host_ipv4_addresses:
query_string: 'reporter:"sshd"'
attribute: 'message'
store_as: 'host_ip'
re: '^\[sshd\] \[\d+\]: Connection from (?:[0-9]{1,3}\.){3}[0-9]{1,3} port \d+ on ((?:[0-9]{1,3}\.){3}[0-9]{1,3}) port \d+(?: rdomain ? .*)?$'
ssh_client_password_ipv4_addresses:
query_string: 'reporter:"sshd"'
attribute: 'message'
store_as: 'client_ip'
re: '(?:Accepted|Failed) (?:password|publickey) for \w+ from ((?:[0-9]{1,3}\.){3}[0-9]{1,3}) port \d+'
ssh_disconnected_username:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'username'
re: 'Disconnected\s+from user (?P<username>[^\s]+) [^\s]+ port \d+$'
ssh_disconnected_ip_address:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'ip_address'
re: 'Disconnected from user [^\s]+ (?P<ip_address>[^\s]+) port \d+$'
ssh_disconnected_port:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'port'
re: 'Disconnected from user [^\s]+ [^\s]+ port (?P<port>\d+)$'
ssh_failed_username:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'username'
re: 'Failed password for (?:invalid user)?\s*(?P<username>[^\s]+) from [^\s]+ port \d+ ssh\d'
ssh_failed_ip_address:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'ip_address'
re: 'Failed password for (?:invalid user)?\s*[^\s]+ from (?P<ip_address>[^\s]+) port \d+ ssh\d'
ssh_failed_port:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'port'
re: 'Failed password for (?:invalid user)?\s*[^\s]+ from [^\s]+ port (?P<port>\d+) ssh\d'
ssh_failed_method:
query_string: 'reporter:"sshd"'
attribute: 'body'
store_as: 'authentication_method'
re: 'Failed (?P<authentication_method>[^\s]+) for .*ssh\d'
win_bits_client_ipv4_addresses:
query_string: 'data_type:"windows:evtx:record" AND source_name:Microsoft-Windows-Bits-Client'
attribute: 'strings'
store_as: 'ip_address'
re: '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
win_bits_client_url:
query_string: 'data_type:"windows:evtx:record" AND source_name:Microsoft-Windows-Bits-Client'
attribute: 'strings'
store_as: 'url'
re: '(?<=")(?:(?:https?:\/\/)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)(?:[\/\?#][^\s,"]*)?(?=")'

View file

@ -0,0 +1,367 @@
title: Timesketch Sigma config
order: 20
backends:
- es-dsl
- es-qs
- es-qr
- es-rule
logsources:
linux_file:
category: file_event
product: linux
conditions:
data_type: "fs:stat"
linux_network:
category: network_connection
product: linux
conditions:
data_type:
- "shell:zsh:history"
- "bash:history:command"
- "apt:history:line"
- "selinux:line"
- "syslog"
linux_process_creation:
category: process_creation
product: linux
conditions:
data_type:
- "shell:zsh:history"
- "bash:history:command"
- "apt:history:line"
- "selinux:line"
sshd:
service: sshd
conditions:
data_type: "syslog/sshd"
auth:
service: auth
conditions:
data_type: "syslog"
apache:
product: apache
conditions:
data_type: "apache:access"
vsftp:
service: vsftp
conditions:
data_type: "vsftpd:log"
webserver:
category: webserver
conditions:
data_type:
- "apache:access"
- "iis:log:line"
santa:
category: osx_santa
conditions:
data_type:
- "filesystem:santa:entry"
shell:
service: shell
conditions:
data_type:
- "shell:zsh:history"
- "bash:history:command"
- "apt:history:line"
- "selinux:line"
selinux:
service: selinux
conditions:
data_type:
- "selinux:line"
winprefetch:
service: winprefetch
conditions:
data_type: "windows:prefetch:execution"
product_windows:
product: windows
conditions:
data_type: "windows:evtx:record"
service_windows_security:
service: security
conditions:
source_name:
- "Microsoft-Windows-Security-Auditing"
- "Microsoft-Windows-Eventlog"
service_windows_system:
service: system
conditions:
source_name:
- "Microsoft-Windows-Eventlog"
powershell:
service: powershell
conditions:
source_name:
- "Microsoft-Windows-Security-Auditing"
files:
service: filesystem
conditions:
data_type:
- "fs:stat"
- "fs:mactime:line"
- "filesystem:santa:entry"
- "fs:bodyfile:entry"
sysmon:
service: sysmon
conditions:
source_name:
- "Microsoft-Windows-Sysmon"
syslog:
service: syslog
conditions:
data_type:
- "syslog:line"
# GCP
gcp_audit:
service: gcp.audit
conditions:
query:
- "cloudaudit.googleapis.com"
# log source configurations for generic sigma rules
process_creation:
category: process_creation
product: windows
conditions:
EventID:
- 1
- 4688
source_name:
- "Microsoft-Windows-Sysmon"
- "Microsoft-Windows-Security-Auditing"
- "Microsoft-Windows-Eventlog"
fieldmappings:
Image: NewProcessName
ParentImage: ParentProcessName
network_connection:
category: network_connection
product: windows
conditions:
EventID: 3
rewrite:
product: windows
service: sysmon
process_terminated:
category: process_termination
product: windows
conditions:
EventID: 5
rewrite:
product: windows
service: sysmon
driver_loaded:
category: driver_load
product: windows
conditions:
EventID: 6
rewrite:
product: windows
service: sysmon
image_loaded:
category: image_load
product: windows
conditions:
EventID: 7
rewrite:
product: windows
service: sysmon
create_remote_thread:
category: create_remote_thread
product: windows
conditions:
EventID: 8
rewrite:
product: windows
service: sysmon
raw_access_thread:
category: raw_access_thread
product: windows
conditions:
EventID: 9
rewrite:
product: windows
service: sysmon
process_access:
category: process_access
product: windows
conditions:
EventID: 10
rewrite:
product: windows
service: sysmon
file_creation:
category: file_event
product: windows
conditions:
EventID: 11
rewrite:
product: windows
service: sysmon
registry_event:
category: registry_event
product: windows
conditions:
EventID:
- 12
- 13
- 14
rewrite:
product: windows
service: sysmon
create_stream_hash:
category: create_stream_hash
product: windows
conditions:
EventID: 15
rewrite:
product: windows
service: sysmon
pipe_created:
category: pipe_created
product: windows
conditions:
EventID:
- 17
- 18
rewrite:
product: windows
service: sysmon
wmi_event:
category: wmi_event
product: windows
conditions:
EventID:
- 19
- 20
- 21
rewrite:
product: windows
service: sysmon
dns_query:
category: dns_query
product: windows
conditions:
EventID: 22
rewrite:
product: windows
service: sysmon
file_delete:
category: file_delete
product: windows
conditions:
EventID: 23
rewrite:
product: windows
service: sysmon
ssh:
service: ssh
conditions:
data_type:
- "syslog:ssh:login"
- "syslog:line"
fieldmappings:
EventID: event_identifier
ComputerName: computer_name
EventType: event_type
EventIdentifier: event_identifier
ObjectName: xml_string # that is a bit hacky but the processName is currently not being parsed that is why searching in the raw xml
ObjectType: xml_string # that is a bit hacky but the processName is currently not being parsed that is why searching in the raw xml
ProcessName: xml_string # that is a bit hacky but the processName is currently not being parsed that is why searching in the raw xml
GroupSid: xml_string
CommandLine: xml_string
ServiceName: xml_string
Service: xml_string
Message: xml_string
keywords: xml_string # that might be wrong, only introduced during powershell stuff
Source: message
LogonType: xml_string
LogonProcessName: xml_string
LogonGuid: xml_string
SubjectDomainName: xml_string
SubjectUserName: xml_string
TargetUserSid: xml_string
TargetUserName: xml_String
TargetDomainName: xml_string
TargetLogonId: xml_string
AuthenticationPackageName: xml_string
WorkstationName: xml_string
TransmittedServices: xml_string
ProcessId: xml_string
IpAddress: xml_string
IpPort: xml_String #not sure if that mapping is used somewhere else
SourceNetworkAddress: xml_string
TargetOutboundUserName: xml_string
TargetOutboundDomainName: xml_string
Level: xml_string # this might also cause conflicts.
ServiceFileName: xml_string
ObjectValueName: xml_string
DestPort: xml_string
LayerRTID: xml_string
AccessMask: xml_string
ShareName: xml_string
RelativeTargetName: xml_string
AccountName: xml_string
PrivilegeList: xml_string
SubjectLogonId: xml_string
CallingProcessName: xml_string
SAMAccountName: xml_string
ObjectServer: xml_string
Properties: xml_string
HiveName: xml_string
AttributeLDAPDisplayName: xml_string
GroupName: xml_string
UserName: xml_string
DeviceDescription: xml_string
DeviceClassName: xml_string
TicketOptions: xml_string
TicketEncryptionType: xml_string
SourceWorkstation: xml_string
DestinationAddress: xml_string
DestinationPort: xml_string
SourceAddress: xml_string
Keywords: xml_string
LDAPDisplayName: xml_string
AuditPolicyChanges: xml_string
SourceImage: xml_string
TargetImage: xml_string
TargetFilename:
product=linux: filename
default: xml_string
ImageLoaded: xml_string
QueryName: xml_string
TargetProcessAddress: xml_string
TargetObject: xml_string
Signature: xml_string
StartModule: xml_string
StartFunction: xml_string
IntegrityLevel: xml_string
Description: xml_string
Signed: xml_string
ScriptBlockText: xml_string
ContextInfo: xml_string
OriginalFileName: xml_string # 80167ada-7a12-41ed-b8e9-aa47195c66a1
Payload: xml_string
HostName: xml_string #96b9f619-aa91-478f-bacb-c3e50f8df575
HostApplication: xml_string #96b9f619-aa91-478f-bacb-c3e50f8df575
gcp.audit.method_name: methodName
ParentImage: xml_string
Mutex: message
Value:
service=windefend: xml_string
default: Value
Provider_Name: xml_string
param1: #6c0a7755-6d31-44fa-80e1-133e57752680
product=windows: xml_string
default: param1
param2: #6c0a7755-6d31-44fa-80e1-133e57752680
product=windows: xml_string
default: param2
GrantedAccess: xml_string #aa35a627-33fb-4d04-a165-d33b4afca3e8
DestinationIp:
product=linux: message
product=windows: xml_string
default: message
Image: #42df45e7-e6e9-43b5-8f26-bec5b39cc239
product=linux: message
product=windows: xml_string
default: message

View file

@ -0,0 +1,39 @@
# Config file for the tagger analyzer.
#
# Please refer to docs/guides/analyzers/tagger.md for
# instructions on how to edit this file.
gcs_bucket_creation_tagger:
query_string: 'serviceName: "storage.googleapis.com" AND methodName: "storage.buckets.create"'
tags: ['gcs-bucket-create']
emojis: ['BUCKET', 'SPARKLES']
save_search: true
search_name: 'Bucket creation'
gcs_bucket_permission_added_tagger:
query_string: 'serviceName: "storage.googleapis.com" AND methodName: "storage.setIamPermissions" AND policyDelta:*ADD*'
tags: ['gcs-bucket-add']
emojis: ['BUCKET', 'PERSON_STANDING']
save_search: true
search_name: 'Bucket permission - Add'
gcs_bucket_permission_remove_tagger:
query_string: 'serviceName: "storage.googleapis.com" AND methodName: "storage.setIamPermissions" AND policyDelta:*REMOVE*'
tags: ['gcs-bucket-remove']
emojis: ['BUCKET', 'WASTEBASKET']
save_search: true
search_name: 'Bucket permission - Remove'
gcs_bucket_permission_world_added_tagger:
query_string: 'serviceName: "storage.googleapis.com" AND methodName: "storage.setIamPermissions" AND policyDelta:*ADD allUsers* OR policyDelta:*ADD allAuthenticatedUsers*'
tags: ['gcs-bucket-world-add']
emojis: ['BUCKET', 'GLOBE']
save_search: true
search_name: 'Bucket permission - World Readable'
yara_match_tagger:
query_string: '_exists_:yara_match AND NOT yara_match.keyword:"-"'
tags: ['yara', '$yara_match']
modifiers: ['split']
save_search: true
search_name: 'Yara rule matches'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "timesketch.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "timesketch.fullname" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "timesketch.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "timesketch.labels" -}}
helm.sh/chart: {{ include "timesketch.chart" . }}
{{ include "timesketch.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
date: "{{ now | htmlDate }}"
{{- end }}
{{/*
Selector labels
*/}}
{{- define "timesketch.selectorLabels" -}}
app.kubernetes.io/name: {{ include "timesketch.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Timesketch service port
*/}}
{{- define "timesketch.service.port" -}}
{{- if .Values.global.timesketch.servicePort -}}
{{ .Values.global.timesketch.servicePort }}
{{- else -}}
{{ .Values.service.port }}
{{- end -}}
{{- end -}}
{{/*
Create the upload path.
*/}}
{{- define "timesketch.uploadPath" -}}
{{- printf "/data/upload" }}
{{- end }}

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "timesketch.fullname" . }}-configs
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "timesketch.labels" . | nindent 4 }}
data:
{{- (.Files.Glob "configs/**.{yaml,mappings}").AsConfig | nindent 2 }}

View file

@ -0,0 +1,20 @@
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "timesketch.fullname" . }}-db-init
labels:
{{- include "timesketch.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: general-db-init
image: "{{ .Values.initContainers.dbInit.image.repository }}:{{ .Values.initContainers.dbInit.image.tag }}"
envFrom:
- secretRef:
name: {{ .Values.postgres.secretName }}
backoffLimit: 3

View file

@ -0,0 +1,93 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "timesketch.fullname" . }}-frontend
namespace: {{ .Release.Namespace | quote }}
labels:
app.kubernetes.io/component: frontend
{{- include "timesketch.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: frontend
{{- include "timesketch.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# Have Deployment restart after each upgrade
roll: {{ randAlphaNum 5 | quote }}
prometheus.io/port: {{ .Values.metrics.port | quote }}
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/component: frontend
{{- include "timesketch.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "timesketch.fullname" . }}
securityContext:
{{- toYaml .Values.frontend.podSecurityContext | nindent 8 }}
containers:
- name: frontend
securityContext:
{{- toYaml .Values.frontend.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["sh", "-c", "gunicorn --bind 0.0.0.0:5000 --log-file - \
--error-logfile - --log-level info \
--capture-output --timeout 600 --limit-request-line 8190 \
--workers 4 timesketch.wsgi:application"]
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "tsctl create-user $TIMESKETCH_USER_USERNAME --password $TIMESKETCH_USER_PASSWORD"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
apiVersion: v1
- name: TIMESKETCH_USER_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.config.existingUserSecret }}
key: TIMESKETCH_USER_USERNAME
- name: TIMESKETCH_USER_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.existingUserSecret }}
key: TIMESKETCH_USER_PASSWORD
volumeMounts:
- name: upload-volume
mountPath: /data/uploads
subPath: uploads
- name: timesketch-default-configs
mountPath: /config
readOnly: true
- name: timesketch-conf
mountPath: /etc/timesketch.conf
subPath: timesketch.conf
readOnly: true
ports:
- containerPort: {{ .Values.metrics.port }}
- containerPort: 5000
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
volumes:
- name: upload-volume
persistentVolumeClaim:
claimName: {{ include "timesketch.fullname" . }}-upload
readOnly: false
- name: timesketch-default-configs
configMap:
name: {{ include "timesketch.fullname" . }}-configs
optional: true
- name: timesketch-conf
secret:
secretName: {{ .Values.config.existingConfSecret }}
optional: true
nodeSelector:
{{- toYaml .Values.frontend.nodeSelector | nindent 8 }}
affinity:
{{- toYaml .Values.frontend.affinity | nindent 8 }}
tolerations:
{{- toYaml .Values.frontend.tolerations | nindent 8 }}

View file

@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "timesketch.fullname" . }}-worker
namespace: {{ .Release.Namespace | quote }}
labels:
app.kubernetes.io/component: worker
{{- include "timesketch.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: worker
{{- include "timesketch.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# Have Deployment restart after each upgrade
roll: {{ randAlphaNum 5 | quote }}
prometheus.io/port: {{ .Values.metrics.port | quote }}
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/component: worker
{{- include "timesketch.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "timesketch.fullname" . }}
securityContext:
{{- toYaml .Values.worker.podSecurityContext | nindent 8 }}
containers:
- name: worker
securityContext:
{{- toYaml .Values.worker.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["sh", "-c", "celery -A timesketch.lib.tasks worker \
--loglevel=DEBUG"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
apiVersion: v1
- name: WORKER_LOG_LEVEL
value: "DEBUG"
- name: TIMESKETCH_USER_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.config.existingUserSecret }}
key: TIMESKETCH_USER_USERNAME
- name: TIMESKETCH_USER_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.config.existingUserSecret }}
key: TIMESKETCH_USER_PASSWORD
volumeMounts:
- name: upload-volume
mountPath: /data/uploads
subPath: uploads
- name: timesketch-default-configs
mountPath: /config
readOnly: true
- name: timesketch-conf
mountPath: /etc/timesketch.conf
subPath: timesketch.conf
readOnly: true
ports:
- containerPort: {{ .Values.metrics.port }}
- containerPort: 5000
resources:
{{- toYaml .Values.worker.resources | nindent 12 }}
volumes:
- name: upload-volume
persistentVolumeClaim:
claimName: {{ include "timesketch.fullname" . }}-upload
readOnly: false
- name: timesketch-default-configs
configMap:
name: {{ include "timesketch.fullname" . }}-configs
optional: true
- name: timesketch-conf
secret:
secretName: {{ .Values.config.existingConfSecret }}
optional: false
nodeSelector:
{{- toYaml .Values.worker.nodeSelector | nindent 8 }}
affinity:
{{- toYaml .Values.worker.affinity | nindent 8 }}
tolerations:
{{- toYaml .Values.worker.tolerations | nindent 8 }}

View file

@ -0,0 +1,20 @@
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ include "timesketch.fullname" . }}-upload
labels:
{{- include "timesketch.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.upload.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.upload.persistence.size | quote }}
{{- if .Values.upload.persistence.storageClass }}
{{- if (eq "-" .Values.upload.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.upload.persistence.storageClass }}"
{{- end }}
{{- end }}

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "timesketch.fullname" . }}-frontend
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "timesketch.labels" . | nindent 4 }}
spec:
type: ClusterIP
ports:
- port: 8080
protocol: TCP
targetPort: 5000
selector:
app.kubernetes.io/component: frontend
{{- include "timesketch.selectorLabels" . | nindent 4 }}

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "timesketch.fullname" . }}
labels:
{{- include "timesketch.labels" . | nindent 4 }}

View file

@ -0,0 +1,31 @@
config:
externalUrl: https://cache.example.com/
persistence:
existingClaim: attic
initContainers:
dbInit:
image:
repository: ghcr.io/onedr0p/postgres-init
tag: "16"
envFrom:
- secretRef:
name: attic-secret
envFromSecret: attic-secret
image:
repository: ghcr.io/zhaofengli/attic
tag: 4dbdbee45728d8ce5788db6461aaaa89d98081f0
postgres:
secretName: attic-secret
resources:
limits:
memory: "3Gi"
cpu: "1000m"
# requests:
# cpu: 100m
# memory: 250Mi

View file

@ -0,0 +1,50 @@
config:
externalUrl: https://timesketch.example.com/
existingConfSecret: timesketch-conf
existingUserSecret: timesketch-user
createUser: true
initContainers:
dbInit:
image:
repository: ghcr.io/onedr0p/postgres-init
tag: "16"
envFrom:
- secretRef:
name: timesketch-secret
image:
repository: us-docker.pkg.dev/osdfir-registry/timesketch/timesketch
pullPolicy: IfNotPresent
tag: "20240508"
imagePullSecrets: []
worker:
podSecurityContext: {}
securityContext: {}
frontend:
podSecurityContext: {}
securityContext: {}
postgres:
secretName: timesketch-secret
upload:
persistence:
accessMode: ReadWriteMany
size: 10Gi
storageClass: ceph-filesystem
persistentVolumeClaim: timesketch-upload
resources:
limits:
memory: "3Gi"
cpu: "1000m"
requests:
cpu: 100m
memory: 250Mi
metrics:
enabled: true
port: 9001