Commit 68bb447a authored by Jaroslav Škarvada's avatar Jaroslav Škarvada

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: default avatarJaroslav Škarvada <jskarvad@redhat.com>
parent e86e1be0
......@@ -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
......
<?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
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]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment