From 9a4942daad2dc017cbb890df79b714205646a695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=20Pr=C3=A9vost-Corvellec=20Arnault?= Date: Wed, 8 Apr 2026 21:36:09 +0200 Subject: [PATCH] Refactor Dockerfile and Helm chart to enhance security and user permissions - Updated Dockerfile to run as non-root user 'nginx' and adjusted Nginx configuration for improved security. - Added pod security context in values.yaml to align with the non-root user setup. - Refined deployment.yaml to utilize the new pod security context for better compliance with Kubernetes security standards. --- server/Dockerfile | 28 +++++++++++++-------- talks-slides-dist/templates/deployment.yaml | 5 ++-- talks-slides-dist/values.yaml | 8 ++++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 5cba54a..10a6f6f 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1 -# BuildKit / buildx : cache apk + permissions posées au build (moins de travail / capabilities au runtime). +# BuildKit / buildx : cache apk + permissions au build. +# L’image nginx:alpine fournit déjà l’utilisateur / groupe nginx (typiquement 101:101) — pas besoin de le créer. FROM nginx:alpine RUN --mount=type=cache,target=/var/cache/apk \ @@ -7,9 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apk \ WORKDIR /usr/share/nginx/html -# Image figée au clone ; le conteneur met à jour via refresh.sh (git pull origin main). -# Seul le dossier content/ est extrait (sparse checkout) : léger et aligné sur la racine Nginx. -# L’image nginx:alpine place déjà des fichiers ici — il faut vider le répertoire avant git clone vers « . ». +# Image figée au clone ; refresh.sh fait git pull en boucle (même utilisateur que nginx grâce à USER nginx). ARG TALKS_REPO_URL=https://git.specificat.io/arnault/Talks.git ARG TALKS_BRANCH=main ARG TALKS_SPARSE_DIR=content @@ -17,14 +16,13 @@ ARG TALKS_SPARSE_DIR=content RUN find . -mindepth 1 -delete \ && git clone --filter=blob:none --sparse --branch "${TALKS_BRANCH}" --single-branch "${TALKS_REPO_URL}" . \ && git sparse-checkout init --cone \ - && git sparse-checkout set "${TALKS_SPARSE_DIR}" \ - && git config --global --add safe.directory /usr/share/nginx/html + && git sparse-checkout set "${TALKS_SPARSE_DIR}" COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY refresh.sh /refresh.sh -# Caches et logs : créés ici avec le même schéma que l’entrypoint nginx (évite le chown au démarrage). -# Propriétaire nginx (cf. /etc/nginx/nginx.conf user) → l’entrypoint ne refait pas chown si tout est déjà cohérent. +# Master non-root : pid hors /run (root-only), directive user commentée (évite setgid vers 101). +# Caches, logs, dépôt : nginx — pas de setuid/setgid ni CAP dans Kubernetes. RUN chmod +x /refresh.sh \ && mkdir -p \ /var/cache/nginx/client_temp \ @@ -36,9 +34,19 @@ RUN chmod +x /refresh.sh \ && chown -R nginx:nginx \ /var/cache/nginx \ /var/log/nginx \ - /usr/share/nginx/html + /usr/share/nginx/html \ + && sed -i 's|pid /run/nginx.pid;|pid /tmp/nginx.pid;|g' /etc/nginx/nginx.conf \ + && sed -i 's|pid /run/nginx/nginx.pid;|pid /tmp/nginx.pid;|g' /etc/nginx/nginx.conf \ + && sed -i 's/^[[:space:]]*user nginx;/# user nginx (master non-root)/' /etc/nginx/nginx.conf \ + && mkdir -p /home/nginx \ + && chown nginx:nginx /home/nginx /refresh.sh \ + && su -s /bin/sh nginx -c 'HOME=/home/nginx git config --global --add safe.directory /usr/share/nginx/html' -# Port non privilégié (pas de CAP_NET_BIND_SERVICE) ; le Service K8s mappe souvent 80 → 8080. +ENV HOME=/home/nginx + +USER nginx + +# Port non privilégié ; le Service K8s mappe souvent 80 → 8080. EXPOSE 8080 CMD sh -c "/refresh.sh & exec nginx -g 'daemon off;'" diff --git a/talks-slides-dist/templates/deployment.yaml b/talks-slides-dist/templates/deployment.yaml index 2e07433..3c88bf2 100644 --- a/talks-slides-dist/templates/deployment.yaml +++ b/talks-slides-dist/templates/deployment.yaml @@ -24,15 +24,14 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - seccompProfile: - type: RuntimeDefault + {{- toYaml .Values.slides.podSecurityContext | nindent 8 }} containers: - name: nginx image: "{{ .Values.slides.image.repository }}:{{ .Values.slides.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.slides.image.pullPolicy }} securityContext: allowPrivilegeEscalation: false - # Port 8080 dans le conteneur : pas besoin de NET_BIND_SERVICE (ports privilégiés). + # Image : USER nginx + master non-root → pas de SETUID/SETGID ; port 8080 → pas de NET_BIND_SERVICE. capabilities: drop: ["ALL"] readOnlyRootFilesystem: false diff --git a/talks-slides-dist/values.yaml b/talks-slides-dist/values.yaml index e65f9a2..7ad0223 100644 --- a/talks-slides-dist/values.yaml +++ b/talks-slides-dist/values.yaml @@ -17,6 +17,14 @@ slides: # Port d’écoute dans le conteneur (doit correspondre à server/nginx/default.conf, ex. 8080). containerPort: 8080 + # Aligné sur l’utilisateur nginx de l’image (UID/GID 101). L’image utilise USER nginx. + podSecurityContext: + runAsUser: 101 + runAsGroup: 101 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + service: type: ClusterIP # Port du Service (Ingress pointe ici) ; le trafic est envoyé vers containerPort sur les pods.