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: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BASE_VERSION=3.23.3
BASE_HASH=25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
OPENSSL_VERSION=3.6.1
APP_VERSION=1.29.5
OPENSSL_VERSION=4.0.0
APP_VERSION=1.29.6
PCRE_VERSION=10.47
ZLIB_VERSION=2.3.3
ZSTD_VERSION=0.1.1
Expand Down
121 changes: 82 additions & 39 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ env:
IMAGE_NAME: ${{ github.repository }}
COSIGN_VERSION: "3.0.5"
BUILDX_VERSION: "0.32.1"
BUILDKIT_VERSION: "0.28.0"
BUILDKIT_VERSION: "0.28.1"
MINT_VERSION: "1.41.8"
DIVE_VERSION: "0.13.1"
GRYPE_VERSION: "0.109.1"
SYFT_VERSION: "1.42.2"
GRYPE_VERSION: "0.110.0"
SYFT_VERSION: "1.42.3"
TRIVY_VERSION: "0.69.3"
DOCKER_BUILDKIT: 1
BUILDKIT_PROGRESS: quiet
# BUILDKIT_PROGRESS: plain
Expand All @@ -33,25 +34,41 @@ jobs:
attestations: write
outputs:
digest: ${{ steps.digest.outputs.digest }}
app_version: ${{ steps.load_env.outputs.APP_VERSION }}
base_version: ${{ steps.load_env.outputs.BASE_VERSION }}
base_hash: ${{ steps.load_env.outputs.BASE_HASH }}
openssl_version: ${{ steps.load_env.outputs.OPENSSL_VERSION }}
pcre_version: ${{ steps.load_env.outputs.PCRE_VERSION }}
zlib_version: ${{ steps.load_env.outputs.ZLIB_VERSION }}
zstd_version: ${{ steps.load_env.outputs.ZSTD_VERSION }}
uid: ${{ steps.load_env.outputs.UID }}
gid: ${{ steps.load_env.outputs.GID }}
build_date: ${{ steps.load_env.outputs.BUILD_DATE }}
vcs_ref: ${{ steps.load_env.outputs.VCS_REF }}

steps:
- name: Checkout repository📥
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Load .env variables☁️
id: load_env
run: |
while IFS= read -r line; do
if [[ ! "$line" =~ ^# ]] && [[ -n "$line" ]]; then
echo "$line" >> $GITHUB_ENV
key="${line%%=*}"
value="${line#*=}"
echo "$key=$value" >> $GITHUB_OUTPUT
fi
done < .env
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
echo "VCS_REF=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_OUTPUT
echo "VCS_REF=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

- name: Install Cosign🔐
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad
- name: Login to GHCR🔑
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
cosign-release: v${{ env.COSIGN_VERSION }}
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}

- name: Setup Docker Buildx🏗️
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
Expand All @@ -72,36 +89,44 @@ jobs:
file: ./Dockerfile.template
platforms: linux/amd64
load: true
tags: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
tags: ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BASE_VERSION=${{ env.BASE_VERSION }}
BASE_HASH=${{ env.BASE_HASH }}
OPENSSL_VERSION=${{ env.OPENSSL_VERSION }}
APP_VERSION=${{ env.APP_VERSION }}
PCRE_VERSION=${{ env.PCRE_VERSION }}
ZLIB_VERSION=${{ env.ZLIB_VERSION }}
ZSTD_VERSION=${{ env.ZSTD_VERSION }}
VCS_REF=${{ env.VCS_REF }}
UID=${{ env.UID }}
GID=${{ env.GID }}
BUILD_DATE=${{ env.BUILD_DATE }}
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 }}
PCRE_VERSION=${{ steps.load_env.outputs.PCRE_VERSION }}
ZLIB_VERSION=${{ steps.load_env.outputs.ZLIB_VERSION }}
ZSTD_VERSION=${{ steps.load_env.outputs.ZSTD_VERSION }}
VCS_REF=${{ steps.load_env.outputs.VCS_REF }}
UID=${{ steps.load_env.outputs.UID }}
GID=${{ steps.load_env.outputs.GID }}
BUILD_DATE=${{ steps.load_env.outputs.BUILD_DATE }}

- name: Slim image with Mint🔧
- name: Slim and push image with Mint🔧
run: |
curl -sSL -o ds.tar.gz https://github.com/mintoolkit/mint/releases/download/${{ env.MINT_VERSION }}/dist_linux.tar.gz
tar -xf ds.tar.gz
./dist_linux/mint --quiet build \
--target ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} \
--tag ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} \
--target ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }} \
--tag ghcr.io/ammnt/freenginx:${{ steps.load_env.outputs.APP_VERSION }} \
--tag ghcr.io/ammnt/freenginx:latest \
--http-probe=false --continue-after=5 \
--include-path=/etc/shadow --include-path=/etc/group
docker push ghcr.io/ammnt/freenginx --all-tags

- name: Upload Mint report📁
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: mint-report
path: slim.report.json

- name: Get image digest🔢
id: digest
run: |
DIGEST=$(docker inspect -f='{{index .RepoDigests 0}}' ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} | cut -d'@' -f2)
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

security-scans:
Expand All @@ -110,11 +135,22 @@ jobs:
permissions:
contents: read
security-events: write
packages: write
id-token: write
strategy:
matrix:
scanner: [hadolint, dive, dockle, scout, trivy, grype, snyk, syft]
fail-fast: false
steps:
- name: Checkout repository📥
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Login to Docker Hub🔑
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Lint Dockerfile with Hadolint🔍
if: matrix.scanner == 'hadolint'
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5
Expand All @@ -129,15 +165,16 @@ jobs:
env:
CI: true
run: |
docker pull ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
wget -q https://github.com/wagoodman/dive/releases/download/v${{ env.DIVE_VERSION }}/dive_${{ env.DIVE_VERSION }}_linux_amd64.tar.gz
tar xzf dive_${{ env.DIVE_VERSION }}_linux_amd64.tar.gz -C /usr/local/bin
dive --config ./dive-ci.yml ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
dive --config ./dive-ci.yml ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}

- name: Dockle scan🛡️
if: matrix.scanner == 'dockle'
uses: goodwithtech/dockle-action@e30e6af832aad6ea7dca2a248d31a85eab6dbd68
with:
image: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
exit-code: "1"
exit-level: "warn"
ignore: "CIS-DI-0010"
Expand All @@ -147,7 +184,7 @@ jobs:
uses: docker/scout-action@f8c776824083494ab0d56b8105ba2ca85c86e4de
with:
command: cves,sbom
image: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
sarif-file: scout.sarif
write-comment: true
summary: false
Expand All @@ -158,20 +195,22 @@ jobs:
if: matrix.scanner == 'trivy'
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518
with:
image-ref: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image-ref: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
scan-type: image
format: sarif
output: trivy.sarif
severity: "MEDIUM,HIGH,CRITICAL"
scanners: "vuln,secret"
exit-code: "1"
github-pat: ${{ secrets.GH_TOKEN }}
version: v${{ env.TRIVY_VERSION }}
trivy-config: trivy.yaml

- name: Grype scan🛡️
if: matrix.scanner == 'grype'
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c
with:
image: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
fail-build: false
severity-cutoff: critical
output-format: sarif
Expand All @@ -185,7 +224,7 @@ jobs:
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
sarif: true
args: --file=Dockerfile.template --sarif-file-output=snyk.sarif

Expand All @@ -194,7 +233,7 @@ jobs:
uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11
with:
syft-version: v${{ env.SYFT_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }}
image: ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
format: cyclonedx-json
upload-artifact: true
dependency-snapshot: true
Expand Down Expand Up @@ -226,6 +265,11 @@ jobs:
id-token: write
attestations: write
steps:
- name: Install Cosign🔐
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad
with:
cosign-release: v${{ env.COSIGN_VERSION }}

- name: Login to GHCR🔑
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
Expand All @@ -239,16 +283,15 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Push images to registries🚚
- name: Push images to Docker Hub🚚
run: |
docker tag ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} ghcr.io/ammnt/freenginx:latest
docker tag ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} ammnt/freenginx:${{ env.APP_VERSION }}
docker tag ghcr.io/ammnt/freenginx:${{ env.APP_VERSION }} ammnt/freenginx:latest
docker push ghcr.io/ammnt/freenginx --all-tags
docker pull ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }}
docker tag ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }} ammnt/freenginx:${{ needs.build.outputs.app_version }}
docker tag ghcr.io/ammnt/freenginx:${{ needs.build.outputs.app_version }} ammnt/freenginx:latest
docker push ammnt/freenginx --all-tags

- name: Attest image provenance📜
continue-on-error: true
# continue-on-error: true
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
Expand All @@ -258,7 +301,7 @@ jobs:
github-token: ${{ secrets.GH_TOKEN }}

- name: Sign container images✍️
continue-on-error: true
# continue-on-error: true
env:
COSIGN_EXPERIMENTAL: "true"
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ RUN set -eu \
linux-headers=6.16.12-r0 \
tzdata=2026a-r0 \
upx=5.0.2-r0 \
perl=5.42.0-r0 \
perl=5.42.1-r0 \
cmake=4.1.3-r0 \
zstd-dev=1.5.7-r2 \
zstd-static=1.5.7-r2 \
Expand Down Expand Up @@ -194,11 +194,11 @@ COPY --chown="${UID}:${GID}" ./default.conf /etc/freenginx/conf.d/default.conf
EXPOSE 8080/tcp 8443/tcp 8443/udp

