From 1b854c76c76b5cb496243653f311bd87ca1e745f Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 22 Aug 2013 14:27:19 +0100 Subject: [PATCH] Add common API for doing polkit authentication There are now two places in libvirt which use polkit. Currently they use pkexec, which is set to be replaced by direct DBus API calls. Add a common API which they will both be able to use for this purpose. No tests are added at this time, since the impl will be gutted in favour of a DBus API call shortly. Signed-off-by: Daniel P. Berrange --- include/libvirt/virterror.h | 2 + po/POTFILES.in | 1 + src/Makefile.am | 7 +- src/libvirt_private.syms | 4 + src/util/virerror.c | 2 + src/util/virpolkit.c | 263 ++++++++++++++++++++++++++++++++++++ src/util/virpolkit.h | 34 +++++ 7 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 src/util/virpolkit.c create mode 100644 src/util/virpolkit.h diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 15ba4f1792..85dd74cbbe 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -124,6 +124,8 @@ typedef enum { VIR_FROM_CRYPTO = 58, /* Error from crypto code */ VIR_FROM_FIREWALL = 59, /* Error from firewall */ + VIR_FROM_POLKIT = 60, /* Error from polkit code */ + # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST # endif diff --git a/po/POTFILES.in b/po/POTFILES.in index f17b35fcaa..1a0b75ed9f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -194,6 +194,7 @@ src/util/virnuma.c src/util/virobject.c src/util/virpci.c src/util/virpidfile.c +src/util/virpolkit.c src/util/virportallocator.c src/util/virprocess.c src/util/virrandom.c diff --git a/src/Makefile.am b/src/Makefile.am index fa741a809d..3afae7b9d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -141,6 +141,7 @@ UTIL_SOURCES = \ util/virobject.c util/virobject.h \ util/virpci.c util/virpci.h \ util/virpidfile.c util/virpidfile.h \ + util/virpolkit.c util/virpolkit.h \ util/virportallocator.c util/virportallocator.h \ util/virprobe.h \ util/virprocess.c util/virprocess.h \ @@ -988,11 +989,13 @@ libvirt_util_la_SOURCES = \ libvirt_util_la_CFLAGS = $(CAPNG_CFLAGS) $(YAJL_CFLAGS) $(LIBNL_CFLAGS) \ $(AM_CFLAGS) $(AUDIT_CFLAGS) $(DEVMAPPER_CFLAGS) \ $(DBUS_CFLAGS) $(LDEXP_LIBM) $(NUMACTL_CFLAGS) \ - $(SYSTEMD_DAEMON_CFLAGS) -I$(top_srcdir)/src/conf + $(SYSTEMD_DAEMON_CFLAGS) $(POLKIT_CFLAGS) \ + -I$(top_srcdir)/src/conf libvirt_util_la_LIBADD = $(CAPNG_LIBS) $(YAJL_LIBS) $(LIBNL_LIBS) \ $(THREAD_LIBS) $(AUDIT_LIBS) $(DEVMAPPER_LIBS) \ $(LIB_CLOCK_GETTIME) $(DBUS_LIBS) $(MSCOM_LIBS) $(LIBXML_LIBS) \ - $(SECDRIVER_LIBS) $(NUMACTL_LIBS) $(SYSTEMD_DAEMON_LIBS) + $(SECDRIVER_LIBS) $(NUMACTL_LIBS) $(SYSTEMD_DAEMON_LIBS) \ + $(POLKIT_LIBS) noinst_LTLIBRARIES += libvirt_conf.la diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a339ced69d..02ac4e9352 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1794,6 +1794,10 @@ virPidFileWrite; virPidFileWritePath; +# util/virpolkit.h +virPolkitCheckAuth; + + # util/virportallocator.h virPortAllocatorAcquire; virPortAllocatorNew; diff --git a/src/util/virerror.c b/src/util/virerror.c index 6bd3d09cfd..4aa6d04088 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -130,6 +130,8 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Bhyve", "Crypto", "Firewall", + + "Polkit", /* 60 */ ) diff --git a/src/util/virpolkit.c b/src/util/virpolkit.c new file mode 100644 index 0000000000..620bfda25e --- /dev/null +++ b/src/util/virpolkit.c @@ -0,0 +1,263 @@ +/* + * virpolkit.c: helpers for using polkit APIs + * + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + */ + +#include + +#if WITH_POLKIT0 +# include +# include +#endif + +#include "virpolkit.h" +#include "vircommand.h" +#include "virerror.h" +#include "virlog.h" +#include "virstring.h" +#include "virprocess.h" +#include "viralloc.h" +#include "virdbus.h" + +#define VIR_FROM_THIS VIR_FROM_POLKIT + +VIR_LOG_INIT("util.polkit"); + +#if WITH_POLKIT1 +/* + * virPolkitCheckAuth: + * @actionid: permission to check + * @pid: client process ID + * @startTime: process start time, or 0 + * @uid: client process user ID + * @details: NULL terminated (key, value) pair list + * @allowInteraction: true if auth prompts are allowed + * + * Check if a client is authenticated with polkit + * + * Returns 0 on success, -1 on failure, -2 on auth denied + */ +int virPolkitCheckAuth(const char *actionid, + pid_t pid, + unsigned long long startTime, + uid_t uid, + const char **details, + bool allowInteraction) +{ + int status = -1; + bool authdismissed = 0; + bool supportsuid = 0; + char *pkout = NULL; + virCommandPtr cmd = NULL; + int ret = -1; + static bool polkitInsecureWarned = false; + + VIR_DEBUG("Checking PID %lld UID %d startTime %llu", + (long long)pid, (int)uid, startTime); + + if (startTime == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Start time is required for polkit auth")); + return -1; + } + + cmd = virCommandNewArgList(PKCHECK_PATH, "--action-id", actionid, NULL); + virCommandSetOutputBuffer(cmd, &pkout); + virCommandSetErrorBuffer(cmd, &pkout); + + virCommandAddArg(cmd, "--process"); +# ifdef PKCHECK_SUPPORTS_UID + supportsuid = 1; +# endif + if (supportsuid) { + virCommandAddArgFormat(cmd, "%lld,%llu,%lu", + (long long)pid, startTime, (unsigned long)uid); + } else { + if (!polkitInsecureWarned) { + VIR_WARN("No support for caller UID with pkcheck. This deployment is known to be insecure."); + polkitInsecureWarned = true; + } + virCommandAddArgFormat(cmd, "%lld,%llu", + (long long)pid, startTime); + } + if (allowInteraction) + virCommandAddArg(cmd, "--allow-user-interaction"); + + while (details && details[0] && details[1]) { + virCommandAddArgList(cmd, "--detail", details[0], details[1], NULL); + details += 2; + } + + if (virCommandRun(cmd, &status) < 0) + goto cleanup; + + authdismissed = (pkout && strstr(pkout, "dismissed=true")); + if (status != 0) { + char *tmp = virProcessTranslateStatus(status); + VIR_DEBUG("Policy kit denied action %s from pid %lld, uid %d: %s", + actionid, (long long)pid, (int)uid, NULLSTR(tmp)); + VIR_FREE(tmp); + ret = -2; + goto cleanup; + } + + VIR_DEBUG("Policy allowed action %s from pid %lld, uid %d", + actionid, (long long)pid, (int)uid); + + ret = 0; + + cleanup: + if (ret < 0) { + virResetLastError(); + + if (authdismissed) { + virReportError(VIR_ERR_AUTH_CANCELLED, "%s", + _("authentication cancelled by user")); + } else if (pkout && *pkout) { + virReportError(VIR_ERR_AUTH_FAILED, _("polkit: %s"), pkout); + } else { + virReportError(VIR_ERR_AUTH_FAILED, "%s", _("authentication failed")); + } + } + + virCommandFree(cmd); + VIR_FREE(pkout); + return ret; +} + + +#elif WITH_POLKIT0 +int virPolkitCheckAuth(const char *actionid, + pid_t pid, + unsigned long long startTime ATTRIBUTE_UNUSED, + uid_t uid, + const char **details, + bool allowInteraction ATTRIBUTE_UNUSED) +{ + PolKitCaller *pkcaller = NULL; + PolKitAction *pkaction = NULL; + PolKitContext *pkcontext = NULL; + PolKitError *pkerr = NULL; + PolKitResult pkresult; + DBusError err; + DBusConnection *sysbus; + int ret = -1; + + if (details) { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("Details not supported with polkit v0")); + return -1; + } + + if (!(sysbus = virDBusGetSystemBus())) + goto cleanup; + + VIR_INFO("Checking PID %lld running as %d", + (long long) pid, uid); + dbus_error_init(&err); + if (!(pkcaller = polkit_caller_new_from_pid(sysbus, + pid, &err))) { + VIR_DEBUG("Failed to lookup policy kit caller: %s", err.message); + dbus_error_free(&err); + goto cleanup; + } + + if (!(pkaction = polkit_action_new())) { + char ebuf[1024]; + VIR_DEBUG("Failed to create polkit action %s", + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + polkit_action_set_action_id(pkaction, actionid); + + if (!(pkcontext = polkit_context_new()) || + !polkit_context_init(pkcontext, &pkerr)) { + char ebuf[1024]; + VIR_DEBUG("Failed to create polkit context %s", + (pkerr ? polkit_error_get_error_message(pkerr) + : virStrerror(errno, ebuf, sizeof(ebuf)))); + if (pkerr) + polkit_error_free(pkerr); + dbus_error_free(&err); + goto cleanup; + } + +# if HAVE_POLKIT_CONTEXT_IS_CALLER_AUTHORIZED + pkresult = polkit_context_is_caller_authorized(pkcontext, + pkaction, + pkcaller, + 0, + &pkerr); + if (pkerr && polkit_error_is_set(pkerr)) { + VIR_DEBUG("Policy kit failed to check authorization %d %s", + polkit_error_get_error_code(pkerr), + polkit_error_get_error_message(pkerr)); + goto cleanup; + } +# else + pkresult = polkit_context_can_caller_do_action(pkcontext, + pkaction, + pkcaller); +# endif + if (pkresult != POLKIT_RESULT_YES) { + VIR_DEBUG("Policy kit denied action %s from pid %lld, uid %d, result: %s", + actionid, (long long) pid, uid, + polkit_result_to_string_representation(pkresult)); + ret = -2; + goto cleanup; + } + + VIR_DEBUG("Policy allowed action %s from pid %lld, uid %d", + actionid, (long long)pid, (int)uid); + + ret = 0; + + cleanup: + if (ret < 0) { + virResetLastError(); + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("authentication failed")); + } + if (pkcontext) + polkit_context_unref(pkcontext); + if (pkcaller) + polkit_caller_unref(pkcaller); + if (pkaction) + polkit_action_unref(pkaction); + return ret; +} + + +#else /* ! WITH_POLKIT1 && ! WITH_POLKIT0 */ + +int virPolkitCheckAuth(const char *actionid, + pid_t pid, + unsigned long long startTime, + uid_t uid, + const char **details, + bool allowInteraction) +{ + VIR_ERROR(_("Polkit auth attempted, even though polkit is not available")); + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("authentication failed")); + return -1; +} + + +#endif /* WITH_POLKIT1 */ diff --git a/src/util/virpolkit.h b/src/util/virpolkit.h new file mode 100644 index 0000000000..36122d04ed --- /dev/null +++ b/src/util/virpolkit.h @@ -0,0 +1,34 @@ +/* + * virpolkit.h: helpers for using polkit APIs + * + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + */ + +#ifndef __VIR_POLKIT_H__ +# define __VIR_POLKIT_H__ + +# include "internal.h" + +int virPolkitCheckAuth(const char *actionid, + pid_t pid, + unsigned long long startTime, + uid_t uid, + const char **details, + bool allowInteraction); + +#endif /* __VIR_POLKIT_H__ */