From 5ad5613ea0b508fa536f174ed691bed7ace22977 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 13 May 2024 17:15:39 +0200 Subject: [PATCH] feat(yeti): add it --- charts/yeti/Chart.yaml | 20 ++ charts/yeti/README.org | 1 + charts/yeti/templates/_env.tpl | 71 +++++++ charts/yeti/templates/_helpers.tpl | 45 +++++ charts/yeti/templates/config.yaml | 181 ++++++++++++++++++ charts/yeti/templates/configmap-frontend.yaml | 25 +++ charts/yeti/templates/deployment-api.yaml | 62 ++++++ .../yeti/templates/deployment-frontend.yaml | 64 +++++++ charts/yeti/templates/deployment-tasks.yaml | 57 ++++++ charts/yeti/templates/service-server.yaml | 33 ++++ charts/yeti/templates/serviceaccount.yaml | 10 + charts/yeti/tests/values.yaml | 31 +++ charts/yeti/values.yaml | 71 +++++++ 13 files changed, 671 insertions(+) create mode 100644 charts/yeti/Chart.yaml create mode 100644 charts/yeti/README.org create mode 100644 charts/yeti/templates/_env.tpl create mode 100644 charts/yeti/templates/_helpers.tpl create mode 100644 charts/yeti/templates/config.yaml create mode 100644 charts/yeti/templates/configmap-frontend.yaml create mode 100644 charts/yeti/templates/deployment-api.yaml create mode 100644 charts/yeti/templates/deployment-frontend.yaml create mode 100644 charts/yeti/templates/deployment-tasks.yaml create mode 100644 charts/yeti/templates/service-server.yaml create mode 100644 charts/yeti/templates/serviceaccount.yaml create mode 100644 charts/yeti/tests/values.yaml create mode 100644 charts/yeti/values.yaml diff --git a/charts/yeti/Chart.yaml b/charts/yeti/Chart.yaml new file mode 100644 index 0000000..b84cea0 --- /dev/null +++ b/charts/yeti/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: yeti +version: 1.0.2 +description: A Helm chart for Yeti Kubernetes deployments. +keywords: +- yeti +- dfir +- security +home: "https://yeti-platform.io/" +maintainers: +- name: Tommy Skaug + email: tommy@skaug.me + url: https://github.com/tommy-skaug/charts +sources: +- https://github.com/yeti-platform/yeti +- https://github.com/google/osdfir-infrastructure +appVersion: "latest" +annotations: + category: Security + licenses: Apache-2.0 diff --git a/charts/yeti/README.org b/charts/yeti/README.org new file mode 100644 index 0000000..274b24c --- /dev/null +++ b/charts/yeti/README.org @@ -0,0 +1 @@ +* TODO figure out what the pvc was used for and if we can do without it \ No newline at end of file diff --git a/charts/yeti/templates/_env.tpl b/charts/yeti/templates/_env.tpl new file mode 100644 index 0000000..66857de --- /dev/null +++ b/charts/yeti/templates/_env.tpl @@ -0,0 +1,71 @@ +{{/* +Init Container for when a Timesketch pod starts. To prevent duplicate code, +this file has been created which then applies to both the Timesketch Web and +Worker pod upon startup. +*/}} +{{- define "yeti.envs" -}} +- name: YETI_REDIS_HOST + value: {{ .Values.redis.host }} +- name: YETI_REDIS_PORT + value: "{{ .Values.redis.port }}" +- name: YETI_REDIS_DATABASE + value: "0" +- name: YETI_ARANGODB_HOST + value: {{ .Values.arangodb.host }} +- name: YETI_ARANGODB_PORT + value: {{ .Values.arangodb.port | quote }} +- name: YETI_ARANGODB_DATABASE + value: {{ .Values.arangodb.database | quote }} +- name: YETI_ARANGODB_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_ARANGODB_USERNAME +- name: YETI_ARANGODB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_ARANGODB_PASSWORD +- name: YETI_AUTH_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_AUTH_SECRET_KEY +- name: YETI_AUTH_ALGORITHM + value: HS256 +- name: YETI_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES + value: "30" +- name: YETI_AUTH_ENABLED + value: "True" +- name: YETI_SYSTEM_PLUGINS_PATH + value: "./plugins" +- name: YETI_USER_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_USER +- name: YETI_USER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_USER_PASSWORD +- name: YETI_API_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_API_KEY +{{- if .Values.timesketch.enabled }} +- name: YETI_TIMESKETCH_ENDPOINT + value: {{ .Values.timesketch.endpoint }} +- name: YETI_TIMESKETCH_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_TIMESKETCH_USERNAME +- name: YETI_TIMESKETCH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: YETI_TIMESKETCH_PASSWORD +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/yeti/templates/_helpers.tpl b/charts/yeti/templates/_helpers.tpl new file mode 100644 index 0000000..cf5d0d8 --- /dev/null +++ b/charts/yeti/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "yeti.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "yeti.fullname" -}} +{{- if contains .Chart.Name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name "yeti" | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "yeti.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "yeti.labels" -}} +helm.sh/chart: {{ include "yeti.chart" . }} +{{ include "yeti.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 "yeti.selectorLabels" -}} +app.kubernetes.io/name: {{ include "yeti.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/yeti/templates/config.yaml b/charts/yeti/templates/config.yaml new file mode 100644 index 0000000..b949e13 --- /dev/null +++ b/charts/yeti/templates/config.yaml @@ -0,0 +1,181 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "timesketch.fullname" . }}-init-configmap + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "timesketch.labels" . | nindent 4 }} +data: + yeti.conf: | + [system] + + ## + ## Basic system settings + ## + + # if export_path is not set, then the default value is /tmp + export_path = /opt/yeti/exports + logging = /var/log/yeti_user_activity.log + plugins_path = ./plugins + audit_logfile = /var/log/yeti_audit.log + + # Public scheme + hostname + port for Yeti. Use it if you want to specify an + # OIDC callback + # for testing use also a port number, e.g. http://localhost:8000 + # webroot = + + [auth] + + ## + ## Use these settings to configure Yeti authentication. + ## + + # oidc, local + module = oidc + + # to get a stronger value run: + # openssl rand -hex 32 + # SECRET_KEY = SECRET + # ALGORITHM = HS256 + # ACCESS_TOKEN_EXPIRE_MINUTES = 30 + enabled = True + + # OIDC + # + # Google can be used as an OIDC provider: + # See Instructions here: https://developers.google.com/identity/protocols/oauth2 + # + # OIDC_CLIENT_ID = LONGRANDOMSTRING.apps.googleusercontent.com + # OIDC_CLIENT_SECRET = BLABLA-BLABLABLA + # OIDC_DISCOVERY_URL = https://accounts.google.com/.well-known/openid-configuration + + [tag] + + ## + ## Use these settings to configure Yeti tags. + ## If you specify default_tag_expiration = 7776000, then the tag will expire for 90 days. + ## Value must be in seconds (7776000 seconds is 90 days). + ## + + # default_tag_expiration = 7776000 + + [arangodb] + + ## + ## Use these settings to configure how to connect to your ArangoDB database. + ## All settings are optional, with default values being the one in the comment. + ## If you do not specify a username and password, there will be no authentication. + ## + + # host = arangodb + # port = 8529 + # username = root + # password = + # database = yeti_dev + + [redis] + + ## + ## Use these settings to configure how to connect to your redis server. + ## All settings are optional, with default values being the one in the comment. + ## + + # host = redis + # port = 6379 + # database = 0 + # tls = ok + + [misp] + + ## + ## Use this setting in order to specify a comma-separated list of MISP instances + ## that should be taken into account by the MISP feed. + ## + + # instances = misp_1 + + [misp_1] + + ## + ## For each instance in the 'misp.instances' setting, you should specify a + ## configuration block with this format, in order to specify at least the URL + ## and the auth key. + ## + + # name = MISP_1 + # url = MISP_URL + # key = MISP_AUTH_KEY + # galaxy_filter = filtering_galaxy_to_drop + # days = days_history_to_change_by_default_60_days + # verifycert = true_or_false + + [proxy] + + # Format proxies like socks5://user:pass@host:port + + http = + https = + + [github] + # Generate token: https://github.com/settings/tokens + # Select repo only + # no token - limit 60 r/h + # w/ token - limit 5k r/h + # token = + + [otx] + key = YourOTXKey + days = 1 + + [abuseIPDB] + key = YourKey + + [phishtank] + key= + + [vt] + key= + + [passivedns] + login= + password= + url= + + [circl_passivessl] + username= + password= + + [circl_pdns] + username= + password= + + [dnsdb] + api_key= + + [macaddressio] + api_key= + + [malshare] + api_key= + + [timesketch] + endpoint = + username = + password = + + [censys] + api_key = + secret = + + [shodan] + + # Set result_limit to -1 for unlimited results, default is 100 + + api_key = + result_limit = + + + [dfiq] + + # Comma-separated list of additional directories to load DFIQ objects from. + extra_dirs = /dfiq diff --git a/charts/yeti/templates/configmap-frontend.yaml b/charts/yeti/templates/configmap-frontend.yaml new file mode 100644 index 0000000..b5a918f --- /dev/null +++ b/charts/yeti/templates/configmap-frontend.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "yeti.fullname" . }}-frontend-nginx + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "yeti.labels" . | nindent 4 }} +data: + default.conf: | + server { + + root /www; + + location /api/v2 { + proxy_pass http://{{ include "yeti.fullname" . }}-api.{{ .Release.Namespace }}.svc.cluster.local:8080; + } + + location ~(^/docs|^/openapi.json) { + proxy_pass http://{{ include "yeti.fullname" . }}-api.{{ .Release.Namespace }}.svc.cluster.local:8080; + } + + location / { + try_files $uri $uri/ /index.html; + } + } \ No newline at end of file diff --git a/charts/yeti/templates/deployment-api.yaml b/charts/yeti/templates/deployment-api.yaml new file mode 100644 index 0000000..c4c4b0f --- /dev/null +++ b/charts/yeti/templates/deployment-api.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "yeti.fullname" . }}-api + namespace: {{ .Release.Namespace | quote }} + labels: + app.kubernetes.io/component: api + {{- include "yeti.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: api + {{- include "yeti.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Have Deployment restart after each upgrade + roll: {{ randAlphaNum 5 | quote }} + {{- if .Values.metrics.enabled }} + prometheus.io/port: {{ .Values.metrics.port | quote }} + prometheus.io/scrape: "true" + {{- end }} + labels: + app.kubernetes.io/component: api + {{- include "yeti.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ .Values.serviceAccount.name }} + securityContext: + {{- toYaml .Values.backend.podSecurityContext | nindent 8 }} + containers: + - name: api + securityContext: + {{- toYaml .Values.backend.containerSecurityContext | nindent 12 }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + command: ["sh", "-c", "/docker-entrypoint.sh webserver"] + lifecycle: + postStart: + exec: + command: ["sh", "-c", "poetry run python yetictl/cli.py create-user $YETI_USER_USERNAME $YETI_USER_PASSWORD --api_key $YETI_API_KEY --admin || true"] + env: + {{- include "yeti.envs" . | nindent 12 }} + ports: + {{- if .Values.metrics.enabled }} + - containerPort: {{ .Values.metrics.port }} + {{- end }} + - containerPort: 8000 + resources: + {{- toYaml .Values.backend.api.resources | nindent 12 }} + {{- with .Values.backend.api.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.api.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.api.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/yeti/templates/deployment-frontend.yaml b/charts/yeti/templates/deployment-frontend.yaml new file mode 100644 index 0000000..86b99ad --- /dev/null +++ b/charts/yeti/templates/deployment-frontend.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "yeti.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app.kubernetes.io/component: frontend + {{- include "yeti.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: frontend + {{- include "yeti.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Have Deployment restart after each upgrade + roll: {{ randAlphaNum 5 | quote }} + {{- if .Values.metrics.enabled }} + prometheus.io/port: {{ .Values.metrics.port | quote }} + prometheus.io/scrape: "true" + {{- end }} + labels: + app.kubernetes.io/component: frontend + {{- include "yeti.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ .Values.serviceAccount.name }} + securityContext: + {{- toYaml .Values.frontend.podSecurityContext | nindent 8 }} + containers: + - name: frontend + securityContext: + {{- toYaml .Values.frontend.containerSecurityContext | nindent 12 }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + {{- if .Values.metrics.enabled }} + - containerPort: {{ .Values.metrics.port }} + {{- end }} + - containerPort: 80 + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} + volumeMounts: + - mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + name: nginx-config + readOnly: true + volumes: + - name: nginx-config + configMap: + name: {{ include "yeti.fullname" . }}-frontend-nginx + {{- with .Values.frontend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/yeti/templates/deployment-tasks.yaml b/charts/yeti/templates/deployment-tasks.yaml new file mode 100644 index 0000000..840c77d --- /dev/null +++ b/charts/yeti/templates/deployment-tasks.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "yeti.fullname" . }}-tasks + namespace: {{ .Release.Namespace | quote }} + labels: + app.kubernetes.io/component: tasks + {{- include "yeti.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: tasks + {{- include "yeti.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Have Deployment restart after each upgrade + roll: {{ randAlphaNum 5 | quote }} + {{- if .Values.metrics.enabled }} + prometheus.io/port: {{ .Values.metrics.port | quote }} + prometheus.io/scrape: "true" + {{- end }} + labels: + app.kubernetes.io/component: tasks + {{- include "yeti.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ .Values.serviceAccount.name }} + securityContext: + {{- toYaml .Values.backend.podSecurityContext | nindent 8 }} + containers: + - name: tasks + securityContext: + {{- toYaml .Values.backend.tasks.containerSecurityContext | nindent 12 }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + command: ["sh", "-c", "echo sleeping && sleep 75 && /docker-entrypoint.sh tasks"] + env: + {{- include "yeti.envs" . | nindent 12 }} + ports: + {{- if .Values.metrics.enabled }} + - containerPort: {{ .Values.metrics.port }} + {{- end }} + resources: + {{- toYaml .Values.backend.tasks.resources | nindent 12 }} + {{- with .Values.backend.tasks.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tasks.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tasks.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/yeti/templates/service-server.yaml b/charts/yeti/templates/service-server.yaml new file mode 100644 index 0000000..dfcdb3a --- /dev/null +++ b/charts/yeti/templates/service-server.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "yeti.fullname" . }}-frontend + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "yeti.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 8080 + protocol: TCP + targetPort: 80 + selector: + app.kubernetes.io/component: frontend + {{- include "yeti.selectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "yeti.fullname" . }}-api + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "yeti.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 8080 + protocol: TCP + targetPort: 8000 + selector: + app.kubernetes.io/component: api + {{- include "yeti.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/yeti/templates/serviceaccount.yaml b/charts/yeti/templates/serviceaccount.yaml new file mode 100644 index 0000000..b2beaee --- /dev/null +++ b/charts/yeti/templates/serviceaccount.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.serviceAccount.name }} + labels: + {{- include "yeti.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/charts/yeti/tests/values.yaml b/charts/yeti/tests/values.yaml new file mode 100644 index 0000000..224a0c7 --- /dev/null +++ b/charts/yeti/tests/values.yaml @@ -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 diff --git a/charts/yeti/values.yaml b/charts/yeti/values.yaml new file mode 100644 index 0000000..5fd5527 --- /dev/null +++ b/charts/yeti/values.yaml @@ -0,0 +1,71 @@ +existingSecret: yeti-secret + +frontend: + image: + repository: yetiplatform/yeti-frontend + tag: 2.1.6 + pullPolicy: IfNotPresent + podSecurityContext: {} + containerSecurityContext: {} + nodeSelector: {} + affinity: {} + tolerations: {} + resources: + requests: + memory: "100Mi" + cpu: "50m" + limits: + memory: "2Gi" + cpu: "1000m" + +backend: + image: + repository: yetiplatform/yeti + tag: 2.1.6 + pullPolicy: IfNotPresent + podSecurityContext: {} + api: + containerSecurityContext: {} + nodeSelector: {} + affinity: {} + tolerations: {} + resources: + requests: + memory: "100Mi" + cpu: "50m" + limits: + memory: "2Gi" + cpu: "1000m" + tasks: + containerSecurityContext: {} + nodeSelector: {} + affinity: {} + tolerations: {} + resources: + requests: + memory: "100Mi" + cpu: "50m" + limits: + memory: "2Gi" + cpu: "1000m" + +redis: + host: dragonfly.databases.svc.cluster.local + port: 6397 + +arangodb: + database: yeti + host: arango-dfir-cluster-ea.databases.svc.cluster.local + port: 8529 + +timesketch: + enabled: false + endpoint: "" + +serviceAccount: + name: "yeti" + annotations: {} + +metrics: + enabled: true + port: 9001