From 5cf972fd83eedb18f99f612eefbf1aae99153d24 Mon Sep 17 00:00:00 2001 From: "mathieu.gueurce" Date: Fri, 13 Mar 2026 15:43:42 +0100 Subject: [PATCH] Fixes support for syslog log when salt is running as non-root user --- salt/utils/parsers.py | 3 +- salt/utils/validate/path.py | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py index f375dc25c91f..c89b20c43ba7 100644 --- a/salt/utils/parsers.py +++ b/salt/utils/parsers.py @@ -40,6 +40,7 @@ import salt.utils.yaml import salt.version as version from salt.defaults import DEFAULT_TARGET_DELIM +from salt.utils.validate.path import is_syslog_path from salt.utils.validate.path import is_writeable from salt.utils.verify import insecure_log, verify_log, verify_log_files @@ -833,7 +834,7 @@ def __setup_logfile_logger_config(self): ), ) - if not is_writeable(logfile, check_parent=True): + if not is_syslog_path(logfile) and not is_writeable(logfile, check_parent=True): # Since we're not be able to write to the log file or its parent # directory (if the log file does not exit), are we the same user # as the one defined in the configuration file? diff --git a/salt/utils/validate/path.py b/salt/utils/validate/path.py index e6f97cfc42f2..7b63dd769b36 100644 --- a/salt/utils/validate/path.py +++ b/salt/utils/validate/path.py @@ -8,7 +8,67 @@ Several path related validators """ +import logging.handlers import os +import pathlib +import stat +from urllib.parse import urlparse + + +def is_syslog_path(path): + """ + Check if a given path is a syslog path. + + ://:/ + + log-facility is optional; must be a valid syslog facility in upper case (e.g. "LOG_USER", "LOG_DAEMON", etc.) + see https://docs.python.org/3/library/logging.handlers.html#logging.handlers.SysLogHandler + + :param path: The path to check + :returns: True or False + """ + + parsed_log_path = urlparse(path) + + if parsed_log_path.scheme in ("tcp", "udp", "file"): + if parsed_log_path.scheme == "file" and parsed_log_path.path: + path = pathlib.Path(parsed_log_path.path) + facility_name = path.name.upper() + if facility_name.startswith("LOG_"): + facility = getattr(logging.handlers.SysLogHandler, facility_name, None) + if facility is None: + return False + file_path = str(path.resolve().parent) + else: + file_path = str(path.resolve()) + + # Check if file_path is a Unix socket + if os.path.exists(file_path): + mode = os.stat(file_path).st_mode + if stat.S_ISSOCK(mode): + return os.access(file_path, os.W_OK) + else: + return False + else: + return False + + elif parsed_log_path.path: + # In case of udp or tcp with a facility specified + path = pathlib.Path(parsed_log_path.path) + facility_name = path.stem.upper() + if not facility_name.startswith("LOG_"): + return False + + facility = getattr(logging.handlers.SysLogHandler, facility_name, None) + if facility is None: + return False + else: + return True + + else: + # This is the case of udp or tcp without a facility specified + return True + return False def is_writeable(path, check_parent=False):