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 2 3 3 let 4 4 inherit (lib) 5 + any 5 6 attrValues 6 7 concatMapStrings 7 8 concatStringsSep ··· 9 10 elem 10 11 escapeShellArgs 11 12 filterAttrs 13 + getName 12 14 isString 13 15 literalExpression 14 16 mapAttrs ··· 31 33 32 34 cfg = config.services.postgresql; 33 35 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; 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; 47 49 48 50 toStr = value: 49 51 if true == value then "yes" ··· 61 63 62 64 groupAccessAvailable = versionAtLeast postgresql.version "11.0"; 63 65 66 + extensionNames = map getName postgresql.installedExtensions; 67 + extensionInstalled = extension: elem extension extensionNames; 64 68 in 65 69 66 70 { ··· 69 73 70 74 (mkRenamedOptionModule [ "services" "postgresql" "logLinePrefix" ] [ "services" "postgresql" "settings" "log_line_prefix" ]) 71 75 (mkRenamedOptionModule [ "services" "postgresql" "port" ] [ "services" "postgresql" "settings" "port" ]) 76 + (mkRenamedOptionModule [ "services" "postgresql" "extraPlugins" ] [ "services" "postgresql" "extensions" ]) 72 77 ]; 73 78 74 79 ###### interface ··· 372 377 ''; 373 378 }; 374 379 375 - extraPlugins = mkOption { 380 + extensions = mkOption { 376 381 type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path)); 377 382 default = _: []; 378 383 example = literalExpression "ps: with ps; [ postgis pg_repack ]"; 379 384 description = '' 380 - List of PostgreSQL plugins. 385 + List of PostgreSQL extensions to install. 381 386 ''; 382 387 }; 383 388 ··· 639 644 PrivateTmp = true; 640 645 ProtectHome = true; 641 646 ProtectSystem = "strict"; 642 - MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off"); 647 + MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off" && (!any extensionInstalled [ "plv8" ])); 643 648 NoNewPrivileges = true; 644 649 LockPersonality = true; 645 650 PrivateDevices = true; ··· 663 668 RestrictRealtime = true; 664 669 RestrictSUIDSGID = true; 665 670 SystemCallArchitectures = "native"; 666 - SystemCallFilter = [ 667 - "@system-service" 668 - "~@privileged @resources" 669 - ]; 671 + SystemCallFilter = 672 + [ 673 + "@system-service" 674 + "~@privileged @resources" 675 + ] 676 + ++ lib.optionals (any extensionInstalled [ "plv8" ]) [ "@pkey" ]; 670 677 UMask = if groupAccessAvailable then "0027" else "0077"; 671 678 } 672 679 (mkIf (cfg.dataDir != "/var/lib/postgresql") {
+1 -1
nixos/modules/services/web-apps/immich.nix
··· 227 227 ensureClauses.login = true; 228 228 } 229 229 ]; 230 - extraPlugins = ps: with ps; [ pgvecto-rs ]; 230 + extensions = ps: with ps; [ pgvecto-rs ]; 231 231 settings = { 232 232 shared_preload_libraries = [ "vectors.so" ]; 233 233 search_path = "\"$user\", public, vectors";
+1 -1
nixos/modules/services/web-apps/mobilizon.nix
··· 383 383 ensureDBOwnership = false; 384 384 } 385 385 ]; 386 - extraPlugins = ps: with ps; [ postgis ]; 386 + extensions = ps: with ps; [ postgis ]; 387 387 }; 388 388 389 389 # Nginx config taken from support/nginx/mobilizon-release.conf
+1 -1
nixos/tests/postgresql/anonymizer.nix
··· 20 20 inherit package; 21 21 enable = true; 22 22 enableJIT = lib.hasInfix "-jit-" package.name; 23 - extraPlugins = ps: [ ps.anonymizer ]; 23 + extensions = ps: [ ps.anonymizer ]; 24 24 settings.shared_preload_libraries = [ "anon" ]; 25 25 }; 26 26 };
+1 -1
nixos/tests/postgresql/pgjwt.nix
··· 24 24 inherit package; 25 25 enable = true; 26 26 enableJIT = lib.hasInfix "-jit-" package.name; 27 - extraPlugins = 27 + extensions = 28 28 ps: with ps; [ 29 29 pgjwt 30 30 pgtap
+1 -1
nixos/tests/postgresql/pgvecto-rs.nix
··· 38 38 inherit package; 39 39 enable = true; 40 40 enableJIT = lib.hasInfix "-jit-" package.name; 41 - extraPlugins = 41 + extensions = 42 42 ps: with ps; [ 43 43 pgvecto-rs 44 44 ];
+38 -14
nixos/tests/postgresql/postgresql.nix
··· 14 14 postgresql-clauses = makeEnsureTestFor package; 15 15 }; 16 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 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 + '' 21 45 ); 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 46 31 47 makeTestForWithBackupAll = 32 48 package: backupAll: 49 + let 50 + enablePLv8Check = !package.pkgs.plv8.meta.broken; 51 + in 33 52 makeTest { 34 53 name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}"; 35 54 meta = with lib.maintainers; { ··· 37 56 }; 38 57 39 58 nodes.machine = 40 - { ... }: 59 + { config, ... }: 41 60 { 42 61 services.postgresql = { 43 62 inherit package; 44 63 enable = true; 45 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 ]); 46 70 }; 47 71 48 72 services.postgresqlBackup = { ··· 69 93 70 94 with subtest("Postgresql is available just after unit start"): 71 95 machine.succeed( 72 - "cat ${test-sql} | sudo -u postgres psql" 96 + "cat ${test-sql enablePLv8Check} | sudo -u postgres psql" 73 97 ) 74 98 75 99 with subtest("Postgresql survives restart (bug #1735)"):
+1 -1
nixos/tests/postgresql/timescaledb.nix
··· 54 54 inherit package; 55 55 enable = true; 56 56 enableJIT = lib.hasInfix "-jit-" package.name; 57 - extraPlugins = 57 + extensions = 58 58 ps: with ps; [ 59 59 timescaledb 60 60 timescaledb_toolkit
+1 -1
nixos/tests/postgresql/tsja.nix
··· 21 21 inherit package; 22 22 enable = true; 23 23 enableJIT = lib.hasInfix "-jit-" package.name; 24 - extraPlugins = 24 + extensions = 25 25 ps: with ps; [ 26 26 tsja 27 27 ];
+1 -1
nixos/tests/postgresql/wal2json.nix
··· 17 17 inherit package; 18 18 enable = true; 19 19 enableJIT = lib.hasInfix "-jit-" package.name; 20 - extraPlugins = with package.pkgs; [ wal2json ]; 20 + extensions = with package.pkgs; [ wal2json ]; 21 21 settings = { 22 22 wal_level = "logical"; 23 23 max_replication_slots = "10";
+20 -12
pkgs/servers/sql/postgresql/generic.nix
··· 315 315 }; 316 316 }); 317 317 318 - postgresqlWithPackages = { postgresql, buildEnv }: f: buildEnv { 318 + postgresqlWithPackages = { postgresql, buildEnv }: f: let 319 + installedExtensions = f postgresql.pkgs; 320 + in buildEnv { 319 321 name = "${postgresql.pname}-and-plugins-${postgresql.version}"; 320 - paths = f postgresql.pkgs ++ [ 322 + paths = installedExtensions ++ [ 321 323 postgresql 322 324 postgresql.man # in case user installs this into environment 323 325 ]; 324 326 325 327 pathsToLink = ["/"]; 326 328 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; 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 + }; 337 345 }; 338 346 339 347 in