diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
index ede7bed609d..5b1d04e4bc1 100644
--- a/nixos/doc/manual/release-notes/rl-2009.xml
+++ b/nixos/doc/manual/release-notes/rl-2009.xml
@@ -235,7 +235,16 @@ php.override {
Be aware that backwards state migrations are not supported by Deluge.
-
+
+
+ Add option services.nginx.enableSandbox to starting Nginx web server with additional sandbox/hardening options.
+ By default, write access to services.nginx.stateDir is allowed. To allow writing to other folders,
+ use systemd.services.nginx.serviceConfig.ReadWritePaths
+
+systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
+
+
+
The NixOS options nesting.clone and
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 1e9cda7e478..312d2b0a21a 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -463,6 +463,14 @@ in
'';
};
+ enableSandbox = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Starting Nginx web server with additional sandbox/hardening options.
+ '';
+ };
+
user = mkOption {
type = types.str;
default = "nginx";
@@ -710,6 +718,27 @@ in
LogsDirectoryMode = "0750";
# Capabilities
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+ # Security
+ NoNewPrivileges = true;
+ } // optionalAttrs cfg.enableSandbox {
+ # Sandboxing
+ ProtectSystem = "strict";
+ ProtectHome = mkDefault true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHostname = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ LockPersonality = true;
+ MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules);
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ PrivateMounts = true;
+ # System Call Filtering
+ SystemCallArchitectures = "native";
};
};
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 4190f70eeee..f3e90f9bfa7 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -225,6 +225,7 @@ in
nginx = handleTest ./nginx.nix {};
nginx-etag = handleTest ./nginx-etag.nix {};
nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
+ nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
nginx-sso = handleTest ./nginx-sso.nix {};
nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
nixos-generate-config = handleTest ./nixos-generate-config.nix {};
diff --git a/nixos/tests/nginx-pubhtml.nix b/nixos/tests/nginx-pubhtml.nix
index 432913cb42d..6e1e605628e 100644
--- a/nixos/tests/nginx-pubhtml.nix
+++ b/nixos/tests/nginx-pubhtml.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix {
name = "nginx-pubhtml";
machine = { pkgs, ... }: {
+ systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
services.nginx.enable = true;
services.nginx.virtualHosts.localhost = {
locations."~ ^/\\~([a-z0-9_]+)(/.*)?$".alias = "/home/$1/public_html$2";
diff --git a/nixos/tests/nginx-sandbox.nix b/nixos/tests/nginx-sandbox.nix
new file mode 100644
index 00000000000..bc9d3ba8add
--- /dev/null
+++ b/nixos/tests/nginx-sandbox.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+ name = "nginx-sandbox";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ izorkin ];
+ };
+
+ # This test checks the creation and reading of a file in sandbox mode. Used simple lua script.
+
+ machine = { pkgs, ... }: {
+ nixpkgs.overlays = [
+ (self: super: {
+ nginx-lua = super.nginx.override {
+ modules = [
+ pkgs.nginxModules.lua
+ ];
+ };
+ })
+ ];
+ services.nginx.enable = true;
+ services.nginx.package = pkgs.nginx-lua;
+ services.nginx.enableSandbox = true;
+ services.nginx.virtualHosts.localhost = {
+ extraConfig = ''
+ location /test1-write {
+ content_by_lua_block {
+ local create = os.execute('${pkgs.coreutils}/bin/mkdir /tmp/test1-read')
+ local create = os.execute('${pkgs.coreutils}/bin/touch /tmp/test1-read/foo.txt')
+ local echo = os.execute('${pkgs.coreutils}/bin/echo worked > /tmp/test1-read/foo.txt')
+ }
+ }
+ location /test1-read {
+ root /tmp;
+ }
+ location /test2-write {
+ content_by_lua_block {
+ local create = os.execute('${pkgs.coreutils}/bin/mkdir /var/web/test2-read')
+ local create = os.execute('${pkgs.coreutils}/bin/touch /var/web/test2-read/bar.txt')
+ local echo = os.execute('${pkgs.coreutils}/bin/echo error-worked > /var/web/test2-read/bar.txt')
+ }
+ }
+ location /test2-read {
+ root /var/web;
+ }
+ '';
+ };
+ users.users.foo.isNormalUser = true;
+ };
+
+ testScript = ''
+ machine.wait_for_unit("nginx")
+ machine.wait_for_open_port(80)
+
+ # Checking write in temporary folder
+ machine.succeed("$(curl -vvv http://localhost/test1-write)")
+ machine.succeed('test "$(curl -fvvv http://localhost/test1-read/foo.txt)" = worked')
+
+ # Checking write in protected folder. In sandbox mode for the nginx service, the folder /var/web is mounted
+ # in read-only mode.
+ machine.succeed("mkdir -p /var/web")
+ machine.succeed("chown nginx:nginx /var/web")
+ machine.succeed("$(curl -vvv http://localhost/test2-write)")
+ assert "404 Not Found" in machine.succeed(
+ "curl -vvv -s http://localhost/test2-read/bar.txt"
+ )
+ '';
+})
diff --git a/pkgs/servers/http/nginx/modules.nix b/pkgs/servers/http/nginx/modules.nix
index 16782966944..1111990435a 100644
--- a/pkgs/servers/http/nginx/modules.nix
+++ b/pkgs/servers/http/nginx/modules.nix
@@ -140,6 +140,7 @@ in
export LUAJIT_LIB="${pkgs.luajit}/lib"
export LUAJIT_INC="${pkgs.luajit}/include/luajit-2.0"
'';
+ allowMemoryWriteExecute = true;
};
lua-upstream = {
@@ -150,6 +151,7 @@ in
sha256 = "1gqccg8airli3i9103zv1zfwbjm27h235qjabfbfqk503rjamkpk";
};
inputs = [ pkgs.luajit ];
+ allowMemoryWriteExecute = true;
};
modsecurity = {
@@ -246,6 +248,7 @@ in
in {
src = ngx_pagespeed;
inputs = [ pkgs.zlib pkgs.libuuid ]; # psol deps
+ allowMemoryWriteExecute = true;
};
pam = {