Add selective Postfix SRS installation and management
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Isolated, non-root tests for the public MX host installer addition.
|
||||
|
||||
No Postfix services, network DNS records or on-host config files are changed.
|
||||
"""
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
INSTALLER = (ROOT / 'install-update-srs.sh').read_text()
|
||||
COMMON = (ROOT / 'helpers/srs-common.sh').read_text()
|
||||
SAMPLE = (ROOT / 'conf/srs.conf.sample').read_text()
|
||||
|
||||
|
||||
def slice_script(source, start, end):
|
||||
return source[source.index(start):source.index(end)]
|
||||
|
||||
|
||||
PROMPTS = slice_script(INSTALLER, 'read_config_domain_hints() {', '# Consistent installer-only confirmation prompts.')
|
||||
DNS = slice_script(INSTALLER, 'verify_srs_domain_dns() {', '# Compact display of APT simulations;')
|
||||
LOAD = slice_script(COMMON, 'load_srs_config() {', '# ----------\n# Persistent pre-SRS state')
|
||||
MIGRATION = re.search(
|
||||
r"<<'PY_MX_MIGRATE'[^\n]*\n(.*?)\nPY_MX_MIGRATE", INSTALLER, re.S
|
||||
)
|
||||
assert MIGRATION is not None
|
||||
|
||||
|
||||
class InstallerMxHostTests(unittest.TestCase):
|
||||
def run_prompt(self, config=None, hostname='o13-mail.oopen.de', choices='\n\n'):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
conf = pathlib.Path(temp) / 'srs.conf'
|
||||
if config is not None:
|
||||
conf.write_text(config)
|
||||
script = f'''SRS_CONF_FILE={str(conf)!r}
|
||||
CONFIG_DOMAIN_HINT=''
|
||||
CONFIG_MX_HINT=''
|
||||
SELECTED_MX_HOST=''
|
||||
SELECTED_SRS_DOMAIN=''
|
||||
hostname() {{ printf '%s\\n' {hostname!r}; }}
|
||||
{PROMPTS}
|
||||
prompt_srs_domain
|
||||
printf 'RESULT_MX=%s\\nRESULT_SRS=%s\\n' "$SELECTED_MX_HOST" "$SELECTED_SRS_DOMAIN"
|
||||
'''
|
||||
return subprocess.run(
|
||||
['bash', '-c', script], input=choices, text=True, capture_output=True
|
||||
)
|
||||
|
||||
def test_first_install_defaults_from_hostname(self):
|
||||
result = self.run_prompt()
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('RESULT_MX=o13-mail.oopen.de', result.stdout)
|
||||
self.assertIn('RESULT_SRS=srs.o13-mail.oopen.de', result.stdout)
|
||||
|
||||
def test_public_mx_override_changes_srs_suggestion(self):
|
||||
result = self.run_prompt(choices='mail.interventionistische-linke.org\n\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('RESULT_MX=mail.interventionistische-linke.org', result.stdout)
|
||||
self.assertIn('RESULT_SRS=srs.mail.interventionistische-linke.org', result.stdout)
|
||||
|
||||
def test_configured_values_take_precedence(self):
|
||||
config = '_MX_HOST="mail.example.org"\n_SRS_DOMAIN="srs.mail.example.org"\n'
|
||||
result = self.run_prompt(config=config)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('RESULT_MX=mail.example.org', result.stdout)
|
||||
self.assertIn('RESULT_SRS=srs.mail.example.org', result.stdout)
|
||||
|
||||
def test_legacy_srs_domain_preserved(self):
|
||||
config = '_SRS_DOMAIN="srs.original.example.org"\n'
|
||||
result = self.run_prompt(config=config, choices='mail.example.org\n\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('RESULT_MX=mail.example.org', result.stdout)
|
||||
self.assertIn('RESULT_SRS=srs.original.example.org', result.stdout)
|
||||
|
||||
def test_invalid_host_reprompted(self):
|
||||
result = self.run_prompt(choices='bad_host\nmail.example.org\n\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('[ invalid ]', result.stderr)
|
||||
self.assertIn('RESULT_MX=mail.example.org', result.stdout)
|
||||
|
||||
def test_prompt_eof_fails_without_defaulting(self):
|
||||
result = self.run_prompt(choices='')
|
||||
self.assertEqual(result.returncode, 2)
|
||||
|
||||
def test_dns_preflight_passes_selected_mx_not_postfix_myhostname(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
binpath = pathlib.Path(temp)
|
||||
(binpath / 'dig').write_text('#!/bin/sh\nexit 0\n')
|
||||
(binpath / 'dig').chmod(0o755)
|
||||
postconf = binpath / 'postconf'
|
||||
postconf.write_text('''#!/bin/sh
|
||||
case "$2" in
|
||||
smtp_bind_address) echo 83.223.90.231 ;;
|
||||
smtp_bind_address6) echo 2a01:31:0:2::ad52:a231 ;;
|
||||
*) echo 'unexpected postconf query' >&2; exit 4 ;;
|
||||
esac
|
||||
''')
|
||||
postconf.chmod(0o755)
|
||||
checker = binpath / 'check.py'
|
||||
checker.write_text('''import sys
|
||||
print('ARGS=' + ';'.join(sys.argv[1:]))
|
||||
''')
|
||||
script = f'''SELECTED_MX_HOST=mail.interventionistische-linke.org
|
||||
SELECTED_SRS_DOMAIN=srs.mail.interventionistische-linke.org
|
||||
SRS_DNS_CHECKER={str(checker)!r}
|
||||
{DNS}
|
||||
verify_srs_domain_dns
|
||||
'''
|
||||
env = dict(os.environ, PATH=f'{temp}:' + os.environ['PATH'])
|
||||
proc = subprocess.run(['bash','-c',script], capture_output=True, text=True, env=env)
|
||||
self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout)
|
||||
self.assertIn('--hostname;mail.interventionistische-linke.org', proc.stdout)
|
||||
self.assertIn('--ipv4;83.223.90.231', proc.stdout)
|
||||
self.assertIn('--ipv6;2a01:31:0:2::ad52:a231', proc.stdout)
|
||||
|
||||
def test_existing_config_migration_is_append_only(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
src = pathlib.Path(tmp) / 'old.conf'
|
||||
dst = pathlib.Path(tmp) / 'new.conf'
|
||||
content = b'# existing comments\n_SRS_DOMAIN="srs.example.org"\n\n'
|
||||
src.write_bytes(content)
|
||||
proc = subprocess.run([sys.executable, '-', str(src), str(dst), 'mail.example.org'],
|
||||
input=MIGRATION.group(1), capture_output=True, text=True)
|
||||
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||
self.assertTrue(dst.read_bytes().startswith(content))
|
||||
self.assertIn(b'_MX_HOST="mail.example.org"\n', dst.read_bytes())
|
||||
self.assertEqual(src.read_bytes(), content)
|
||||
|
||||
def test_migration_rejects_existing_mx_assignment(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
src = pathlib.Path(tmp) / 'old.conf'
|
||||
dst = pathlib.Path(tmp) / 'new.conf'
|
||||
src.write_text('_MX_HOST="other.example.org"\n')
|
||||
proc = subprocess.run([sys.executable, '-', str(src), str(dst), 'mail.example.org'],
|
||||
input=MIGRATION.group(1), capture_output=True, text=True)
|
||||
self.assertNotEqual(proc.returncode, 0)
|
||||
self.assertFalse(dst.exists())
|
||||
|
||||
def run_config_load(self, content):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
conf = pathlib.Path(tmp) / 'srs.conf'
|
||||
conf.write_text(content)
|
||||
script = f'''SRS_CONF_FILE={str(conf)!r}
|
||||
SRS_CONF_SAMPLE_FILE=unused
|
||||
check_secure_source_file() {{ :; }}
|
||||
echononl() {{ :; }}
|
||||
echo_failed() {{ :; }}
|
||||
echo_ok() {{ :; }}
|
||||
fatal() {{ echo "FATAL: $*" >&2; exit 1; }}
|
||||
{LOAD}
|
||||
load_srs_config
|
||||
printf 'MX_IS_SET=%s\\n' "${{_MX_HOST+x}}"
|
||||
'''
|
||||
return subprocess.run(['bash','-c',script],text=True,capture_output=True)
|
||||
|
||||
def test_load_config_accepts_legacy_without_mx_host(self):
|
||||
result = self.run_config_load('_SRS_DOMAIN="srs.example.org"\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('MX_IS_SET=', result.stdout)
|
||||
|
||||
def test_load_config_accepts_valid_mx_host(self):
|
||||
result = self.run_config_load('_MX_HOST="mail.example.org"\n_SRS_DOMAIN="srs.example.org"\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn('MX_IS_SET=x', result.stdout)
|
||||
|
||||
def test_load_config_rejects_invalid_mx_host(self):
|
||||
result = self.run_config_load('_MX_HOST="bad_host"\n_SRS_DOMAIN="srs.example.org"\n')
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('Invalid public MX hostname', result.stderr)
|
||||
|
||||
def test_templates_and_security_contract(self):
|
||||
self.assertIn('_MX_HOST=""', SAMPLE)
|
||||
self.assertIn('_SRS_DOMAIN=""', SAMPLE)
|
||||
self.assertIn('unset _MX_HOST', COMMON)
|
||||
self.assertIn('if [[ -n "${_MX_HOST+x}" ]]', COMMON)
|
||||
self.assertIn('"${_MX_HOST,,}" != "$SELECTED_MX_HOST"', INSTALLER)
|
||||
self.assertIn('install_regular_file "${STAGE_DIR}/rendered/srs.conf" "$SRS_CONF_FILE"', INSTALLER)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
Reference in New Issue
Block a user