diff --git a/.env b/.env index 3148b18..71e3f2e 100644 --- a/.env +++ b/.env @@ -1,4 +1,2 @@ -BASE_VERSION=3.24.1 -BASE_HASH=bec4ccd3817e7c824eb0388971a0b83fab111d586285511ba0266b77e8dc65a9 -OPENSSL_VERSION=4.0.2 +OPENSSL_VERSION=4.0.3 APP_VERSION=1.31.4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a623db0..087dd2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ env: jobs: build-and-publish: if: github.actor == 'ammnt' - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 permissions: contents: read packages: write @@ -40,8 +40,9 @@ jobs: - name: Load .env variables☁️ id: load_env run: | + set -euo pipefail while IFS= read -r line; do - if [[ ! "$line" =~ ^# ]] && [[ -n "$line" ]]; then + if [[ ! "$line" =~ ^# ]] && [[ -n "$line" ]] && [[ "$line" == *=* ]]; then key="${line%%=*}" value="${line#*=}" echo "$key=$value" >> $GITHUB_OUTPUT @@ -82,8 +83,6 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max build-args: | - BASE_VERSION=${{ steps.load_env.outputs.BASE_VERSION }} - BASE_HASH=${{ steps.load_env.outputs.BASE_HASH }} OPENSSL_VERSION=${{ steps.load_env.outputs.OPENSSL_VERSION }} APP_VERSION=${{ steps.load_env.outputs.APP_VERSION }} VCS_REF=${{ steps.load_env.outputs.VCS_REF }} @@ -91,6 +90,7 @@ jobs: - name: Slim and push image with Mint🔧 run: | + set -euo pipefail curl -sSfL "https://github.com/mintoolkit/mint/releases/latest/download/dist_linux.tar.gz" | tar -zxf - ./dist_linux/mint --quiet build \ --target ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }} \ @@ -110,6 +110,7 @@ jobs: - name: Get image digest🔢 id: digest run: | + set -euo pipefail DIGEST=$(docker inspect -f='{{index .RepoDigests 0}}' ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }} | cut -d'@' -f2) echo "digest=$DIGEST" >> $GITHUB_OUTPUT @@ -117,6 +118,7 @@ jobs: env: CI: true run: | + set -euo pipefail export DIVE_VERSION=$(curl -sSfL "https://api.github.com/repos/wagoodman/dive/releases/latest" | jq -r '.tag_name // empty' | sed -E 's/^v?([0-9][^"]+).*/\1/') curl -sSfL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.tar.gz" | tar -zxf - ./dive --config ./dive-ci.yml -j dive.report.json ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }} @@ -151,12 +153,13 @@ jobs: COSIGN_KEY: ${{ secrets.COSIGN_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} run: | + set -euo pipefail cosign sign -y --key env://COSIGN_KEY ghcr.io/ammnt/freenginx@${{ steps.digest.outputs.digest }} cosign sign -y --key env://COSIGN_KEY ammnt/freenginx@${{ steps.digest.outputs.digest }} security-scans: needs: build-and-publish - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 permissions: contents: read security-events: write @@ -182,10 +185,11 @@ jobs: - name: Checkov scan🔍 if: matrix.scanner == 'checkov' run: | + set -euo pipefail curl -sSfLO "https://github.com/bridgecrewio/checkov/releases/latest/download/checkov_linux_X86_64.zip" unzip -q ./checkov_linux_X86_64.zip ./dist/checkov \ - --quiet \ + --quiet -s \ --output sarif \ --output-file-path results \ --framework dockerfile \ @@ -199,8 +203,6 @@ jobs: with: image: ghcr.io/ammnt/freenginx:${{ needs.build-and-publish.outputs.app_version }} format: sarif - exit-code: "1" - exit-level: "warn" ignore: "CIS-DI-0005,CIS-DI-0010" output: "dockle.sarif" @@ -218,8 +220,7 @@ jobs: image: ghcr.io/ammnt/freenginx:${{ needs.build-and-publish.outputs.app_version }} sarif-file: scout.sarif write-comment: true - summary: false - exit-code: true + summary: true github-token: ${{ secrets.GH_TOKEN }} - name: Trivy scan🛡️ @@ -230,9 +231,6 @@ jobs: scan-type: image format: sarif output: trivy.sarif - severity: "MEDIUM,HIGH,CRITICAL" - scanners: "vuln,secret" - exit-code: "1" github-pat: ${{ secrets.GH_TOKEN }} trivy-config: trivy.yaml @@ -242,7 +240,6 @@ jobs: with: image: ghcr.io/ammnt/freenginx:${{ needs.build-and-publish.outputs.app_version }} fail-build: false - severity-cutoff: critical output-format: sarif output-file: grype.sarif @@ -254,7 +251,7 @@ jobs: with: image: ghcr.io/ammnt/freenginx:${{ needs.build-and-publish.outputs.app_version }} sarif: true - args: --file=Dockerfile.template --sarif-file-output=snyk.sarif + args: --file=Dockerfile.template --severity-threshold=critical --sarif-file-output=snyk.sarif - name: Generate SBOM with Syft📋 if: matrix.scanner == 'syft' @@ -286,7 +283,7 @@ jobs: release: needs: [build-and-publish, security-scans] - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 permissions: contents: write steps: diff --git a/Dockerfile.template b/Dockerfile.template index 1149918..eb1856a 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -1,6 +1,6 @@ # Build arguments for version pinning and reproducibility -ARG BASE_VERSION=3.24.1 -ARG BASE_HASH=bec4ccd3817e7c824eb0388971a0b83fab111d586285511ba0266b77e8dc65a9 +ARG BASE_VERSION=3.24.2 +ARG BASE_HASH=31b6477333eb8257db9e5d7c3a7264fd0467928756f0bbcc27d35bea5d28cdbd FROM alpine:${BASE_VERSION}@sha256:${BASE_HASH} AS builder ARG OPENSSL_VERSION ARG APP_VERSION @@ -17,8 +17,8 @@ ENV CC=clang \ WORKDIR /tmp # System setup and dependency installation RUN addgroup --system --gid "10001" freenginx && adduser --disabled-password --shell /bin/false --ingroup freenginx --uid "10001" --no-create-home freenginx \ - # Update system and install build dependencies - && apk -U -q upgrade && apk add -q --no-cache \ + # Install build dependencies + && apk add -q --no-cache \ clang22=22.1.3-r2 \ lld22=22.1.3-r0 \ llvm=22-r0 \ @@ -26,34 +26,46 @@ RUN addgroup --system --gid "10001" freenginx && adduser --disabled-password --s git=2.54.0-r0 \ mimalloc2-dev=2.2.7-r0 \ build-base=0.5-r4 \ - ca-certificates=20260611-r0 \ linux-headers=7.0.0-r1 \ upx=5.2.0-r0 \ - perl=5.42.2-r0 \ + perl=5.42.2-r1 \ cmake=4.2.3-r0 \ zstd-dev=1.5.7-r2 \ zstd-static=1.5.7-r2 \ - brotli-dev=1.2.0-r1 \ + brotli-dev=1.2.0-r1 \ brotli-static=1.2.0-r1 \ - curl=8.22.0-r0 \ - # Update CA certificates for SSL verification - && update-ca-certificates \ + autoconf=2.73-r0 \ + automake=1.18.1-r1 \ + libtool=2.6.0-r1 \ + python3-dev=3.14.8-r0 \ && git config --global advice.detachedHead false \ - && PCRE_VERSION=$(curl -sSfL "https://api.github.com/repos/PCRE2Project/pcre2/releases/latest" | grep '"tag_name":' | sed -E 's/.*"pcre2\-([^"]+)".*/\1/') \ - && ZLIB_VERSION=$(curl -sSfL "https://api.github.com/repos/zlib-ng/zlib-ng/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') \ - && ZSTD_VERSION=$(curl -sSfL "https://api.github.com/repos/tokers/zstd-nginx-module/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') \ + && PCRE_VERSION=$(git ls-remote --tags --refs https://github.com/PCRE2Project/pcre2 'pcre2-*' | grep -v -- '-RC' | sed 's|.*/||' | tail -1) \ + && ZLIB_VERSION=$(git ls-remote --tags --refs https://github.com/zlib-ng/zlib-ng | sed 's|.*/||' | sed 's/^v//' | sort -V | tail -1) \ + && ZSTD_VERSION=$(git ls-remote --tags --refs https://github.com/tokers/zstd-nginx-module | sed 's|.*/||' | sort -V | tail -1) \ + && NJS_VERSION=$(git ls-remote --tags --refs https://github.com/nginx/njs '[0-9]*' | sed 's|.*/||' | sort -V | tail -1) \ + && QJS_VERSION=$(git ls-remote --tags --refs https://github.com/quickjs-ng/quickjs 'v*' | sed 's|.*/||' | sort -V | tail -1) \ + && LIBXML2_VERSION=$(git ls-remote --tags --refs https://github.com/GNOME/libxml2 'v*' | sed 's|.*/||' | sort -V | tail -1) \ + && LIBXSLT_VERSION=$(git ls-remote --tags --refs https://github.com/GNOME/libxslt 'v*' | sed 's|.*/||' | sort -V | tail -1) \ # Clone FreeNGINX webserver and remove documentation && git clone -q --depth 1 --recursive -j8 --single-branch -b "release-${APP_VERSION}" https://github.com/freenginx/nginx freenginx && rm -rf /tmp/freenginx/docs/html/* \ # Clone OpenSSL with HTTP/3 and QUIC support && git clone -q --depth 1 --recursive -j8 --single-branch -b "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl \ # Clone PCRE2 for regex with JIT support - && git clone -q --depth 1 --recursive -j8 --single-branch -b "pcre2-${PCRE_VERSION}" https://github.com/PCRE2Project/pcre2 \ + && git clone -q --depth 1 --recursive -j8 --single-branch -b "${PCRE_VERSION}" https://github.com/PCRE2Project/pcre2 \ # Clone zlib-ng for modern compression performance && git clone -q --depth 1 --recursive -j8 --single-branch -b "${ZLIB_VERSION}" https://github.com/zlib-ng/zlib-ng \ # Clone Brotli compression module && git clone -q --depth 1 --recurse-submodules -j8 https://github.com/google/ngx_brotli \ # Clone ZSTD compression module && git clone -q --depth 1 --recursive -j8 --single-branch -b "${ZSTD_VERSION}" https://github.com/tokers/zstd-nginx-module \ + # Clone NJS module + && git clone -q --depth 1 --recursive -j8 --single-branch -b "${NJS_VERSION}" https://github.com/nginx/njs \ + # Clone QuickJS-NG engine + && git clone -q --depth 1 --recursive -j8 --single-branch -b "${QJS_VERSION}" https://github.com/quickjs-ng/quickjs.git \ + # Clone libxml2 for NJS + && git clone -q --depth 1 --recursive -j8 --single-branch -b "${LIBXML2_VERSION}" https://github.com/GNOME/libxml2 \ + # Clone libxslt for NJS + && git clone -q --depth 1 --recursive -j8 --single-branch -b "${LIBXSLT_VERSION}" https://github.com/GNOME/libxslt \ # Remove Server header from HTTP responses (HTTP/1.1, HTTP/2, HTTP/3) && sed -ie 's@r->headers_out.server == NULL@0@g' \ /tmp/freenginx/src/http/ngx_http_header_filter_module.c \ @@ -62,6 +74,32 @@ RUN addgroup --system --gid "10001" freenginx && adduser --disabled-password --s # Remove default footer from error pages && sed -ie 's@
freenginx
@@g' /tmp/freenginx/src/http/ngx_http_special_response.c +# Build QuickJS-NG library +WORKDIR /tmp/quickjs +RUN make -s -j "$(nproc)" \ + && cp ./build/libqjs.a ./libquickjs.a + +# Build libxml2 library +WORKDIR /tmp/libxml2 +RUN ./autogen.sh \ + && CFLAGS="-fPIC -O3 -march=x86-64 -fstack-protector-strong -fcf-protection=full \ + -fvisibility=hidden -flto=full" \ + ./configure \ + --disable-shared \ + --enable-static \ + && make -s -j "$(nproc)" && make -s install && make -s clean + +# Build libxslt library +WORKDIR /tmp/libxslt +RUN ./autogen.sh \ + && CFLAGS="-fPIC -O3 -march=x86-64 -fstack-protector-strong -fcf-protection=full \ + -fvisibility=hidden -flto=full" \ + ./configure \ + --disable-shared \ + --enable-static \ + --with-libxml-src=/tmp/libxml2 \ + && make -s -j "$(nproc)" && make -s install && make -s clean + # Build zlib-ng and Brotli compression libraries with optimizations WORKDIR /tmp/zlib-ng RUN mkdir -p /tmp/ngx_brotli/deps/brotli/out \ @@ -102,6 +140,7 @@ RUN ./auto/configure \ --with-zlib="/tmp/zlib-ng" \ # Compilation flags --with-cc-opt="-fPIE -O3 -march=x86-64 -flto=full \ + -I/tmp/quickjs -I/usr/local/include \ -Wall -Wextra -Wformat=2 -Wimplicit-fallthrough \ -Werror=format-security -Werror=return-type -Wno-deprecated-declarations \ -Wno-unused-parameter -Wno-implicit-fallthrough -Wno-sign-compare \ @@ -109,9 +148,9 @@ RUN ./auto/configure \ -fstrict-flex-arrays=3 -fstack-clash-protection -fstack-protector-strong \ -fcf-protection=full -ftrivial-auto-var-init=pattern \ -fno-delete-null-pointer-checks -fno-strict-overflow \ - -fasynchronous-unwind-tables -fomit-frame-pointer \ - -DTCP_FASTOPEN=23" \ - --with-ld-opt="-fuse-ld=lld -flto=full -lmimalloc -L/usr/local/lib -lz -static-pie \ + -fasynchronous-unwind-tables -fomit-frame-pointer -DTCP_FASTOPEN=23" \ + --with-ld-opt="-fuse-ld=lld -flto=full -L/tmp/quickjs -L/usr/local/lib \ + -lmimalloc -lxml2 -lxslt -lexslt -lz -lm -static-pie \ -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now \ -Wl,-z,separate-code -Wl,-z,shstk -Wl,-z,force-ibt -Wl,--no-warnings \ -Wl,--as-needed -Wl,--no-copy-dt-needed-entries" \ @@ -152,10 +191,12 @@ RUN ./auto/configure \ # Third-party modules --add-module="/tmp/ngx_brotli" \ --add-module="/tmp/zstd-nginx-module" \ + --add-module="/tmp/njs/nginx" \ # Build and install FreeNGINX - && make -s -j "$(nproc)" && make -s install && make -s clean \ + && make -s -j "$(nproc)" && make -s install \ + && file objs/src/http/ngx_http.o objs/src/core/nginx.o | grep -i 'LLVM IR bitcode' && make -s clean \ # Security hardening: strip debug symbols and compress binary - && strings /usr/sbin/freenginx | grep -iE "zlib-ng/zlib.h|mimalloc_version" \ + && strings /usr/sbin/freenginx | grep -iE "zlib-ng/zlib.h|mimalloc_version|quickjs/quickjs.h" \ && llvm-strip --strip-all /usr/sbin/freenginx \ && upx -qq --best --lzma /usr/sbin/freenginx \ && ls -lash /usr/sbin/freenginx \ diff --git a/README.md b/README.md index fda9384..1872cb6 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,6 @@ > [!IMPORTANT] > The QuicTLS is now deprecated. I use OpenSSL, since this library natively supports OCSP, PQC, ECH and QUIC⚠️ -> [!IMPORTANT] -> NJS module has been removed due to security vulnerabilities in libxml2/libxslt dependencies⚠️ - > [!TIP] > You can find an example [configuration file](example.conf) in the repository for successfully configuring HTTP/3, ECH and PQC💡 @@ -173,6 +170,7 @@ freenginx/ ### **Performance Features** - **zlib-ng** with modern compression algorithms (RFC 1950, RFC 1951, RFC 1952) - **PCRE2 with JIT** compilation for regex performance +- **NJS with QuickJS-NG** - JavaScript scripting support with modern engine - **Thread pool support** for async I/O operations - **TCP Fast Open** and **SSL session resumption** (RFC 7413, RFC 8446) - **Graceful shutdown** - SIGQUIT handling for proper connection draining (RFC 7230) diff --git a/example.conf b/example.conf index 3ee185f..c6740a2 100644 --- a/example.conf +++ b/example.conf @@ -47,11 +47,11 @@ http { ssl_session_timeout 1440m; ssl_buffer_size 4k; # ssl_protocols TLSv1.3; - # ssl_ecdh_curve X25519MLKEM768:X25519; + # ssl_ecdh_curve ?X25519MLKEM768:X25519; # ssl_ciphers TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE:!COMPLEMENTOFDEFAULT; # ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384; ssl_protocols TLSv1.3 TLSv1.2; - ssl_ecdh_curve X25519MLKEM768:X25519:SecP384r1MLKEM1024:SecP256r1MLKEM768:secp521r1:secp384r1; + ssl_ecdh_curve ?X25519MLKEM768:X25519:?SecP384r1MLKEM1024:?SecP256r1MLKEM768:secp521r1:secp384r1; ssl_ciphers TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDH+AESGCM+AES256:ECDH+CHACHA20; ssl_conf_command Options ServerPreference,PrioritizeChaCha; ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDH+AESGCM+AES256:ECDH+CHACHA20; diff --git a/hadolint.yaml b/hadolint.yaml index aeabe0a..c90f807 100644 --- a/hadolint.yaml +++ b/hadolint.yaml @@ -1,6 +1,6 @@ -failure-threshold: warning +failure-threshold: error no-color: false -no-fail: false +no-fail: true trustedRegistries: - docker.io - ghcr.io