diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e78c806..4dd387b 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 @@ -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@v1 - 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/.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/CONTRIBUTING.md b/CONTRIBUTING.md index 17e24b3..fb2359a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing +# Contributing ## File organization @@ -40,6 +40,8 @@ helm upgrade --install myapp . -f values.yaml --namespace myns --create-namespac ### Run locally CI checks + + Lint charts with [KubeLinter](https://docs.kubelinter.io/): ```bash -docker run --rm -v $(pwd)/charts:/charts -v $(pwd)/.kube-linter.yaml:/etc/config.yaml stackrox/kube-linter \ - lint /charts --config /etc/config.yaml +kube-linter lint charts --config .kube-linter.yaml ``` + + ## Documentation website ### Static Site Generator 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..1f88bb3 --- /dev/null +++ b/charts/dvwa/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contribution guide + +## Validate on a test cluster + +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 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. 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..114e2cb --- /dev/null +++ b/charts/dvwa/README.md @@ -0,0 +1,65 @@ +# 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 +``` + +### 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` +`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..da61858 --- /dev/null +++ b/charts/dvwa/templates/NOTES.txt @@ -0,0 +1,56 @@ +🎯 DVWA (Damn Vulnerable Web Application) deployed! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ACCESS THE APP +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +{{- 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 }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 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 }} + +Change security level mid-workshop: + helm upgrade {{ .Release.Name }} ./dvwa -n {{ .Release.Namespace }} \ + --set ingress.domain={{ .Values.ingress.domain }} \ + --set dvwa.securityLevel=medium + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + USEFUL COMMANDS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + # 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 long-term! 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..4f8098c --- /dev/null +++ b/charts/dvwa/templates/deployment.yaml @@ -0,0 +1,136 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "dvwa.fullname" . }} + labels: + {{- include "dvwa.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + selector: + matchLabels: + {{- include "dvwa.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "dvwa.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - 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" + - name: DVWA_SECURITY + value: {{ .Values.dvwa.securityLevel | quote }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + readinessProbe: + httpGet: + path: /login.php + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 20 + livenessProbe: + httpGet: + path: /login.php + port: 80 + initialDelaySeconds: 90 + periodSeconds: 20 + - 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 + 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 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: Always diff --git a/charts/dvwa/templates/ingress.yaml b/charts/dvwa/templates/ingress.yaml new file mode 100644 index 0000000..4e18eeb --- /dev/null +++ b/charts/dvwa/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- 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: {{ $fullName }} + labels: + {{- include "dvwa.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: + - {{ .Values.ingress.domain | quote }} + secretName: {{ $tlsSecret | quote }} + {{- end }} + rules: + - host: {{ .Values.ingress.domain | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ .Values.service.port }} +{{- 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..366f4dd --- /dev/null +++ b/charts/dvwa/templates/service.yaml @@ -0,0 +1,15 @@ +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 diff --git a/charts/dvwa/values.yaml b/charts/dvwa/values.yaml new file mode 100644 index 0000000..6c4ca19 --- /dev/null +++ b/charts/dvwa/values.yaml @@ -0,0 +1,55 @@ +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:12.2 + rootPassword: dvwa + database: dvwa + username: dvwa + password: dvwa + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + domain: "" # e.g. dvwa.workshop.example.com + className: "" # e.g. traefik + tls: + enabled: false + secretName: "" # defaults to "-dvwa-tls if empty" + annotations: {} + # 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: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +podSecurityContext: {} +securityContext: {} + +persistence: + enabled: false + storageClass: "" + size: 1Gi + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/charts/webgoat/CONTRIBUTING.md b/charts/webgoat/CONTRIBUTING.md new file mode 100644 index 0000000..95f8338 --- /dev/null +++ b/charts/webgoat/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# 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..8c05230 --- /dev/null +++ b/charts/webgoat/templates/NOTES.txt @@ -0,0 +1,31 @@ +🎯 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 }}/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/WebWolf +{{- 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 }} -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..d62af02 --- /dev/null +++ b/charts/webgoat/templates/deployment.yaml @@ -0,0 +1,93 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "webgoat.fullname" . }} + labels: + {{- include "webgoat.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + 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: 8080 + protocol: TCP + - name: webwolf + containerPort: 9090 + protocol: TCP + env: + - name: TZ + value: "Europe/Amsterdam" + - name: WEBGOAT_HOST + value: {{ if .Values.ingress.domain }}{{ printf "webgoat.%s" .Values.ingress.domain | quote }}{{ else }}"localhost"{{ end }} + - 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" + {{- 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 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + readinessProbe: + httpGet: + path: /WebGoat/login + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /WebGoat/login + port: 8080 + initialDelaySeconds: 90 + periodSeconds: 20 + {{- if .Values.persistence.enabled }} + volumeMounts: + - 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 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: Always 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..5f638f8 --- /dev/null +++ b/charts/webgoat/values.yaml @@ -0,0 +1,43 @@ +replicaCount: 1 + +webgoat: + image: + repository: webgoat/webgoat + tag: "v2025.3" + pullPolicy: IfNotPresent + +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: {}