Skip to content

Commit 024723a

Browse files
author
vshepard
committedSep 4, 2024
tests 2.8.2
1 parent 09da114 commit 024723a

16 files changed

+1135
-44
lines changed
 

‎.env

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
TARGET_OS=ubuntu
2+
TARGET_OS_VERSION=22.04
3+
PG_PRODUCT=enterprise
4+
PG_REPO=postgrespro
5+
PG_SRCDIR=./postgrespro
6+
PG_PRODUCT_SUFFIX=-ent
7+
PG_VERSION=15
8+
PG_VERSION_SUFFIX=-15
9+
PTRACK=ON
10+
PG_PROBACKUP_PTRACK=ON
11+
PGPROBACKUPBIN=/home/vshepard/pbckp/ent-15/bin/pg_probackup
12+
PG_CONFIG=/home/vshepard/pbckp/ent-15/bin/pg_config
13+
PGPROBACKUPBIN3=/home/vshepard/workspace/work/pg_probackup/dev-ee-probackup/pg_probackup3/builddir/src/pg_probackup3
14+
LANG=C.UTF-8
15+
LC_ALL=C
16+
17+
PG_PROBACKUP_S3_HOST=10.5.52.86
18+
PG_PROBACKUP_S3_PORT=9000
19+
PG_PROBACKUP_S3_REGION=us-east-1
20+
PG_PROBACKUP_S3_BUCKET_NAME=test1
21+
PG_PROBACKUP_S3_ACCESS_KEY=minioadmin
22+
PG_PROBACKUP_S3_SECRET_ACCESS_KEY=minioadmin
23+
PG_PROBACKUP_S3_HTTPS=OFF
24+
PG_PROBACKUP_S3_TEST=minio
25+
PG_PROBACKUP_S3_BUFFER_SIZE=64
26+
PG_PROBACKUP_S3_RETRIES=10

‎tests/archive_test.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import subprocess
88
from sys import exit
99
from time import sleep
10+
from pathlib import PurePath
1011
from testgres import ProcessType
1112

1213

‎tests/auth_test.py

