1{
2 pkgs,
3 makeTest,
4 genTests,
5}:
6
7let
8 inherit (pkgs) lib;
9
10 makeTestFor =
11 package:
12 lib.recurseIntoAttrs {
13 postgresql = makeTestForWithBackupAll package false;
14 postgresql-backup-all = makeTestForWithBackupAll package true;
15 postgresql-clauses = makeEnsureTestFor package;
16 };
17
18 test-sql = pkgs.writeText "postgresql-test" (''
19 CREATE EXTENSION pgcrypto; -- just to check if lib loading works
20 CREATE TABLE sth (
21 id int
22 );
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 INSERT INTO sth (id) VALUES (1);
28 CREATE TABLE xmltest ( doc xml );
29 INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
30
31 -- check if hardening gets relaxed
32 CREATE EXTENSION plv8;
33 -- try to trigger the V8 JIT, which requires MemoryDenyWriteExecute
34 DO $$
35 let xs = [];
36 for (let i = 0, n = 400000; i < n; i++) {
37 xs.push(Math.round(Math.random() * n))
38 }
39 console.log(xs.reduce((acc, x) => acc + x, 0));
40 $$ LANGUAGE plv8;
41 '');
42
43 makeTestForWithBackupAll =
44 package: backupAll:
45 makeTest {
46 name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}";
47 meta = with lib.maintainers; {
48 maintainers = [ zagy ];
49 };
50
51 nodes.machine =
52 { config, ... }:
53 {
54 services.postgresql = {
55 inherit package;
56 enable = true;
57 identMap = ''
58 postgres root postgres
59 '';
60 # TODO(@Ma27) split this off into its own VM test and move a few other
61 # extension tests to use postgresqlTestExtension.
62 extensions = ps: with ps; [ plv8 ];
63 };
64
65 services.postgresqlBackup = {
66 enable = true;
67 databases = lib.optional (!backupAll) "postgres";
68 pgdumpOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
69 pgdumpAllOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
70 };
71 };
72
73 testScript =
74 let
75 backupName = if backupAll then "all" else "postgres";
76 backupService = if backupAll then "postgresqlBackup" else "postgresqlBackup-postgres";
77 backupFileBase = "/var/backup/postgresql/${backupName}";
78 in
79 ''
80 def check_count(statement, lines):
81 return 'test $(psql -U postgres postgres -tAc "{}"|wc -l) -eq {}'.format(
82 statement, lines
83 )
84
85
86 machine.start()
87 machine.wait_for_unit("postgresql.target")
88
89 with subtest("Postgresql is available just after unit start"):
90 machine.succeed(
91 "cat ${test-sql} | sudo -u postgres psql"
92 )
93
94 with subtest("Postgresql survives restart (bug #1735)"):
95 machine.shutdown()
96 import time
97 time.sleep(2)
98 machine.start()
99 machine.wait_for_unit("postgresql.target")
100
101 machine.fail(check_count("SELECT * FROM sth;", 3))
102 machine.succeed(check_count("SELECT * FROM sth;", 5))
103 machine.fail(check_count("SELECT * FROM sth;", 4))
104 machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1))
105
106 with subtest("killing postgres process should trigger an automatic restart"):
107 machine.succeed("systemctl kill -s KILL postgresql")
108
109 machine.wait_until_succeeds("systemctl is-active postgresql.service")
110 machine.wait_until_succeeds("systemctl is-active postgresql.target")
111
112 with subtest("Backup service works"):
113 machine.succeed(
114 "systemctl start ${backupService}.service",
115 "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
116 "ls -hal /var/backup/postgresql/ >/dev/console",
117 "stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
118 )
119 with subtest("Backup service removes prev files"):
120 machine.succeed(
121 # Create dummy prev files.
122 "touch ${backupFileBase}.prev.sql{,.gz,.zstd}",
123 "chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}",
124
125 # Run backup.
126 "systemctl start ${backupService}.service",
127 "ls -hal /var/backup/postgresql/ >/dev/console",
128
129 # Since nothing has changed in the database, the cur and prev files
130 # should match.
131 "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
132 "cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz",
133
134 # The prev files with unused suffix should be removed.
135 "[ ! -f '${backupFileBase}.prev.sql' ]",
136 "[ ! -f '${backupFileBase}.prev.sql.zstd' ]",
137
138 # Both cur and prev file should only be accessible by the postgres user.
139 "stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
140 "stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600",
141 )
142 with subtest("Backup service fails gracefully"):
143 # Sabotage the backup process
144 machine.succeed("rm /run/postgresql/.s.PGSQL.5432")
145 machine.fail(
146 "systemctl start ${backupService}.service",
147 )
148 machine.succeed(
149 "ls -hal /var/backup/postgresql/ >/dev/console",
150 "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
151 "stat ${backupFileBase}.in-progress.sql.gz",
152 )
153 # In a previous version, the second run would overwrite prev.sql.gz,
154 # so we test a second run as well.
155 machine.fail(
156 "systemctl start ${backupService}.service",
157 )
158 machine.succeed(
159 "stat ${backupFileBase}.in-progress.sql.gz",
160 "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
161 )
162
163
164 with subtest("Initdb works"):
165 machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2")
166
167 machine.log(machine.execute("systemd-analyze security postgresql.service | grep -v ✓")[1])
168
169 machine.shutdown()
170 '';
171 };
172
173 makeEnsureTestFor =
174 package:
175 makeTest {
176 name = "postgresql-clauses-${package.name}";
177 meta = with lib.maintainers; {
178 maintainers = [ zagy ];
179 };
180
181 nodes.machine =
182 { ... }:
183 {
184 services.postgresql = {
185 inherit package;
186 enable = true;
187 ensureUsers = [
188 {
189 name = "all-clauses";
190 ensureClauses = {
191 superuser = true;
192 createdb = true;
193 createrole = true;
194 "inherit" = true;
195 login = true;
196 replication = true;
197 bypassrls = true;
198 };
199 }
200 {
201 name = "default-clauses";
202 }
203 ];
204 };
205 };
206
207 testScript =
208 let
209 getClausesQuery =
210 user:
211 lib.concatStringsSep " " [
212 "SELECT row_to_json(row)"
213 "FROM ("
214 "SELECT"
215 "rolsuper,"
216 "rolinherit,"
217 "rolcreaterole,"
218 "rolcreatedb,"
219 "rolcanlogin,"
220 "rolreplication,"
221 "rolbypassrls"
222 "FROM pg_roles"
223 "WHERE rolname = '${user}'"
224 ") row;"
225 ];
226 in
227 ''
228 import json
229 machine.start()
230 machine.wait_for_unit("postgresql.target")
231
232 with subtest("All user permissions are set according to the ensureClauses attr"):
233 clauses = json.loads(
234 machine.succeed(
235 "sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\""
236 )
237 )
238 print(clauses)
239 assert clauses['rolsuper'], 'expected user with clauses to have superuser clause'
240 assert clauses['rolinherit'], 'expected user with clauses to have inherit clause'
241 assert clauses['rolcreaterole'], 'expected user with clauses to have create role clause'
242 assert clauses['rolcreatedb'], 'expected user with clauses to have create db clause'
243 assert clauses['rolcanlogin'], 'expected user with clauses to have login clause'
244 assert clauses['rolreplication'], 'expected user with clauses to have replication clause'
245 assert clauses['rolbypassrls'], 'expected user with clauses to have bypassrls clause'
246
247 with subtest("All user permissions default when ensureClauses is not provided"):
248 clauses = json.loads(
249 machine.succeed(
250 "sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\""
251 )
252 )
253 assert not clauses['rolsuper'], 'expected user with no clauses set to have default superuser clause'
254 assert clauses['rolinherit'], 'expected user with no clauses set to have default inherit clause'
255 assert not clauses['rolcreaterole'], 'expected user with no clauses set to have default create role clause'
256 assert not clauses['rolcreatedb'], 'expected user with no clauses set to have default create db clause'
257 assert clauses['rolcanlogin'], 'expected user with no clauses set to have default login clause'
258 assert not clauses['rolreplication'], 'expected user with no clauses set to have default replication clause'
259 assert not clauses['rolbypassrls'], 'expected user with no clauses set to have default bypassrls clause'
260
261 machine.shutdown()
262 '';
263 };
264in
265genTests { inherit makeTestFor; }