diff --git a/Makefile b/Makefile index f0e22226..63dbb4ae 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,11 @@ test-build-application: ## Build the test application image -t ghcr.io/hoverkraft-tech/ci-github-container/application-test:0.1.0 ./tests/application test-ct-install: ## Run ct install to install the test application - @ct install --config ct.yaml --helm-extra-set-args '--set=image.tag=0.1.0' + @namespace="test-chart-$$(uuidgen | tr '[:upper:]' '[:lower:]')"; \ + cleanup() { kubectl delete namespace "$$namespace" --ignore-not-found >/dev/null 2>&1 || true; }; \ + trap cleanup EXIT; \ + kubectl create namespace "$$namespace" >/dev/null; \ + ct install --config ct.yaml --namespace "$$namespace" --helm-extra-set-args "--set=namespace=$$namespace,image.tag=0.1.0,image.digest=" define run_linter DEFAULT_WORKSPACE="$(CURDIR)"; \ diff --git a/tests/charts/application/Chart.lock b/tests/charts/application/Chart.lock index c3f85945..bded9aa5 100644 --- a/tests/charts/application/Chart.lock +++ b/tests/charts/application/Chart.lock @@ -2,5 +2,5 @@ dependencies: - name: mysql repository: https://charts.bitnami.com/bitnami version: 14.0.3 -digest: sha256:801482030fdbfbb0e9bc66d808541458d0549644d295c43c088203014920c9c9 -generated: "2025-08-15T04:17:01.63589655Z" +digest: sha256:80cd59471fc8937944ac535c25f1da52a9820c3f2ed001e3ed741adb41f9e121 +generated: "2026-05-18T16:32:39.279673538+02:00" diff --git a/tests/charts/application/Chart.yaml b/tests/charts/application/Chart.yaml index 9b6f25a2..06932a50 100644 --- a/tests/charts/application/Chart.yaml +++ b/tests/charts/application/Chart.yaml @@ -28,3 +28,4 @@ dependencies: - name: mysql version: 14.0.3 repository: https://charts.bitnami.com/bitnami + condition: mysql.enabled diff --git a/tests/charts/application/templates/configmap.yaml b/tests/charts/application/templates/configmap.yaml index d56fe892..a3ebb8ac 100644 --- a/tests/charts/application/templates/configmap.yaml +++ b/tests/charts/application/templates/configmap.yaml @@ -4,7 +4,7 @@ kind: ConfigMap apiVersion: v1 metadata: name: {{ template "test-application.fullname" . }}-config - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} data: diff --git a/tests/charts/application/templates/deployment.yaml b/tests/charts/application/templates/deployment.yaml index d44847b2..8e4de3d3 100644 --- a/tests/charts/application/templates/deployment.yaml +++ b/tests/charts/application/templates/deployment.yaml @@ -1,10 +1,9 @@ # jscpd:ignore-start ---- apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "test-application.fullname" . }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} spec: @@ -30,10 +29,7 @@ spec: {{- end }} serviceAccountName: {{ include "test-application.serviceAccountName" . }} securityContext: - runAsNonRoot: true - runAsUser: 101 - runAsGroup: 101 - fsGroup: 101 + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: - name: cache-nginx emptyDir: {} @@ -43,19 +39,14 @@ spec: emptyDir: {} containers: - name: {{ .Chart.Name }} + {{- if .Values.image.digest }} + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}@{{ .Values.image.digest }}" + {{- else }} image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: - privileged: false - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE + {{- toYaml .Values.securityContext | nindent 12 }} envFrom: - configMapRef: name: {{ template "test-application.fullname" . }}-config diff --git a/tests/charts/application/templates/hpa.yaml b/tests/charts/application/templates/hpa.yaml index 605c3db1..2f11010e 100644 --- a/tests/charts/application/templates/hpa.yaml +++ b/tests/charts/application/templates/hpa.yaml @@ -5,6 +5,7 @@ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ include "test-application.fullname" . }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} spec: diff --git a/tests/charts/application/templates/ingress.yaml b/tests/charts/application/templates/ingress.yaml index 8e9edd5e..3a320ea4 100644 --- a/tests/charts/application/templates/ingress.yaml +++ b/tests/charts/application/templates/ingress.yaml @@ -18,6 +18,7 @@ apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ $fullName }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} diff --git a/tests/charts/application/templates/networkpolicy.yaml b/tests/charts/application/templates/networkpolicy.yaml new file mode 100644 index 00000000..67f7e67d --- /dev/null +++ b/tests/charts/application/templates/networkpolicy.yaml @@ -0,0 +1,55 @@ +{{- if .Values.networkPolicy.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "test-application.fullname" . }} + namespace: {{ .Values.namespace | default "app-system" }} + labels: + {{- include "test-application.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "test-application.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + {{- if .Values.networkPolicy.ingress }} + {{- range .Values.networkPolicy.ingress }} + - {{- toYaml . | nindent 6 }} + {{- end }} + {{- else }} + - from: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 8080 + {{- end }} + egress: + {{- if .Values.networkPolicy.egress }} + {{- range .Values.networkPolicy.egress }} + - {{- toYaml . | nindent 6 }} + {{- end }} + {{- else }} + - to: [] + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - podSelector: + matchLabels: + app.kubernetes.io/name: mysql + ports: + - protocol: TCP + port: 3306 + - to: [] + ports: + - protocol: TCP + port: 80 + - protocol: TCP + port: 443 + {{- end }} +{{- end }} \ No newline at end of file diff --git a/tests/charts/application/templates/service.yaml b/tests/charts/application/templates/service.yaml index a2295fe2..b425ee93 100644 --- a/tests/charts/application/templates/service.yaml +++ b/tests/charts/application/templates/service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: name: {{ include "test-application.fullname" . }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} spec: diff --git a/tests/charts/application/templates/serviceaccount.yaml b/tests/charts/application/templates/serviceaccount.yaml index 4f5e2a8d..d3739ad4 100644 --- a/tests/charts/application/templates/serviceaccount.yaml +++ b/tests/charts/application/templates/serviceaccount.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "test-application.serviceAccountName" . }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} diff --git a/tests/charts/application/templates/tests/test-connection.yaml b/tests/charts/application/templates/tests/test-connection.yaml index b3e7cac7..ca59f46c 100644 --- a/tests/charts/application/templates/tests/test-connection.yaml +++ b/tests/charts/application/templates/tests/test-connection.yaml @@ -1,43 +1,97 @@ # jscpd:ignore-start --- -apiVersion: v1 -kind: Pod +apiVersion: batch/v1 +kind: Job metadata: name: "{{ include "test-application.fullname" . }}-test-connection" - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "test-application.labels" . | nindent 4 }} annotations: "helm.sh/hook": test spec: - automountServiceAccountToken: false - securityContext: - containers: - - name: wget - image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 - command: - - /bin/sh - - -c - - | - echo "+ testing the application using wget" - set -x - wget -O /dev/null -q '{{ include "test-application.fullname" . }}:{{ .Values.service.port }}' - resources: - limits: - cpu: "100m" - memory: "128Mi" - requests: - cpu: "100m" - memory: "128Mi" + backoffLimit: 0 + template: + metadata: + labels: + # Distinct labels so the chart's NetworkPolicy podSelector does not + # match this hook pod (a dedicated NetworkPolicy is defined below). + app.kubernetes.io/name: {{ include "test-application.name" . }}-test-connection + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: test + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "test-application.chart" . }} + spec: + automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault - readOnlyRootFilesystem: true runAsUser: 10001 - allowPrivilegeEscalation: false - capabilities: - drop: - - NET_RAW - - ALL - restartPolicy: Never + runAsNonRoot: true + containers: + - name: wget + image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 + command: ['sh', '-ec'] + args: + - | + attempts=0 + until wget -T 5 -O /dev/null -q 'http://{{ include "test-application.fullname" . }}:{{ .Values.service.port }}/health/check/'; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 30 ]; then + exit 1 + fi + sleep 5 + done + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "100m" + memory: "128Mi" + securityContext: + seccompProfile: + type: RuntimeDefault + readOnlyRootFilesystem: true + runAsUser: 10001 + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + - ALL + restartPolicy: Never +{{- if .Values.networkPolicy.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: "{{ include "test-application.fullname" . }}-test-connection" + namespace: {{ .Values.namespace | default "app-system" }} + labels: + {{- include "test-application.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "test-application.name" . }}-test-connection + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: test + policyTypes: + - Egress + egress: + - to: [] + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - podSelector: + matchLabels: + {{- include "test-application.selectorLabels" . | nindent 14 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} +{{- end }} # jscpd:ignore-end diff --git a/tests/charts/application/values.yaml b/tests/charts/application/values.yaml index 0a4bc3aa..1cba3eac 100644 --- a/tests/charts/application/values.yaml +++ b/tests/charts/application/values.yaml @@ -3,6 +3,8 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. --- +namespace: "app-system" + application: dbConnection: mysql dbHost: "mysql" @@ -19,7 +21,7 @@ image: registry: "ghcr.io" repository: "hoverkraft-tech/ci-github-container/application-test" tag: "" - digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000" + digest: "sha256:da3b65f32ea75f8041079d220b72da4f605738996256a7dc32715424cc117271" imagePullSecrets: [] @@ -38,8 +40,10 @@ serviceAccount: podAnnotations: {} podSecurityContext: - {} - # fsGroup: 2000 + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 10001 + fsGroup: 10001 securityContext: capabilities: @@ -98,6 +102,11 @@ tolerations: [] affinity: {} +networkPolicy: + enabled: true + ingress: [] + egress: [] + # chart dependencies mysql: fullnameOverride: mysql diff --git a/tests/charts/umbrella-application/charts/app/templates/deployment.yaml b/tests/charts/umbrella-application/charts/app/templates/deployment.yaml index 725aa754..0c62217e 100644 --- a/tests/charts/umbrella-application/charts/app/templates/deployment.yaml +++ b/tests/charts/umbrella-application/charts/app/templates/deployment.yaml @@ -30,6 +30,13 @@ spec: serviceAccountName: {{ include "app.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + volumes: + - name: cache-nginx + emptyDir: {} + - name: var-run + emptyDir: {} + - name: tmp + emptyDir: {} containers: - name: {{ .Chart.Name }} securityContext: @@ -47,6 +54,13 @@ spec: - name: http containerPort: 8080 protocol: TCP + volumeMounts: + - name: cache-nginx + mountPath: /var/cache/nginx + - name: var-run + mountPath: /var/run + - name: tmp + mountPath: /tmp livenessProbe: httpGet: path: /health/check diff --git a/tests/charts/umbrella-application/charts/app/templates/tests/test-connection.yaml b/tests/charts/umbrella-application/charts/app/templates/tests/test-connection.yaml index 10a87c41..13f1c6a7 100644 --- a/tests/charts/umbrella-application/charts/app/templates/tests/test-connection.yaml +++ b/tests/charts/umbrella-application/charts/app/templates/tests/test-connection.yaml @@ -1,71 +1,95 @@ --- -apiVersion: v1 -kind: Pod +apiVersion: batch/v1 +kind: Job metadata: name: "{{ include "app.fullname" . }}-test-connection" - namespace: {{ .Values.namespace }} + namespace: {{ .Values.namespace | default "app-system" }} labels: {{- include "app.labels" . | nindent 4 }} annotations: "helm.sh/hook": test spec: - automountServiceAccountToken: false - securityContext: - seccompProfile: - type: RuntimeDefault - readOnlyRootFilesystem: true - runAsUser: 10001 - allowPrivilegeEscalation: false - capabilities: - drop: - - NET_RAW - - ALL - containers: - - name: wget - image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 - command: ['wget'] - args: ['{{ include "app.fullname" . }}:{{ .Values.service.port }}'] - readinessProbe: - exec: - command: - - wget - - -O - - /dev/null - - -q - - "{{ include "app.fullname" . }}:{{ .Values.service.port }}" - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - livenessProbe: - exec: - command: - - wget - - -O - - /dev/null - - -q - - "{{ include "app.fullname" . }}:{{ .Values.service.port }}" - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - resources: - limits: - cpu: "100m" - memory: "128Mi" - requests: - cpu: "100m" - memory: "128Mi" + backoffLimit: 0 + template: + metadata: + labels: + # Distinct labels so the chart's NetworkPolicy podSelector does not + # match this hook pod (a dedicated NetworkPolicy is defined below). + app.kubernetes.io/name: {{ include "app.name" . }}-test-connection + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: test + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "app.chart" . }} + spec: + automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault - readOnlyRootFilesystem: true runAsUser: 10001 - allowPrivilegeEscalation: false - capabilities: - drop: - - NET_RAW - - ALL - restartPolicy: Never + runAsNonRoot: true + containers: + - name: wget + image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 + command: ['sh', '-ec'] + args: + - | + attempts=0 + until wget -T 5 -O /dev/null -q 'http://{{ include "app.fullname" . }}:{{ .Values.service.port }}/health/check/'; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 30 ]; then + exit 1 + fi + sleep 5 + done + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "100m" + memory: "128Mi" + securityContext: + seccompProfile: + type: RuntimeDefault + readOnlyRootFilesystem: true + runAsUser: 10001 + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + - ALL + restartPolicy: Never +{{- if .Values.networkPolicy.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: "{{ include "app.fullname" . }}-test-connection" + namespace: {{ .Values.namespace | default "app-system" }} + labels: + {{- include "app.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "app.name" . }}-test-connection + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: test + policyTypes: + - Egress + egress: + - to: [] + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - podSelector: + matchLabels: + {{- include "app.selectorLabels" . | nindent 14 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} +{{- end }} \ No newline at end of file diff --git a/tests/charts/umbrella-application/values.yaml b/tests/charts/umbrella-application/values.yaml index d7c40815..f680ce62 100644 --- a/tests/charts/umbrella-application/values.yaml +++ b/tests/charts/umbrella-application/values.yaml @@ -14,6 +14,27 @@ app: database: enabled: true fullnameOverride: database + namespaceOverride: app-system auth: database: test-umbrella-application username: test-umbrella-application + image: + repository: bitnamilegacy/mysql + digest: sha256:ec13e229247a737f7149b7f255d8f2d9c72da861f8bf263b22091bf131540da3 + pullPolicy: Always + primary: + podSecurityContext: + enabled: true + fsGroup: 10001 + containerSecurityContext: + enabled: true + runAsUser: 10001 + runAsGroup: 10001 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault