From 8c26988846c7a9506613c7c073dce583bf9115cb Mon Sep 17 00:00:00 2001 From: jonasschultheiss Date: Tue, 9 Jun 2026 09:10:28 +0200 Subject: [PATCH 1/2] feat(helm): add Gateway API HTTPRoute and External Secrets Operator support Add HTTPRoute template as an alternative to the classic Ingress resource for users running Kubernetes Gateway API controllers. The template supports parentRefs, hostnames, flexible match rules with filters, and automatically selects port 80/443 based on the existing nginx TLS configuration. Add ExternalSecret template for integrating with the External Secrets Operator (ESO), allowing users to sync secrets from external stores (e.g. HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) instead of managing them directly in Helm values or Kubernetes Secrets. Both features are disabled by default and fully opt-in. --- .../templates/django-httproute.yaml | 55 +++++++++++++++++++ .../defectdojo/templates/external-secret.yaml | 37 +++++++++++++ helm/defectdojo/values.yaml | 37 +++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 helm/defectdojo/templates/django-httproute.yaml create mode 100644 helm/defectdojo/templates/external-secret.yaml diff --git a/helm/defectdojo/templates/django-httproute.yaml b/helm/defectdojo/templates/django-httproute.yaml new file mode 100644 index 00000000000..c04106ce7ff --- /dev/null +++ b/helm/defectdojo/templates/django-httproute.yaml @@ -0,0 +1,55 @@ +{{- if .Values.django.httpRoute.enabled -}} +{{- $fullName := include "defectdojo.fullname" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + defectdojo.org/component: django + app.kubernetes.io/name: {{ include "defectdojo.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "defectdojo.chart" . }} + {{- range $key, $value := .Values.extraLabels }} + {{ $key }}: {{ quote $value }} + {{- end }} + {{- with .Values.django.httpRoute.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if or .Values.extraAnnotations .Values.django.httpRoute.annotations }} + annotations: + {{- range $key, $value := .Values.extraAnnotations }} + {{ $key }}: {{ quote $value }} + {{- end }} + {{- with .Values.django.httpRoute.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- with .Values.django.httpRoute.parentRefs }} + parentRefs: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.django.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.django.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - group: "" + kind: Service + name: {{ $fullName }}-django + port: {{ $.Values.django.nginx.tls.enabled | ternary 443 80 }} + weight: 1 + {{- end }} +{{- end }} diff --git a/helm/defectdojo/templates/external-secret.yaml b/helm/defectdojo/templates/external-secret.yaml new file mode 100644 index 00000000000..4f3070430cc --- /dev/null +++ b/helm/defectdojo/templates/external-secret.yaml @@ -0,0 +1,37 @@ +{{- if .Values.externalSecret.enabled }} +{{- $fullName := include "defectdojo.fullname" . -}} +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + defectdojo.org/component: django + app.kubernetes.io/name: {{ include "defectdojo.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "defectdojo.chart" . }} + {{- range $key, $value := .Values.extraLabels }} + {{ $key }}: {{ quote $value }} + {{- end }} + {{- with .Values.externalSecret.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if or .Values.extraAnnotations .Values.externalSecret.annotations }} + annotations: + {{- range $key, $value := .Values.extraAnnotations }} + {{ $key }}: {{ quote $value }} + {{- end }} + {{- with .Values.externalSecret.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + refreshInterval: {{ .Values.externalSecret.refreshInterval | default "1h0m0s" }} + secretStoreRef: + {{- toYaml .Values.externalSecret.secretStoreRef | nindent 4 }} + target: + name: {{ .Values.externalSecret.targetSecretName }} + data: + {{- toYaml .Values.externalSecret.data | nindent 4 }} +{{- end }} diff --git a/helm/defectdojo/values.yaml b/helm/defectdojo/values.yaml index f59981dc463..0d521783448 100644 --- a/helm/defectdojo/values.yaml +++ b/helm/defectdojo/values.yaml @@ -13,6 +13,28 @@ createSecret: false createValkeySecret: false # -- create postgresql secret in defectdojo chart, outside of postgresql chart createPostgresqlSecret: false + +# -- External Secrets Operator integration +externalSecret: + # -- Enable the ExternalSecret resource + enabled: false + labels: {} + annotations: {} + # -- How often to refresh the secret from the external store + refreshInterval: 1h0m0s + # -- Reference to a SecretStore or ClusterSecretStore + secretStoreRef: + name: "" + kind: "" + # -- Name of the Kubernetes Secret created by ESO + targetSecretName: "" + # -- Data mappings from the external store + data: [] + # - secretKey: DD_ADMIN_PASSWORD + # remoteRef: + # key: my-vault-path + # property: admin-password + # -- Track configuration (trackConfig): will automatically respin application pods in case of config changes detection # can be: # 1. disabled (default) @@ -372,6 +394,21 @@ django: # `nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"` # `nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"` annotations: {} + # -- Kubernetes Gateway API HTTPRoute configuration (alternative to Ingress) + httpRoute: + enabled: false + labels: {} + annotations: {} + parentRefs: [] + # - name: my-gateway + # namespace: default + hostnames: [] + # - defectdojo.example.com + rules: [] + # - matches: + # - path: + # type: PathPrefix + # value: / nginx: # -- If empty, uses values from images.nginx.image image: From 05d3c9130befc5ea3014a3f6c85915721cc6bddb Mon Sep 17 00:00:00 2001 From: jonasschultheiss Date: Fri, 3 Jul 2026 13:52:25 +0200 Subject: [PATCH 2/2] fix(helm): address review feedback and CI failures - Fix HTTPRoute template YAML structure: emit list dash unconditionally per rule so match-less catch-all rules produce valid YAML arrays - Update artifacthub.io/changes annotation in Chart.yaml - Regenerate README.md via helm-docs - Add httpRoute and externalSecret fields to values.schema.json --- helm/defectdojo/Chart.yaml | 6 +- helm/defectdojo/README.md | 7 ++ .../templates/django-httproute.yaml | 14 ++-- helm/defectdojo/values.schema.json | 67 +++++++++++++++++++ 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index ebabf056e43..7eca4a337f8 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -34,4 +34,8 @@ dependencies: # description: Critical bug annotations: artifacthub.io/prerelease: "true" - artifacthub.io/changes: "" + artifacthub.io/changes: | + - kind: added + description: Add Gateway API HTTPRoute support as an alternative to Ingress + - kind: added + description: Add External Secrets Operator (ESO) support for external secret stores diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index 89b8ae46c79..71d18ba3116 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -618,6 +618,7 @@ A Helm chart for Kubernetes to install DefectDojo | django.extraInitContainers | list | `[]` | A list of additional initContainers to run before the uwsgi and nginx containers. | | django.extraVolumeMounts | list | `[]` | Array of additional volume mount points common to all containers and initContainers. | | django.extraVolumes | list | `[]` | A list of extra volumes to mount. | +| django.httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":[],"labels":{},"parentRefs":[],"rules":[]}` | Kubernetes Gateway API HTTPRoute configuration (alternative to Ingress) | | django.ingress.activateTLS | bool | `true` | | | django.ingress.annotations | object | `{}` | Restricts the type of ingress controller that can interact with our chart (nginx, traefik, ...) `kubernetes.io/ingress.class: nginx` Depending on the size and complexity of your scans, you might want to increase the default ingress timeouts if you see repeated 504 Gateway Timeouts `nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"` `nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"` | | django.ingress.enabled | bool | `true` | | @@ -683,6 +684,12 @@ A Helm chart for Kubernetes to install DefectDojo | django.uwsgi.startupProbe.periodSeconds | int | `5` | | | django.uwsgi.startupProbe.successThreshold | int | `1` | | | django.uwsgi.startupProbe.timeoutSeconds | int | `1` | | +| externalSecret | object | `{"annotations":{},"data":[],"enabled":false,"labels":{},"refreshInterval":"1h0m0s","secretStoreRef":{"kind":"","name":""},"targetSecretName":""}` | External Secrets Operator integration | +| externalSecret.data | list | `[]` | Data mappings from the external store | +| externalSecret.enabled | bool | `false` | Enable the ExternalSecret resource | +| externalSecret.refreshInterval | string | `"1h0m0s"` | How often to refresh the secret from the external store | +| externalSecret.secretStoreRef | object | `{"kind":"","name":""}` | Reference to a SecretStore or ClusterSecretStore | +| externalSecret.targetSecretName | string | `""` | Name of the Kubernetes Secret created by ESO | | extraAnnotations | object | `{}` | Annotations globally added to all resources | | extraConfigs | object | `{}` | To add extra variables not predefined by helm config it is possible to define in extraConfigs block, e.g. below: NOTE Do not store any kind of sensitive information inside of it ``` DD_SOCIAL_AUTH_AUTH0_OAUTH2_ENABLED: 'true' DD_SOCIAL_AUTH_AUTH0_KEY: 'dev' DD_SOCIAL_AUTH_AUTH0_DOMAIN: 'xxxxx' ``` | | extraEnv | list | `[]` | To add (or override) extra variables which need to be pulled from another configMap, you can use extraEnv. For example: ``` - name: DD_DATABASE_HOST valueFrom: configMapKeyRef: name: my-other-postgres-configmap key: cluster_endpoint ``` | diff --git a/helm/defectdojo/templates/django-httproute.yaml b/helm/defectdojo/templates/django-httproute.yaml index c04106ce7ff..e426d786eb8 100644 --- a/helm/defectdojo/templates/django-httproute.yaml +++ b/helm/defectdojo/templates/django-httproute.yaml @@ -37,14 +37,14 @@ spec: {{- end }} rules: {{- range .Values.django.httpRoute.rules }} - {{- with .matches }} - - matches: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .filters }} + - {{- with .matches }} + matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} filters: - {{- toYaml . | nindent 8 }} - {{- end }} + {{- toYaml . | nindent 8 }} + {{- end }} backendRefs: - group: "" kind: Service diff --git a/helm/defectdojo/values.schema.json b/helm/defectdojo/values.schema.json index 54120f850bf..fc08c4c95b9 100644 --- a/helm/defectdojo/values.schema.json +++ b/helm/defectdojo/values.schema.json @@ -388,6 +388,46 @@ "description": "create valkey secret in defectdojo chart, outside of valkey chart", "type": "boolean" }, + "externalSecret": { + "description": "External Secrets Operator integration", + "type": "object", + "properties": { + "enabled": { + "description": "Enable the ExternalSecret resource", + "type": "boolean" + }, + "labels": { + "type": "object" + }, + "annotations": { + "type": "object" + }, + "refreshInterval": { + "description": "How often to refresh the secret from the external store", + "type": "string" + }, + "secretStoreRef": { + "description": "Reference to a SecretStore or ClusterSecretStore", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string" + } + } + }, + "targetSecretName": { + "description": "Name of the Kubernetes Secret created by ESO", + "type": "string" + }, + "data": { + "description": "Data mappings from the external store", + "type": "array" + } + } + }, "dbMigrationChecker": { "type": "object", "properties": { @@ -511,6 +551,33 @@ "description": "A list of extra volumes to mount.", "type": "array" }, + "httpRoute": { + "description": "Kubernetes Gateway API HTTPRoute configuration (alternative to Ingress)", + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "labels": { + "type": "object" + }, + "annotations": { + "type": "object" + }, + "parentRefs": { + "type": "array" + }, + "hostnames": { + "type": "array", + "items": { + "type": "string" + } + }, + "rules": { + "type": "array" + } + } + }, "ingress": { "type": "object", "properties": {