+342-14
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,348 @@
1+
"""
2+
The Test suite check behavior of pg_probackup utility, if password is required for connection to PostgreSQL instance.
3+
- https://confluence.postgrespro.ru/pages/viewpage.action?pageId=16777522
4+
"""
5+
6+
import os
7+
import unittest
8+
import signal
9+
import time
10+
111
from .helpers.ptrack_helpers import ProbackupTest
12+
from testgres import StartNodeException
13+
14+
skip_test = False
15+
16+
try:
17+
from pexpect import *
18+
except ImportError:
19+
skip_test = True
20+
21+
22+
class SimpleAuthTest(ProbackupTest):
23+
24+
# @unittest.skip("skip")
25+
def test_backup_via_unprivileged_user(self):
26+
"""
27+
Make node, create unprivileged user, try to
28+
run a backups without EXECUTE rights on
29+
certain functions
30+
"""
31+
node = self.pg_node.make_simple('node',
32+
set_replication=True,
33+
ptrack_enable=self.ptrack)
34+
35+
self.pb.init()
36+
self.pb.add_instance('node', node)
37+
self.pb.set_archiving('node', node)
38+
node.slow_start()
39+
40+
if self.ptrack:
41+
node.safe_psql(
42+
"postgres",
43+
"CREATE EXTENSION ptrack")
44+
45+
node.safe_psql("postgres", "CREATE ROLE backup with LOGIN")
46+
47+
self.pb.backup_node('node', node, options=['-U', 'backup'],
48+
expect_error='due to missing grant on EXECUTE')
49+
if self.pg_config_version < 150000:
50+
self.assertMessage(contains=
51+
"ERROR: Query failed: ERROR: permission denied "
52+
"for function pg_start_backup")
53+
else:
54+
self.assertMessage(contains=
55+
"ERROR: Query failed: ERROR: permission denied "
56+
"for function pg_backup_start")
57+
58+
if self.pg_config_version < 150000:
59+
node.safe_psql(
60+
"postgres",
61+
"GRANT EXECUTE ON FUNCTION"
62+
" pg_start_backup(text, boolean, boolean) TO backup;")
63+
else:
64+
node.safe_psql(
65+
"postgres",
66+
"GRANT EXECUTE ON FUNCTION"
67+
" pg_backup_start(text, boolean) TO backup;")
68+
69+
node.safe_psql(
70+
'postgres',
71+
"GRANT EXECUTE ON FUNCTION pg_catalog.pg_switch_wal() TO backup")
72+
73+
self.pb.backup_node('node', node,
74+
options=['-U', 'backup'],
75+
expect_error='due to missing grant on EXECUTE')
76+
self.assertMessage(contains=
77+
"ERROR: Query failed: ERROR: permission denied for function "
78+
"pg_create_restore_point\nquery was: "
79+
"SELECT pg_catalog.pg_create_restore_point($1)")
80+
81+
node.safe_psql(
82+
"postgres",
83+
"GRANT EXECUTE ON FUNCTION"
84+
" pg_create_restore_point(text) TO backup;")
85+
86+
self.pb.backup_node('node', node,
87+
options=['-U', 'backup'],
88+
expect_error='due to missing grant on EXECUTE')
89+
if self.pg_config_version < 150000:
90+
self.assertMessage(contains=
91+
"ERROR: Query failed: ERROR: permission denied "
92+
"for function pg_stop_backup")
93+
else:
94+
self.assertMessage(contains=
95+
"ERROR: Query failed: ERROR: permission denied "
96+
"for function pg_backup_stop")
97+
98+
if self.pg_config_version < self.version_to_num('15.0'):
99+
node.safe_psql(
100+
"postgres",
101+
"GRANT EXECUTE ON FUNCTION pg_stop_backup() TO backup; "
102+
"GRANT EXECUTE ON FUNCTION pg_stop_backup(boolean, boolean) TO backup;")
103+
else:
104+
node.safe_psql(
105+
"postgres",
106+
"GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO backup;")
107+
108+
self.pb.backup_node('node', node, options=['-U', 'backup'])
109+
110+
node.safe_psql("postgres", "CREATE DATABASE test1")
111+
112+
self.pb.backup_node('node', node, options=['-U', 'backup'])
113+
114+
node.safe_psql(
115+
"test1", "create table t1 as select generate_series(0,100)")
116+
117+
node.stop()
118+
node.slow_start()
119+
120+
node.safe_psql(
121+
"postgres",
122+
"ALTER ROLE backup REPLICATION")
123+
124+
# FULL
125+
self.pb.backup_node('node', node, options=['-U', 'backup'])
126+
127+
# PTRACK
128+
if self.ptrack:
129+
self.pb.backup_node('node', node,
130+
backup_type='ptrack', options=['-U', 'backup'])
131+
132+
133+
class AuthTest(ProbackupTest):
134+
pb = None
135+
node = None
136+
137+
# TODO move to object scope, replace module_name
138+
@unittest.skipIf(skip_test, "Module pexpect isn't installed. You need to install it.")
139+
def setUp(self):
140+
141+
super().setUp()
142+
143+
self.node = self.pg_node.make_simple("node",
144+
set_replication=True,
145+
initdb_params=['--auth-host=md5'],
146+
pg_options={'archive_timeout': '5s'},
147+
)
148+
149+
self.modify_pg_hba(self.node)
150+
151+
self.pb.init()
152+
self.pb.add_instance(self.node.name, self.node)
153+
self.pb.set_archiving(self.node.name, self.node)
154+
try:
155+
self.node.slow_start()
156+
except StartNodeException:
157+
raise unittest.skip("Node hasn't started")
158+
159+
160+
version = self.pg_config_version
161+
if version < 150000:
162+
self.node.safe_psql(
163+
"postgres",
164+
"CREATE ROLE backup WITH LOGIN PASSWORD 'password'; "
165+
"GRANT USAGE ON SCHEMA pg_catalog TO backup; "
166+
"GRANT EXECUTE ON FUNCTION current_setting(text) TO backup; "
167+
"GRANT EXECUTE ON FUNCTION pg_is_in_recovery() TO backup; "
168+
"GRANT EXECUTE ON FUNCTION pg_start_backup(text, boolean, boolean) TO backup; "
169+
"GRANT EXECUTE ON FUNCTION pg_stop_backup() TO backup; "
170+
"GRANT EXECUTE ON FUNCTION pg_stop_backup(boolean, boolean) TO backup; "
171+
"GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO backup; "
172+
"GRANT EXECUTE ON FUNCTION pg_switch_wal() TO backup; "
173+
"GRANT EXECUTE ON FUNCTION txid_current() TO backup; "
174+
"GRANT EXECUTE ON FUNCTION txid_current_snapshot() TO backup; "
175+
"GRANT EXECUTE ON FUNCTION txid_snapshot_xmax(txid_snapshot) TO backup;")
176+
else:
177+
self.node.safe_psql(
178+
"postgres",
179+
"CREATE ROLE backup WITH LOGIN PASSWORD 'password'; "
180+
"GRANT USAGE ON SCHEMA pg_catalog TO backup; "
181+
"GRANT EXECUTE ON FUNCTION current_setting(text) TO backup; "
182+
"GRANT EXECUTE ON FUNCTION pg_is_in_recovery() TO backup; "
183+
"GRANT EXECUTE ON FUNCTION pg_backup_start(text, boolean) TO backup; "
184+
"GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO backup; "
185+
"GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO backup; "
186+
"GRANT EXECUTE ON FUNCTION pg_switch_wal() TO backup; "
187+
"GRANT EXECUTE ON FUNCTION txid_current() TO backup; "
188+
"GRANT EXECUTE ON FUNCTION txid_current_snapshot() TO backup; "
189+
"GRANT EXECUTE ON FUNCTION txid_snapshot_xmax(txid_snapshot) TO backup;")
190+
191+
if version >= 150000:
192+
home_dir = os.path.join(self.test_path, "home")
193+
os.makedirs(home_dir, exist_ok=True)
194+
self.test_env['HOME'] = home_dir
195+
self.pgpass_file = os.path.join(home_dir, '.pgpass')
196+
self.pgpass_file_lock = None
197+
else:
198+
# before PGv15 only true home dir were inspected.
199+
# Since we can't have separate file per test, we have to serialize
200+
# tests.
201+
self.pgpass_file = os.path.join(os.path.expanduser('~'), '.pgpass')
202+
self.pgpass_file_lock = self.pgpass_file + '~probackup_test_lock'
203+
# have to lock pgpass by creating file in exclusive mode
204+
for i in range(120):
205+
try:
206+
open(self.pgpass_file_lock, "x").close()
207+
except FileExistsError:
208+
time.sleep(1)
209+
else:
210+
break
211+
else:
212+
raise TimeoutError("can't create ~/.pgpass~probackup_test_lock for 120 seconds")
213+
214+
self.pb_cmd = ['backup',
215+
'--instance', self.node.name,
216+
'-h', '127.0.0.1',
217+
'-p', str(self.node.port),
218+
'-U', 'backup',
219+
'-d', 'postgres',
220+
'-b', 'FULL',
221+
'--no-sync'
222+
]
223+
224+
def tearDown(self):
225+
super().tearDown()
226+
if not self.pgpass_file_lock:
227+
return
228+
if hasattr(self, "pgpass_line") and os.path.exists(self.pgpass_file):
229+
with open(self.pgpass_file, 'r') as fl:
230+
lines = fl.readlines()
231+
if self.pgpass_line in lines:
232+
lines.remove(self.pgpass_line)
233+
if len(lines) == 0:
234+
os.remove(self.pgpass_file)
235+
else:
236+
with open(self.pgpass_file, 'w') as fl:
237+
fl.writelines(lines)
238+
os.remove(self.pgpass_file_lock)
239+
240+
def test_empty_password(self):
241+
""" Test case: PGPB_AUTH03 - zero password length """
242+
try:
243+
self.assertIn("ERROR: no password supplied",
244+
self.run_pb_with_auth('\0\r\n'))
245+
except (TIMEOUT, ExceptionPexpect) as e:
246+
self.fail(e.value)
247+
248+
def test_wrong_password(self):
249+
""" Test case: PGPB_AUTH04 - incorrect password """
250+
self.assertIn("password authentication failed",
251+
self.run_pb_with_auth('wrong_password\r\n'))
252+
253+
def test_right_password(self):
254+
""" Test case: PGPB_AUTH01 - correct password """
255+
self.assertIn("completed",
256+
self.run_pb_with_auth('password\r\n'))
257+
258+
def test_right_password_and_wrong_pgpass(self):
259+
""" Test case: PGPB_AUTH05 - correct password and incorrect .pgpass (-W)"""
260+
line = ":".join(['127.0.0.1', str(self.node.port), 'postgres', 'backup', 'wrong_password'])
261+
self.create_pgpass(self.pgpass_file, line)
262+
self.assertIn("completed",
263+
self.run_pb_with_auth('password\r\n', add_args=["-W"]))
264+
265+
def test_ctrl_c_event(self):
266+
""" Test case: PGPB_AUTH02 - send interrupt signal """
267+
try:
268+
self.run_pb_with_auth(kill=True)
269+
except TIMEOUT:
270+
self.fail("Error: CTRL+C event ignored")
271+
272+
def test_pgpassfile_env(self):
273+
""" Test case: PGPB_AUTH06 - set environment var PGPASSFILE """
274+
path = os.path.join(self.test_path, 'pgpass.conf')
275+
line = ":".join(['127.0.0.1', str(self.node.port), 'postgres', 'backup', 'password'])
276+
self.create_pgpass(path, line)
277+
self.test_env["PGPASSFILE"] = path
278+
self.assertEqual(
279+
"OK",
280+
self.pb.show(self.node.name, self.pb.run(self.pb_cmd + ['-w']))["status"],
281+
"ERROR: Full backup status is not valid."
282+
)
283+
284+
def test_pgpass(self):
285+
""" Test case: PGPB_AUTH07 - Create file .pgpass in home dir. """
286+
line = ":".join(['127.0.0.1', str(self.node.port), 'postgres', 'backup', 'password'])
287+
self.create_pgpass(self.pgpass_file, line)
288+
self.assertEqual(
289+
"OK",
290+
self.pb.show(self.node.name, self.pb.run(self.pb_cmd + ['-w']))["status"],
291+
"ERROR: Full backup status is not valid."
292+
)
293+
294+
def test_pgpassword(self):
295+
""" Test case: PGPB_AUTH08 - set environment var PGPASSWORD """
296+
self.test_env["PGPASSWORD"] = "password"
297+
self.assertEqual(
298+
"OK",
299+
self.pb.show(self.node.name, self.pb.run(self.pb_cmd + ['-w']))["status"],
300+
"ERROR: Full backup status is not valid."
301+
)
302+
303+
def test_pgpassword_and_wrong_pgpass(self):
304+
""" Test case: PGPB_AUTH09 - Check priority between PGPASSWORD and .pgpass file"""
305+
line = ":".join(['127.0.0.1', str(self.node.port), 'postgres', 'backup', 'wrong_password'])
306+
self.create_pgpass(self.pgpass_file, line)
307+
self.test_env["PGPASSWORD"] = "password"
308+
self.assertEqual(
309+
"OK",
310+
self.pb.show(self.node.name, self.pb.run(self.pb_cmd + ['-w']))["status"],
311+
"ERROR: Full backup status is not valid."
312+
)
2313

