mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-07 21:58:06 -04:00
Drive a loopback daemon (secure stdio-pipe transport by default, also green
under --use-tcp) via the new write_daemon_conf helper and assert the behaviour
of the security-relevant rsyncd.conf parameters, transferring >=3-deep trees:
daemon-access path / read only / write only / list, incl. a deep sub-path
pull and that a list=no module is hidden yet usable by name.
daemon-filter daemon exclude hides matching files everywhere; incoming /
outgoing chmod rewrite modes of every transferred file.
daemon-auth auth users + secrets file accept the right password, reject a
wrong one and an unauthenticated request; strict modes rejects
a world-readable secrets file.
daemon-exec pre-/post-xfer exec run with RSYNC_MODULE_NAME /
RSYNC_EXIT_STATUS; a failing pre-xfer exec aborts the transfer
(marker files polled for, since post-xfer exec runs after the
client disconnects under TCP).
daemon-munge munge symlinks stores incoming links with the /rsyncd-munged/
prefix and strips it on the way out.
daemon-refuse refuse options: a named option, a wildcard, and the '* !a !v'
allow-list idiom.
Green on master under pipe and --use-tcp transports and under --protocol=29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Daemon coverage: auth users, secrets file, strict modes.
|
|
|
|
A module with auth users + a secrets file must accept the right password,
|
|
reject a wrong one, and (with the default strict modes) refuse a
|
|
world-readable secrets file. Authentication happens in the daemon protocol, so
|
|
it works over the default secure stdio-pipe transport.
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
|
|
from rsyncfns import (
|
|
FROMDIR, SCRATCHDIR,
|
|
make_tree, makepath, rmtree, rsync_argv, start_test_daemon, test_fail,
|
|
verify_dirs, write_daemon_conf,
|
|
)
|
|
|
|
DAEMON_PORT = 12888
|
|
|
|
# When a daemon module needs auth and no password is available, rsync falls back
|
|
# to an interactive getpass() prompt that reads /dev/tty directly -- which the
|
|
# test harness cannot redirect, so it would hang `make coverage` (or any run
|
|
# with a controlling terminal). Give every client a fallback password via the
|
|
# environment so it never prompts: the --password-file cases below override it,
|
|
# and the invalid-credentials case uses it and is correctly rejected.
|
|
os.environ['RSYNC_PASSWORD'] = 'env-fallback-wrong'
|
|
|
|
src = FROMDIR
|
|
rmtree(src)
|
|
make_tree(src, depth=3)
|
|
|
|
authdir = SCRATCHDIR / 'authdest'
|
|
secrets = SCRATCHDIR / 'rsyncd.secrets'
|
|
secrets.write_text('tuser:secretpass\n')
|
|
secrets.chmod(0o600)
|
|
|
|
conf = write_daemon_conf([
|
|
('auth', {'path': authdir, 'read only': 'no',
|
|
'auth users': 'tuser', 'secrets file': secrets}),
|
|
])
|
|
url = start_test_daemon(conf, DAEMON_PORT)
|
|
userurl = url.replace('rsync://', 'rsync://tuser@', 1)
|
|
|
|
|
|
def pwfile(name, text):
|
|
p = SCRATCHDIR / name
|
|
p.write_text(text)
|
|
p.chmod(0o600)
|
|
return p
|
|
|
|
|
|
def push(pw, **kw):
|
|
rmtree(authdir)
|
|
makepath(authdir)
|
|
return subprocess.run(
|
|
rsync_argv('-a', f'--password-file={pw}', f'{src}/', f'{userurl}auth/'),
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, **kw)
|
|
|
|
|
|
# --- correct password succeeds ----------------------------------------------
|
|
ok = pwfile('pw.ok', 'secretpass\n')
|
|
proc = push(ok)
|
|
if proc.returncode not in (0, 23):
|
|
test_fail(f"auth with the correct password failed: {proc.stderr}")
|
|
verify_dirs(src, authdir, label="auth success")
|
|
|
|
# --- wrong password is rejected ---------------------------------------------
|
|
bad = pwfile('pw.bad', 'wrongpass\n')
|
|
proc = push(bad)
|
|
if proc.returncode == 0:
|
|
test_fail("auth with the wrong password unexpectedly succeeded")
|
|
|
|
# --- a request with invalid credentials is rejected ------------------------
|
|
# Local user (not an auth user) with the wrong env-supplied password; rejected
|
|
# without ever prompting on the tty.
|
|
proc = subprocess.run(
|
|
rsync_argv('-a', f'{src}/', f'{url}auth/'),
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True,
|
|
stdin=subprocess.DEVNULL)
|
|
if proc.returncode == 0:
|
|
test_fail("a request with invalid credentials succeeded against an "
|
|
"auth-users module")
|
|
|
|
# --- strict modes rejects a world-readable secrets file ---------------------
|
|
secrets.chmod(0o644)
|
|
proc = push(ok)
|
|
if proc.returncode == 0:
|
|
test_fail("strict modes did not reject a world-readable secrets file")
|
|
secrets.chmod(0o600)
|
|
|
|
print("daemon-auth: auth users / secrets file / strict modes verified")
|