# OCI labels for image metadata
LABEL description="Distroless FreeNGINX with HTTP/3, QUIC and PQC support🚀" \
LABEL description="Distroless FreeNGINX with HTTP/3, QUIC, ECH and PQC support🚀" \
maintainer="ammnt <admin@msftcnsi.com>" \
org.opencontainers.image.description="Distroless FreeNGINX with HTTP/3, QUIC and PQC support🚀" \
org.opencontainers.image.description="Distroless FreeNGINX with HTTP/3, QUIC, ECH and PQC support🚀" \
org.opencontainers.image.authors="ammnt, admin@msftcnsi.com" \
org.opencontainers.image.title="Distroless FreeNGINX with HTTP/3, QUIC and PQC support🚀" \
org.opencontainers.image.title="Distroless FreeNGINX with HTTP/3, QUIC, ECH and PQC support🚀" \
org.opencontainers.image.source="https://github.com/ammnt/freenginx/" \
org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.documentation="https://github.com/ammnt/freenginx/blob/main/README.md" \
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
![GitHub Maintained](https://img.shields.io/badge/open%20source-yes-orange)
![GitHub Maintained](https://img.shields.io/badge/maintained-yes-yellow)

> **Production-ready, security-focused FreeNGINX image with HTTP/3, QUIC and PQC support.**
> **Production-ready, security-focused FreeNGINX image with HTTP/3, QUIC, ECH and PQC support.**

> [!IMPORTANT]
> QuicTLS is now deprecated. I use OpenSSL, since this library natively supports OCSP, PQC and QUIC⚠️
> 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 and PQC💡
> You can find an example [configuration file](example.conf) in the repository for successfully configuring HTTP/3, ECH and PQC💡

> [!IMPORTANT]
> UID/GID changed to 10001 - it's [recommended](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for Kubernetes and prevents conflicts with system users⚠️
Expand Down Expand Up @@ -114,6 +114,7 @@ spec:
- **Native QUIC and HTTP/3 support** - OpenSSL and QUIC without patches or experimental implementations (RFC 9114, RFC 9000)
- **Native PQC support** - hybrid post-quantum key exchange algorithms in elliptic curves (NIST PQC Standardization, FIPS 203/204/205)
- **Native TLS 1.3 with 0-RTT** (RFC 8446, RFC 9001)
- **Native support for the Encrypted Client Hello (ECH)** - extension of the TLS 1.3 protocol (RFC 9849)

### **Supply Chain Integrity**
- **Signed images** - signatures and **provenance attestation** (SLSA Level 3 requirements, in-toto attestations)
Expand Down
14 changes: 11 additions & 3 deletions example.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This is an example of a configuration file for enabling QUIC, HTTP3 and "A+" SSL tests rating. Further configuration is required.
# This is an example of a configuration file for enabling QUIC, HTTP3, PQC, ECH and "A+" SSL tests rating. Further configuration is required.
worker_processes auto;
worker_rlimit_nofile 65536;
pid /tmp/freenginx.pid;
Expand Down Expand Up @@ -46,6 +46,10 @@ http {
ssl_session_tickets off;
ssl_session_timeout 1440m;
ssl_buffer_size 4k;
# ssl_protocols TLSv1.3;
# 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_ciphers TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDH+AESGCM+AES256:ECDH+CHACHA20;
Expand All @@ -56,6 +60,7 @@ http {
ssl_certificate_key /etc/freenginx/ssl/privkey.pem;
ssl_trusted_certificate /etc/freenginx/ssl/fullchain.pem;
ssl_dhparam /etc/freenginx/ssl/dhparam.pem;
# ssl_encrypted_hello_key /etc/angie/ssl/ech.pem.ech;
ssl_stapling on;
ssl_stapling_verify on;
ssl_ocsp on;
Expand All @@ -68,7 +73,9 @@ http {
add_header Alt-Svc 'h3=":443"; ma=2592000; persist=1' always;
quic_retry on;
quic_gso on;
log_format main '$time_iso8601 $ssl_server_name $scheme $ssl_alpn_protocol $status $uri $ssl_protocol $server_protocol $ssl_curve $ssl_cipher';
log_format main '[$time_local] $request_time $request_method $uri $status $ssl_protocol $server_protocol $ssl_curve $ssl_cipher';
# log_format ech '[$time_local] $request_time $request_method "$request" $status $uri $status $ssl_protocol $server_protocol $ssl_curve $ssl_cipher'
# 'ECH:$ssl_ech_status:$ssl_ech_outer_server_name $ssl_server_name';
log_format debug escape=json '{'
'"@timestamp": "$time_iso8601", '
'"msec": "$msec", '
Expand Down Expand Up @@ -114,8 +121,9 @@ http {
'"http_cf_ray": "$http_cf_ray", '
'"http_x_forwarded_proto": "$http_x_forwarded_proto" '
'}';
access_log /dev/stdout simple;
access_log /dev/stdout main;
error_log stderr warn;

gzip on;
gzip_vary on;
gzip_proxied any;
Expand Down
15 changes: 15 additions & 0 deletions trivy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cache:
# Same as '--cache-dir'
dir: "/tmp/cache"

# Same as '--debug'
debug: false

# Same as '--insecure'
insecure: false

# Same as '--quiet'
quiet: true

# Same as '--timeout'
timeout: 5m0s