314+
def run_pb_with_auth(self, password=None, add_args = [], kill=False):
315+
cmd = [*self.pb_cmd, *add_args, *self.backup_dir.pb_args]
316+
with spawn(self.probackup_path, cmd,
317+
encoding='utf-8', timeout=60, env=self.test_env) as probackup:
318+
result = probackup.expect(u"Password for user .*:", 10)
319+
if kill:
320+
probackup.kill(signal.SIGINT)
321+
elif result == 0:
322+
probackup.sendline(password)
323+
probackup.expect(EOF)
324+
return str(probackup.before)
325+
else:
326+
raise ExceptionPexpect("Other pexpect errors.")
3327

4-
class AuthorizationTest(ProbackupTest):
5-
"""
6-
Check connect to S3 via pre_start_checks() function
7-
calling pg_probackup init --s3
8328

9-
test that s3 keys allow to connect to all types of storages
10-
"""
329+
def modify_pg_hba(self, node):
330+
"""
331+
Description:
332+
Add trust authentication for user postgres. Need for add new role and set grant.
333+
:param node:
334+
:return None:
335+
"""
336+
hba_conf = os.path.join(node.data_dir, "pg_hba.conf")
337+
with open(hba_conf, 'r+') as fio:
338+
data = fio.read()
339+
fio.seek(0)
340+
fio.write('host\tall\t%s\t127.0.0.1/0\ttrust\n%s' % (self.username, data))
11341

12-
def test_s3_auth_test(self):
13-
console_output = self.pb.init(options=["--log-level-console=VERBOSE"])
14342

15-
self.assertNotIn(': 403', console_output) # Because we can have just '403' substring in timestamp
16-
self.assertMessage(console_output, contains='S3_pre_start_check successful')
17-
self.assertMessage(console_output, contains='HTTP response: 200')
18-
self.assertIn(
19-
f"INFO: Backup catalog '{self.backup_dir}' successfully initialized",
20-
console_output)
343+
def create_pgpass(self, path, line):
344+
self.pgpass_line = line+"\n"
345+
with open(path, 'a') as passfile:
346+
# host:port:db:username:password
347+
passfile.write(self.pgpass_line)
348+
os.chmod(path, 0o600)

0 commit comments

Comments
 (0)
Failed to load comments.