nixpkgs/pkgs/servers/http/nginx/generic.nix
Dee Anzorge f124c73686 nginx: change etags for statically compressed files served from store
Per RFC 9110, [section 8.8.1][1], different representations of the same
resource should have different Etags:

> A strong validator is unique across all versions of all
> representations associated with a particular resource over time.
> However, there is no implication of uniqueness across representations
> of different resources (i.e., the same strong validator might be in
> use for representations of multiple resources at the same time and
> does not imply that those representations are equivalent)

When serving statically compressed files (ie, when there is an existing
corresponding .gz/.br/etc. file on disk), Nginx sends the Etag marked
as strong. These tags should be different for each compressed format
(as shown in  an explicit example in section [8.8.3.3][2] of the RFC).
Upstream Etags are composed of the file modification timestamp and
content length, and the latter generally changes between these
representations.

Previous implementation of Nix-specific Etags for things served from
store used the store hash. This is fine to share between different
files, but it becomes a problem for statically compressed versions of
the same file, as it means Nginx was serving different representations
of the same resource with the same Etag, marked as strong.

This patch addresses this by imitating the upstream Nginx behavior, and
appending the value of content length to the store hash.

[1]: https://www.rfc-editor.org/rfc/rfc9110.html#name-validator-fields
[2]:
https://www.rfc-editor.org/rfc/rfc9110.html#name-example-entity-tags-varying
2024-01-13 22:07:50 +01:00

209 lines
6.9 KiB
Nix

