diff --git a/Makefile b/Makefile
index 60c6126b..a0dc2837 100644
--- a/Makefile
+++ b/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
diff --git a/com.redhat.tuned.policy b/com.redhat.tuned.policy
new file mode 100644
index 00000000..0ada6110
--- /dev/null
+++ b/com.redhat.tuned.policy
@@ -0,0 +1,129 @@
+
+
+
+
+ Tuned
+ https://fedorahosted.org/tuned/
+ tuned
+
+
+ Show active profile
+ Authentication is required to show active profile
+
+ yes
+ yes
+ yes
+
+
+
+
+ Disable Tuned
+ Authentication is required to disable Tuned
+
+ auth_admin
+ auth_admin
+ yes
+
+
+
+
+ Check whether Tuned is running
+ Authentication is required to check whether Tuned is running
+
+ yes
+ yes
+ yes
+
+
+
+
+ Show information about Tuned profile
+ Authentication is required to show information about Tuned profile
+
+ yes
+ yes
+ yes
+
+
+
+
+ List Tuned profiles
+ Authentication is required to list Tuned profiles
+
+ yes
+ yes
+ yes
+
+
+
+
+ List Tuned profiles
+ Authentication is required to list Tuned profiles
+
+ yes
+ yes
+ yes
+
+
+
+
+ Show Tuned profile name which is recommended for your system
+ Authentication is required to show recommended profile name
+
+ yes
+ yes
+ yes
+
+
+
+
+ Reload Tuned configuration
+ Authentication is required to reload Tuned configuration
+
+ auth_admin
+ auth_admin
+ yes
+
+
+
+
+ Start Tuned daemon
+ Authentication is required to start Tuned daemon
+
+ auth_admin
+ auth_admin
+ yes
+
+
+
+
+ Stop Tuned daemon
+ Authentication is required to stop Tuned daemon
+
+ auth_admin
+ auth_admin
+ yes
+
+
+
+
+ Switch Tuned profile
+ Authentication is required to switch Tuned profile
+
+ auth_admin
+ auth_admin
+ yes
+
+
+
+
+ Verify Tuned profile
+ Authentication is required to verify Tuned profile
+
+ yes
+ yes
+ yes
+
+
+
diff --git a/dbus.conf b/dbus.conf
index 120778a8..c3b42779 100644
--- a/dbus.conf
+++ b/dbus.conf
@@ -4,18 +4,13 @@
-
-
+
-
-
-
-
diff --git a/tuned.spec b/tuned.spec
index 76f5c099..321028c7 100644
--- a/tuned.spec
+++ b/tuned.spec
@@ -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
diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py
index 3a774526..f4f4426a 100644
--- a/tuned/admin/admin.py
+++ b/tuned/admin/admin.py
@@ -24,7 +24,7 @@ def __init__(self, dbus = True, debug = False, async = False, timeout = consts.A
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:
diff --git a/tuned/admin/dbus_controller.py b/tuned/admin/dbus_controller.py
index c7305200..1b11b189 100644
--- a/tuned/admin/dbus_controller.py
+++ b/tuned/admin/dbus_controller.py
@@ -13,6 +13,7 @@ def __init__(self, bus_name, interface_name, object_name, debug = False):
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 @@ def _init_proxy(self):
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 @@ def _call(self, method_name, *args, **kwargs):
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"
diff --git a/tuned/consts.py b/tuned/consts.py
index 482702c7..e3658e9d 100644
--- a/tuned/consts.py
+++ b/tuned/consts.py
@@ -3,7 +3,8 @@
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"
diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py
index 75b31b0b..346ca3a1 100644
--- a/tuned/daemon/controller.py
+++ b/tuned/daemon/controller.py
@@ -45,8 +45,15 @@ def terminate(self):
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 @@ def start(self):
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 @@ def switch_profile(self, profile_name):
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 @@ def disable(self):
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)
diff --git a/tuned/exports/dbus_exporter.py b/tuned/exports/dbus_exporter.py
index 6338bc4e..616729cd 100644
--- a/tuned/exports/dbus_exporter.py
+++ b/tuned/exports/dbus_exporter.py
@@ -2,11 +2,17 @@
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 @@ def __init__(self, bus_name, interface_name, object_name):
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 @@ def export(self, method, in_signature, out_signature):
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 @@ def stop(self):
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 @@ def _thread_code(self):
self._main_loop.run()
del self._bus_object
self._bus_object = None
-
diff --git a/tuned/utils/polkit.py b/tuned/utils/polkit.py
new file mode 100644
index 00000000..319b5538
--- /dev/null
+++ b/tuned/utils/polkit.py
@@ -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]