Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release-24.11] nixos/postgresql: extension based hardening relaxation #356574

Merged
merged 4 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions nixos/modules/services/databases/postgresql.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

let
inherit (lib)
any
attrValues
concatMapStrings
concatStringsSep
const
elem
escapeShellArgs
filterAttrs
getName
isString
literalExpression
mapAttrs
Expand All @@ -31,19 +33,19 @@ let

cfg = config.services.postgresql;

postgresql =
let
# ensure that
# services.postgresql = {
# enableJIT = true;
# package = pkgs.postgresql_<major>;
# };
# works.
base = if cfg.enableJIT then cfg.package.withJIT else cfg.package.withoutJIT;
in
if cfg.extraPlugins == []
then base
else base.withPackages cfg.extraPlugins;
# ensure that
# services.postgresql = {
# enableJIT = true;
# package = pkgs.postgresql_<major>;
# };
# works.
basePackage = if cfg.enableJIT
then cfg.package.withJIT
else cfg.package.withoutJIT;

postgresql = if cfg.extensions == []
then basePackage
else basePackage.withPackages cfg.extensions;

toStr = value:
if true == value then "yes"
Expand All @@ -61,6 +63,8 @@ let

groupAccessAvailable = versionAtLeast postgresql.version "11.0";

extensionNames = map getName postgresql.installedExtensions;
extensionInstalled = extension: elem extension extensionNames;
in

{
Expand All @@ -69,6 +73,7 @@ in

(mkRenamedOptionModule [ "services" "postgresql" "logLinePrefix" ] [ "services" "postgresql" "settings" "log_line_prefix" ])
(mkRenamedOptionModule [ "services" "postgresql" "port" ] [ "services" "postgresql" "settings" "port" ])
(mkRenamedOptionModule [ "services" "postgresql" "extraPlugins" ] [ "services" "postgresql" "extensions" ])
];

###### interface
Expand Down Expand Up @@ -372,12 +377,12 @@ in
'';
};

extraPlugins = mkOption {
extensions = mkOption {
type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path));
default = _: [];
example = literalExpression "ps: with ps; [ postgis pg_repack ]";
description = ''
List of PostgreSQL plugins.
List of PostgreSQL extensions to install.
'';
};

Expand Down Expand Up @@ -639,7 +644,7 @@ in
PrivateTmp = true;
ProtectHome = true;
ProtectSystem = "strict";
MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off");
MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off" && (!any extensionInstalled [ "plv8" ]));
NoNewPrivileges = true;
LockPersonality = true;
PrivateDevices = true;
Expand All @@ -663,10 +668,12 @@ in
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged @resources"
];
SystemCallFilter =
[
"@system-service"
"~@privileged @resources"
]
++ lib.optionals (any extensionInstalled [ "plv8" ]) [ "@pkey" ];
UMask = if groupAccessAvailable then "0027" else "0077";
}
(mkIf (cfg.dataDir != "/var/lib/postgresql") {
Expand Down
2 changes: 1 addition & 1 deletion nixos/modules/services/web-apps/immich.nix
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ in
ensureClauses.login = true;
}
];
extraPlugins = ps: with ps; [ pgvecto-rs ];
extensions = ps: with ps; [ pgvecto-rs ];
settings = {
shared_preload_libraries = [ "vectors.so" ];
search_path = "\"$user\", public, vectors";
Expand Down
2 changes: 1 addition & 1 deletion nixos/modules/services/web-apps/mobilizon.nix
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ in
ensureDBOwnership = false;
}
];
extraPlugins = ps: with ps; [ postgis ];
extensions = ps: with ps; [ postgis ];
};

# Nginx config taken from support/nginx/mobilizon-release.conf
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/anonymizer.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins = ps: [ ps.anonymizer ];
extensions = ps: [ ps.anonymizer ];
settings.shared_preload_libraries = [ "anon" ];
};
};
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/pgjwt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins =
extensions =
ps: with ps; [
pgjwt
pgtap
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/pgvecto-rs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins =
extensions =
ps: with ps; [
pgvecto-rs
];
Expand Down
52 changes: 38 additions & 14 deletions nixos/tests/postgresql/postgresql.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,59 @@ let
postgresql-clauses = makeEnsureTestFor package;
};

