Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .env
Original file line number Diff line number Diff line change
@@ -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
29 changes: 13 additions & 16 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -82,15 +83,14 @@ 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 }}
BUILD_DATE=${{ steps.load_env.outputs.BUILD_DATE }}

- 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 }} \
Expand All @@ -110,13 +110,15 @@ 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

- name: Explore with Dive🔍
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 }}
Expand Down Expand Up @@ -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
Expand All @@ -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 \
Expand All @@ -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"

Expand All @@ -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🛡️
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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'
Expand Down Expand Up @@ -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:
Expand Down
79 changes: 60 additions & 19 deletions Dockerfile.template
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,43 +17,55 @@ 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 \
make=4.4.1-r4 \
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 \
Expand All @@ -62,6 +74,32 @@ RUN addgroup --system --gid "10001" freenginx && adduser --disabled-password --s
# Remove default footer from error pages
&& sed -ie 's@<hr><center>freenginx</center>@@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 \
Expand Down Expand Up @@ -102,16 +140,17 @@ 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 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS \
-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" \
Expand Down Expand Up @@ -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 \
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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💡

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions hadolint.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
failure-threshold: warning
failure-threshold: error
no-color: false
no-fail: false
no-fail: true
trustedRegistries:
- docker.io
- ghcr.io
Loading