def loop(self): """Main body of daemon request loop""" LOG.info(_LI('privsep daemon running as pid %s'), os.getpid()) # We *are* this context now - any calls through it should be # executed locally. self.context.set_client_mode(False) for msgid, msg in self.channel: LOG.debug('privsep: request[%(msgid)s]: %(req)s', {'msgid': msgid, 'req': msg}) try: reply = self._process_cmd(*msg) except Exception as e: LOG.debug( 'privsep: Exception during request[%(msgid)s]: %(err)s', {'msgid': msgid, 'err': e}, exc_info=True) cls = e.__class__ cls_name = '%s.%s' % (cls.__module__, cls.__name__) reply = (Message.ERR.value, cls_name, e.args) try: LOG.debug('privsep: reply[%(msgid)s]: %(reply)s', {'msgid': msgid, 'reply': reply}) self.channel.send((msgid, reply)) except IOError as e: if e.errno == errno.EPIPE: # Write stream closed, exit loop break raise LOG.debug('Socket closed, shutting down privsep daemon')
def __init__(self, context): """Start privsep daemon using exec() Uses sudo/rootwrap to gain privileges. """ listen_sock = socket.socket(socket.AF_UNIX) # Note we listen() on the unprivileged side, and connect to it # from the privileged process. This means there is no exposed # attack point on the privileged side. # NB: Permissions on sockets are not checked on some (BSD) Unices # so create socket in a private directory for safety. Privsep # daemon will (initially) be running as root, so will still be # able to connect to sock path. tmpdir = tempfile.mkdtemp() # NB: created with 0700 perms try: sockpath = os.path.join(tmpdir, 'privsep.sock') listen_sock.bind(sockpath) listen_sock.listen(1) cmd = self._helper_command(context, sockpath) LOG.info(_LI('Running privsep helper: %s'), cmd) proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger()) if proc.wait() != 0: msg = (_LE('privsep helper command exited non-zero (%s)') % proc.returncode) LOG.critical(msg) raise FailedToDropPrivileges(msg) LOG.info(_LI('Spawned new privsep daemon via rootwrap')) sock, _addr = listen_sock.accept() LOG.debug('Accepted privsep connection to %s', sockpath) finally: # Don't need listen_sock anymore, so clean up. listen_sock.close() try: os.unlink(sockpath) except OSError as e: if e.errno != errno.ENOENT: raise os.rmdir(tmpdir) super(RootwrapClientChannel, self).__init__(sock)
def __init__(self, context): """Start privsep daemon using exec() Uses sudo/rootwrap to gain privileges. """ listen_sock = socket.socket(socket.AF_UNIX) # Note we listen() on the unprivileged side, and connect to it # from the privileged process. This means there is no exposed # attack point on the privileged side. # NB: Permissions on sockets are not checked on some (BSD) Unices # so create socket in a private directory for safety. Privsep # daemon will (initially) be running as root, so will still be # able to connect to sock path. tmpdir = tempfile.mkdtemp() # NB: created with 0700 perms try: sockpath = os.path.join(tmpdir, 'privsep.sock') listen_sock.bind(sockpath) listen_sock.listen(1) cmd = context.helper_command(sockpath) LOG.info(_LI('Running privsep helper: %s'), cmd) proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger()) if proc.wait() != 0: msg = (_LE('privsep helper command exited non-zero (%s)') % proc.returncode) LOG.critical(msg) raise FailedToDropPrivileges(msg) LOG.info(_LI('Spawned new privsep daemon via rootwrap')) sock, _addr = listen_sock.accept() LOG.debug('Accepted privsep connection to %s', sockpath) finally: # Don't need listen_sock anymore, so clean up. listen_sock.close() try: os.unlink(sockpath) except OSError as e: if e.errno != errno.ENOENT: raise os.rmdir(tmpdir) super(RootwrapClientChannel, self).__init__(sock)
def _drop_privs(self): try: # Keep current capabilities across setuid away from root. capabilities.set_keepcaps(True) if self.group is not None: try: os.setgroups([]) except OSError: msg = _('Failed to remove supplemental groups') LOG.critical(msg) raise FailedToDropPrivileges(msg) if self.user is not None: setuid(self.user) if self.group is not None: setgid(self.group) finally: capabilities.set_keepcaps(False) LOG.info(_LI('privsep process running with uid/gid: %(uid)s/%(gid)s'), { 'uid': os.getuid(), 'gid': os.getgid() }) capabilities.drop_all_caps_except(self.caps, self.caps, []) def fmt_caps(capset): if not capset: return 'none' fc = [capabilities.CAPS_BYVALUE.get(c, str(c)) for c in capset] fc.sort() return '|'.join(fc) eff, prm, inh = capabilities.get_caps() LOG.info( _LI('privsep process running with capabilities ' '(eff/prm/inh): %(eff)s/%(prm)s/%(inh)s'), { 'eff': fmt_caps(eff), 'prm': fmt_caps(prm), 'inh': fmt_caps(inh), })
def _drop_privs(self): try: # Keep current capabilities across setuid away from root. capabilities.set_keepcaps(True) if self.group is not None: try: os.setgroups([]) except OSError: msg = _('Failed to remove supplemental groups') LOG.critical(msg) raise FailedToDropPrivileges(msg) if self.user is not None: setuid(self.user) if self.group is not None: setgid(self.group) finally: capabilities.set_keepcaps(False) LOG.info(_LI('privsep process running with uid/gid: %(uid)s/%(gid)s'), {'uid': os.getuid(), 'gid': os.getgid()}) capabilities.drop_all_caps_except(self.caps, self.caps, []) def fmt_caps(capset): if not capset: return 'none' fc = [capabilities.CAPS_BYVALUE.get(c, str(c)) for c in capset] fc.sort() return '|'.join(fc) eff, prm, inh = capabilities.get_caps() LOG.info( _LI('privsep process running with capabilities ' '(eff/prm/inh): %(eff)s/%(prm)s/%(inh)s'), { 'eff': fmt_caps(eff), 'prm': fmt_caps(prm), 'inh': fmt_caps(inh), })
def helper_main(): """Start privileged process, serving requests over a Unix socket.""" cfg.CONF.register_cli_opts([ cfg.StrOpt('privsep_context', required=True), cfg.StrOpt('privsep_sock_path', required=True), ]) logging.register_options(cfg.CONF) cfg.CONF(args=sys.argv[1:], project='privsep') logging.setup(cfg.CONF, 'privsep') # We always log to stderr. Replace the root logger we just set up. replace_logging(pylogging.StreamHandler(sys.stderr)) LOG.info(_LI('privsep daemon starting')) context = importutils.import_class(cfg.CONF.privsep_context) from oslo_privsep import priv_context # Avoid circular import if not isinstance(context, priv_context.PrivContext): LOG.fatal( _LE('--privsep_context must be the (python) name of a ' 'PrivContext object')) sock = socket.socket(socket.AF_UNIX) sock.connect(cfg.CONF.privsep_sock_path) set_cloexec(sock) channel = comm.ServerChannel(sock) # Channel is set up, so fork off daemon "in the background" and exit if os.fork() != 0: # parent return # child # Note we don't move into a new process group/session like a # regular daemon might, since we _want_ to remain associated with # the originating (unprivileged) process. try: Daemon(channel, context).run() except Exception as e: LOG.exception(e) sys.exit(str(e)) LOG.debug('privsep daemon exiting') sys.exit(0)
def helper_main(): """Start privileged process, serving requests over a Unix socket.""" cfg.CONF.register_cli_opts([ cfg.StrOpt('privsep_context', required=True), cfg.StrOpt('privsep_sock_path', required=True), ]) logging.register_options(cfg.CONF) cfg.CONF(args=sys.argv[1:], project='privsep') logging.setup(cfg.CONF, 'privsep') # We always log to stderr. Replace the root logger we just set up. replace_logging(pylogging.StreamHandler(sys.stderr)) LOG.info(_LI('privsep daemon starting')) context = importutils.import_class(cfg.CONF.privsep_context) from oslo_privsep import priv_context # Avoid circular import if not isinstance(context, priv_context.PrivContext): LOG.fatal(_LE('--privsep_context must be the (python) name of a ' 'PrivContext object')) sock = socket.socket(socket.AF_UNIX) sock.connect(cfg.CONF.privsep_sock_path) set_cloexec(sock) channel = comm.ServerChannel(sock) # Channel is set up, so fork off daemon "in the background" and exit if os.fork() != 0: # parent return # child # Note we don't move into a new process group/session like a # regular daemon might, since we _want_ to remain associated with # the originating (unprivileged) process. try: Daemon(channel, context).run() except Exception as e: LOG.exception(e) sys.exit(str(e)) LOG.debug('privsep daemon exiting') sys.exit(0)
def loop(self): """Main body of daemon request loop""" LOG.info(_LI('privsep daemon running as pid %s'), os.getpid()) # We *are* this context now - any calls through it should be # executed locally. self.context.set_client_mode(False) for msgid, msg in self.channel: LOG.debug('privsep: request[%(msgid)s]: %(req)s', { 'msgid': msgid, 'req': msg }) try: reply = self._process_cmd(*msg) except Exception as e: LOG.debug( 'privsep: Exception during request[%(msgid)s]: %(err)s', { 'msgid': msgid, 'err': e }, exc_info=True) cls = e.__class__ cls_name = '%s.%s' % (cls.__module__, cls.__name__) reply = (Message.ERR.value, cls_name, e.args) try: LOG.debug('privsep: reply[%(msgid)s]: %(reply)s', { 'msgid': msgid, 'reply': reply }) self.channel.send((msgid, reply)) except IOError as e: if e.errno == errno.EPIPE: # Write stream closed, exit loop break raise LOG.debug('Socket closed, shutting down privsep daemon')
def __init__(self, context): """Start privsep daemon using exec() Uses sudo/rootwrap to gain privileges. """ # We need to be able to reconstruct the context object in the new # python process we'll get after rootwrap/sudo. This means we # need to construct the context object and store it somewhere # globally accessible, and then use that python name to find it # again in the new python interpreter. Yes, it's all a bit # clumsy, and none of it is required when using the fork-based # alternative above. # These asserts here are just attempts to catch errors earlier. # TODO(gus): Consider replacing with setuptools entry_points. assert context.pypath is not None, ( 'RootwrapClientChannel requires priv_context ' 'pypath to be specified') assert importutils.import_class(context.pypath) is context, ( 'RootwrapClientChannel requires priv_context pypath ' 'for context object') listen_sock = socket.socket(socket.AF_UNIX) # Note we listen() on the unprivileged side, and connect to it # from the privileged process. This means there is no exposed # attack point on the privileged side. # NB: Permissions on sockets are not checked on some (BSD) Unices # so create socket in a private directory for safety. Privsep # daemon will (initially) be running as root, so will still be # able to connect to sock path. tmpdir = tempfile.mkdtemp() # NB: created with 0700 perms try: sockpath = os.path.join(tmpdir, 'privsep.sock') listen_sock.bind(sockpath) listen_sock.listen(1) cmd = shlex.split(context.conf.helper_command) + [ '--privsep_context', context.pypath, '--privsep_sock_path', sockpath] LOG.info(_LI('Running privsep helper: %s'), cmd) proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger()) if proc.wait() != 0: msg = (_LE('privsep helper command exited non-zero (%s)') % proc.returncode) LOG.critical(msg) raise FailedToDropPrivileges(msg) LOG.info(_LI('Spawned new privsep daemon via rootwrap')) sock, _addr = listen_sock.accept() LOG.debug('Accepted privsep connection to %s', sockpath) finally: # Don't need listen_sock anymore, so clean up. listen_sock.close() try: os.unlink(sockpath) except OSError as e: if e.errno != errno.ENOENT: raise os.rmdir(tmpdir) super(RootwrapClientChannel, self).__init__(sock)