test-sql = pkgs.writeText "postgresql-test" ''
CREATE EXTENSION pgcrypto; -- just to check if lib loading works
CREATE TABLE sth (
id int
test-sql =
enablePLv8Test:
pkgs.writeText "postgresql-test" (
''
CREATE EXTENSION pgcrypto; -- just to check if lib loading works
CREATE TABLE sth (
id int
);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
CREATE TABLE xmltest ( doc xml );
INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
''
+ lib.optionalString enablePLv8Test ''
-- check if hardening gets relaxed
CREATE EXTENSION plv8;
-- try to trigger the V8 JIT, which requires MemoryDenyWriteExecute
DO $$
let xs = [];
for (let i = 0, n = 400000; i < n; i++) {
xs.push(Math.round(Math.random() * n))
}
console.log(xs.reduce((acc, x) => acc + x, 0));
$$ LANGUAGE plv8;
''
);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
INSERT INTO sth (id) VALUES (1);
CREATE TABLE xmltest ( doc xml );
INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
'';

makeTestForWithBackupAll =
package: backupAll:
let
enablePLv8Check = !package.pkgs.plv8.meta.broken;
in
makeTest {
name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}";
meta = with lib.maintainers; {
maintainers = [ zagy ];
};

nodes.machine =
{ ... }:
{ config, ... }:
{
services.postgresql = {
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
# plv8 doesn't support postgresql with JIT, so we only run the test
# for the non-jit variant.
# TODO(@Ma27) split this off into its own VM test and move a few other
# extension tests to use postgresqlTestExtension.
extensions = lib.mkIf enablePLv8Check (ps: with ps; [ plv8 ]);
};

services.postgresqlBackup = {
Expand All @@ -69,7 +93,7 @@ let

with subtest("Postgresql is available just after unit start"):
machine.succeed(
"cat ${test-sql} | sudo -u postgres psql"
"cat ${test-sql enablePLv8Check} | sudo -u postgres psql"
)

with subtest("Postgresql survives restart (bug #1735)"):
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/timescaledb.nix
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins =
extensions =
ps: with ps; [
timescaledb
timescaledb_toolkit
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/tsja.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins =
extensions =
ps: with ps; [
tsja
];
Expand Down
2 changes: 1 addition & 1 deletion nixos/tests/postgresql/wal2json.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let
inherit package;
enable = true;
enableJIT = lib.hasInfix "-jit-" package.name;
extraPlugins = with package.pkgs; [ wal2json ];
extensions = with package.pkgs; [ wal2json ];
settings = {
wal_level = "logical";
max_replication_slots = "10";
Expand Down
32 changes: 20 additions & 12 deletions pkgs/servers/sql/postgresql/generic.nix
Original file line number Diff line number Diff line change
Expand Up @@ -315,25 +315,33 @@ let
};
});

postgresqlWithPackages = { postgresql, buildEnv }: f: buildEnv {
postgresqlWithPackages = { postgresql, buildEnv }: f: let
installedExtensions = f postgresql.pkgs;
in buildEnv {
name = "${postgresql.pname}-and-plugins-${postgresql.version}";
paths = f postgresql.pkgs ++ [
paths = installedExtensions ++ [
postgresql
postgresql.man # in case user installs this into environment
];

pathsToLink = ["/"];

passthru.version = postgresql.version;
passthru.psqlSchema = postgresql.psqlSchema;
passthru.withJIT = postgresqlWithPackages {
inherit buildEnv;
postgresql = postgresql.withJIT;
} f;
passthru.withoutJIT = postgresqlWithPackages {
inherit buildEnv;
postgresql = postgresql.withoutJIT;
} f;
passthru = {
inherit installedExtensions;
inherit (postgresql)
psqlSchema
version
;

withJIT = postgresqlWithPackages {
inherit buildEnv;
postgresql = postgresql.withJIT;
} f;
withoutJIT = postgresqlWithPackages {
inherit buildEnv;
postgresql = postgresql.withoutJIT;
} f;
};
};

in
Expand Down