def _process_cmd(self, msgid, cmd, *args): """Executes the requested command in an execution thread. This executes a call within a thread executor and returns the results of the execution. :param msgid: The message identifier. :param cmd: The `Message` type indicating the command type. :param args: The function, args, and kwargs if a Message.CALL type. :return: A tuple of the return status, optional call output, and optional error information. """ if cmd == Message.PING: return (Message.PONG.value,) try: if cmd != Message.CALL: raise ProtocolError(_('Unknown privsep cmd: %s') % cmd) # Extract the callable and arguments name, f_args, f_kwargs = args func = importutils.import_class(name) if not self.context.is_entrypoint(func): msg = _('Invalid privsep function: %s not exported') % name raise NameError(msg) ret = func(*f_args, **f_kwargs) return (Message.RET.value, ret) 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__) return (Message.ERR.value, cls_name, e.args)
def _process_cmd(self, cmd, *args): if cmd == Message.PING: return (Message.PONG.value, ) elif cmd == Message.CALL: name, f_args, f_kwargs = args func = importutils.import_class(name) if not self.context.is_entrypoint(func): msg = _('Invalid privsep function: %s not exported') % name raise NameError(msg) ret = func(*f_args, **f_kwargs) return (Message.RET.value, ret) raise ProtocolError(_('Unknown privsep cmd: %s') % cmd)
def _reader_main(self, reader): """This thread owns and demuxes the read channel""" with self.lock: self.running = True for msg in reader: msgid, data = msg if msgid is None: self.out_of_band(data) else: with self.lock: if msgid not in self.outstanding_msgs: raise AssertionError("msgid should in " "outstanding_msgs.") self.outstanding_msgs[msgid].set_result(data) # EOF. Perhaps the privileged process exited? # Send an IOError to any oustanding waiting readers. Assuming # the write direction is also closed, any new writes should # get an immediate similar error. LOG.debug('EOF on privsep read channel') exc = IOError(_('Premature eof waiting for privileged process')) with self.lock: for mbox in self.outstanding_msgs.values(): mbox.set_exception(exc) self.running = False
def _process_cmd(self, cmd, *args): if cmd == Message.PING: return (Message.PONG.value,) elif cmd == Message.CALL: name, f_args, f_kwargs = args func = importutils.import_class(name) if not self.context.is_entrypoint(func): msg = _('Invalid privsep function: %s not exported') % name raise NameError(msg) ret = func(*f_args, **f_kwargs) return (Message.RET.value, ret) raise ProtocolError(_('Unknown privsep cmd: %s') % cmd)
def _read_n(self, n): """Read exactly N bytes. Raises EOFError on premature EOF""" data = [] while n > 0: tmp = self.readsock.recv(n) if not tmp: raise EOFError(_("Premature EOF during deserialization")) data.append(tmp) n -= len(tmp) return b"".join(data)
def setuid(user_id_or_name): try: new_uid = int(user_id_or_name) except (TypeError, ValueError): new_uid = pwd.getpwnam(user_id_or_name).pw_uid if new_uid != 0: try: os.setuid(new_uid) except OSError: msg = _('Failed to set uid %s') % new_uid LOG.critical(msg) raise FailedToDropPrivileges(msg)
def setgid(group_id_or_name): try: new_gid = int(group_id_or_name) except (TypeError, ValueError): new_gid = grp.getgrnam(group_id_or_name).gr_gid if new_gid != 0: try: os.setgid(new_gid) except OSError: msg = _('Failed to set gid %s') % new_gid LOG.critical(msg) raise FailedToDropPrivileges(msg)
def exchange_ping(self): try: # exchange "ready" messages reply = self.send_recv((Message.PING.value, )) success = reply[0] == Message.PONG except Exception as e: LOG.exception('Error while sending initial PING to privsep: %s', e) success = False if not success: msg = _('Privsep daemon failed to start') LOG.critical(msg) raise FailedToDropPrivileges(msg)
def exchange_ping(self): try: # exchange "ready" messages reply = self.send_recv((Message.PING.value,)) success = reply[0] == Message.PONG except Exception as e: LOG.exception( _LE('Error while sending initial PING to privsep: %s'), e) success = False if not success: msg = _('Privsep daemon failed to start') LOG.critical(msg) raise FailedToDropPrivileges(msg)
def remote_call(self, name, args, kwargs): result = self.send_recv((Message.CALL.value, name, args, kwargs)) if result[0] == Message.RET: # (RET, return value) return result[1] elif result[0] == Message.ERR: # (ERR, exc_type, args) # # TODO(gus): see what can be done to preserve traceback # (without leaking local values) exc_type = importutils.import_class(result[1]) raise exc_type(*result[2]) else: raise ProtocolError(_('Unexpected response: %r') % result)
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 _reader_main(self, reader): """This thread owns and demuxes the read channel""" for msg in reader: msgid, data = msg with self.lock: assert msgid in self.outstanding_msgs self.outstanding_msgs[msgid].set_result(data) # EOF. Perhaps the privileged process exited? # Send an IOError to any oustanding waiting readers. Assuming # the write direction is also closed, any new writes should # get an immediate similar error. LOG.debug("EOF on privsep read channel") exc = IOError(_("Premature eof waiting for privileged process")) with self.lock: for mbox in self.outstanding_msgs.values(): mbox.set_exception(exc)
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), })
LOG = logging.getLogger(__name__) def CapNameOrInt(value): value = str(value).strip() try: return capabilities.CAPS_BYNAME[value] except KeyError: return int(value) OPTS = [ cfg.StrOpt('user', help=_('User that the privsep daemon should run as.')), cfg.StrOpt('group', help=_('Group that the privsep daemon should run as.')), cfg.Opt('capabilities', type=types.List(CapNameOrInt), default=[], help=_('List of Linux capabilities retained by the privsep ' 'daemon.')), cfg.StrOpt('helper_command', default=('sudo privsep-helper' # TODO(gus): how do I find a good config path? ' --config-file=/etc/$project/$project.conf'), help=_('Command to invoke via sudo/rootwrap to start ' 'the privsep daemon.')), ] _ENTRYPOINT_ATTR = 'privsep_entrypoint'
from oslo_privsep import capabilities from oslo_privsep import daemon LOG = logging.getLogger(__name__) def CapNameOrInt(value): value = str(value).strip() try: return capabilities.CAPS_BYNAME[value] except KeyError: return int(value) OPTS = [ cfg.StrOpt('user', help=_('User that the privsep daemon should run as.')), cfg.StrOpt('group', help=_('Group that the privsep daemon should run as.')), cfg.Opt('capabilities', type=types.List(CapNameOrInt), default=[], help=_('List of Linux capabilities retained by the privsep ' 'daemon.')), cfg.IntOpt('thread_pool_size', min=1, help=_("The number of threads available for privsep to " "concurrently run processes. Defaults to the number of " "CPU cores in the system."), default=multiprocessing.cpu_count(), sample_default='multiprocessing.cpu_count()'), cfg.StrOpt('helper_command',
from oslo_privsep._i18n import _, _LW, _LE LOG = logging.getLogger(__name__) def CapNameOrInt(value): value = str(value).strip() try: return capabilities.CAPS_BYNAME[value] except KeyError: return int(value) OPTS = [ cfg.StrOpt("user", help=_("User that the privsep daemon should run as.")), cfg.StrOpt("group", help=_("Group that the privsep daemon should run as.")), cfg.Opt( "capabilities", type=types.List(CapNameOrInt), default=[], help=_("List of Linux capabilities retained by the privsep " "daemon."), ), cfg.StrOpt( "helper_command", help=_( "Command to invoke to start the privsep daemon if " 'not using the "fork" method. ' "If not specified, a default is generated using " '"sudo privsep-helper" and arguments designed to ' "recreate the current configuration. "
from oslo_privsep import daemon from oslo_privsep._i18n import _, _LW, _LE LOG = logging.getLogger(__name__) def CapNameOrInt(value): value = str(value).strip() try: return capabilities.CAPS_BYNAME[value] except KeyError: return int(value) OPTS = [ cfg.StrOpt('user', help=_('User that the privsep daemon should run as.')), cfg.StrOpt('group', help=_('Group that the privsep daemon should run as.')), cfg.Opt('capabilities', type=types.List(CapNameOrInt), default=[], help=_('List of Linux capabilities retained by the privsep ' 'daemon.')), cfg.StrOpt('helper_command', help=_('Command to invoke to start the privsep daemon if ' 'not using the "fork" method. ' 'If not specified, a default is generated using ' '"sudo privsep-helper" and arguments designed to ' 'recreate the current configuration. ' 'This command must accept suitable --privsep_context ' 'and --privsep_sock_path arguments.')),
LOG = logging.getLogger(__name__) def CapNameOrInt(value): value = str(value).strip() try: return capabilities.CAPS_BYNAME[value] except KeyError: return int(value) OPTS = [ cfg.StrOpt('user', help=_('User that the privsep daemon should run as.')), cfg.StrOpt('group', help=_('Group that the privsep daemon should run as.')), cfg.Opt('capabilities', type=types.List(CapNameOrInt), default=[], help=_('List of Linux capabilities retained by the privsep ' 'daemon.')), cfg.StrOpt('helper_command', help=_('Command to invoke to start the privsep daemon if ' 'not using the "fork" method. ' 'If not specified, a default is generated using ' '"sudo privsep-helper" and arguments designed to ' 'recreate the current configuration. ' 'This command must accept suitable --privsep_context ' 'and --privsep_sock_path arguments.')), ]