diff --git a/.github/workflows/verifyimage.yml b/.github/workflows/verifyimage.yml index 7906d1b..9de7b95 100644 --- a/.github/workflows/verifyimage.yml +++ b/.github/workflows/verifyimage.yml @@ -78,3 +78,11 @@ jobs: - name: Verify ${{ matrix.target }} run: | [ $(docker inspect ${{ matrix.target }}-test --format='{{.State.Running}}') = 'true' ] + if grep -q "nginx "<<< "${{ matrix.target }}"; then + curl -q -D headers.txt http://localhost:8080/?test=../../etc/passwd + grep -q "HTTP/1.1 403 Forbidden" headers.txt + grep -q "Access-Control-Allow-Origin: *" headers.txt + grep -q "Access-Control-Max-Age: 3600" headers.txt + grep -q "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS" headers.txt + grep -q "Access-Control-Allow-Headers: *" headers.txt + fi diff --git a/README.md b/README.md index 7f7730e..4f41a14 100644 --- a/README.md +++ b/README.md @@ -202,14 +202,12 @@ These variables are common to image variants and will set defaults based on the ### Nginx ENV Variables - - - | Name | Description| | -------- | ------------------------------------------------------------------- | +| CORS_HEADER_403_ALLOW_ORIGIN | The value of the [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header for `403` responses. Determines which origins can access the response. (Default: `"*"`). | +| CORS_HEADER_403_ALLOW_METHODS | The value of the [Access-Control-Request-Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method) header for `403` responses. Determines the allowed request methods for the resource. Default: `"GET, POST, PUT, DELETE, OPTIONS"` | +| CORS_HEADER_403_CONTENT_TYPE | The value of the `Content-Type` header for `403` responses. Default: (`"text/plain"`) | +| CORS_HEADER_403_MAX_AGE | The value of the [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) header for `403` responses. The number of seconds that preflight requests for this resource may be cached by the browser. (Default: `3600`) | | DNS_SERVER | A string indicating the name servers used to resolve names of upstream servers into addresses. For localhost backend this value should not be defined (Default: _not defined_) | | KEEPALIVE_TIMEOUT | Number of seconds for a keep-alive client connection to stay open on the server side (Default: `60s`) | | NGINX_ALWAYS_TLS_REDIRECT | A string value indicating if http should redirect to https (Allowed values: `on`, `off`. Default: `off`) | diff --git a/docker-bake.hcl b/docker-bake.hcl index 9553a67..e7c6cb5 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -55,6 +55,14 @@ variable "REPOS" { ] } +variable "nginx-dynamic-modules" { + # List of dynamic modules to include in the nginx build + default = [ + "owasp-modsecurity/ModSecurity-nginx", + "openresty/headers-more-nginx-module" + ] +} + function "major" { params = [version] result = split(".", version)[0] @@ -139,8 +147,9 @@ target "nginx" { inherits = ["platforms-base"] dockerfile="nginx/Dockerfile" args = { - NGINX_VERSION = "${nginx-version}" LUA_MODULES = join(" ", lua-modules-debian) + NGINX_VERSION = "${nginx-version}" + NGINX_DYNAMIC_MODULES = join(" ", nginx-dynamic-modules) } tags = concat(tag("nginx"), vtag("${crs-version}", "nginx") @@ -151,8 +160,9 @@ target "nginx-alpine" { inherits = ["platforms-base"] dockerfile="nginx/Dockerfile-alpine" args = { - NGINX_VERSION = "${nginx-version}" LUA_MODULES = join(" ", lua-modules-alpine) + NGINX_DYNAMIC_MODULES = join(" ", nginx-dynamic-modules) + NGINX_VERSION = "${nginx-version}" } tags = concat(tag("nginx-alpine"), vtag("${crs-version}", "nginx-alpine") diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 3b4c493..ba662a6 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -5,6 +5,7 @@ FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS build ARG MODSEC3_VERSION="n/a" ARG LMDB_VERSION="n/a" ARG LUA_VERSION="n/a" +ARG NGINX_DYNAMIC_MODULES="n/a" USER root @@ -43,6 +44,7 @@ RUN set -eux; \ make -C lmdb/libraries/liblmdb install; \ strip /usr/local/lib/liblmdb*.so* + RUN set -eux; \ git clone https://github.com/owasp-modsecurity/ModSecurity --branch "v${MODSEC3_VERSION}" --depth 1 --recursive; \ cd ModSecurity; \ @@ -54,16 +56,22 @@ RUN set -eux; \ make install; \ strip /usr/local/modsecurity/lib/lib*.so* -# We use master +# Build modules RUN set -eux; \ - git clone -b master --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx.git; \ - curl -sSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz -o nginx-${NGINX_VERSION}.tar.gz; \ + modules=""; \ + for module in ${NGINX_DYNAMIC_MODULES}; \ + do \ + repo=$(echo "${module}" | awk -F'/' '{print $2}'); \ + git clone -b master --depth 1 "https://github.com/${module}.git" ; \ + modules="${modules} --add-dynamic-module=../${repo}"; \ + done; \ + curl -sSL "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" -o nginx-${NGINX_VERSION}.tar.gz; \ tar -xzf nginx-${NGINX_VERSION}.tar.gz; \ cd ./nginx-${NGINX_VERSION}; \ - ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx; \ + ./configure --with-compat ${modules}; \ make modules; \ - strip objs/ngx_http_modsecurity_module.so; \ - cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/; \ + strip objs/*.so; \ + cp objs/*.so /etc/nginx/modules/; \ mkdir /etc/modsecurity.d; \ curl -sSL https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/unicode.mapping \ -o /etc/modsecurity.d/unicode.mapping @@ -197,7 +205,7 @@ ENV \ BLOCKING_PARANOIA=1 COPY --from=build /usr/local/modsecurity/lib/libmodsecurity.so.${MODSEC3_VERSION} /usr/local/modsecurity/lib/ -COPY --from=build /etc/nginx/modules/ngx_http_modsecurity_module.so /etc/nginx/modules/ngx_http_modsecurity_module.so +COPY --from=build /etc/nginx/modules/*.so /etc/nginx/modules/ COPY --from=build /usr/local/lib/liblmdb.so /usr/local/lib/ COPY --from=build /usr/share/TLS/dhparam-* /etc/ssl/certs/ COPY --from=build /etc/modsecurity.d/unicode.mapping /etc/modsecurity.d/unicode.mapping diff --git a/nginx/Dockerfile-alpine b/nginx/Dockerfile-alpine index 069eded..6779e59 100644 --- a/nginx/Dockerfile-alpine +++ b/nginx/Dockerfile-alpine @@ -4,6 +4,7 @@ FROM nginxinc/nginx-unprivileged:${NGINX_VERSION}-alpine AS build ARG MODSEC3_VERSION="n/a" ARG LUA_VERSION="n/a" +ARG NGINX_DYNAMIC_MODULES="n/a" USER root @@ -51,14 +52,22 @@ RUN set -eux; \ make install; \ strip /usr/local/modsecurity/lib/lib*.so* -# We use master + # Build modules RUN set -eux; \ - git clone -b master --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx.git; \ - curl -sSL http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xzf -; \ + modules=""; \ + for module in ${NGINX_DYNAMIC_MODULES}; \ + do \ + repo=$(echo "${module}" | awk -F'/' '{print $2}'); \ + git clone -b master --depth 1 "https://github.com/${module}.git" ; \ + modules="${modules} --add-dynamic-module=../${repo}"; \ + done; \ + curl -sSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz -o nginx-${NGINX_VERSION}.tar.gz; \ + tar -xzf nginx-${NGINX_VERSION}.tar.gz; \ cd ./nginx-${NGINX_VERSION}; \ - ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx; \ + ./configure --with-compat ${modules}; \ make modules; \ - cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/; \ + strip objs/*.so; \ + cp objs/*.so /etc/nginx/modules/; \ mkdir /etc/modsecurity.d; \ curl -sSL https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/unicode.mapping \ -o /etc/modsecurity.d/unicode.mapping @@ -191,7 +200,7 @@ ENV \ BLOCKING_PARANOIA=1 COPY --from=build /usr/local/modsecurity/lib/libmodsecurity.so.${MODSEC3_VERSION} /usr/local/modsecurity/lib/ -COPY --from=build /etc/nginx/modules/ngx_http_modsecurity_module.so /etc/nginx/modules/ngx_http_modsecurity_module.so +COPY --from=build /etc/nginx/modules/*.so /etc/nginx/modules/ COPY --from=build /usr/share/TLS/dhparam-* /etc/ssl/certs/ COPY --from=build /etc/modsecurity.d/unicode.mapping /etc/modsecurity.d/unicode.mapping COPY --from=crs_release /opt/owasp-crs /opt/owasp-crs diff --git a/nginx/templates/conf.d/default.conf.template b/nginx/templates/conf.d/default.conf.template index 0ca4d73..7f25d71 100644 --- a/nginx/templates/conf.d/default.conf.template +++ b/nginx/templates/conf.d/default.conf.template @@ -23,6 +23,7 @@ server { return 301 https://$host$request_uri; } + include includes/cors.conf; include includes/proxy_backend.conf; index index.html index.htm; @@ -62,8 +63,7 @@ server { location / { client_max_body_size 0; - # temporarily disabled, since the upstream image doesn't include the required module - # include includes/cors.conf; + include includes/cors.conf; include includes/proxy_backend.conf; index index.html index.htm; diff --git a/nginx/templates/includes/cors.conf.template b/nginx/templates/includes/cors.conf.template index 1cce89e..c03d36d 100644 --- a/nginx/templates/includes/cors.conf.template +++ b/nginx/templates/includes/cors.conf.template @@ -1,5 +1,5 @@ -more_set_headers -s 403 'Content-Type' '${CORS_HEADER_403_CONTENT_TYPE}'; -more_set_headers -s 403 'Access-Control-Allow-Origin' '${CORS_HEADER_403_ALLOW_ORIGIN}'; -more_set_headers -s 403 'Access-Control-Max-Age' '${CORS_HEADER_403_MAX_AGE}'; -more_set_headers -s 403 'Access-Control-Allow-Methods' '${CORS_HEADER_403_ALLOW_METHODS}'; -more_set_headers 'Access-Control-Allow-Headers' '${CORS_HEADER_ACCESS_CONTROL_ALLOW_HEADERS}'; +more_set_headers -s 403 'Content-Type: ${CORS_HEADER_403_CONTENT_TYPE}'; +more_set_headers -s 403 'Access-Control-Allow-Origin: ${CORS_HEADER_403_ALLOW_ORIGIN}'; +more_set_headers -s 403 'Access-Control-Max-Age: ${CORS_HEADER_403_MAX_AGE}'; +more_set_headers -s 403 'Access-Control-Allow-Methods: ${CORS_HEADER_403_ALLOW_METHODS}'; +more_set_headers 'Access-Control-Allow-Headers: ${CORS_HEADER_ACCESS_CONTROL_ALLOW_HEADERS}'; diff --git a/nginx/templates/nginx.conf.template b/nginx/templates/nginx.conf.template index 6392a38..53f0c27 100644 --- a/nginx/templates/nginx.conf.template +++ b/nginx/templates/nginx.conf.template @@ -1,7 +1,6 @@ load_module modules/ngx_http_modsecurity_module.so; # allows to add cors headers when replying with 403 -# temporarily disabled, since the upstream image doesn't include the module -# load_module modules/ngx_http_headers_more_filter_module.so; +load_module modules/ngx_http_headers_more_filter_module.so; worker_processes auto; pid /tmp/nginx.pid;