owasp-modsecurity / ModSecurity-nginx

ModSecurity v3 Nginx Connector
Apache License 2.0
1.59k stars 283 forks source link

Add support to build ModSecurity-nginx on Windows #321

Closed eduar-hte closed 6 months ago

eduar-hte commented 7 months ago

This PR includes changes to support building nginx with the ModSecurity-nginx module on Windows (which has been requested in the past in ModSecurity Issue #2480).

This work depends on the PR to build libModSecurity v3 on Windows, see ModSecurity PR #3132.

The changes to build the ModSecurity-nginx module are few. The build process requires a number of tools and third-party libraries due to the fact that both libModSecurity v3 and nginx w/ModSecurity-nginx need to be built. This process is documented step-by-step in the README.md file included in the new win32 folder.

Additionally, a Windows Docker container configuration is included to simplify prerequisites setup and build.

Summary of changes

Tests

All ModSecurity-nginx tests were executed successfully on Windows by building nginx with the optional ngx_http_v2_module & ngx_http_auth_request_module modules. Additionally, the tests were successfully run on the Linux gcc/clang builds too.

Miscellaneous

The ModSecurity-nginx connector is built as a static nginx module. It looks as if there's currently no support for dynamic modules on nginx for Windows using MSVC.

It may be possible to cross-compile for Windows using gcc/clang, which may enable building using dynamic modules too.

eduar-hte commented 7 months ago

I set up a GitHub workflow to build and test libModSecurity v3 & nginx w/ModSecurity-nginx in my fork's development branch. If and when this PR is merged, it'd be useful include this too.

  build-windows:
    runs-on: windows-2022
    defaults:
      run:
        shell: msys2 {0}
    steps:
      - name: Set up MSVC
        uses: ilammy/msvc-dev-cmd@v1
      - name: Set up msys
        uses: msys2/setup-msys2@v2
        with:
          msystem: UCRT64
          path-type: inherit
      - name: Get Nginx source
        uses: actions/checkout@v4
        with:
          repository: nginx/nginx
          path: nginx
          fetch-depth: 1
      - name: Get Nginx tests
        uses: actions/checkout@v4
        with:
          repository: nginx/nginx-tests
          path: nginx/test
          fetch-depth: 1
      - name: Set up third-party libraries
        working-directory: nginx
        run: |
          mkdir objs
          mkdir objs/lib
          cd objs/lib
          wget -q -O - https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.gz | tar -xzf -
          wget -q -O - https://www.zlib.net/fossils/zlib-1.3.tar.gz | tar -xzf -
          wget -q -O - https://www.openssl.org/source/openssl-3.0.13.tar.gz | tar -xzf -
      - name: Get libModSecurity source
        uses: actions/checkout@v4
        with:
          repository: eduar-hte/ModSecurity
          ref: windows-port
          submodules: true
          path: nginx/objs/lib/ModSecurity
          fetch-depth: 1
      - name: Setup Conan
        shell: cmd
        run: |
          pip3 install conan --upgrade
          conan profile detect
      - name: Build libModSecurity
        working-directory: nginx/objs/lib/ModSecurity
        shell: cmd
        run: |
          vcbuild.bat
      - name: Get ModSecurity-nginx source code
        uses: actions/checkout@v4
        with:
          path: nginx/objs/lib/ModSecurity-nginx
      - name: Copy ModSecurity-nginx tests to nginx/test
        working-directory: nginx/test
        run: |
          cp ../objs/lib/ModSecurity-nginx/tests/* .
      - name: Remove /usr/bin/link conflicting with MSVC link.exe
        run: |
          set -ex
          which link
          rm /usr/bin/link
      - name: Build nginx w/ModSecurity-nginx module
        working-directory: nginx
        run: |
          : # Windows native version of Perl is required by nginx build
          export PATH=/c/Strawberry/perl/bin:$PATH
          : # Set env variables to point to libModSecurity v3 include & lib directories
          export MODSECURITY_INC=objs/lib/ModSecurity/headers
          export MODSECURITY_LIB=objs/lib/ModSecurity/build/win32/build/Release
          : # Copy libModSecurity.dll to objs dir (to be able to run nginx later)
          cp $MODSECURITY_LIB/libModSecurity.dll objs
          : # Configure nginx build w/ModSecurity-nginx module
          auto/configure \
              --with-cc=cl \
              --with-debug \
              --prefix= \
              --conf-path=conf/nginx.conf \
              --pid-path=logs/nginx.pid \
              --http-log-path=logs/access.log \
              --error-log-path=logs/error.log \
              --sbin-path=nginx.exe \
              --http-client-body-temp-path=temp/client_body_temp \
              --http-proxy-temp-path=temp/proxy_temp \
              --http-fastcgi-temp-path=temp/fastcgi_temp \
              --http-scgi-temp-path=temp/scgi_temp \
              --http-uwsgi-temp-path=temp/uwsgi_temp \
              --with-cc-opt=-DFD_SETSIZE=1024 \
              --with-pcre=objs/lib/pcre2-10.39 \
              --with-zlib=objs/lib/zlib-1.3 \
              --with-openssl=objs/lib/openssl-3.0.13 \
              --with-openssl-opt=no-asm \
              --with-http_ssl_module \
              --with-http_v2_module \
              --with-http_auth_request_module \
              --add-module=objs/lib/ModSecurity-nginx
          nmake
      - name: Run ModSecurity-nginx tests
        working-directory: nginx/test
        shell: cmd  # tests need to run on a "windows" shell
        run: |
          md temp
          set TEMP=temp
          set TEST_NGINX_BINARY=..\objs\nginx.exe
          prove modsecurity*.t

I took the liberty to make a couple of changes on the Linux builds too. The changes are:

  build-linux:
    permissions:
      contents: read
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-22.04]
        compiler: [gcc, clang]
    env:
        CC: "/usr/bin/${{ matrix.compiler }}"
        CXX: "/usr/bin/${{ matrix.compiler == 'gcc' && 'g' || 'clang' }}++"
        COMPDEPS: "${{ matrix.compiler == 'gcc' && 'gcc g++' || 'clang' }}"
    steps:
      - name: Setup Dependencies
        run: |
          sudo dpkg --add-architecture i386
          sudo apt-get update -y -qq
          sudo apt-get install -y make autoconf automake make libyajl-dev libxml2-dev libmaxminddb-dev libcurl4-gnutls-dev $COMPDEPS
      - name: Get libModSecurity source
        uses: actions/checkout@v4
        with:
          repository: owasp-modsecurity/ModSecurity
          path: ModSecurity
          submodules: true
          fetch-depth: 1
      - name: Build libModSecurity
        working-directory: ModSecurity
        run: |
          ./build.sh
          ./configure --without-lmdb --prefix=/usr
          make -j $(nproc)
          sudo make install
      - uses: actions/checkout@v4
        with:
          path: ModSecurity-nginx
          fetch-depth: 1
      - name: Get Nginx source
        uses: actions/checkout@v4
        with:
          repository: nginx/nginx
          path: nginx
          fetch-depth: 1
      - name: Get Nginx tests
        uses: actions/checkout@v4
        with:
          repository: nginx/nginx-tests
          path: nginx/test
          fetch-depth: 1
      - name: Copy ModSecurity-nginx tests to nginx/test
        run: |
          cp ModSecurity-nginx/tests/* nginx/test
      - name: Build nginx with ModSecurity-nginx module
        working-directory: nginx
        run: |
          ./auto/configure --with-ld-opt="-Wl,-rpath,/usr/local/lib" --without-pcre2 --with-http_v2_module --with-http_auth_request_module --add-module=../ModSecurity-nginx
          make
          make modules
          sudo make install
      - name: Run ModSecurity-nginx tests
        working-directory: nginx/test
        run: |
          TEST_NGINX_BINARY=../objs/nginx prove modsecurity*.t
      - name: Start Nginx
        run: |
          sudo /usr/local/nginx/sbin/nginx -c /home/runner/work/ModSecurity-nginx/ModSecurity-nginx/ModSecurity-nginx/.github/nginx/nginx.conf
      - name: Run attack test vhost 1
        run: |
          status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest1" "http://localhost/?q=attack")
          if [ "${status}" == "403" ]; then
            echo "OK"
          else
            echo "FAIL"
            exit 1
          fi
      - name: Run non-attack test vhost 1
        run: |
          status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest1" "http://localhost/?q=1")
          if [ "${status}" == "200" ]; then
            echo "OK"
          else
            echo "FAIL"
            exit 1
          fi
      - name: Run attack test vhost 2
        run: |
          status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest2" "http://localhost/?q=attack")
          if [ "${status}" == "403" ]; then
            echo "OK"
          else
            echo "FAIL"
            exit 1
          fi
      - name: Run non-attack test vhost 2
        run: |
          status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest2" "http://localhost/?q=1")
          if [ "${status}" == "200" ]; then
            echo "OK"
          else
            echo "FAIL"
            exit 1
          fi
eduar-hte commented 6 months ago

I updated the changes to the config file and the one that introduces strdup so that they apply only when the MSVC C++ compiler is used, because it may be possible to cross-compile for Windows using gcc/clang (looking at the build configuration of nginx) and they may not be needed in that case.

sonarcloud[bot] commented 6 months ago

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarCloud

eduar-hte commented 6 months ago

I've just updated the branch to reference that ModSecurity PR 3132 has been merged. The documentation and Docker build no longer references the forked repository in order to build the library.

I've also updated the GitHub workflow based on the previous comment to:

airween commented 6 months ago

Thanks for contributing, this is also a huge step!