mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
fix: filter native discovery helper services
This commit is contained in:
@@ -69,7 +69,13 @@ _SYSTEM = {
|
||||
'elasticsearch', 'opensearch', 'fail2ban', 'named', 'bind9', 'dnsmasq',
|
||||
'avahi-daemon', 'smbd', 'nmbd', 'samba', 'vsftpd', 'proftpd', 'cups', 'tor',
|
||||
}
|
||||
_RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|sh|bash|dash|env|java|ruby|perl)\Z')
|
||||
_RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|npm|pnpm|yarn|bun|sh|bash|dash|env|java|ruby|perl)\Z')
|
||||
_PACKAGE_ROOT_RUNTIMES = {'node', 'nodejs', 'npm', 'pnpm', 'yarn', 'bun'}
|
||||
_HELPER_ROLES = {
|
||||
'backup', 'bootstrap', 'bridge', 'cleanup', 'housekeeping', 'maintenance',
|
||||
'migrate', 'migration', 'restore', 'rotate', 'update', 'updater', 'upgrade',
|
||||
'watchdog', 'worker',
|
||||
}
|
||||
|
||||
|
||||
class ProbeError(Exception):
|
||||
@@ -218,8 +224,23 @@ def _system(name):
|
||||
'unattended-', 'ifup', 'ifdown', 'user@', 'btrfs-', 'lvm2-', 'lxc-')))
|
||||
|
||||
|
||||
def _helper(name):
|
||||
"""Reject service roles that support an application but are not one themselves."""
|
||||
raw = name.lower().removesuffix('.service')
|
||||
if raw.endswith('@'):
|
||||
return True # A systemd template is not a concrete running application.
|
||||
return any(part in _HELPER_ROLES for part in re.split(r'[._-]+', raw) if part)
|
||||
|
||||
|
||||
def _custom(path):
|
||||
return _path(path) and path.startswith(('/opt/', '/srv/', '/usr/local/'))
|
||||
if not _path(path):
|
||||
return False
|
||||
if path.startswith(('/opt/', '/srv/', '/usr/local/')):
|
||||
return True
|
||||
# Native apps commonly keep their runtime below /home/<service-user>/<app>.
|
||||
# Do not accept a home directory itself: a second path component is required.
|
||||
parts = path.split('/')
|
||||
return len(parts) >= 4 and parts[1] == 'home' and bool(parts[2]) and bool(parts[3])
|
||||
|
||||
|
||||
def parse_records(text, kind):
|
||||
@@ -240,10 +261,21 @@ def parse_records(text, kind):
|
||||
if service in seen_services:
|
||||
continue # /etc overrides vendor units; no duplicate installations.
|
||||
seen_services.add(service)
|
||||
if not _NAME.fullmatch(service) or _system(service) or not _path(exe):
|
||||
if (not _NAME.fullmatch(service) or _system(service) or _helper(service)
|
||||
or not _path(exe)):
|
||||
continue
|
||||
runtime = bool(_RUNTIMES.fullmatch(Path(exe).name))
|
||||
install = script if runtime else exe
|
||||
runtime_name = Path(exe).name
|
||||
runtime = bool(_RUNTIMES.fullmatch(runtime_name))
|
||||
if runtime and not _path(script):
|
||||
# A Node/npm wrapper may keep its entry point relative to an
|
||||
# operator-owned working directory. `package.json` is a bounded,
|
||||
# declarative footprint that proves the application root without
|
||||
# reading command arguments, environment or service secrets.
|
||||
if runtime_name not in _PACKAGE_ROOT_RUNTIMES or not _custom(directory):
|
||||
continue
|
||||
install = str(Path(directory) / 'package.json')
|
||||
else:
|
||||
install = script if runtime else exe
|
||||
if (not _path(install) or _system(Path(install).name)
|
||||
or install.startswith(('/usr/lib/systemd/', '/lib/systemd/',
|
||||
'/usr/lib/udev/', '/lib/udev/', '/usr/lib/apt/'))):
|
||||
@@ -399,6 +431,7 @@ def scan(vmid, run=bounded_run):
|
||||
related[0].setdefault('listening_ports', []).extend(ports)
|
||||
elif not related and (_custom(exe) and _custom(directory)
|
||||
and not _system(Path(exe).name)
|
||||
and not _helper(Path(exe).name)
|
||||
and not _RUNTIMES.fullmatch(Path(exe).name)
|
||||
and exe.startswith(directory.rstrip('/') + '/')
|
||||
and _NAME.fullmatch(Path(exe).name)):
|
||||
|
||||
@@ -119,6 +119,55 @@ class DiscoveryFixtures(unittest.TestCase):
|
||||
self.assertEqual(result['candidates'], [])
|
||||
self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls))
|
||||
|
||||
def test_helper_services_and_templates_have_zero_footprint_probes(self):
|
||||
cases = [
|
||||
('netpulse-stable-update', '/bin/bash', '/opt/netpulse', '/opt/netpulse/stable-apply.sh'),
|
||||
('rent-tool-backup', '/bin/bash', '/home/app/rent-tool', '/home/app/rent-tool/scripts/backup-db.sh'),
|
||||
('keynest-update', '/bin/bash', '/opt/keynest', '/opt/keynest/keynest-update.sh'),
|
||||
('cortendesk-bootstrap-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/bootstrap-worker.sh'),
|
||||
('cortendesk-source-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/source-worker.sh'),
|
||||
('cortendesk-managed-password@', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/password-bridge.sh'),
|
||||
]
|
||||
for name, exe, directory, script in cases:
|
||||
with self.subTest(name=name):
|
||||
self.calls.clear()
|
||||
result = native.scan(101, self.runner(service(
|
||||
name=name, exe=exe, directory=directory, script=script)))
|
||||
self.assertEqual(result['candidates'], [])
|
||||
self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls))
|
||||
|
||||
def test_node_and_npm_wrappers_use_only_custom_package_roots(self):
|
||||
cases = [
|
||||
('keynest', '/usr/bin/node', '/opt/keynest'),
|
||||
('rent-tool', '/usr/local/bin/npm', '/home/app/rent-tool'),
|
||||
]
|
||||
for name, exe, directory in cases:
|
||||
with self.subTest(name=name):
|
||||
self.calls.clear()
|
||||
result = native.scan(101, self.runner(
|
||||
service(name=name, exe=exe, directory=directory),
|
||||
footprint='FILE\n{"version":"1.2.3"}'))
|
||||
item, = result['candidates']
|
||||
self.assertEqual(item['name'], name)
|
||||
self.assertEqual(item['installation_path'], directory + '/package.json')
|
||||
self.assertEqual(item['installed_version'], '1.2.3')
|
||||
footprint_calls = [call for call in self.calls if native.FOOTPRINT_SCRIPT in call]
|
||||
self.assertEqual(len(footprint_calls), 1)
|
||||
self.assertEqual(footprint_calls[0][-2:], [directory + '/package.json', directory])
|
||||
|
||||
self.calls.clear()
|
||||
result = native.scan(101, self.runner(service(
|
||||
name='ambiguous-home-runtime', exe='/usr/bin/node', directory='/home/app')))
|
||||
self.assertEqual(result['candidates'], [])
|
||||
self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls))
|
||||
|
||||
def test_helper_process_fallback_has_zero_footprint_probes(self):
|
||||
self.calls.clear()
|
||||
result = native.scan(101, self.runner(
|
||||
services='END\n', processes='P\t123\t/opt/cortendesk/run-worker\t/opt/cortendesk\nEND\n'))
|
||||
self.assertEqual(result['candidates'], [])
|
||||
self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls))
|
||||
|
||||
def test_missing_remote_qemu_stopped_busy_oci_guards_precede_exec(self):
|
||||
scenarios = ['missing', 'stopped', 'busy', 'oci', 'invalid-oci', 'unmanaged']
|
||||
for scenario in scenarios:
|
||||
|
||||
Reference in New Issue
Block a user