Implemented polkit authorization
Dropped dbus at_console policy and implemented polkit authorization. By the default policy the query actions are allowed for all users, actions which do modification to the system settings or Tuned state are by default allowed only for active users that have console, others need admin authorization. Resolves: rhbz#1095142 Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
parent
e86e1be083
commit
68bb447a0f
10 changed files with 222 additions and 29 deletions
5
Makefile
5
Makefile
|
|
@ -37,8 +37,8 @@ release-cp: release-dir
|
|||
|
||||
cp -a tuned.py tuned.spec tuned.service tuned.tmpfiles Makefile tuned-adm.py \
|
||||
tuned-adm.bash dbus.conf recommend.conf tuned-main.conf 00_tuned \
|
||||
bootcmdline com.redhat.tuned.gui.policy tuned-gui.py tuned-gui.glade \
|
||||
tuned-gui.desktop $(VERSIONED_NAME)
|
||||
bootcmdline com.redhat.tuned.policy com.redhat.tuned.gui.policy \
|
||||
tuned-gui.py tuned-gui.glade tuned-gui.desktop $(VERSIONED_NAME)
|
||||
cp -a doc experiments libexec man profiles systemtap tuned contrib icons \
|
||||
$(VERSIONED_NAME)
|
||||
|
||||
|
|
@ -144,6 +144,7 @@ install: install-dirs
|
|||
install -Dpm 0755 00_tuned $(DESTDIR)/etc/grub.d/00_tuned
|
||||
|
||||
# polkit configuration
|
||||
install -Dpm 0644 com.redhat.tuned.policy $(DESTDIR)$(DATADIR)/polkit-1/actions/com.redhat.tuned.policy
|
||||
install -Dpm 0644 com.redhat.tuned.gui.policy $(DESTDIR)$(DATADIR)/polkit-1/actions/com.redhat.tuned.gui.policy
|
||||
|
||||
# manual pages
|
||||
|
|
|
|||
129
com.redhat.tuned.policy
Normal file
129
com.redhat.tuned.policy
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<vendor>Tuned</vendor>
|
||||
<vendor_url>https://fedorahosted.org/tuned/</vendor_url>
|
||||
<icon_name>tuned</icon_name>
|
||||
|
||||
<action id="com.redhat.tuned.active_profile">
|
||||
<description>Show active profile</description>
|
||||
<message>Authentication is required to show active profile</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.disable">
|
||||
<description>Disable Tuned</description>
|
||||
<message>Authentication is required to disable Tuned</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.is_running">
|
||||
<description>Check whether Tuned is running</description>
|
||||
<message>Authentication is required to check whether Tuned is running</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.profile_info">
|
||||
<description>Show information about Tuned profile</description>
|
||||
<message>Authentication is required to show information about Tuned profile</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.profiles">
|
||||
<description>List Tuned profiles</description>
|
||||
<message>Authentication is required to list Tuned profiles</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.profiles2">
|
||||
<description>List Tuned profiles</description>
|
||||
<message>Authentication is required to list Tuned profiles</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.recommend_profile">
|
||||
<description>Show Tuned profile name which is recommended for your system</description>
|
||||
<message>Authentication is required to show recommended profile name</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.reload">
|
||||
<description>Reload Tuned configuration</description>
|
||||
<message>Authentication is required to reload Tuned configuration</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.start">
|
||||
<description>Start Tuned daemon</description>
|
||||
<message>Authentication is required to start Tuned daemon</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.stop">
|
||||
<description>Stop Tuned daemon</description>
|
||||
<message>Authentication is required to stop Tuned daemon</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.switch_profile">
|
||||
<description>Switch Tuned profile</description>
|
||||
<message>Authentication is required to switch Tuned profile</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.verify_profile">
|
||||
<description>Verify Tuned profile</description>
|
||||
<message>Authentication is required to verify Tuned profile</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
|
|
@ -4,18 +4,13 @@
|
|||
|
||||
<busconfig>
|
||||
<policy context="default">
|
||||
<deny send_destination="com.redhat.tuned" />
|
||||
<allow receive_sender="com.redhat.tuned" />
|
||||
<allow send_destination="com.redhat.tuned" send_interface="org.freedesktop.DBus.Introspectable" />
|
||||
<allow send_destination="com.redhat.tuned" send_interface="com.redhat.tuned.control" send_member="active_profile" />
|
||||
<allow send_destination="com.redhat.tuned" send_interface="com.redhat.tuned.control" />
|
||||
</policy>
|
||||
|
||||
<policy user="root">
|
||||
<allow own="com.redhat.tuned" />
|
||||
<allow send_destination="com.redhat.tuned" />
|
||||
</policy>
|
||||
|
||||
<policy at_console="true">
|
||||
<allow send_destination="com.redhat.tuned" />
|
||||
</policy>
|
||||
</busconfig>
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ fi
|
|||
%{_sbindir}/tuned-gui
|
||||
%{python_sitelib}/tuned/gtk
|
||||
%{_datadir}/tuned/ui
|
||||
%{_datadir}/polkit-1/actions/com.redhat.tuned.policy
|
||||
%{_datadir}/polkit-1/actions/com.redhat.tuned.gui.policy
|
||||
%{_datadir}/icons/hicolor/scalable/apps/tuned.svg
|
||||
%{_datadir}/applications/tuned-gui.desktop
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class Admin(object):
|
|||
self._daemon_action_errstr = ""
|
||||
self._controller = None
|
||||
if self._dbus:
|
||||
self._controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE, debug)
|
||||
self._controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_INTERFACE, consts.DBUS_OBJECT, debug)
|
||||
try:
|
||||
self._controller.set_signal_handler(consts.DBUS_SIGNAL_PROFILE_CHANGED, self._signal_profile_changed_cb)
|
||||
except TunedAdminDBusException as e:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class DBusController(object):
|
|||
self._interface_name = interface_name
|
||||
self._object_name = object_name
|
||||
self._proxy = None
|
||||
self._interface = None
|
||||
self._debug = debug
|
||||
self._main_loop = None
|
||||
self._action = None
|
||||
|
|
@ -29,7 +30,8 @@ class DBusController(object):
|
|||
DBusGMainLoop(set_as_default=True)
|
||||
self._main_loop = GLib.MainLoop()
|
||||
bus = dbus.SystemBus()
|
||||
self._proxy = bus.get_object(self._bus_name, self._interface_name, self._object_name)
|
||||
self._proxy = bus.get_object(self._bus_name, self._object_name)
|
||||
self._interface = dbus.Interface(self._proxy, dbus_interface = self._interface_name)
|
||||
except dbus.exceptions.DBusException:
|
||||
raise TunedAdminDBusException("Cannot talk to Tuned daemon via DBus. Is Tuned daemon running?")
|
||||
|
||||
|
|
@ -67,7 +69,7 @@ class DBusController(object):
|
|||
self._init_proxy()
|
||||
|
||||
try:
|
||||
method = self._proxy.get_dbus_method(method_name)
|
||||
method = self._interface.get_dbus_method(method_name)
|
||||
return method(*args, **kwargs)
|
||||
except dbus.exceptions.DBusException as dbus_exception:
|
||||
err_str = "DBus call to Tuned daemon failed"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ ACTIVE_PROFILE_FILE = "/etc/tuned/active_profile"
|
|||
PROFILE_FILE = "tuned.conf"
|
||||
AUTODETECT_FILE = "recommend.conf"
|
||||
DAEMONIZE_PARENT_TIMEOUT = 5
|
||||
DBUS_BUS = "com.redhat.tuned"
|
||||
NAMESPACE = "com.redhat.tuned"
|
||||
DBUS_BUS = NAMESPACE
|
||||
DBUS_INTERFACE = "com.redhat.tuned.control"
|
||||
DBUS_OBJECT = "/Tuned"
|
||||
DEFAULT_PROFILE = "balanced"
|
||||
|
|
|
|||
|
|
@ -45,8 +45,15 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
def profile_changed(self, profile_name, result, errstr):
|
||||
pass
|
||||
|
||||
# exports decorator checks the authorization (currently through polkit), caller is None if
|
||||
# no authorization was performed (i.e. the call should process as authorized), string
|
||||
# identifying caller (with DBus it's the caller bus name) if authorized and empty
|
||||
# string if not authorized, caller must be the last argument
|
||||
|
||||
@exports.export("", "b")
|
||||
def start(self):
|
||||
def start(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
if self._global_config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON):
|
||||
if self._daemon.is_running():
|
||||
return True
|
||||
|
|
@ -55,21 +62,27 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
return self._daemon.start()
|
||||
|
||||
@exports.export("", "b")
|
||||
def stop(self):
|
||||
def stop(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
if not self._daemon.is_running():
|
||||
return True
|
||||
else:
|
||||
return self._daemon.stop()
|
||||
|
||||
@exports.export("", "b")
|
||||
def reload(self):
|
||||
def reload(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
if not self._daemon.is_running():
|
||||
return False
|
||||
else:
|
||||
return self.stop() and self.start()
|
||||
|
||||
@exports.export("s", "(bs)")
|
||||
def switch_profile(self, profile_name):
|
||||
def switch_profile(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
was_running = self._daemon.is_running()
|
||||
msg = "OK"
|
||||
success = True
|
||||
|
|
@ -88,14 +101,18 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
return (success, msg)
|
||||
|
||||
@exports.export("", "s")
|
||||
def active_profile(self):
|
||||
def active_profile(self, caller = None):
|
||||
if caller == "":
|
||||
return ""
|
||||
if self._daemon.profile is not None:
|
||||
return self._daemon.profile.name
|
||||
else:
|
||||
return ""
|
||||
|
||||
@exports.export("", "b")
|
||||
def disable(self):
|
||||
def disable(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
if self._daemon.is_running():
|
||||
self._daemon.stop()
|
||||
if self._daemon.is_enabled():
|
||||
|
|
@ -103,31 +120,45 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
return True
|
||||
|
||||
@exports.export("", "b")
|
||||
def is_running(self):
|
||||
def is_running(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
return self._daemon.is_running()
|
||||
|
||||
@exports.export("", "as")
|
||||
def profiles(self):
|
||||
def profiles(self, caller = None):
|
||||
if caller == "":
|
||||
return []
|
||||
return self._daemon.profile_loader.profile_locator.get_known_names()
|
||||
|
||||
@exports.export("", "a(ss)")
|
||||
def profiles2(self):
|
||||
def profiles2(self, caller = None):
|
||||
if caller == "":
|
||||
return []
|
||||
return self._daemon.profile_loader.profile_locator.get_known_names_summary()
|
||||
|
||||
@exports.export("s", "(bsss)")
|
||||
def profile_info(self, profile_name):
|
||||
def profile_info(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return tuple(False, "", "", "")
|
||||
if profile_name is None or profile_name == "":
|
||||
profile_name = self.active_profile()
|
||||
return tuple(self._daemon.profile_loader.profile_locator.get_profile_attrs(profile_name, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], [""]))
|
||||
|
||||
@exports.export("", "s")
|
||||
def recommend_profile(self):
|
||||
def recommend_profile(self, caller = None):
|
||||
if caller == "":
|
||||
return ""
|
||||
return self._cmd.recommend_profile(hardcoded = not self._global_config.get_bool(consts.CFG_RECOMMEND_COMMAND, consts.CFG_DEF_RECOMMEND_COMMAND))
|
||||
|
||||
@exports.export("", "b")
|
||||
def verify_profile(self):
|
||||
def verify_profile(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
return self._daemon.verify_profile(ignore_missing = False)
|
||||
|
||||
@exports.export("", "b")
|
||||
def verify_profile_ignore_missing(self):
|
||||
def verify_profile_ignore_missing(self, caller = None):
|
||||
if caller == "":
|
||||
return False
|
||||
return self._daemon.verify_profile(ignore_missing = True)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@ import interfaces
|
|||
import decorator
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
import dbus.exceptions
|
||||
import inspect
|
||||
import threading
|
||||
import signal
|
||||
import tuned.logs
|
||||
import tuned.consts as consts
|
||||
from tuned.utils.polkit import polkit
|
||||
from gi.repository import GObject as gobject
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
class DBusExporter(interfaces.ExporterInterface):
|
||||
"""
|
||||
Export method calls through DBus Interface.
|
||||
|
|
@ -19,6 +25,7 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
|
||||
def __init__(self, bus_name, interface_name, object_name):
|
||||
gobject.threads_init()
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
|
||||
self._dbus_object_cls = None
|
||||
self._dbus_object = None
|
||||
|
|
@ -30,6 +37,7 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
self._object_name = object_name
|
||||
self._thread = None
|
||||
self._bus_object = None
|
||||
self._polkit = polkit()
|
||||
|
||||
# dirty hack that fixes KeyboardInterrupt handling
|
||||
# the hack is needed because PyGObject / GTK+-3 developers are morons
|
||||
|
|
@ -58,10 +66,22 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
raise Exception("Method with this name is already exported.")
|
||||
|
||||
def wrapper(wrapped, owner, *args, **kwargs):
|
||||
action_id = consts.NAMESPACE + "." + method.__name__
|
||||
caller = args[-1]
|
||||
log.debug("checking authorization for for action '%s' requested by caller '%s'" % (action_id, caller))
|
||||
try:
|
||||
if self._polkit.check_authorization(caller, action_id):
|
||||
log.debug("action '%s' requested by caller '%s' was successfully authorized by polkit" % (action_id, caller))
|
||||
else:
|
||||
log.info("action '%s' requested by caller '%s' wasn't authorized by polkit, ignoring the request" % (action_id, caller))
|
||||
args[-1] = ""
|
||||
except (dbus.exceptions.DBusException, ValueError) as e:
|
||||
log.error("unable to query polkit to authorize action '%s' requested by caller '%s': %s, ignoring the request" % (action_id, caller, e))
|
||||
args[-1] = ""
|
||||
return method(*args, **kwargs)
|
||||
|
||||
wrapper = decorator.decorator(wrapper, method.im_func)
|
||||
wrapper = dbus.service.method(self._interface_name, in_signature, out_signature)(wrapper)
|
||||
wrapper = dbus.service.method(self._interface_name, in_signature, out_signature, sender_keyword = "caller")(wrapper)
|
||||
|
||||
self._dbus_methods[method_name] = wrapper
|
||||
|
||||
|
|
@ -119,8 +139,6 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
self._thread = None
|
||||
|
||||
def _thread_code(self):
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
|
||||
bus = dbus.SystemBus()
|
||||
bus_name = dbus.service.BusName(self._bus_name, bus)
|
||||
self._bus_object = self._dbus_object_cls(bus, self._object_name, bus_name)
|
||||
|
|
@ -128,4 +146,3 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
self._main_loop.run()
|
||||
del self._bus_object
|
||||
self._bus_object = None
|
||||
|
||||
|
|
|
|||
16
tuned/utils/polkit.py
Normal file
16
tuned/utils/polkit.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import dbus
|
||||
|
||||
class polkit():
|
||||
def __init__(self):
|
||||
bus = dbus.SystemBus()
|
||||
proxy = bus.get_object('org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority')
|
||||
self._authority = dbus.Interface(proxy, dbus_interface='org.freedesktop.PolicyKit1.Authority')
|
||||
|
||||
def check_authorization(self, sender, action_id):
|
||||
if sender is None or action_id is None:
|
||||
return False
|
||||
details = {}
|
||||
flags = 1 # AllowUserInteraction flag
|
||||
cancellation_id = '' # No cancellation id
|
||||
subject = ('system-bus-name', {'name' : sender})
|
||||
return self._authority.CheckAuthorization(subject, action_id, details, flags, cancellation_id)[0]
|
||||
Loading…
Reference in a new issue