Merge: nixos/postgresql: extension based hardening relaxation (#355010)

authored by Maximilian Bosch and committed by GitHub 97a911e8 319acd0b

+93 -54
+27 -20
nixos/modules/services/databases/postgresql.nix
··· 2 3 let 4 inherit (lib) 5 attrValues 6 concatMapStrings 7 concatStringsSep ··· 9 elem 10 escapeShellArgs 11 filterAttrs 12 isString 13 literalExpression 14 mapAttrs ··· 31 32 cfg = config.services.postgresql; 33 34 - postgresql = 35 - let 36 - # ensure that 37 - # services.postgresql = { 38 - # enableJIT = true; 39 - # package = pkgs.postgresql_<major>; 40 - # }; 41 - # works. 42 - base = if cfg.enableJIT then cfg.package.withJIT else cfg.package.withoutJIT; 43 - in 44 - if cfg.extraPlugins == [] 45 - then base 46 - else base.withPackages cfg.extraPlugins; 47 48 toStr = value: 49 if true == value then "yes" ··· 61 62 groupAccessAvailable = versionAtLeast postgresql.version "11.0"; 63 64 in 65 66 { ··· 69 70 (mkRenamedOptionModule [ "services" "postgresql" "logLinePrefix" ] [ "services" "postgresql" "settings" "log_line_prefix" ]) 71 (mkRenamedOptionModule [ "services" "postgresql" "port" ] [ "services" "postgresql" "settings" "port" ]) 72 ]; 73 74 ###### interface ··· 372 ''; 373 }; 374 375 - extraPlugins = mkOption { 376 type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path)); 377 default = _: []; 378 example = literalExpression "ps: with ps; [ postgis pg_repack ]"; 379 description = '' 380 - List of PostgreSQL plugins. 381 ''; 382 }; 383 ··· 639 PrivateTmp = true; 640 ProtectHome = true; 641 ProtectSystem = "strict"; 642 - MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off"); 643 NoNewPrivileges = true; 644 LockPersonality = true; 645 PrivateDevices = true; ··· 663 RestrictRealtime = true; 664 RestrictSUIDSGID = true; 665 SystemCallArchitectures = "native"; 666 - SystemCallFilter = [ 667 - "@system-service" 668 - "~@privileged @resources" 669 - ]; 670 UMask = if groupAccessAvailable then "0027" else "0077"; 671 } 672 (mkIf (cfg.dataDir != "/var/lib/postgresql") {
··· 2 3 let 4 inherit (lib) 5 + any 6 attrValues 7 concatMapStrings 8 concatStringsSep ··· 10 elem 11 escapeShellArgs 12 filterAttrs 13 + getName 14 isString 15 literalExpression 16 mapAttrs ··· 33 34 cfg = config.services.postgresql; 35 36 + # ensure that 37 + # services.postgresql = { 38 + # enableJIT = true; 39 + # package = pkgs.postgresql_<major>; 40 + # }; 41 + # works. 42 + basePackage = if cfg.enableJIT 43 + then cfg.package.withJIT 44 + else cfg.package.withoutJIT; 45 + 46 + postgresql = if cfg.extensions == [] 47 + then basePackage 48 + else basePackage.withPackages cfg.extensions; 49 50 toStr = value: 51 if true == value then "yes" ··· 63 64 groupAccessAvailable = versionAtLeast postgresql.version "11.0"; 65 66 + extensionNames = map getName postgresql.installedExtensions; 67 + extensionInstalled = extension: elem extension extensionNames; 68 in 69 70 { ··· 73 74 (mkRenamedOptionModule [ "services" "postgresql" "logLinePrefix" ] [ "services" "postgresql" "settings" "log_line_prefix" ]) 75 (mkRenamedOptionModule [ "services" "postgresql" "port" ] [ "services" "postgresql" "settings" "port" ]) 76 + (mkRenamedOptionModule [ "services" "postgresql" "extraPlugins" ] [ "services" "postgresql" "extensions" ]) 77 ]; 78 79 ###### interface ··· 377 ''; 378 }; 379 380 + extensions = mkOption { 381 type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path)); 382 default = _: []; 383 example = literalExpression "ps: with ps; [ postgis pg_repack ]"; 384 description = '' 385 + List of PostgreSQL extensions to install. 386 ''; 387 }; 388 ··· 644 PrivateTmp = true; 645 ProtectHome = true; 646 ProtectSystem = "strict"; 647 + MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off" && (!any extensionInstalled [ "plv8" ])); 648 NoNewPrivileges = true; 649 LockPersonality = true; 650 PrivateDevices = true; ··· 668 RestrictRealtime = true; 669 RestrictSUIDSGID = true; 670 SystemCallArchitectures = "native"; 671 + SystemCallFilter = 672 + [ 673 + "@system-service" 674 + "~@privileged @resources" 675 + ] 676 + ++ lib.optionals (any extensionInstalled [ "plv8" ]) [ "@pkey" ]; 677 UMask = if groupAccessAvailable then "0027" else "0077"; 678 } 679 (mkIf (cfg.dataDir != "/var/lib/postgresql") {
+1 -1
nixos/modules/services/web-apps/immich.nix
··· 227 ensureClauses.login = true; 228 } 229 ]; 230 - extraPlugins = ps: with ps; [ pgvecto-rs ]; 231 settings = { 232 shared_preload_libraries = [ "vectors.so" ]; 233 search_path = "\"$user\", public, vectors";
··· 227 ensureClauses.login = true; 228 } 229 ]; 230 + extensions = ps: with ps; [ pgvecto-rs ]; 231 settings = { 232 shared_preload_libraries = [ "vectors.so" ]; 233 search_path = "\"$user\", public, vectors";
+1 -1
nixos/modules/services/web-apps/mobilizon.nix
··· 383 ensureDBOwnership = false; 384 } 385 ]; 386 - extraPlugins = ps: with ps; [ postgis ]; 387 }; 388 389 # Nginx config taken from support/nginx/mobilizon-release.conf
··· 383 ensureDBOwnership = false; 384 } 385 ]; 386 + extensions = ps: with ps; [ postgis ]; 387 }; 388 389 # Nginx config taken from support/nginx/mobilizon-release.conf
+1 -1
nixos/tests/postgresql/anonymizer.nix
··· 20 inherit package; 21 enable = true; 22 enableJIT = lib.hasInfix "-jit-" package.name; 23 - extraPlugins = ps: [ ps.anonymizer ]; 24 settings.shared_preload_libraries = [ "anon" ]; 25 }; 26 };
··· 20 inherit package; 21 enable = true; 22 enableJIT = lib.hasInfix "-jit-" package.name; 23 + extensions = ps: [ ps.anonymizer ]; 24 settings.shared_preload_libraries = [ "anon" ]; 25 }; 26 };
+1 -1
nixos/tests/postgresql/pgjwt.nix
··· 24 inherit package; 25 enable = true; 26 enableJIT = lib.hasInfix "-jit-" package.name; 27 - extraPlugins = 28 ps: with ps; [ 29 pgjwt 30 pgtap
··· 24 inherit package; 25 enable = true; 26 enableJIT = lib.hasInfix "-jit-" package.name; 27 + extensions = 28 ps: with ps; [ 29 pgjwt 30 pgtap
+1 -1
nixos/tests/postgresql/pgvecto-rs.nix
··· 38 inherit package; 39 enable = true; 40 enableJIT = lib.hasInfix "-jit-" package.name; 41 - extraPlugins = 42 ps: with ps; [ 43 pgvecto-rs 44 ];
··· 38 inherit package; 39 enable = true; 40 enableJIT = lib.hasInfix "-jit-" package.name; 41 + extensions = 42 ps: with ps; [ 43 pgvecto-rs 44 ];
+38 -14
nixos/tests/postgresql/postgresql.nix
··· 14 postgresql-clauses = makeEnsureTestFor package; 15 }; 16 17 - test-sql = pkgs.writeText "postgresql-test" '' 18 - CREATE EXTENSION pgcrypto; -- just to check if lib loading works 19 - CREATE TABLE sth ( 20 - id int 21 ); 22 - INSERT INTO sth (id) VALUES (1); 23 - INSERT INTO sth (id) VALUES (1); 24 - INSERT INTO sth (id) VALUES (1); 25 - INSERT INTO sth (id) VALUES (1); 26 - INSERT INTO sth (id) VALUES (1); 27 - CREATE TABLE xmltest ( doc xml ); 28 - INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled 29 - ''; 30 31 makeTestForWithBackupAll = 32 package: backupAll: 33 makeTest { 34 name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}"; 35 meta = with lib.maintainers; { ··· 37 }; 38 39 nodes.machine = 40 - { ... }: 41 { 42 services.postgresql = { 43 inherit package; 44 enable = true; 45 enableJIT = lib.hasInfix "-jit-" package.name; 46 }; 47 48 services.postgresqlBackup = { ··· 69 70 with subtest("Postgresql is available just after unit start"): 71 machine.succeed( 72 - "cat ${test-sql} | sudo -u postgres psql" 73 ) 74 75 with subtest("Postgresql survives restart (bug #1735)"):
··· 14 postgresql-clauses = makeEnsureTestFor package; 15 }; 16 17 + test-sql = 18 + enablePLv8Test: 19 + pkgs.writeText "postgresql-test" ( 20 + '' 21 + CREATE EXTENSION pgcrypto; -- just to check if lib loading works 22 + CREATE TABLE sth ( 23 + id int 24 + ); 25 + INSERT INTO sth (id) VALUES (1); 26 + INSERT INTO sth (id) VALUES (1); 27 + INSERT INTO sth (id) VALUES (1); 28 + INSERT INTO sth (id) VALUES (1); 29 + INSERT INTO sth (id) VALUES (1); 30 + CREATE TABLE xmltest ( doc xml ); 31 + INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled 32 + '' 33 + + lib.optionalString enablePLv8Test '' 34 + -- check if hardening gets relaxed 35 + CREATE EXTENSION plv8; 36 + -- try to trigger the V8 JIT, which requires MemoryDenyWriteExecute 37 + DO $$ 38 + let xs = []; 39 + for (let i = 0, n = 400000; i < n; i++) { 40 + xs.push(Math.round(Math.random() * n)) 41 + } 42 + console.log(xs.reduce((acc, x) => acc + x, 0)); 43 + $$ LANGUAGE plv8; 44 + '' 45 ); 46 47 makeTestForWithBackupAll = 48 package: backupAll: 49 + let 50 + enablePLv8Check = !package.pkgs.plv8.meta.broken; 51 + in 52 makeTest { 53 name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}"; 54 meta = with lib.maintainers; { ··· 56 }; 57 58 nodes.machine = 59 + { config, ... }: 60 { 61 services.postgresql = { 62 inherit package; 63 enable = true; 64 enableJIT = lib.hasInfix "-jit-" package.name; 65 + # plv8 doesn't support postgresql with JIT, so we only run the test 66 + # for the non-jit variant. 67 + # TODO(@Ma27) split this off into its own VM test and move a few other 68 + # extension tests to use postgresqlTestExtension. 69 + extensions = lib.mkIf enablePLv8Check (ps: with ps; [ plv8 ]); 70 }; 71 72 services.postgresqlBackup = { ··· 93 94 with subtest("Postgresql is available just after unit start"): 95 machine.succeed( 96 + "cat ${test-sql enablePLv8Check} | sudo -u postgres psql" 97 ) 98 99 with subtest("Postgresql survives restart (bug #1735)"):
+1 -1
nixos/tests/postgresql/timescaledb.nix
··· 54 inherit package; 55 enable = true; 56 enableJIT = lib.hasInfix "-jit-" package.name; 57 - extraPlugins = 58 ps: with ps; [ 59 timescaledb 60 timescaledb_toolkit
··· 54 inherit package; 55 enable = true; 56 enableJIT = lib.hasInfix "-jit-" package.name; 57 + extensions = 58 ps: with ps; [ 59 timescaledb 60 timescaledb_toolkit
+1 -1
nixos/tests/postgresql/tsja.nix
··· 21 inherit package; 22 enable = true; 23 enableJIT = lib.hasInfix "-jit-" package.name; 24 - extraPlugins = 25 ps: with ps; [ 26 tsja 27 ];
··· 21 inherit package; 22 enable = true; 23 enableJIT = lib.hasInfix "-jit-" package.name; 24 + extensions = 25 ps: with ps; [ 26 tsja 27 ];
+1 -1
nixos/tests/postgresql/wal2json.nix
··· 17 inherit package; 18 enable = true; 19 enableJIT = lib.hasInfix "-jit-" package.name; 20 - extraPlugins = with package.pkgs; [ wal2json ]; 21 settings = { 22 wal_level = "logical"; 23 max_replication_slots = "10";
··· 17 inherit package; 18 enable = true; 19 enableJIT = lib.hasInfix "-jit-" package.name; 20 + extensions = with package.pkgs; [ wal2json ]; 21 settings = { 22 wal_level = "logical"; 23 max_replication_slots = "10";
+20 -12
pkgs/servers/sql/postgresql/generic.nix
··· 315 }; 316 }); 317 318 - postgresqlWithPackages = { postgresql, buildEnv }: f: buildEnv { 319 name = "${postgresql.pname}-and-plugins-${postgresql.version}"; 320 - paths = f postgresql.pkgs ++ [ 321 postgresql 322 postgresql.man # in case user installs this into environment 323 ]; 324 325 pathsToLink = ["/"]; 326 327 - passthru.version = postgresql.version; 328 - passthru.psqlSchema = postgresql.psqlSchema; 329 - passthru.withJIT = postgresqlWithPackages { 330 - inherit buildEnv; 331 - postgresql = postgresql.withJIT; 332 - } f; 333 - passthru.withoutJIT = postgresqlWithPackages { 334 - inherit buildEnv; 335 - postgresql = postgresql.withoutJIT; 336 - } f; 337 }; 338 339 in
··· 315 }; 316 }); 317 318 + postgresqlWithPackages = { postgresql, buildEnv }: f: let 319 + installedExtensions = f postgresql.pkgs; 320 + in buildEnv { 321 name = "${postgresql.pname}-and-plugins-${postgresql.version}"; 322 + paths = installedExtensions ++ [ 323 postgresql 324 postgresql.man # in case user installs this into environment 325 ]; 326 327 pathsToLink = ["/"]; 328 329 + passthru = { 330 + inherit installedExtensions; 331 + inherit (postgresql) 332 + psqlSchema 333 + version 334 + ; 335 + 336 + withJIT = postgresqlWithPackages { 337 + inherit buildEnv; 338 + postgresql = postgresql.withJIT; 339 + } f; 340 + withoutJIT = postgresqlWithPackages { 341 + inherit buildEnv; 342 + postgresql = postgresql.withoutJIT; 343 + } f; 344 + }; 345 }; 346 347 in