From 12ec00864517e69d5ca0b69e19e7bd361708b595 Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sat, 18 Apr 2026 22:09:20 +0200 Subject: [PATCH 1/8] Comment calls to unverified container images --- CONTRIBUTING.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17e24b3..0907f7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing +# Contributing ## File organization @@ -38,6 +38,8 @@ Install the application: helm upgrade --install myapp . -f values.yaml --namespace myns --create-namespace ``` + ## Documentation website From 7a1e23a04d7a6873727c6fca3e8119ebd43066fd Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sat, 18 Apr 2026 22:09:32 +0200 Subject: [PATCH 2/8] Start dvwa helm chart --- charts/dvwa/.helmignore | 4 + charts/dvwa/CONTRIBUTING.md | 24 +++++ charts/dvwa/Chart.yaml | 14 +++ charts/dvwa/README.md | 77 ++++++++++++++ charts/dvwa/templates/NOTES.txt | 51 ++++++++++ charts/dvwa/templates/_helpers.tpl | 49 +++++++++ charts/dvwa/templates/deployment.yaml | 140 ++++++++++++++++++++++++++ charts/dvwa/templates/ingress.yaml | 35 +++++++ charts/dvwa/templates/pvc.yaml | 17 ++++ charts/dvwa/templates/secret.yaml | 12 +++ charts/dvwa/templates/service.yaml | 18 ++++ charts/dvwa/values.yaml | 59 +++++++++++ 12 files changed, 500 insertions(+) create mode 100644 charts/dvwa/.helmignore create mode 100644 charts/dvwa/CONTRIBUTING.md create mode 100644 charts/dvwa/Chart.yaml create mode 100644 charts/dvwa/README.md create mode 100644 charts/dvwa/templates/NOTES.txt create mode 100644 charts/dvwa/templates/_helpers.tpl create mode 100644 charts/dvwa/templates/deployment.yaml create mode 100644 charts/dvwa/templates/ingress.yaml create mode 100644 charts/dvwa/templates/pvc.yaml create mode 100644 charts/dvwa/templates/secret.yaml create mode 100644 charts/dvwa/templates/service.yaml create mode 100644 charts/dvwa/values.yaml diff --git a/charts/dvwa/.helmignore b/charts/dvwa/.helmignore new file mode 100644 index 0000000..1b59443 --- /dev/null +++ b/charts/dvwa/.helmignore @@ -0,0 +1,4 @@ +.DS_Store +.git +.gitignore +README.md diff --git a/charts/dvwa/CONTRIBUTING.md b/charts/dvwa/CONTRIBUTING.md new file mode 100644 index 0000000..8710570 --- /dev/null +++ b/charts/dvwa/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contribution guide + +## Validate on a test cluster + +Create a `values.mine.yaml` file: + +```yaml +``` + +Install, or update, the chart: + +```bash +helm upgrade --install dvwa . -f values.yaml -f values.mine.yaml --namespace dvwa --create-namespace +``` + +```bash +kubectl get all -n dvwa +``` + +Open the web application in a browser. + +```bash +echo "DVWA URL → http://$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}'):30080" +``` diff --git a/charts/dvwa/Chart.yaml b/charts/dvwa/Chart.yaml new file mode 100644 index 0000000..12f7c91 --- /dev/null +++ b/charts/dvwa/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: dvwa +description: Damn Vulnerable Web Application - for container security workshops +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - security + - vulnerable + - workshop + - training +maintainers: + - name: devpro + email: bertrand@devpro.fr diff --git a/charts/dvwa/README.md b/charts/dvwa/README.md new file mode 100644 index 0000000..170c4a0 --- /dev/null +++ b/charts/dvwa/README.md @@ -0,0 +1,77 @@ +# DVWA Helm Chart + +Helm chart for [Damn Vulnerable Web Application](https://github.com/digininja/DVWA) — designed for container security workshops. + +## Architecture + +Single pod with two containers: + +- **DVWA** — the PHP web app (port 80) +- **MariaDB** — sidecar database (port 3306, localhost only) + +## Quick Start + +Add the chart repository: + +```bash +helm repo add devpro https://devpro.github.io/helm-charts +helm repo update +``` + +Create the `values.yaml` file to override [default values](values.yaml). + +Install the chart: + +```bash +helm upgrade --install dvwa devpro/dvwa -f values.yaml --namespace dvwa --create-namespace +``` + +Verify the installation: + +```bash +kubectl rollout status deployment/dvwa-dvwa -n dvwa + +export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') +echo "Open: http://$NODE_IP:30080" +``` + +### First-time Setup + +1. Browse to the URL above +2. Log in: `admin` / `password` +3. Click **Create / Reset Database** +4. Log in again — ready! + +## Values + +Key | Default | Description +----------------------|------------|----------------------------------------- +`dvwa.adminUsername` | `admin` | DVWA login +`dvwa.adminPassword` | `password` | DVWA password +`dvwa.securityLevel` | `low` | `low` / `medium` / `high` / `impossible` +`service.type` | `NodePort` | Service type +`service.nodePort` | `30080` | NodePort value +`persistence.enabled` | `false` | Persist MariaDB data across pod restarts + +## Security Level + +Change mid-workshop to increase difficulty: + +```bash +helm upgrade dvwa ./dvwa -n dvwa --set dvwa.securityLevel=medium +``` + +## Uninstall + +```bash +helm uninstall dvwa -n dvwa +kubectl delete namespace dvwa +``` + +## Going further + +Check the [contribution guide](CONTRIBUTING.md). + +--- + +> ⚠️ **FOR WORKSHOP USE ONLY** — intentionally vulnerable, never expose to the internet. diff --git a/charts/dvwa/templates/NOTES.txt b/charts/dvwa/templates/NOTES.txt new file mode 100644 index 0000000..8882fa2 --- /dev/null +++ b/charts/dvwa/templates/NOTES.txt @@ -0,0 +1,51 @@ +🎯 DVWA (Damn Vulnerable Web Application) deployed! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ACCESS THE APP +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +{{- if eq .Values.service.type "NodePort" }} +Get the Node IP: + export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + +Open in browser: + http://$NODE_IP:{{ .Values.service.nodePort }} +{{- else if eq .Values.service.type "ClusterIP" }} +Forward the port: + kubectl port-forward svc/{{ include "dvwa.fullname" . }} 8080:80 -n {{ .Release.Namespace }} + +Open in browser: + http://localhost:8080 +{{- end }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + CREDENTIALS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Username : {{ .Values.dvwa.adminUsername }} + Password : {{ .Values.dvwa.adminPassword }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + FIRST-TIME SETUP (important!) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. Wait ~30s for MariaDB to initialise +2. Browse to the URL above +3. Log in with the credentials above +4. Click "Create / Reset Database" on the setup page +5. Log in again — you're ready! + +Current security level: {{ .Values.dvwa.securityLevel | upper }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + WORKSHOP EXERCISES +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Available vulnerabilities to explore: + • SQL Injection + • Command Injection + • XSS (Reflected & Stored) + • File Inclusion / Path Traversal + • File Upload (arbitrary) + • CSRF + • Brute Force + • Insecure CAPTCHA + +⚠️ FOR WORKSHOP USE ONLY — never expose to the internet! diff --git a/charts/dvwa/templates/_helpers.tpl b/charts/dvwa/templates/_helpers.tpl new file mode 100644 index 0000000..27af2b0 --- /dev/null +++ b/charts/dvwa/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "dvwa.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "dvwa.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart label. +*/}} +{{- define "dvwa.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "dvwa.labels" -}} +helm.sh/chart: {{ include "dvwa.chart" . }} +{{ include "dvwa.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "dvwa.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dvwa.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/dvwa/templates/deployment.yaml b/charts/dvwa/templates/deployment.yaml new file mode 100644 index 0000000..14aedd1 --- /dev/null +++ b/charts/dvwa/templates/deployment.yaml @@ -0,0 +1,140 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "dvwa.fullname" . }} + labels: + {{- include "dvwa.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "dvwa.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "dvwa.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + + initContainers: + # Wait for MariaDB to be ready before starting DVWA + - name: wait-for-db + image: busybox:1.36 + command: + - sh + - -c + - | + echo "Waiting for MariaDB..." + until nc -z 127.0.0.1 3306; do sleep 2; done + echo "MariaDB is up!" + + containers: + # ── DVWA ────────────────────────────────────────────────── + - name: dvwa + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + env: + - name: DB_SERVER + value: "127.0.0.1" + - name: DB_PORT + value: "3306" + - name: DB_DATABASE + value: {{ .Values.mariadb.database | quote }} + - name: DB_USER + value: {{ .Values.mariadb.username | quote }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "dvwa.fullname" . }}-secret + key: mariadb-password + - name: DVWA_NO_HTTPS + value: "true" + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + readinessProbe: + httpGet: + path: /login.php + port: 80 + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 10 + livenessProbe: + httpGet: + path: /login.php + port: 80 + initialDelaySeconds: 40 + periodSeconds: 15 + + # ── MariaDB sidecar ─────────────────────────────────────── + - name: mariadb + image: {{ .Values.mariadb.image | quote }} + ports: + - name: mysql + containerPort: 3306 + protocol: TCP + env: + - name: MARIADB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "dvwa.fullname" . }}-secret + key: mariadb-root-password + - name: MARIADB_DATABASE + value: {{ .Values.mariadb.database | quote }} + - name: MARIADB_USER + value: {{ .Values.mariadb.username | quote }} + - name: MARIADB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "dvwa.fullname" . }}-secret + key: mariadb-password + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 300m + memory: 256Mi + {{- if .Values.persistence.enabled }} + volumeMounts: + - name: mariadb-data + mountPath: /var/lib/mysql + {{- end }} + readinessProbe: + exec: + command: + - sh + - -c + - mysqladmin ping -u root -p${MARIADB_ROOT_PASSWORD} + initialDelaySeconds: 10 + periodSeconds: 5 + + {{- if .Values.persistence.enabled }} + volumes: + - name: mariadb-data + persistentVolumeClaim: + claimName: {{ include "dvwa.fullname" . }}-mariadb + {{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/dvwa/templates/ingress.yaml b/charts/dvwa/templates/ingress.yaml new file mode 100644 index 0000000..8b3e811 --- /dev/null +++ b/charts/dvwa/templates/ingress.yaml @@ -0,0 +1,35 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "dvwa.fullname" . }} + labels: + {{- include "dvwa.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "dvwa.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/dvwa/templates/pvc.yaml b/charts/dvwa/templates/pvc.yaml new file mode 100644 index 0000000..3fd2073 --- /dev/null +++ b/charts/dvwa/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "dvwa.fullname" . }}-mariadb + labels: + {{- include "dvwa.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- end }} diff --git a/charts/dvwa/templates/secret.yaml b/charts/dvwa/templates/secret.yaml new file mode 100644 index 0000000..195d561 --- /dev/null +++ b/charts/dvwa/templates/secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "dvwa.fullname" . }}-secret + labels: + {{- include "dvwa.labels" . | nindent 4 }} +type: Opaque +stringData: + mariadb-root-password: {{ .Values.mariadb.rootPassword | quote }} + mariadb-password: {{ .Values.mariadb.password | quote }} + dvwa-admin-username: {{ .Values.dvwa.adminUsername | quote }} + dvwa-admin-password: {{ .Values.dvwa.adminPassword | quote }} diff --git a/charts/dvwa/templates/service.yaml b/charts/dvwa/templates/service.yaml new file mode 100644 index 0000000..4731736 --- /dev/null +++ b/charts/dvwa/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "dvwa.fullname" . }} + labels: + {{- include "dvwa.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "dvwa.selectorLabels" . | nindent 4 }} + ports: + - name: http + protocol: TCP + port: {{ .Values.service.port }} + targetPort: http + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/charts/dvwa/values.yaml b/charts/dvwa/values.yaml new file mode 100644 index 0000000..6918291 --- /dev/null +++ b/charts/dvwa/values.yaml @@ -0,0 +1,59 @@ +replicaCount: 1 + +image: + repository: ghcr.io/digininja/dvwa + pullPolicy: Always + tag: "latest" + +dvwa: + adminUsername: admin + adminPassword: password + # security level: low, medium, high, impossible + securityLevel: low + +# MariaDB (bundled sidecar) +mariadb: + image: mariadb:10.11 + rootPassword: dvwa + database: dvwa + username: dvwa + password: dvwa + +service: + type: NodePort + port: 80 + nodePort: 30080 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: dvwa.local + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# Run as root — intentionally insecure for the workshop +podSecurityContext: {} + +securityContext: {} + +# Persist DB between pod restarts (optional) +persistence: + enabled: false + storageClass: "" + size: 1Gi + +nodeSelector: {} +tolerations: [] +affinity: {} From d96e96e2ee07cf243e52f52cff4fb4cabd45f385 Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sat, 18 Apr 2026 22:33:58 +0200 Subject: [PATCH 3/8] Update dvwa chart --- charts/dvwa/CONTRIBUTING.md | 17 ++++++--- charts/dvwa/templates/NOTES.txt | 51 +++++++++++++++------------ charts/dvwa/templates/deployment.yaml | 26 ++++---------- charts/dvwa/templates/ingress.yaml | 37 +++++++++---------- charts/dvwa/templates/service.yaml | 3 -- charts/dvwa/values.yaml | 20 +++++------ 6 files changed, 74 insertions(+), 80 deletions(-) diff --git a/charts/dvwa/CONTRIBUTING.md b/charts/dvwa/CONTRIBUTING.md index 8710570..1f88bb3 100644 --- a/charts/dvwa/CONTRIBUTING.md +++ b/charts/dvwa/CONTRIBUTING.md @@ -5,20 +5,27 @@ Create a `values.mine.yaml` file: ```yaml +ingress: + enabled: true + domain: "tfbckmdb.console.$SANDBOX_ID.instruqt.io" + className: "traefik" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + tls: + enabled: true + ``` -Install, or update, the chart: +Install the chart: ```bash helm upgrade --install dvwa . -f values.yaml -f values.mine.yaml --namespace dvwa --create-namespace ``` +Wait for all pods to be ready: + ```bash kubectl get all -n dvwa ``` Open the web application in a browser. - -```bash -echo "DVWA URL → http://$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}'):30080" -``` diff --git a/charts/dvwa/templates/NOTES.txt b/charts/dvwa/templates/NOTES.txt index 8882fa2..da61858 100644 --- a/charts/dvwa/templates/NOTES.txt +++ b/charts/dvwa/templates/NOTES.txt @@ -4,18 +4,19 @@ ACCESS THE APP ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -{{- if eq .Values.service.type "NodePort" }} -Get the Node IP: - export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') - -Open in browser: - http://$NODE_IP:{{ .Values.service.nodePort }} -{{- else if eq .Values.service.type "ClusterIP" }} -Forward the port: - kubectl port-forward svc/{{ include "dvwa.fullname" . }} 8080:80 -n {{ .Release.Namespace }} - -Open in browser: - http://localhost:8080 +{{- if and .Values.ingress.enabled .Values.ingress.domain }} + {{- if .Values.ingress.tls.enabled }} + URL: https://{{ .Values.ingress.domain }} + {{- else }} + URL: http://{{ .Values.ingress.domain }} + {{- end }} + + TLS certificate will be issued automatically by cert-manager. + Check status: kubectl get certificate -n {{ .Release.Namespace }} +{{- else }} + Port-forward: + kubectl port-forward svc/{{ include "dvwa.fullname" . }} 8080:80 -n {{ .Release.Namespace }} + Then open: http://localhost:8080 {{- end }} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -35,17 +36,21 @@ Open in browser: Current security level: {{ .Values.dvwa.securityLevel | upper }} +Change security level mid-workshop: + helm upgrade {{ .Release.Name }} ./dvwa -n {{ .Release.Namespace }} \ + --set ingress.domain={{ .Values.ingress.domain }} \ + --set dvwa.securityLevel=medium + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - WORKSHOP EXERCISES + USEFUL COMMANDS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Available vulnerabilities to explore: - • SQL Injection - • Command Injection - • XSS (Reflected & Stored) - • File Inclusion / Path Traversal - • File Upload (arbitrary) - • CSRF - • Brute Force - • Insecure CAPTCHA + # Certificate status + kubectl get certificate,certificaterequest -n {{ .Release.Namespace }} + + # Pod logs + kubectl logs -l app.kubernetes.io/name=dvwa -n {{ .Release.Namespace }} -c dvwa + + # MariaDB logs + kubectl logs -l app.kubernetes.io/name=dvwa -n {{ .Release.Namespace }} -c mariadb -⚠️ FOR WORKSHOP USE ONLY — never expose to the internet! +⚠️ FOR WORKSHOP USE ONLY — never expose to the internet long-term! diff --git a/charts/dvwa/templates/deployment.yaml b/charts/dvwa/templates/deployment.yaml index 14aedd1..afb6471 100644 --- a/charts/dvwa/templates/deployment.yaml +++ b/charts/dvwa/templates/deployment.yaml @@ -1,4 +1,4 @@ -apiVersion: apps/v1 +apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "dvwa.fullname" . }} @@ -19,20 +19,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} - initContainers: - # Wait for MariaDB to be ready before starting DVWA - - name: wait-for-db - image: busybox:1.36 - command: - - sh - - -c - - | - echo "Waiting for MariaDB..." - until nc -z 127.0.0.1 3306; do sleep 2; done - echo "MariaDB is up!" - containers: - # ── DVWA ────────────────────────────────────────────────── - name: dvwa image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} @@ -56,6 +43,8 @@ spec: key: mariadb-password - name: DVWA_NO_HTTPS value: "true" + - name: DVWA_SECURITY + value: {{ .Values.dvwa.securityLevel | quote }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -66,17 +55,16 @@ spec: httpGet: path: /login.php port: 80 - initialDelaySeconds: 20 + initialDelaySeconds: 30 periodSeconds: 10 - failureThreshold: 10 + failureThreshold: 20 livenessProbe: httpGet: path: /login.php port: 80 - initialDelaySeconds: 40 - periodSeconds: 15 + initialDelaySeconds: 90 + periodSeconds: 20 - # ── MariaDB sidecar ─────────────────────────────────────── - name: mariadb image: {{ .Values.mariadb.image | quote }} ports: diff --git a/charts/dvwa/templates/ingress.yaml b/charts/dvwa/templates/ingress.yaml index 8b3e811..4e18eeb 100644 --- a/charts/dvwa/templates/ingress.yaml +++ b/charts/dvwa/templates/ingress.yaml @@ -1,35 +1,36 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.ingress.enabled -}} +{{- if not .Values.ingress.domain }} + {{- fail "ingress.domain is required when ingress.enabled=true. Set it with --set ingress.domain=dvwa.yourdomain.com" }} +{{- end }} +{{- $fullName := include "dvwa.fullname" . }} +{{- $tlsSecret := .Values.ingress.tls.secretName | default (printf "%s-tls" $fullName) }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ include "dvwa.fullname" . }} + name: {{ $fullName }} labels: {{- include "dvwa.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} annotations: + {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} - {{- end }} + {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - {{- if .Values.ingress.tls }} + ingressClassName: {{ .Values.ingress.className | quote }} + {{- if .Values.ingress.tls.enabled }} tls: - {{- toYaml .Values.ingress.tls | nindent 4 }} + - hosts: + - {{ .Values.ingress.domain | quote }} + secretName: {{ $tlsSecret | quote }} {{- end }} rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} + - host: {{ .Values.ingress.domain | quote }} http: paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType }} + - path: / + pathType: Prefix backend: service: - name: {{ include "dvwa.fullname" $ }} + name: {{ $fullName }} port: - number: {{ $.Values.service.port }} - {{- end }} - {{- end }} + number: {{ .Values.service.port }} {{- end }} diff --git a/charts/dvwa/templates/service.yaml b/charts/dvwa/templates/service.yaml index 4731736..366f4dd 100644 --- a/charts/dvwa/templates/service.yaml +++ b/charts/dvwa/templates/service.yaml @@ -13,6 +13,3 @@ spec: protocol: TCP port: {{ .Values.service.port }} targetPort: http - {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} diff --git a/charts/dvwa/values.yaml b/charts/dvwa/values.yaml index 6918291..82918bb 100644 --- a/charts/dvwa/values.yaml +++ b/charts/dvwa/values.yaml @@ -20,20 +20,19 @@ mariadb: password: dvwa service: - type: NodePort + type: ClusterIP port: 80 - nodePort: 30080 ingress: enabled: false - className: "" + domain: "" # e.g. dvwa.workshop.example.com + className: "" # e.g. traefik + tls: + enabled: false + secretName: "" # defaults to "-dvwa-tls if empty" annotations: {} - hosts: - - host: dvwa.local - paths: - - path: / - pathType: Prefix - tls: [] + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd # forces HTTPS redirect via Traefik middleware resources: requests: @@ -43,12 +42,9 @@ resources: cpu: 500m memory: 512Mi -# Run as root — intentionally insecure for the workshop podSecurityContext: {} - securityContext: {} -# Persist DB between pod restarts (optional) persistence: enabled: false storageClass: "" From f2e99b56ec820f6895020ca34029706c7a4bee8a Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sat, 18 Apr 2026 22:50:02 +0200 Subject: [PATCH 4/8] Add webgoat chart --- charts/dvwa/README.md | 12 --- charts/webgoat/CONTRIBUTING.md | 31 +++++++ charts/webgoat/Chart.yaml | 13 +++ charts/webgoat/README.md | 39 +++++++++ charts/webgoat/templates/NOTES.txt | 32 +++++++ charts/webgoat/templates/_helpers.tpl | 34 +++++++ charts/webgoat/templates/deployment.yaml | 107 +++++++++++++++++++++++ charts/webgoat/templates/ingress.yaml | 69 +++++++++++++++ charts/webgoat/templates/pvc.yaml | 17 ++++ charts/webgoat/templates/service.yaml | 19 ++++ charts/webgoat/values.yaml | 51 +++++++++++ 11 files changed, 412 insertions(+), 12 deletions(-) create mode 100644 charts/webgoat/CONTRIBUTING.md create mode 100644 charts/webgoat/Chart.yaml create mode 100644 charts/webgoat/README.md create mode 100644 charts/webgoat/templates/NOTES.txt create mode 100644 charts/webgoat/templates/_helpers.tpl create mode 100644 charts/webgoat/templates/deployment.yaml create mode 100644 charts/webgoat/templates/ingress.yaml create mode 100644 charts/webgoat/templates/pvc.yaml create mode 100644 charts/webgoat/templates/service.yaml create mode 100644 charts/webgoat/values.yaml diff --git a/charts/dvwa/README.md b/charts/dvwa/README.md index 170c4a0..114e2cb 100644 --- a/charts/dvwa/README.md +++ b/charts/dvwa/README.md @@ -26,15 +26,6 @@ Install the chart: helm upgrade --install dvwa devpro/dvwa -f values.yaml --namespace dvwa --create-namespace ``` -Verify the installation: - -```bash -kubectl rollout status deployment/dvwa-dvwa -n dvwa - -export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') -echo "Open: http://$NODE_IP:30080" -``` - ### First-time Setup 1. Browse to the URL above @@ -49,8 +40,6 @@ Key | Default | Description `dvwa.adminUsername` | `admin` | DVWA login `dvwa.adminPassword` | `password` | DVWA password `dvwa.securityLevel` | `low` | `low` / `medium` / `high` / `impossible` -`service.type` | `NodePort` | Service type -`service.nodePort` | `30080` | NodePort value `persistence.enabled` | `false` | Persist MariaDB data across pod restarts ## Security Level @@ -73,5 +62,4 @@ kubectl delete namespace dvwa Check the [contribution guide](CONTRIBUTING.md). --- - > ⚠️ **FOR WORKSHOP USE ONLY** — intentionally vulnerable, never expose to the internet. diff --git a/charts/webgoat/CONTRIBUTING.md b/charts/webgoat/CONTRIBUTING.md new file mode 100644 index 0000000..131032c --- /dev/null +++ b/charts/webgoat/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contribution guide + +## Validate on a test cluster + +Create a `values.mine.yaml` file: + +```yaml +ingress: + enabled: true + domain: "console.$SANDBOX_ID.instruqt.io" + className: "traefik" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + tls: + enabled: true + +``` + +Install the chart: + +```bash +helm upgrade --install webgoat . -f values.yaml -f values.mine.yaml --namespace webgoat --create-namespace +``` + +Wait for all pods to be ready: + +```bash +kubectl get all -n webgoat +``` + +Open the web application in a browser. diff --git a/charts/webgoat/Chart.yaml b/charts/webgoat/Chart.yaml new file mode 100644 index 0000000..1a8e9e6 --- /dev/null +++ b/charts/webgoat/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: webgoat +description: OWASP WebGoat — intentionally vulnerable Java/Spring app for security workshops +type: application +version: 1.0.0 +appVersion: "2023.8" +keywords: + - security + - vulnerable + - workshop + - owasp +maintainers: + - name: workshop diff --git a/charts/webgoat/README.md b/charts/webgoat/README.md new file mode 100644 index 0000000..67e2bc1 --- /dev/null +++ b/charts/webgoat/README.md @@ -0,0 +1,39 @@ +# OWASP WebGoat Helm Chart + +Helm chart for [WebGoat Web Application](https://owasp.org/www-project-webgoat/) — designed for container security workshops. + +## Architecture + +- **WebGoat** — main app +- **WebWolf** — companion app for email/webhook exercises + +## Quick Start + +Add the chart repository: + +```bash +helm repo add devpro https://devpro.github.io/helm-charts +helm repo update +``` + +Create the `values.yaml` file to override [default values](values.yaml). + +Install the chart: + +```bash +helm upgrade --install webgoat devpro/webgoat -f values.yaml --namespace webgoat --create-namespace +``` + +## Uninstall + +```bash +helm uninstall webgoat -n webgoat +kubectl delete namespace webgoat +``` + +## Going further + +Check the [contribution guide](CONTRIBUTING.md). + +--- +> ⚠️ **FOR WORKSHOP USE ONLY** — intentionally vulnerable, never expose to the internet. diff --git a/charts/webgoat/templates/NOTES.txt b/charts/webgoat/templates/NOTES.txt new file mode 100644 index 0000000..5a49819 --- /dev/null +++ b/charts/webgoat/templates/NOTES.txt @@ -0,0 +1,32 @@ +🎯 WebGoat + WebWolf deployed! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ACCESS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{{- if and .Values.ingress.enabled .Values.ingress.domain }} + WebGoat : https://webgoat.{{ .Values.ingress.domain }}/WebGoat + WebWolf : https://webwolf.{{ .Values.ingress.domain }}/landing + + TLS certificates issued automatically by cert-manager. + Check: kubectl get certificate -n {{ .Release.Namespace }} +{{- else }} + kubectl port-forward svc/{{ include "webgoat.fullname" . }} 8080:8080 9090:9090 -n {{ .Release.Namespace }} + WebGoat : http://localhost:8080/WebGoat + WebWolf : http://localhost:9090/landing +{{- end }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + FIRST-TIME SETUP +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 1. Wait ~45s for the JVM to start + 2. Browse to WebGoat URL above + 3. Click "Register new user" — create any account + 4. Same credentials work in WebWolf + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + USEFUL COMMANDS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + kubectl logs -l app.kubernetes.io/name=webgoat -n {{ .Release.Namespace }} -c webgoat -f + kubectl logs -l app.kubernetes.io/name=webgoat -n {{ .Release.Namespace }} -c webwolf -f + +⚠️ FOR WORKSHOP USE ONLY diff --git a/charts/webgoat/templates/_helpers.tpl b/charts/webgoat/templates/_helpers.tpl new file mode 100644 index 0000000..7cb4cd4 --- /dev/null +++ b/charts/webgoat/templates/_helpers.tpl @@ -0,0 +1,34 @@ +{{- define "webgoat.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "webgoat.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "webgoat.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "webgoat.labels" -}} +helm.sh/chart: {{ include "webgoat.chart" . }} +{{ include "webgoat.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "webgoat.selectorLabels" -}} +app.kubernetes.io/name: {{ include "webgoat.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/webgoat/templates/deployment.yaml b/charts/webgoat/templates/deployment.yaml new file mode 100644 index 0000000..936fff2 --- /dev/null +++ b/charts/webgoat/templates/deployment.yaml @@ -0,0 +1,107 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "webgoat.fullname" . }} + labels: + {{- include "webgoat.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "webgoat.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "webgoat.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + + containers: + - name: webgoat + image: "{{ .Values.webgoat.image.repository }}:{{ .Values.webgoat.image.tag }}" + imagePullPolicy: {{ .Values.webgoat.image.pullPolicy }} + ports: + - name: webgoat + containerPort: {{ .Values.webgoat.port }} + protocol: TCP + env: + - name: WEBWOLF_HOST + value: "localhost" + - name: WEBWOLF_PORT + value: {{ .Values.webwolf.port | quote }} + - name: WEBGOAT_HOST + value: {{ if .Values.ingress.domain }}{{ printf "webgoat.%s" .Values.ingress.domain | quote }}{{ else }}"localhost"{{ end }} + - name: SERVER_PORT + value: {{ .Values.webgoat.port | quote }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + readinessProbe: + httpGet: + path: /WebGoat/login + port: {{ .Values.webgoat.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /WebGoat/login + port: {{ .Values.webgoat.port }} + initialDelaySeconds: 60 + periodSeconds: 20 + {{- if .Values.persistence.enabled }} + volumeMounts: + - name: webgoat-data + mountPath: /root/.webgoat + {{- end }} + + - name: webwolf + image: "{{ .Values.webwolf.image.repository }}:{{ .Values.webwolf.image.tag }}" + imagePullPolicy: {{ .Values.webwolf.image.pullPolicy }} + ports: + - name: webwolf + containerPort: {{ .Values.webwolf.port }} + protocol: TCP + env: + - name: SERVER_PORT + value: {{ .Values.webwolf.port | quote }} + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + readinessProbe: + httpGet: + path: /landing + port: {{ .Values.webwolf.port }} + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 10 + + {{- if .Values.persistence.enabled }} + volumes: + - name: webgoat-data + persistentVolumeClaim: + claimName: {{ include "webgoat.fullname" . }}-data + {{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/webgoat/templates/ingress.yaml b/charts/webgoat/templates/ingress.yaml new file mode 100644 index 0000000..fa8176b --- /dev/null +++ b/charts/webgoat/templates/ingress.yaml @@ -0,0 +1,69 @@ +{{- if .Values.ingress.enabled -}} +{{- if not .Values.ingress.domain }} + {{- fail "ingress.domain is required when ingress.enabled=true. Set it with --set ingress.domain=workshop.example.com" }} +{{- end }} +{{- $fullName := include "webgoat.fullname" . }} +{{- $webgoatHost := printf "webgoat.%s" .Values.ingress.domain }} +{{- $webwolfHost := printf "webwolf.%s" .Values.ingress.domain }} +{{- $webgoatTLSSecret := printf "%s-webgoat-tls" $fullName }} +{{- $webwolfTLSSecret := printf "%s-webwolf-tls" $fullName }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-webgoat + labels: + {{- include "webgoat.labels" . | nindent 4 }} + annotations: + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className | quote }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ $webgoatHost | quote }} + secretName: {{ $webgoatTLSSecret | quote }} + {{- end }} + rules: + - host: {{ $webgoatHost | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ .Values.service.webgoatPort }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-webwolf + labels: + {{- include "webgoat.labels" . | nindent 4 }} + annotations: + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className | quote }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ $webwolfHost | quote }} + secretName: {{ $webwolfTLSSecret | quote }} + {{- end }} + rules: + - host: {{ $webwolfHost | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ .Values.service.webwolfPort }} +{{- end }} diff --git a/charts/webgoat/templates/pvc.yaml b/charts/webgoat/templates/pvc.yaml new file mode 100644 index 0000000..da6ca2f --- /dev/null +++ b/charts/webgoat/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "webgoat.fullname" . }}-data + labels: + {{- include "webgoat.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- end }} diff --git a/charts/webgoat/templates/service.yaml b/charts/webgoat/templates/service.yaml new file mode 100644 index 0000000..9d3df84 --- /dev/null +++ b/charts/webgoat/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "webgoat.fullname" . }} + labels: + {{- include "webgoat.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "webgoat.selectorLabels" . | nindent 4 }} + ports: + - name: webgoat + protocol: TCP + port: {{ .Values.service.webgoatPort }} + targetPort: webgoat + - name: webwolf + protocol: TCP + port: {{ .Values.service.webwolfPort }} + targetPort: webwolf diff --git a/charts/webgoat/values.yaml b/charts/webgoat/values.yaml new file mode 100644 index 0000000..855a224 --- /dev/null +++ b/charts/webgoat/values.yaml @@ -0,0 +1,51 @@ +replicaCount: 1 + +webgoat: + image: + repository: webgoat/webgoat + tag: "2023.8" + pullPolicy: IfNotPresent + port: 8080 + +webwolf: + image: + repository: webgoat/webwolf + tag: "2023.8" + pullPolicy: IfNotPresent + port: 9090 + +service: + type: ClusterIP + webgoatPort: 8080 + webwolfPort: 9090 + +ingress: + enabled: false + className: "" + # subdomains are prefixed automatically from the base domain (e.g. webgoat.workshop.example.com and webwolf.workshop.example.com) + domain: "" + tls: + enabled: false + annotations: {} + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd + +resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi + +podSecurityContext: {} +securityContext: {} + +persistence: + enabled: false + storageClass: "" + size: 1Gi + +nodeSelector: {} +tolerations: [] +affinity: {} From 19a49a58212d02ae8b5dc4313c4c811ee359d14f Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sat, 18 Apr 2026 23:09:08 +0200 Subject: [PATCH 5/8] Update webgoat --- charts/webgoat/CONTRIBUTING.md | 5 +++ charts/webgoat/templates/deployment.yaml | 52 ++++++++---------------- charts/webgoat/values.yaml | 10 +---- 3 files changed, 22 insertions(+), 45 deletions(-) diff --git a/charts/webgoat/CONTRIBUTING.md b/charts/webgoat/CONTRIBUTING.md index 131032c..95f8338 100644 --- a/charts/webgoat/CONTRIBUTING.md +++ b/charts/webgoat/CONTRIBUTING.md @@ -29,3 +29,8 @@ kubectl get all -n webgoat ``` Open the web application in a browser. + + diff --git a/charts/webgoat/templates/deployment.yaml b/charts/webgoat/templates/deployment.yaml index 936fff2..22675fd 100644 --- a/charts/webgoat/templates/deployment.yaml +++ b/charts/webgoat/templates/deployment.yaml @@ -25,17 +25,22 @@ spec: imagePullPolicy: {{ .Values.webgoat.image.pullPolicy }} ports: - name: webgoat - containerPort: {{ .Values.webgoat.port }} + containerPort: 8080 + protocol: TCP + - name: webwolf + containerPort: 9090 protocol: TCP env: - - name: WEBWOLF_HOST - value: "localhost" - - name: WEBWOLF_PORT - value: {{ .Values.webwolf.port | quote }} + - name: TZ + value: "Europe/Amsterdam" - name: WEBGOAT_HOST value: {{ if .Values.ingress.domain }}{{ printf "webgoat.%s" .Values.ingress.domain | quote }}{{ else }}"localhost"{{ end }} - - name: SERVER_PORT - value: {{ .Values.webgoat.port | quote }} + - name: WEBWOLF_HOST + value: {{ if .Values.ingress.domain }}{{ printf "webwolf.%s" .Values.ingress.domain | quote }}{{ else }}"localhost"{{ end }} + - name: WEBGOAT_PORT + value: "8080" + - name: WEBWOLF_PORT + value: "9090" {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -45,15 +50,15 @@ spec: readinessProbe: httpGet: path: /WebGoat/login - port: {{ .Values.webgoat.port }} - initialDelaySeconds: 30 + port: 8080 + initialDelaySeconds: 60 periodSeconds: 10 failureThreshold: 12 livenessProbe: httpGet: path: /WebGoat/login - port: {{ .Values.webgoat.port }} - initialDelaySeconds: 60 + port: 8080 + initialDelaySeconds: 90 periodSeconds: 20 {{- if .Values.persistence.enabled }} volumeMounts: @@ -61,31 +66,6 @@ spec: mountPath: /root/.webgoat {{- end }} - - name: webwolf - image: "{{ .Values.webwolf.image.repository }}:{{ .Values.webwolf.image.tag }}" - imagePullPolicy: {{ .Values.webwolf.image.pullPolicy }} - ports: - - name: webwolf - containerPort: {{ .Values.webwolf.port }} - protocol: TCP - env: - - name: SERVER_PORT - value: {{ .Values.webwolf.port | quote }} - resources: - requests: - cpu: 100m - memory: 256Mi - limits: - cpu: 500m - memory: 512Mi - readinessProbe: - httpGet: - path: /landing - port: {{ .Values.webwolf.port }} - initialDelaySeconds: 20 - periodSeconds: 10 - failureThreshold: 10 - {{- if .Values.persistence.enabled }} volumes: - name: webgoat-data diff --git a/charts/webgoat/values.yaml b/charts/webgoat/values.yaml index 855a224..5f638f8 100644 --- a/charts/webgoat/values.yaml +++ b/charts/webgoat/values.yaml @@ -3,16 +3,8 @@ webgoat: image: repository: webgoat/webgoat - tag: "2023.8" + tag: "v2025.3" pullPolicy: IfNotPresent - port: 8080 - -webwolf: - image: - repository: webgoat/webwolf - tag: "2023.8" - pullPolicy: IfNotPresent - port: 9090 service: type: ClusterIP From 0788cfe06a8ec9b7938054ef14ccd8563067c22f Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sun, 19 Apr 2026 00:31:58 +0200 Subject: [PATCH 6/8] Improve charts --- .github/workflows/ci.yml | 4 ++-- charts/dvwa/templates/deployment.yaml | 13 +++++++++++-- charts/dvwa/values.yaml | 2 +- charts/webgoat/templates/NOTES.txt | 9 ++++----- charts/webgoat/templates/deployment.yaml | 12 ++++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e78c806..9cd9f63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# title: Continuous Integration +# title: Continuous Integration # uses: # - https://github.com/helm/chart-testing-action # - https://github.com/stackrox/kube-linter-action @@ -48,7 +48,7 @@ jobs: check-latest: true - name: Use KubeLinter id: kube-lint-scan - uses: stackrox/kube-linter-action@v1 + uses: stackrox/kube-linter-action@87802a2f4e01abebb3ee3c67a3002fea71f6eae5 with: directory: charts config: .kube-linter.yaml diff --git a/charts/dvwa/templates/deployment.yaml b/charts/dvwa/templates/deployment.yaml index afb6471..9b7aa3b 100644 --- a/charts/dvwa/templates/deployment.yaml +++ b/charts/dvwa/templates/deployment.yaml @@ -6,6 +6,8 @@ metadata: {{- include "dvwa.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate selector: matchLabels: {{- include "dvwa.selectorLabels" . | nindent 6 }} @@ -106,14 +108,21 @@ spec: - mysqladmin ping -u root -p${MARIADB_ROOT_PASSWORD} initialDelaySeconds: 10 periodSeconds: 5 - + livenessProbe: + exec: + command: + - sh + - -c + - mysqladmin ping -u root -p${MARIADB_ROOT_PASSWORD} + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 {{- if .Values.persistence.enabled }} volumes: - name: mariadb-data persistentVolumeClaim: claimName: {{ include "dvwa.fullname" . }}-mariadb {{- end }} - {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/dvwa/values.yaml b/charts/dvwa/values.yaml index 82918bb..6c4ca19 100644 --- a/charts/dvwa/values.yaml +++ b/charts/dvwa/values.yaml @@ -13,7 +13,7 @@ dvwa: # MariaDB (bundled sidecar) mariadb: - image: mariadb:10.11 + image: mariadb:12.2 rootPassword: dvwa database: dvwa username: dvwa diff --git a/charts/webgoat/templates/NOTES.txt b/charts/webgoat/templates/NOTES.txt index 5a49819..8c05230 100644 --- a/charts/webgoat/templates/NOTES.txt +++ b/charts/webgoat/templates/NOTES.txt @@ -1,18 +1,18 @@ -🎯 WebGoat + WebWolf deployed! +🎯 WebGoat + WebWolf deployed! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ACCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{- if and .Values.ingress.enabled .Values.ingress.domain }} WebGoat : https://webgoat.{{ .Values.ingress.domain }}/WebGoat - WebWolf : https://webwolf.{{ .Values.ingress.domain }}/landing + WebWolf : https://webwolf.{{ .Values.ingress.domain }}/WebWolf TLS certificates issued automatically by cert-manager. Check: kubectl get certificate -n {{ .Release.Namespace }} {{- else }} kubectl port-forward svc/{{ include "webgoat.fullname" . }} 8080:8080 9090:9090 -n {{ .Release.Namespace }} WebGoat : http://localhost:8080/WebGoat - WebWolf : http://localhost:9090/landing + WebWolf : http://localhost:9090/WebWolf {{- end }} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -26,7 +26,6 @@ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ USEFUL COMMANDS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - kubectl logs -l app.kubernetes.io/name=webgoat -n {{ .Release.Namespace }} -c webgoat -f - kubectl logs -l app.kubernetes.io/name=webgoat -n {{ .Release.Namespace }} -c webwolf -f + kubectl logs -l app.kubernetes.io/name=webgoat -n {{ .Release.Namespace }} -f ⚠️ FOR WORKSHOP USE ONLY diff --git a/charts/webgoat/templates/deployment.yaml b/charts/webgoat/templates/deployment.yaml index 22675fd..35e0812 100644 --- a/charts/webgoat/templates/deployment.yaml +++ b/charts/webgoat/templates/deployment.yaml @@ -4,8 +4,14 @@ metadata: name: {{ include "webgoat.fullname" . }} labels: {{- include "webgoat.labels" . | nindent 4 }} + ignore-check.kube-linter.io/read-secret-from-env-var: >- + DVWA is a deliberately vulnerable PHP app that reads DB credentials from env vars by design. + SecretKeyRef is used to avoid plaintext in manifests. + Sidecar MariaDB pattern is intentional for this security training tool. spec: replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate selector: matchLabels: {{- include "webgoat.selectorLabels" . | nindent 6 }} @@ -41,6 +47,12 @@ spec: value: "8080" - name: WEBWOLF_PORT value: "9090" + {{- if .Values.ingress.enabled }} + - name: WEBGOAT_URL + value: {{ printf "%s://webgoat.%s/WebGoat" (ternary "https" "http" .Values.ingress.tls.enabled) .Values.ingress.domain | quote }} + - name: WEBWOLF_URL + value: {{ printf "%s://webwolf.%s/WebWolf" (ternary "https" "http" .Values.ingress.tls.enabled) .Values.ingress.domain | quote }} + {{- end }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} From 9b7ba101d3126c13e6c332e5b1df5bb355d4f8c8 Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sun, 19 Apr 2026 00:42:04 +0200 Subject: [PATCH 7/8] Add restartpolicy --- .kube-linter.yaml | 2 ++ charts/dvwa/templates/deployment.yaml | 3 +-- charts/webgoat/templates/deployment.yaml | 8 +------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.kube-linter.yaml b/.kube-linter.yaml index f2f8b35..b591822 100644 --- a/.kube-linter.yaml +++ b/.kube-linter.yaml @@ -24,3 +24,5 @@ - charts/**/charts/** # disable for now (too many issues for something to rework from the container image) - charts/nfs-ganesha/** + # disable as no easy solution for secret management for MariaDB + - charts/dvwa/** diff --git a/charts/dvwa/templates/deployment.yaml b/charts/dvwa/templates/deployment.yaml index 9b7aa3b..4f8098c 100644 --- a/charts/dvwa/templates/deployment.yaml +++ b/charts/dvwa/templates/deployment.yaml @@ -20,7 +20,6 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} - containers: - name: dvwa image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" @@ -66,7 +65,6 @@ spec: port: 80 initialDelaySeconds: 90 periodSeconds: 20 - - name: mariadb image: {{ .Values.mariadb.image | quote }} ports: @@ -135,3 +133,4 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + restartPolicy: Always diff --git a/charts/webgoat/templates/deployment.yaml b/charts/webgoat/templates/deployment.yaml index 35e0812..d62af02 100644 --- a/charts/webgoat/templates/deployment.yaml +++ b/charts/webgoat/templates/deployment.yaml @@ -4,10 +4,6 @@ metadata: name: {{ include "webgoat.fullname" . }} labels: {{- include "webgoat.labels" . | nindent 4 }} - ignore-check.kube-linter.io/read-secret-from-env-var: >- - DVWA is a deliberately vulnerable PHP app that reads DB credentials from env vars by design. - SecretKeyRef is used to avoid plaintext in manifests. - Sidecar MariaDB pattern is intentional for this security training tool. spec: replicas: {{ .Values.replicaCount }} strategy: @@ -24,7 +20,6 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} - containers: - name: webgoat image: "{{ .Values.webgoat.image.repository }}:{{ .Values.webgoat.image.tag }}" @@ -77,14 +72,12 @@ spec: - name: webgoat-data mountPath: /root/.webgoat {{- end }} - {{- if .Values.persistence.enabled }} volumes: - name: webgoat-data persistentVolumeClaim: claimName: {{ include "webgoat.fullname" . }}-data {{- end }} - {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} @@ -97,3 +90,4 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + restartPolicy: Always From a4d5dd4543a2c4a8b73a64bde1faf556442935ac Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Sun, 19 Apr 2026 01:07:38 +0200 Subject: [PATCH 8/8] Improve pipelines --- .github/workflows/ci.yml | 27 ++++++++++++++------------- .github/workflows/pkg.yml | 6 +++--- CONTRIBUTING.md | 16 ++++++++-------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cd9f63..4dd387b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,22 +38,23 @@ jobs: - name: Build with VitePress run: npm run docs:build - name: Install Helm - uses: azure/setup-helm@v4.3.0 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 with: version: latest - - name: Install Python - uses: actions/setup-python@v6 - with: - python-version: '3.x' - check-latest: true - - name: Use KubeLinter - id: kube-lint-scan - uses: stackrox/kube-linter-action@87802a2f4e01abebb3ee3c67a3002fea71f6eae5 - with: - directory: charts - config: .kube-linter.yaml + - name: Install kube-linter + env: + KUBE_LINTER_VERSION: "0.8.3" + KUBE_LINTER_SHA256: "1a6d8419b11971372971fdbc22682b684ebfb7cf1c39591662d1b6ca736c41df" + run: | + curl -sSLo kube-linter.tar.gz "https://github.com/stackrox/kube-linter/releases/download/v${KUBE_LINTER_VERSION}/kube-linter-linux.tar.gz" + echo "${KUBE_LINTER_SHA256} kube-linter.tar.gz" | sha256sum -c - + tar -xzf kube-linter.tar.gz kube-linter + sudo mv kube-linter /usr/local/bin/ + rm kube-linter.tar.gz + - name: Run kube-linter + run: kube-linter lint charts --config .kube-linter.yaml - name: Set up chart-testing - uses: helm/chart-testing-action@v2.8.0 + uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f - name: Add dependency chart repositories run: ./scripts/add_helm_repo.sh - name: List changed charts diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml index bb2c3fb..4bfcee1 100644 --- a/.github/workflows/pkg.yml +++ b/.github/workflows/pkg.yml @@ -1,4 +1,4 @@ -# title: Continuous Delivery (Packaging) +# title: Continuous Delivery (Packaging) # requirements: # - Create manually "gh-pages" branch # - Maintain pages files in "gh-pages" branch: index.tpl, placeholder.png @@ -53,13 +53,13 @@ jobs: git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Install Helm - uses: azure/setup-helm@v4.3.0 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 with: version: latest - name: Add dependency chart repositories run: ./scripts/add_helm_repo.sh - name: Host charts repository on GitHub Pages - uses: helm/chart-releaser-action@v1.7.0 + uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # important: it needs to be done in the same pipeline as another pages workflow won't be triggered by the pust on the gh-pages branch diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0907f7b..fb2359a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,10 +38,10 @@ Install the application: helm upgrade --install myapp . -f values.yaml --namespace myns --create-namespace ``` - + Lint charts with [KubeLinter](https://docs.kubelinter.io/): ```bash -docker run --rm \ - -v $(pwd)/charts:/charts \ - -v $(pwd)/.kube-linter.yaml:/etc/config.yaml \ - -v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro \ - stackrox/kube-linter:latest \ - lint /charts --config /etc/config.yaml +kube-linter lint charts --config .kube-linter.yaml ``` + + ## Documentation website