diff --git a/base/Dockerfile b/base/Dockerfile index c7a048b..e6ac11d 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -1,23 +1,50 @@ -FROM alpine:3.23 +# Build stage: the Docker Hardened "-dev" variant ships a shell + apk, which we +# need to create the skpr user, lay down our config and prepare the writable +# runtime directories. The results are copied into the minimal (shell-less) +# runtime image below. +FROM dhi.io/nginx:1.30-alpine3.23-dev AS build + +USER root + +# Create the skpr user (uid 1000) used for non-root, read-only execution. RUN adduser -D -u 1000 skpr -RUN apk --update --no-cache add util-linux ca-certificates nginx nginx-mod-http-headers-more + +# CA certificates for TLS to upstreams (parity with the previous image). +RUN apk --update --no-cache add ca-certificates COPY --chown=skpr:skpr etc /etc -# We need to create and chown these directory for -# readonly and non-root execution. -RUN mkdir -p /run/nginx /var/tmp/nginx -RUN ln -sf /dev/stderr /var/log/nginx/error.log -RUN chown -R skpr:skpr /var/lib/nginx \ - /var/log/nginx \ - /var/tmp/nginx \ - /var/lib/nginx/tmp \ - /var/lib/nginx/logs \ - /usr/lib/nginx/modules \ - /run/nginx - -# Declaring these here means they inherit the "chown" -# directive from above. +# Create and chown the directories nginx writes to at runtime so the image can +# run non-root against a read-only root filesystem. Paths match the nginx.conf +# pid / *_temp_path directives. +RUN mkdir -p /run/nginx /var/tmp/nginx /var/lib/nginx/tmp \ + && ln -sf /dev/stderr /var/log/nginx/error.log \ + && chown -R skpr:skpr /run/nginx \ + /var/tmp/nginx \ + /var/lib/nginx \ + /var/log/nginx \ + /etc/nginx + +# Runtime stage: minimal Docker Hardened Image, no shell or package manager. +# Tag floats nginx 1.30.x patches + alpine 3.23 rebuilds (stable stream); switch +# to 1.31-alpine3.23 for the mainline stream. Keep both stages on the same tag. +FROM dhi.io/nginx:1.30-alpine3.23 + +# Carry over the skpr user so nginx runs as uid 1000 and downstream images can +# COPY --chown=skpr. +COPY --from=build /etc/passwd /etc/group /etc/ + +# CA certificates for TLS to upstreams. +COPY --from=build /etc/ssl/certs /etc/ssl/certs + +# Config and the pre-chowned writable directories. +COPY --from=build --chown=1000:1000 /etc/nginx /etc/nginx +COPY --from=build --chown=1000:1000 /run/nginx /run/nginx +COPY --from=build --chown=1000:1000 /var/tmp/nginx /var/tmp/nginx +COPY --from=build --chown=1000:1000 /var/lib/nginx /var/lib/nginx +COPY --from=build --chown=1000:1000 /var/log/nginx /var/log/nginx + +# Declaring these here means they inherit the "chown" directive from above. VOLUME /run/nginx VOLUME /var/tmp/nginx VOLUME /var/lib/nginx/tmp @@ -28,4 +55,8 @@ STOPSIGNAL SIGTERM USER skpr +# The DHI runtime image sets ENTRYPOINT ["nginx"] and CMD ["-g", "daemon off;"]. +# Reset both so our single "nginx" invocation runs with "daemon off;" from +# nginx.conf, matching the previous image and avoiding a duplicate directive. +ENTRYPOINT [] CMD ["nginx"] diff --git a/base/etc/nginx/conf.d/header/server.conf b/base/etc/nginx/conf.d/header/server.conf index a8dce96..3d0aa7c 100644 --- a/base/etc/nginx/conf.d/header/server.conf +++ b/base/etc/nginx/conf.d/header/server.conf @@ -1 +1,3 @@ -more_clear_headers Server; +# The Server header cannot be fully removed with stock nginx; that previously +# relied on the headers-more module (more_clear_headers Server). "server_tokens +# off" (set in nginx.conf) hides the version, leaving only "Server: nginx". diff --git a/base/etc/nginx/nginx.conf b/base/etc/nginx/nginx.conf index 64d58fe..9076c41 100644 --- a/base/etc/nginx/nginx.conf +++ b/base/etc/nginx/nginx.conf @@ -2,6 +2,10 @@ daemon off; worker_processes 1; +# Written to a skpr-owned directory so nginx can run non-root with a read-only +# root filesystem (the base image's default pid path is not writable by skpr). +pid /run/nginx/nginx.pid; + include /etc/nginx/modules/*.conf; events { @@ -16,6 +20,14 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; + # Temporary paths in a skpr-owned directory so nginx can run non-root with a + # read-only root filesystem (the base image's defaults are not writable by skpr). + client_body_temp_path /var/lib/nginx/tmp/client_body; + proxy_temp_path /var/lib/nginx/tmp/proxy; + fastcgi_temp_path /var/lib/nginx/tmp/fastcgi; + uwsgi_temp_path /var/lib/nginx/tmp/uwsgi; + scgi_temp_path /var/lib/nginx/tmp/scgi; + server_tokens off; keepalive_timeout 65; diff --git a/drupal/dev/Dockerfile b/drupal/dev/Dockerfile index 43dd54d..a370f15 100644 --- a/drupal/dev/Dockerfile +++ b/drupal/dev/Dockerfile @@ -1,4 +1,6 @@ FROM from_image -RUN rm -f /etc/nginx/conf.d/header/x_drupal_cache.conf -RUN rm -f /etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf +# Leave the Drupal cache headers visible for dev images. These empty overrides +# replace the production fastcgi_hide_header directives (shell-free so they work +# on the no-shell hardened base image). +COPY --chown=skpr:skpr etc /etc diff --git a/drupal/dev/etc/nginx/conf.d/header/x_drupal_cache.conf b/drupal/dev/etc/nginx/conf.d/header/x_drupal_cache.conf new file mode 100644 index 0000000..1893f36 --- /dev/null +++ b/drupal/dev/etc/nginx/conf.d/header/x_drupal_cache.conf @@ -0,0 +1,2 @@ +# Intentionally empty for dev images: the X-Drupal-Cache header is left visible +# (production hides it via fastcgi_hide_header). Overrides the base file. diff --git a/drupal/dev/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf b/drupal/dev/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf new file mode 100644 index 0000000..842004c --- /dev/null +++ b/drupal/dev/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf @@ -0,0 +1,2 @@ +# Intentionally empty for dev images: the X-Drupal-Dynamic-Cache header is left +# visible (production hides it via fastcgi_hide_header). Overrides the base file. diff --git a/drupal/etc/nginx/conf.d/header/x_drupal_cache.conf b/drupal/etc/nginx/conf.d/header/x_drupal_cache.conf index b0dcc22..2a87edf 100644 --- a/drupal/etc/nginx/conf.d/header/x_drupal_cache.conf +++ b/drupal/etc/nginx/conf.d/header/x_drupal_cache.conf @@ -1 +1 @@ -more_clear_headers X-Drupal-Cache; +fastcgi_hide_header X-Drupal-Cache; diff --git a/drupal/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf b/drupal/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf index e9676f0..05a85cc 100644 --- a/drupal/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf +++ b/drupal/etc/nginx/conf.d/header/x_drupal_dynamic_cache.conf @@ -1 +1 @@ -more_clear_headers X-Drupal-Dynamic-Cache; +fastcgi_hide_header X-Drupal-Dynamic-Cache; diff --git a/drupal/etc/nginx/conf.d/header/x_generator.conf b/drupal/etc/nginx/conf.d/header/x_generator.conf index c460eb3..a811156 100644 --- a/drupal/etc/nginx/conf.d/header/x_generator.conf +++ b/drupal/etc/nginx/conf.d/header/x_generator.conf @@ -1 +1 @@ -more_clear_headers X-Generator; +fastcgi_hide_header X-Generator; diff --git a/php-fpm/dev/Dockerfile b/php-fpm/dev/Dockerfile index cef5f25..cfa9f31 100644 --- a/php-fpm/dev/Dockerfile +++ b/php-fpm/dev/Dockerfile @@ -1,6 +1,6 @@ FROM from_image +# Allow errors to be displayed for non production PHP images. The dev copy of +# 10-error-page.conf disables the error_page 500 interception (shell-free so it +# works on the no-shell hardened base image). COPY --chown=skpr:skpr etc /etc - -# Allow errors to be displayed for non production PHP images. -RUN sed -i '/error_page 500/s/^/#/' /etc/nginx/conf.d/location/10-error-page.conf diff --git a/php-fpm/dev/etc/nginx/conf.d/location/10-error-page.conf b/php-fpm/dev/etc/nginx/conf.d/location/10-error-page.conf new file mode 100644 index 0000000..ba65793 --- /dev/null +++ b/php-fpm/dev/etc/nginx/conf.d/location/10-error-page.conf @@ -0,0 +1,15 @@ +error_page 400 /error-pages/400.html; +#error_page 403 /error-pages/403.html; +#error_page 404 /error-pages/404.html; +error_page 405 /error-pages/405.html; +error_page 414 /error-pages/414.html; +error_page 416 /error-pages/416.html; +error_page 418 /error-pages/418.html; +# error_page 500 is intentionally disabled for dev images so PHP errors are +# displayed instead of the static 500 page. Keep the rest in sync with +# base/etc/nginx/conf.d/location/10-error-page.conf. +#error_page 500 /error-pages/500.html; +error_page 501 /error-pages/501.html; +error_page 502 /error-pages/502.html; +error_page 503 /error-pages/503.html; +error_page 504 /error-pages/504.html;