From c69952265facecd81cfcf51e3679c460723bee61 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 7 May 2026 08:44:16 -0400 Subject: [PATCH] Permit override of unix ownership/permissions on sockets If an environment knows more specifically what should have access in terms of group, allow service.cfg to indicate. --- confluent_server/confluent/httpapi.py | 18 ++++++++++++++---- confluent_server/confluent/main.py | 12 +++++++----- confluent_server/confluent/sockapi.py | 17 +++++++++++------ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index db521cc1..bb577107 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -17,6 +17,7 @@ # This SCGI server provides a http wrap to confluent api # It additionally manages httprequest console sessions import base64 +import shutil try: import Cookie except ModuleNotFoundError: @@ -1217,7 +1218,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None): rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')) -def serve(bind_host, bind_port): +def serve(bind_host, bind_port, bind_group=None, bind_perms=None): # TODO(jbjohnso): move to unix socket and explore # either making apache deal with it # or just supporting nginx or lighthttpd @@ -1232,13 +1233,17 @@ def serve(bind_host, bind_port): while not sock: try: if '/' in bind_host: + oldumask = os.umask(0o777 - bind_perms) try: os.remove(bind_host) except Exception: pass sock = eventlet.listen( bind_host, family=socket.AF_UNIX) - os.chmod(bind_host, 0o666) + os.umask(oldumask) + os.chmod(bind_host, bind_perms) + if bind_group: + shutil.chown(bind_host, group=bind_group) else: addrinfo = socket.getaddrinfo(bind_host, bind_port)[0] sock = eventlet.listen( @@ -1264,17 +1269,22 @@ def serve(bind_host, bind_port): class HttpApi(object): - def __init__(self, bind_host=None, bind_port=None): + def __init__(self, bind_host=None, bind_port=None, bind_group=None, bind_perms=None): self.server = None self.bind_host = bind_host or '127.0.0.1' self.bind_port = bind_port or 4005 + # Ultimately, a unix socket is being used in lieu of a TCP socket, + # so open permissions make sense as the security is not based solely on socket access + # however, steering it to webserver group can be done for extra confidence + self.bind_group = bind_group + self.bind_perms = bind_perms or 0o666 def start(self): global auditlog global tracelog tracelog = log.Logger('trace') auditlog = log.Logger('audit') - self.server = eventlet.spawn(serve, self.bind_host, self.bind_port) + self.server = eventlet.spawn(serve, self.bind_host, self.bind_port, self.bind_group, self.bind_perms) _cleaner = eventlet.spawn(_sessioncleaner) diff --git a/confluent_server/confluent/main.py b/confluent_server/confluent/main.py index b8f47e94..4172f8b3 100644 --- a/confluent_server/confluent/main.py +++ b/confluent_server/confluent/main.py @@ -314,14 +314,14 @@ def run(args): auth.check_for_yaml() collective.startup() consoleserver.initialize() - http_bind_host, http_bind_port = _get_connector_config('http') - sock_bind_host, sock_bind_port = _get_connector_config('socket') + http_bind_host, http_bind_port, http_bind_group, http_bind_perms = _get_connector_config('http') + sock_bind_host, sock_bind_port, sock_bind_group, sock_bind_perms = _get_connector_config('socket') try: - sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port) + sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port, sock_bind_group, sock_bind_perms) sockservice.start() except NameError: pass - webservice = httpapi.HttpApi(http_bind_host, http_bind_port) + webservice = httpapi.HttpApi(http_bind_host, http_bind_port, http_bind_group, http_bind_perms) webservice.start() while len(list(configmanager.list_collective())) >= 2: # If in a collective, stall automatic startup activity @@ -340,7 +340,9 @@ def run(args): def _get_connector_config(session): host = conf.get_option(session, 'bindhost') port = conf.get_int_option(session, 'bindport') - return (host, port) + group = conf.get_option(session, 'bindgroup') + perms = conf.get_option(session, 'bindperms') + return (host, port, group, perms) def _get_logdirectory(): return conf.get_option('globals', 'logdirectory') \ No newline at end of file diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index 48b1065d..11dc2371 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -26,6 +26,7 @@ import ctypes.util import errno import os import pwd +import shutil import stat import struct import sys @@ -458,7 +459,7 @@ def removesocket(): except OSError: pass -def _unixdomainhandler(): +def _unixdomainhandler(bind_group=None, bind_perms=None): unixsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: os.remove("/var/run/confluent/api.sock") @@ -466,10 +467,12 @@ def _unixdomainhandler(): pass if not os.path.isdir("/var/run/confluent"): os.makedirs('/var/run/confluent', 0o755) + oldumask = os.umask(0o777 - bind_perms) unixsocket.bind("/var/run/confluent/api.sock") - os.chmod("/var/run/confluent/api.sock", - stat.S_IWOTH | stat.S_IROTH | stat.S_IWGRP | - stat.S_IRGRP | stat.S_IWUSR | stat.S_IRUSR) + os.chmod("/var/run/confluent/api.sock", bind_perms) + if bind_group: + shutil.chown("/var/run/confluent/api.sock", group=bind_group) + os.umask(oldumask) atexit.register(removesocket) unixsocket.listen(5) while True: @@ -498,11 +501,13 @@ def _unixdomainhandler(): class SockApi(object): - def __init__(self, bindhost=None, bindport=None): + def __init__(self, bindhost=None, bindport=None, bind_group=None, bind_perms=None): self.tlsserver = None self.unixdomainserver = None self.bind_host = bindhost or '::' self.bind_port = bindport or 13001 + self.bind_group = bind_group + self.bind_perms = bind_perms or 0o666 def start(self): global auditlog @@ -515,7 +520,7 @@ class SockApi(object): else: eventlet.spawn_n(self.watch_for_cert) eventlet.spawn_n(self.watch_resolv) - self.unixdomainserver = eventlet.spawn(_unixdomainhandler) + self.unixdomainserver = eventlet.spawn(_unixdomainhandler, self.bind_group, self.bind_perms) def watch_resolv(self): while True: