Add polkit support

This allows non-root users access to the rpm-ostree daemon, which is
a pre-requirement for gnome-software rpm-ostree support.

Closes: #745

Closes: #825
Approved by: cgwalters
This commit is contained in:
Kalev Lember 2017-06-09 12:52:01 +02:00 committed by Atomic Bot
parent 5803fbc807
commit 84f6bcb4f0
6 changed files with 292 additions and 3 deletions

View File

@ -105,8 +105,12 @@ servicedir = $(dbusservicedir)
%.service: %.service.in Makefile %.service: %.service.in Makefile
$(SED_SUBST) $@.in > $@.tmp && mv $@.tmp $@ $(SED_SUBST) $@.in > $@.tmp && mv $@.tmp $@
polkit_policy_DATA = $(srcdir)/src/daemon/org.projectatomic.rpmostree1.policy
polkit_policydir = $(datadir)/polkit-1/actions
EXTRA_DIST += \ EXTRA_DIST += \
$(dbusservice_DATA) \ $(dbusservice_DATA) \
$(polkit_policy_DATA) \
$(service_in_files) \ $(service_in_files) \
$(systemdunit_in_files) \ $(systemdunit_in_files) \
$(NULL) $(NULL)

View File

@ -88,6 +88,7 @@ PKG_CHECK_MODULES(PKGDEP_GIO_UNIX, [gio-unix-2.0])
PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.40.0 json-glib-1.0 PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.40.0 json-glib-1.0
ostree-1 >= 2017.4 ostree-1 >= 2017.4
libsystemd libsystemd
polkit-gobject-1
rpm librepo rpm librepo
libarchive]) libarchive])
dnl bundled libdnf dnl bundled libdnf

View File

@ -21,6 +21,7 @@ BuildRequires: pkgconfig(json-glib-1.0)
BuildRequires: pkgconfig(rpm) BuildRequires: pkgconfig(rpm)
BuildRequires: pkgconfig(libarchive) BuildRequires: pkgconfig(libarchive)
BuildRequires: pkgconfig(libsystemd) BuildRequires: pkgconfig(libsystemd)
BuildRequires: pkgconfig(polkit-gobject-1)
BuildRequires: libcap-devel BuildRequires: libcap-devel
BuildRequires: libattr-devel BuildRequires: libattr-devel
@ -115,7 +116,8 @@ python autofiles.py > files \
'%{_sysconfdir}/dbus-1/system.d/*' \ '%{_sysconfdir}/dbus-1/system.d/*' \
'%{_prefix}/lib/systemd/system/*' \ '%{_prefix}/lib/systemd/system/*' \
'%{_libexecdir}/rpm-ostree*' \ '%{_libexecdir}/rpm-ostree*' \
'%{_datadir}/dbus-1/system-services' '%{_datadir}/dbus-1/system-services' \
'%{_datadir}/polkit-1/actions/org.projectatomic.rpmostree1.policy'
python autofiles.py > files.devel \ python autofiles.py > files.devel \
'%{_libdir}/lib*.so' \ '%{_libdir}/lib*.so' \
'%{_includedir}/*' \ '%{_includedir}/*' \

View File

@ -10,12 +10,16 @@
<allow send_destination="org.projectatomic.rpmostree1"/> <allow send_destination="org.projectatomic.rpmostree1"/>
</policy> </policy>
<!-- Allow anyone to call into the service - we'll reject callers using PolicyKit -->
<policy context="default"> <policy context="default">
<deny send_destination="org.projectatomic.rpmostree1"/> <deny send_destination="org.projectatomic.rpmostree1"/>
<allow send_destination="org.projectatomic.rpmostree1" <allow send_destination="org.projectatomic.rpmostree1"
send_interface="org.freedesktop.DBus.Introspectable"/> send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.projectatomic.rpmostree1"
send_interface="org.freedesktop.DBus.ObjectManager"/>
<allow send_destination="org.projectatomic.rpmostree1" <allow send_destination="org.projectatomic.rpmostree1"
send_interface="org.freedesktop.DBus.Peer"/> send_interface="org.freedesktop.DBus.Peer"/>
@ -27,6 +31,9 @@
send_interface="org.freedesktop.DBus.Properties" send_interface="org.freedesktop.DBus.Properties"
send_member="GetAll"/> send_member="GetAll"/>
<allow send_destination="org.projectatomic.rpmostree1"
send_interface="org.projectatomic.rpmostree1.OS"/>
<allow send_destination="org.projectatomic.rpmostree1" <allow send_destination="org.projectatomic.rpmostree1"
send_interface="org.projectatomic.rpmostree1.Sysroot" send_interface="org.projectatomic.rpmostree1.Sysroot"
send_member="GetOS"/> send_member="GetOS"/>

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<vendor>Project Atomic</vendor>
<vendor_url>https://www.projectatomic.io/</vendor_url>
<icon_name>package-x-generic</icon_name>
<action id="org.projectatomic.rpmostree1.install-uninstall-packages">
<description>Install and remove packages</description>
<message>Authentication is required to install and remove software</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.install-local-packages">
<description>Install local packages</description>
<message>Authentication is required to install software</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.override">
<description>Override packages</description>
<message>Authentication is required to override base OS software</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.deploy">
<description>Update base OS</description>
<message>Authentication is required to update software</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.upgrade">
<description>Update base OS</description>
<message>Authentication is required to update software</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.rebase">
<description>Switch to a different base OS</description>
<message>Authentication is required to switch to a different base OS</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.rollback">
<description>Rollback OS updates</description>
<message>Authentication is required to roll back software updates</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.bootconfig">
<description>Change boot configuration</description>
<message>Authentication is required to change boot configuration</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.cleanup">
<description>Clear cache</description>
<message>Authentication is required to clear cache / pending data</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.repo-refresh">
<description>Refresh repository metadata</description>
<message>Authentication is required to check available updates</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>

View File

@ -19,7 +19,9 @@
#include "config.h" #include "config.h"
#include "ostree.h" #include "ostree.h"
#include <err.h>
#include <libglnx.h> #include <libglnx.h>
#include <polkit/polkit.h>
#include "rpmostreed-sysroot.h" #include "rpmostreed-sysroot.h"
#include "rpmostreed-daemon.h" #include "rpmostreed-daemon.h"
@ -40,6 +42,7 @@ typedef struct _RpmostreedOSClass RpmostreedOSClass;
struct _RpmostreedOS struct _RpmostreedOS
{ {
RPMOSTreeOSSkeleton parent_instance; RPMOSTreeOSSkeleton parent_instance;
PolkitAuthority *authority;
RpmostreedTransactionMonitor *transaction_monitor; RpmostreedTransactionMonitor *transaction_monitor;
guint signal_id; guint signal_id;
}; };
@ -53,6 +56,10 @@ static void rpmostreed_os_iface_init (RPMOSTreeOSIface *iface);
static gboolean rpmostreed_os_load_internals (RpmostreedOS *self, GError **error); static gboolean rpmostreed_os_load_internals (RpmostreedOS *self, GError **error);
static inline void *vardict_lookup_ptr (GVariantDict *dict, const char *key, const char *fmt);
static gboolean vardict_lookup_bool (GVariantDict *dict, const char *key, gboolean dfault);
G_DEFINE_TYPE_WITH_CODE (RpmostreedOS, G_DEFINE_TYPE_WITH_CODE (RpmostreedOS,
rpmostreed_os, rpmostreed_os,
RPMOSTREE_TYPE_OS_SKELETON, RPMOSTREE_TYPE_OS_SKELETON,
@ -77,6 +84,146 @@ sysroot_changed (RpmostreedSysroot *sysroot,
g_warning ("%s", local_error->message); g_warning ("%s", local_error->message);
} }
static gboolean
os_authorize_method (GDBusInterfaceSkeleton *interface,
GDBusMethodInvocation *invocation)
{
RpmostreedOS *self = RPMOSTREED_OS (interface);
const gchar *method_name = g_dbus_method_invocation_get_method_name (invocation);
const gchar *sender = g_dbus_method_invocation_get_sender (invocation);
GVariant *parameters = g_dbus_method_invocation_get_parameters (invocation);
g_autoptr(GPtrArray) actions = g_ptr_array_new ();
gboolean authorized = FALSE;
if (g_strcmp0 (method_name, "GetDeploymentsRpmDiff") == 0 ||
g_strcmp0 (method_name, "GetCachedDeployRpmDiff") == 0 ||
g_strcmp0 (method_name, "DownloadDeployRpmDiff") == 0 ||
g_strcmp0 (method_name, "GetCachedUpdateRpmDiff") == 0 ||
g_strcmp0 (method_name, "DownloadUpdateRpmDiff") == 0 ||
g_strcmp0 (method_name, "GetCachedRebaseRpmDiff") == 0 ||
g_strcmp0 (method_name, "DownloadRebaseRpmDiff") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.repo-refresh");
}
else if (g_strcmp0 (method_name, "Deploy") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.deploy");
}
else if (g_strcmp0 (method_name, "Upgrade") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.upgrade");
}
else if (g_strcmp0 (method_name, "Rebase") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.rebase");
}
else if (g_strcmp0 (method_name, "SetInitramfsState") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.bootconfig");
}
else if (g_strcmp0 (method_name, "Cleanup") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.cleanup");
}
else if (g_strcmp0 (method_name, "Rollback") == 0 ||
g_strcmp0 (method_name, "ClearRollbackTarget") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.rollback");
}
else if (g_strcmp0 (method_name, "PkgChange") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.install-uninstall-packages");
}
else if (g_strcmp0 (method_name, "UpdateDeployment") == 0)
{
g_autoptr(GVariant) modifiers = g_variant_get_child_value (parameters, 0);
g_autoptr(GVariant) options = g_variant_get_child_value (parameters, 1);
g_auto(GVariantDict) modifiers_dict;
g_auto(GVariantDict) options_dict;
g_variant_dict_init (&modifiers_dict, modifiers);
g_variant_dict_init (&options_dict, options);
const char *refspec =
vardict_lookup_ptr (&modifiers_dict, "set-refspec", "&s");
const char *revision =
vardict_lookup_ptr (&modifiers_dict, "set-revision", "&s");
g_autofree char **install_pkgs =
vardict_lookup_ptr (&modifiers_dict, "install-packages", "^a&s");
g_autofree char **uninstall_pkgs =
vardict_lookup_ptr (&modifiers_dict, "uninstall-packages", "^a&s");
g_autofree const char *const *override_replace_pkgs =
vardict_lookup_ptr (&modifiers_dict, "override-replace-packages", "^a&s");
g_autofree const char *const *override_remove_pkgs =
vardict_lookup_ptr (&modifiers_dict, "override-remove-packages", "^a&s");
g_autofree const char *const *override_reset_pkgs =
vardict_lookup_ptr (&modifiers_dict, "override-reset-packages", "^a&s");
g_autoptr(GVariant) install_local_pkgs =
g_variant_dict_lookup_value (&modifiers_dict, "install-local-packages",
G_VARIANT_TYPE("ah"));
g_autoptr(GVariant) override_replace_local_pkgs =
g_variant_dict_lookup_value (&modifiers_dict, "override-replace-local-packages",
G_VARIANT_TYPE("ah"));
gboolean no_pull_base =
vardict_lookup_bool (&options_dict, "no-pull-base", FALSE);
gboolean no_overrides =
vardict_lookup_bool (&options_dict, "no-overrides", FALSE);
if (refspec != NULL)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.rebase");
else if (revision != NULL)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.deploy");
else if (!no_pull_base)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.upgrade");
if (install_pkgs != NULL || uninstall_pkgs != NULL)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.install-uninstall-packages");
if (install_local_pkgs != NULL && g_variant_n_children (install_local_pkgs) > 0)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.install-local-packages");
if (override_replace_pkgs != NULL || override_remove_pkgs != NULL || override_reset_pkgs != NULL ||
(override_replace_local_pkgs != NULL && g_variant_n_children (override_replace_local_pkgs) > 0) ||
no_overrides)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.override");
}
else
{
authorized = FALSE;
}
for (guint i = 0; i < actions->len; i++)
{
const gchar *action = g_ptr_array_index (actions, i);
glnx_unref_object PolkitSubject *subject = polkit_system_bus_name_new (sender);
glnx_unref_object PolkitAuthorizationResult *result = NULL;
g_autoptr(GError) error = NULL;
result = polkit_authority_check_authorization_sync (self->authority, subject,
action, NULL,
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
NULL, &error);
if (result == NULL)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"Authorization error: %s", error->message);
return FALSE;
}
authorized = polkit_authorization_result_get_is_authorized (result);
if (!authorized)
break;
}
if (!authorized)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"rpmostreed OS operation %s not allowed for user", method_name);
}
return authorized;
}
static void static void
os_dispose (GObject *object) os_dispose (GObject *object)
{ {
@ -90,6 +237,7 @@ os_dispose (GObject *object)
object_path, object); object_path, object);
} }
g_clear_object (&self->authority);
g_clear_object (&self->transaction_monitor); g_clear_object (&self->transaction_monitor);
if (self->signal_id > 0) if (self->signal_id > 0)
@ -104,9 +252,13 @@ static void
os_constructed (GObject *object) os_constructed (GObject *object)
{ {
RpmostreedOS *self = RPMOSTREED_OS (object); RpmostreedOS *self = RPMOSTREED_OS (object);
g_autoptr(GError) error = NULL;
/* TODO Integrate with PolicyKit via the "g-authorize-method" signal. */ self->authority = polkit_authority_get_sync (NULL, &error);
if (self->authority == NULL)
{
errx (1, "Can't get polkit authority: %s", error->message);
}
self->signal_id = g_signal_connect (rpmostreed_sysroot_get (), self->signal_id = g_signal_connect (rpmostreed_sysroot_get (),
"updated", "updated",
G_CALLBACK (sysroot_changed), self); G_CALLBACK (sysroot_changed), self);
@ -117,11 +269,14 @@ static void
rpmostreed_os_class_init (RpmostreedOSClass *klass) rpmostreed_os_class_init (RpmostreedOSClass *klass)
{ {
GObjectClass *gobject_class; GObjectClass *gobject_class;
GDBusInterfaceSkeletonClass *gdbus_interface_skeleton_class;
gobject_class = G_OBJECT_CLASS (klass); gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = os_dispose; gobject_class->dispose = os_dispose;
gobject_class->constructed = os_constructed; gobject_class->constructed = os_constructed;
gdbus_interface_skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass);
gdbus_interface_skeleton_class->g_authorize_method = os_authorize_method;
} }
static void static void