nixos/nginx: validate syntax of config file at build time

Shamelessly stolen from nixcloud-webservices:
https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/web/webserver/lib/nginx_check_config.nix

The nixos test testing the behavior of nginx in case of faulty config
would not build with this change (on purpose), so I modified it so that
the failure is not syntactic.
This commit is contained in:
Guillaume Girol 2022-12-10 12:00:00 +00:00
parent 2787fc7d1e
commit a768871934
2 changed files with 45 additions and 4 deletions

View file

@ -241,7 +241,7 @@ let
configPath = if cfg.enableReload
then "/etc/nginx/nginx.conf"
else configFile;
else finalConfigFile;
execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
@ -391,6 +391,38 @@ let
);
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
snakeOilCert = pkgs.runCommand "nginx-config-validate-cert" { nativeBuildInputs = [ pkgs.openssl.bin ]; } ''
mkdir $out
openssl genrsa -des3 -passout pass:xxxxx -out server.pass.key 2048
openssl rsa -passin pass:xxxxx -in server.pass.key -out $out/server.key
openssl req -new -key $out/server.key -out server.csr \
-subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
openssl x509 -req -days 1 -in server.csr -signkey $out/server.key -out $out/server.crt
'';
validatedConfigFile = pkgs.runCommand "validated-nginx.conf" { nativeBuildInputs = [ cfg.package ]; } ''
# nginx absolutely wants to read the certificates even when told to only validate config, so let's provide fake certs
sed ${configFile} \
-e "s|ssl_certificate .*;|ssl_certificate ${snakeOilCert}/server.crt;|g" \
-e "s|ssl_trusted_certificate .*;|ssl_trusted_certificate ${snakeOilCert}/server.crt;|g" \
-e "s|ssl_certificate_key .*;|ssl_certificate_key ${snakeOilCert}/server.key;|g" \
> conf
LD_PRELOAD=${pkgs.libredirect}/lib/libredirect.so \
NIX_REDIRECTS="/etc/resolv.conf=/dev/null" \
nginx -t -c $(readlink -f ./conf) > out 2>&1 || true
if ! grep -q "syntax is ok" out; then
echo nginx config validation failed.
echo config was ${configFile}.
echo 'in case of false positive, set `services.nginx.validateConfig` to false.'
echo nginx output:
cat out
exit 1
fi
cp ${configFile} $out
'';
finalConfigFile = if cfg.validateConfig then validatedConfigFile else configFile;
in
{
@ -489,6 +521,15 @@ in
'';
};
validateConfig = mkOption {
default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
defaultText = literalExpression "pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform";
type = types.bool;
description = lib.mdDoc ''
Validate the generated nginx config at build time. The check is not very robust and can be disabled in case of false positives. This is notably the case when cross-compiling or when using `include` with files outside of the store.
'';
};
additionalModules = mkOption {
default = [];
type = types.listOf (types.attrsOf types.anything);
@ -1027,7 +1068,7 @@ in
};
environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
source = configFile;
source = finalConfigFile;
};
# This service waits for all certificates to be available
@ -1046,7 +1087,7 @@ in
# certs are updated _after_ config has been reloaded.
before = sslTargets;
after = sslServices;
restartTriggers = optionals (cfg.enableReload) [ configFile ];
restartTriggers = optionals (cfg.enableReload) [ finalConfigFile ];
# Block reloading if not all certs exist yet.
# Happens when config changes add new vhosts/certs.
unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);

View file

@ -61,7 +61,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
specialisation.reloadWithErrorsSystem.configuration = {
services.nginx.package = pkgs.nginxMainline;
services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
services.nginx.virtualHosts."hello".extraConfig = "access_log /does/not/exist.log;";
};
};
};