outer@{ lib, stdenv, fetchurl, fetchpatch, openssl, zlib, pcre, libxml2, libxslt
, nginx-doc
, nixosTests
, installShellFiles, substituteAll, removeReferencesTo, gd, geoip, perl
, withDebug ? false
, withKTLS ? true
, withStream ? true
, withMail ? false
, withPerl ? true
, withSlice ? false
, modules ? []
, ...
}:
{ pname ? "nginx"
, version
, nginxVersion ? version
, src ? null # defaults to upstream nginx ${version}
, hash ? null # when not specifying src
, configureFlags ? []
, nativeBuildInputs ? []
, buildInputs ? []
, extraPatches ? []
, fixPatch ? p: p
, postPatch ? ""
, preConfigure ? ""
, preInstall ? ""
, postInstall ? ""
, meta ? null
, nginx-doc ? outer.nginx-doc
, passthru ? { tests = {}; }
}:
let
moduleNames = map (mod: mod.name or (throw "The nginx module with source ${toString mod.src} does not have a `name` attribute. This prevents duplicate module detection and is no longer supported."))
modules;
mapModules = attrPath: lib.flip lib.concatMap modules
(mod:
let supports = mod.supports or (_: true);
in
if supports nginxVersion then mod.${attrPath} or []
else throw "Module at ${toString mod.src} does not support nginx version ${nginxVersion}!");
in
assert lib.assertMsg (lib.unique moduleNames == moduleNames)
"nginx: duplicate modules: ${lib.concatStringsSep ", " moduleNames}. A common cause for this is that services.nginx.additionalModules adds a module which the nixos module itself already adds.";
stdenv.mkDerivation {
inherit pname version nginxVersion;
outputs = [ "out" "doc" ];
src = if src != null then src else fetchurl {
url = "https://nginx.org/download/nginx-${version}.tar.gz";
inherit hash;
};
nativeBuildInputs = [
installShellFiles
removeReferencesTo
] ++ nativeBuildInputs;
buildInputs = [ openssl zlib pcre libxml2 libxslt gd geoip perl ]
++ buildInputs
++ mapModules "inputs";
configureFlags = [
"--sbin-path=bin/nginx"
"--with-http_ssl_module"
"--with-http_v2_module"
"--with-http_realip_module"
"--with-http_addition_module"
"--with-http_xslt_module"
"--with-http_sub_module"
"--with-http_dav_module"
"--with-http_flv_module"
"--with-http_mp4_module"
"--with-http_gunzip_module"
"--with-http_gzip_static_module"
"--with-http_auth_request_module"
"--with-http_random_index_module"
"--with-http_secure_link_module"
"--with-http_degradation_module"
"--with-http_stub_status_module"
"--with-threads"
"--with-pcre-jit"
"--http-log-path=/var/log/nginx/access.log"
"--error-log-path=/var/log/nginx/error.log"
"--pid-path=/var/log/nginx/nginx.pid"
"--http-client-body-temp-path=/tmp/nginx_client_body"
"--http-proxy-temp-path=/tmp/nginx_proxy"
"--http-fastcgi-temp-path=/tmp/nginx_fastcgi"
"--http-uwsgi-temp-path=/tmp/nginx_uwsgi"
"--http-scgi-temp-path=/tmp/nginx_scgi"
] ++ lib.optionals withDebug [
"--with-debug"
] ++ lib.optionals withKTLS [
"--with-openssl-opt=enable-ktls"
] ++ lib.optionals withStream [
"--with-stream"
"--with-stream_realip_module"
"--with-stream_ssl_module"
"--with-stream_ssl_preread_module"
] ++ lib.optionals withMail [
"--with-mail"
"--with-mail_ssl_module"
] ++ lib.optionals withPerl [
"--with-http_perl_module"
"--with-perl=${perl}/bin/perl"
"--with-perl_modules_path=lib/perl5"
] ++ lib.optional withSlice "--with-http_slice_module"
++ lib.optional (gd != null) "--with-http_image_filter_module"
++ lib.optional (geoip != null) "--with-http_geoip_module"
++ lib.optional (withStream && geoip != null) "--with-stream_geoip_module"
++ lib.optional (with stdenv.hostPlatform; isLinux || isFreeBSD) "--with-file-aio"
++ configureFlags
++ map (mod: "--add-module=${mod.src}") modules;
env.NIX_CFLAGS_COMPILE = toString ([
"-I${libxml2.dev}/include/libxml2"
"-Wno-error=implicit-fallthrough"
] ++ lib.optionals (stdenv.cc.isGNU && lib.versionAtLeast stdenv.cc.version "11") [
# fix build vts module on gcc11
"-Wno-error=stringop-overread"
] ++ lib.optionals stdenv.isDarwin [
"-Wno-error=deprecated-declarations"
"-Wno-error=gnu-folding-constant"
"-Wno-error=unused-but-set-variable"
]);
configurePlatforms = [];
# Disable _multioutConfig hook which adds --bindir=$out/bin into configureFlags,
# which breaks build, since nginx does not actually use autoconf.
preConfigure = ''
setOutputFlags=
'' + preConfigure
+ lib.concatMapStringsSep "\n" (mod: mod.preConfigure or "") modules;
patches = map fixPatch ([
(substituteAll {
src = ./nix-etag-1.15.4.patch;
preInstall = ''
export nixStoreDir="$NIX_STORE" nixStoreDirLen="''${#NIX_STORE}"
'';
})
./nix-skip-check-logs-path.patch
] ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
(fetchpatch {
url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/102-sizeof_test_fix.patch";
sha256 = "0i2k30ac8d7inj9l6bl0684kjglam2f68z8lf3xggcc2i5wzhh8a";
})
(fetchpatch {
url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/101-feature_test_fix.patch";
sha256 = "0v6890a85aqmw60pgj3mm7g8nkaphgq65dj4v9c6h58wdsrc6f0y";
})
(fetchpatch {
url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/103-sys_nerr.patch";
sha256 = "0s497x6mkz947aw29wdy073k8dyjq8j99lax1a1mzpikzr4rxlmd";
})
] ++ mapModules "patches")
++ extraPatches;
inherit postPatch;
hardeningEnable = lib.optional (!stdenv.isDarwin) "pie";
enableParallelBuilding = true;
preInstall = ''
mkdir -p $doc
cp -r ${nginx-doc}/* $doc
# TODO: make it unconditional when `openresty` and `nginx` are not
# sharing this code.
if [[ -e man/nginx.8 ]]; then
installManPage man/nginx.8
fi
'' + preInstall;
disallowedReferences = map (m: m.src) modules;
postInstall =
let
noSourceRefs = lib.concatMapStrings (m: "remove-references-to -t ${m.src} $out/bin/nginx\n") modules;
in noSourceRefs + postInstall;
passthru = {
inherit modules;
tests = {
inherit (nixosTests) nginx nginx-auth nginx-etag nginx-etag-compression nginx-globalredirect nginx-http3 nginx-proxyprotocol nginx-pubhtml nginx-sso nginx-status-page nginx-unix-socket;
variants = lib.recurseIntoAttrs nixosTests.nginx-variants;
acme-integration = nixosTests.acme;
} // passthru.tests;
};
meta = if meta != null then meta else with lib; {
description = "A reverse proxy and lightweight webserver";
homepage = "http://nginx.org";
license = [ licenses.bsd2 ]
++ concatMap (m: m.meta.license) modules;
platforms = platforms.all;
maintainers = with maintainers; [ fpletz raitobezarius ] ++ teams.helsinki-systems.members;
};
}