[libvirt PATCH v3 00/10] make internal only secrets work with split daemons

If you define a secret with private="yes", then libvirt won't let any client query the secret value after it is set. Only other libvirt drivers inside the daemon can query it by passing a special internal only flag to the virSecretGetValue API. The remote driver/daemon refuses to let this internal flag go over the wire preventing normal clients from using it This doesn't work with the split daemons because the virSecretGetValue API done by virqemud / virtstoraged has to go over the wire to reach the virsecretd. We need to come up with an alternative way to "prove" that the caller of virSecretGetValue is a libvirt daemon, as opposed to a general libvirt client. Note with if only traditional POSIX DAC permissions are in effect then we could consider it pointless trying to restrict access to clients running the same user/group as the libvirt daemon. We ought to take into account that the client might be confined by SELinux though, so the "private secret" concept isn't entirely pointless. Thus doing a simple uid of client == uid of daemon check is a bit too weak. The UID check might also not fly if the modular daemons are run inside containers with user namespaces, as the container for virtsecretd and virtqemud might have different user mappings in theory. This series adds a concept of a "token" which is known only to the libvirt daemons. The first daemon to use it writes a random hex string to /var/run/libvirt/common/system.token. Other daemons can read and compare this. Unless a MAC system is present this is still largely security theatre, but that's not really worse than the historical behaviour. When an API call is made the virIdentity by default reflects the identity of the UNIX process that initiated it. When connecting to virtproxyd, the client apps' identity is forwarded to the next virtNNNNd daemon. When libvirt drivers, however, initiate an API call we never set any identity. With monolithic libvirtd, they'd inherit the current client identity automagically since it was all in the same thread local. With modular daemons the othe driver would see the identity of the other libvirt daemon which is bad as this gives elevated privileges in the ACL check. Thus we fix the code which drivers use to open a connection to other daemons, such that it applies the current caller's identity. It does this using an "elevated" identity though, which means, we have added in the system token. Thus the virtsecretd daemon getting the call virSecretGetValue sees the virIdentity reflecting the client application which originally called the virDomainCreate() API, but with the system token set. Thus virsecretd can see that the virSecretGetValue was invoked by another daemon, not a libvirt client app. Changed in v3... Properly mock the new APIs in test suite Changed in v2... We can't set the elevated identity only when opening the virConnect for the secret driver. This works for modular daemons, as the identity is passed to the virsecretd at time of opening and thus applies to the later virSecretGetValue call on that connection. For monolithic daemon, the identity present at virConnectOpen is irrelevant. The virSecretGetValue call will just directly query the current thread's identity. IOW, to work in both deployment scenarios we need to have the elevated identity set across both virConnectOpen and virSecretGetValue Daniel P. Berrangé (10): util: add virRandomToken API util: introduce concept of a system token into identities util: generate a persistent system token util: set system token for system identity util: add API for copying identity objects util: helper to temporary elevate privileges of the current identity src: add API to determine if current identity is a system identity src: set identity when opening secondary drivers src: elevate current identity privilege when fetching secret secret: rework handling of private secrets src/driver-secret.h | 9 +- src/driver.c | 27 +++ src/libvirt-secret.c | 2 +- src/libvirt_private.syms | 8 + src/libxl/libxl_conf.c | 5 + src/qemu/qemu_domain.c | 11 +- src/qemu/qemu_tpm.c | 5 + src/remote/remote_driver.c | 8 +- src/secret/secret_driver.c | 34 ++- src/storage/storage_backend_iscsi.c | 5 + src/storage/storage_backend_iscsi_direct.c | 5 + src/storage/storage_backend_rbd.c | 5 + src/storage/storage_util.c | 5 + src/util/viridentity.c | 244 ++++++++++++++++++++- src/util/viridentity.h | 11 + src/util/viridentitypriv.h | 30 +++ src/util/virrandom.c | 18 ++ src/util/virrandom.h | 1 + src/util/virsecret.c | 3 +- tests/qemuxml2argvmock.c | 9 + tests/qemuxml2argvtest.c | 9 +- tests/viridentitytest.c | 11 +- 22 files changed, 435 insertions(+), 30 deletions(-) create mode 100644 src/util/viridentitypriv.h -- 2.31.1

A random token is simply a string of random bytes formatted in hexidecimal. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virrandom.c | 18 ++++++++++++++++++ src/util/virrandom.h | 1 + 3 files changed, 20 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 1b12c49018..23621fcfd0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3081,6 +3081,7 @@ virRandomBits; virRandomBytes; virRandomGenerateWWN; virRandomInt; +virRandomToken; # util/virresctrl.h diff --git a/src/util/virrandom.c b/src/util/virrandom.c index 3ae1297e6b..c3f3aa1fa6 100644 --- a/src/util/virrandom.c +++ b/src/util/virrandom.c @@ -161,3 +161,21 @@ virRandomGenerateWWN(char **wwn, (unsigned long long)virRandomBits(36)); return 0; } + +char *virRandomToken(size_t len) +{ + g_autofree unsigned char *data = g_new0(unsigned char, len); + g_autofree char *token = g_new0(char, (len * 2) + 1); + static const char hex[] = "0123456789abcdef"; + size_t i; + + if (virRandomBytes(data, len) < 0) + return NULL; + + for (i = 0; i < len; i++) { + token[(i*2)] = hex[data[i] & 0xf]; + token[(i*2)+1] = hex[(data[i] >> 4) & 0xf]; + } + + return g_steal_pointer(&token); +} diff --git a/src/util/virrandom.h b/src/util/virrandom.h index 297721f912..aac684ada9 100644 --- a/src/util/virrandom.h +++ b/src/util/virrandom.h @@ -26,3 +26,4 @@ uint32_t virRandomInt(uint32_t max); int virRandomBytes(unsigned char *buf, size_t buflen) ATTRIBUTE_NONNULL(1) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NO_INLINE; int virRandomGenerateWWN(char **wwn, const char *virt_type) G_GNUC_NO_INLINE; +char *virRandomToken(size_t len); -- 2.31.1

We want a way to distinguish between calls from a libvirt daemon, and a regular client application when both are running as the same user account. This is not possible with the current set of attributes recorded against an identity, as there is nothing that is common to all of the modular libvirt daemons, while distinct to all other processes. We thus introduce the idea of a system token, which is simply a random hex string that is only known by the libvirt daemons, to be recorded against the system identity. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 2 ++ src/util/viridentity.c | 34 ++++++++++++++++++++++++++++++++++ src/util/viridentity.h | 4 ++++ 3 files changed, 40 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 23621fcfd0..aaae1c8002 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2404,6 +2404,7 @@ virIdentityGetProcessTime; virIdentityGetSASLUserName; virIdentityGetSELinuxContext; virIdentityGetSystem; +virIdentityGetSystemToken; virIdentityGetUNIXGroupID; virIdentityGetUNIXUserID; virIdentityGetUserName; @@ -2416,6 +2417,7 @@ virIdentitySetProcessID; virIdentitySetProcessTime; virIdentitySetSASLUserName; virIdentitySetSELinuxContext; +virIdentitySetSystemToken; virIdentitySetUNIXGroupID; virIdentitySetUNIXUserID; virIdentitySetUserName; diff --git a/src/util/viridentity.c b/src/util/viridentity.c index 7edb6a171a..7da4ea12f5 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -40,6 +40,8 @@ #define VIR_FROM_THIS VIR_FROM_IDENTITY +#define VIR_CONNECT_IDENTITY_SYSTEM_TOKEN "system.token" + VIR_LOG_INIT("util.identity"); struct _virIdentity { @@ -382,6 +384,17 @@ int virIdentityGetSELinuxContext(virIdentity *ident, } +int virIdentityGetSystemToken(virIdentity *ident, + const char **token) +{ + *token = NULL; + return virTypedParamsGetString(ident->params, + ident->nparams, + VIR_CONNECT_IDENTITY_SYSTEM_TOKEN, + token); +} + + int virIdentitySetUserName(virIdentity *ident, const char *username) { @@ -554,6 +567,25 @@ int virIdentitySetSELinuxContext(virIdentity *ident, } +int virIdentitySetSystemToken(virIdentity *ident, + const char *token) +{ + if (virTypedParamsGet(ident->params, + ident->nparams, + VIR_CONNECT_IDENTITY_SYSTEM_TOKEN)) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("Identity attribute is already set")); + return -1; + } + + return virTypedParamsAddString(&ident->params, + &ident->nparams, + &ident->maxparams, + VIR_CONNECT_IDENTITY_SYSTEM_TOKEN, + token); +} + + int virIdentitySetParameters(virIdentity *ident, virTypedParameterPtr params, int nparams) @@ -577,6 +609,8 @@ int virIdentitySetParameters(virIdentity *ident, VIR_TYPED_PARAM_STRING, VIR_CONNECT_IDENTITY_SELINUX_CONTEXT, VIR_TYPED_PARAM_STRING, + VIR_CONNECT_IDENTITY_SYSTEM_TOKEN, + VIR_TYPED_PARAM_STRING, NULL) < 0) return -1; diff --git a/src/util/viridentity.h b/src/util/viridentity.h index fa3f46788c..640a7ba2e4 100644 --- a/src/util/viridentity.h +++ b/src/util/viridentity.h @@ -52,6 +52,8 @@ int virIdentityGetX509DName(virIdentity *ident, const char **dname); int virIdentityGetSELinuxContext(virIdentity *ident, const char **context); +int virIdentityGetSystemToken(virIdentity *ident, + const char **token); int virIdentitySetUserName(virIdentity *ident, @@ -72,6 +74,8 @@ int virIdentitySetX509DName(virIdentity *ident, const char *dname); int virIdentitySetSELinuxContext(virIdentity *ident, const char *context); +int virIdentitySetSystemToken(virIdentity *ident, + const char *token); int virIdentitySetParameters(virIdentity *ident, virTypedParameterPtr params, -- 2.31.1

When creating the system identity set the system token. The system token is currently stored in a local path /var/run/libvirt/common/system.token Obviously with only traditional UNIX DAC in effect, this is largely security through obscurity, if the client is running at the same privilege level as the daemon. It does, however, reliably distinguish an unprivileged client from the system daemons. With a MAC system like SELinux though, or possible use of containers, access can be further restricted. A possible future improvement for Linux would be to populate the kernel keyring with a secret for libvirt daemons to share. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 1 + src/util/viridentity.c | 107 ++++++++++++++++++++++++++++++++++++- src/util/viridentitypriv.h | 30 +++++++++++ tests/viridentitytest.c | 11 +++- 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/util/viridentitypriv.h diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index aaae1c8002..9c3c473c1c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2396,6 +2396,7 @@ virHostGetBootTime; # util/viridentity.h +virIdentityEnsureSystemToken; virIdentityGetCurrent; virIdentityGetGroupName; virIdentityGetParameters; diff --git a/src/util/viridentity.c b/src/util/viridentity.c index 7da4ea12f5..5174f5a2d3 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -22,21 +22,27 @@ #include <config.h> #include <unistd.h> +#include <fcntl.h> #if WITH_SELINUX # include <selinux/selinux.h> #endif +#define LIBVIRT_VIRIDENTITYPRIV_H_ALLOW + #include "internal.h" #include "viralloc.h" #include "virerror.h" -#include "viridentity.h" +#include "viridentitypriv.h" #include "virlog.h" #include "virobject.h" +#include "virrandom.h" #include "virthread.h" #include "virutil.h" #include "virstring.h" #include "virprocess.h" #include "virtypedparam.h" +#include "virfile.h" +#include "configmake.h" #define VIR_FROM_THIS VIR_FROM_IDENTITY @@ -55,6 +61,7 @@ struct _virIdentity { G_DEFINE_TYPE(virIdentity, vir_identity, G_TYPE_OBJECT) static virThreadLocal virIdentityCurrent; +static char *systemToken; static void virIdentityFinalize(GObject *obj); @@ -73,6 +80,9 @@ static int virIdentityOnceInit(void) return -1; } + if (!(systemToken = virIdentityEnsureSystemToken())) + return -1; + return 0; } @@ -144,6 +154,101 @@ int virIdentitySetCurrent(virIdentity *ident) } +#define TOKEN_BYTES 16 +#define TOKEN_STRLEN (TOKEN_BYTES * 2) + +static char * +virIdentityConstructSystemTokenPath(void) +{ + g_autofree char *commondir = NULL; + if (geteuid() == 0) { + commondir = g_strdup(RUNSTATEDIR "/libvirt/common"); + } else { + g_autofree char *rundir = virGetUserRuntimeDirectory(); + commondir = g_strdup_printf("%s/common", rundir); + } + + if (g_mkdir_with_parents(commondir, 0700) < 0) { + virReportSystemError(errno, + _("Cannot create daemon common directory '%s'"), + commondir); + return NULL; + } + + return g_strdup_printf("%s/system.token", commondir); +} + + +char * +virIdentityEnsureSystemToken(void) +{ + g_autofree char *tokenfile = virIdentityConstructSystemTokenPath(); + g_autofree char *token = NULL; + VIR_AUTOCLOSE fd = -1; + struct stat st; + + if (!tokenfile) + return NULL; + + fd = open(tokenfile, O_RDWR|O_APPEND|O_CREAT, 0600); + if (fd < 0) { + virReportSystemError(errno, + _("Unable to open system token %s"), + tokenfile); + return NULL; + } + + if (virSetCloseExec(fd) < 0) { + virReportSystemError(errno, + _("Failed to set close-on-exec flag '%s'"), + tokenfile); + return NULL; + } + + if (virFileLock(fd, false, 0, 1, true) < 0) { + virReportSystemError(errno, + _("Failed to lock system token '%s'"), + tokenfile); + return NULL; + } + + if (fstat(fd, &st) < 0) { + virReportSystemError(errno, + _("Failed to check system token '%s'"), + tokenfile); + return NULL; + } + + /* Ok, we're the first one here, so we must populate it */ + if (st.st_size == 0) { + if (!(token = virRandomToken(TOKEN_BYTES))) { + return NULL; + } + if (safewrite(fd, token, TOKEN_STRLEN) != TOKEN_STRLEN) { + virReportSystemError(errno, + _("Failed to write system token '%s'"), + tokenfile); + return NULL; + } + } else { + if (virFileReadLimFD(fd, TOKEN_STRLEN, &token) < 0) { + virReportSystemError(errno, + _("Failed to write system token '%s'"), + tokenfile); + return NULL; + } + if (strlen(token) != TOKEN_STRLEN) { + virReportSystemError(errno, + _("System token in %s was corrupt"), + tokenfile); + return NULL; + } + } + + return g_steal_pointer(&token); +} + + /** * virIdentityGetSystem: * diff --git a/src/util/viridentitypriv.h b/src/util/viridentitypriv.h new file mode 100644 index 0000000000..e5ca8430f8 --- /dev/null +++ b/src/util/viridentitypriv.h @@ -0,0 +1,30 @@ +/* + * viridentitypriv.h: helper APIs for managing user identities + * + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + */ + +#ifndef LIBVIRT_VIRIDENTITYPRIV_H_ALLOW +# error "viridentitypriv.h may only be included by viridentity.c or test suites" +#endif /* LIBVIRT_VIRIDENTITYPRIV_H_ALLOW */ + +#pragma once + +#include "viridentity.h" + +char * +virIdentityEnsureSystemToken(void) G_GNUC_NO_INLINE; diff --git a/tests/viridentitytest.c b/tests/viridentitytest.c index afb9fdaec4..99c7899ed7 100644 --- a/tests/viridentitytest.c +++ b/tests/viridentitytest.c @@ -25,7 +25,9 @@ #include "testutils.h" -#include "viridentity.h" +#define LIBVIRT_VIRIDENTITYPRIV_H_ALLOW + +#include "viridentitypriv.h" #include "virerror.h" #include "viralloc.h" #include "virlog.h" @@ -36,6 +38,13 @@ VIR_LOG_INIT("tests.identitytest"); +char * +virIdentityEnsureSystemToken(void) +{ + return g_strdup("3de80bcbf22d4833897f1638e01be9b2"); +} + + static int testIdentityAttrs(const void *data G_GNUC_UNUSED) { g_autoptr(virIdentity) ident = virIdentityNew(); -- 2.31.1

Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/util/viridentity.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/util/viridentity.c b/src/util/viridentity.c index 5174f5a2d3..e822f0bd74 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -266,6 +266,7 @@ virIdentity *virIdentityGetSystem(void) #if WITH_SELINUX char *con; #endif + g_autofree char *token = NULL; if (!(ret = virIdentityNew())) return NULL; @@ -308,6 +309,12 @@ virIdentity *virIdentityGetSystem(void) } #endif + if (!(token = virIdentityEnsureSystemToken())) + return NULL; + + if (virIdentitySetSystemToken(ret, token) < 0) + return NULL; + return g_steal_pointer(&ret); } -- 2.31.1

Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 1 + src/util/viridentity.c | 21 +++++++++++++++++++++ src/util/viridentity.h | 1 + 3 files changed, 23 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9c3c473c1c..443a78e698 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2411,6 +2411,7 @@ virIdentityGetUNIXUserID; virIdentityGetUserName; virIdentityGetX509DName; virIdentityNew; +virIdentityNewCopy; virIdentitySetCurrent; virIdentitySetGroupName; virIdentitySetParameters; diff --git a/src/util/viridentity.c b/src/util/viridentity.c index e822f0bd74..01edabf2d7 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -333,6 +333,27 @@ virIdentity *virIdentityNew(void) } +/** + * virIdentityNewCopy: + * + * Creates a new identity object that is a deep copy of an + * existing identity. + * + * Returns: a copy of the source identity + */ +virIdentity *virIdentityNewCopy(virIdentity *src) +{ + g_autoptr(virIdentity) ident = virIdentityNew(); + + if (virTypedParamsCopy(&ident->params, src->params, src->nparams) < 0) + return NULL; + ident->nparams = src->nparams; + ident->maxparams = src->nparams; + + return g_steal_pointer(&ident); +} + + static void virIdentityFinalize(GObject *object) { virIdentity *ident = VIR_IDENTITY(object); diff --git a/src/util/viridentity.h b/src/util/viridentity.h index 640a7ba2e4..512bca286d 100644 --- a/src/util/viridentity.h +++ b/src/util/viridentity.h @@ -33,6 +33,7 @@ int virIdentitySetCurrent(virIdentity *ident); virIdentity *virIdentityGetSystem(void); virIdentity *virIdentityNew(void); +virIdentity *virIdentityNewCopy(virIdentity *src); int virIdentityGetUserName(virIdentity *ident, const char **username); -- 2.31.1

When talking to the secret driver, the callers inside libvirt daemons need to be able to run with an elevated privileges that prove the API calls are made by a libvirt daemon, not an end user application. The virIdentityElevateCurrent method will take the current identity and, if not already present, add the system token. The old current identity is returned to the caller. With the VIR_IDENTITY_AUTORESTORE annotation, the old current identity will be restored upon leaving the codeblock scope. ... early work with regular privileges ... if (something needing elevated privs) { VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); if (!oldident) return -1; ... do something with elevated privileges ... } ... later work with regular privileges ... Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 2 ++ src/util/viridentity.c | 47 ++++++++++++++++++++++++++++++++++++++++ src/util/viridentity.h | 5 +++++ 3 files changed, 54 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 443a78e698..2ea950c5cd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2396,6 +2396,7 @@ virHostGetBootTime; # util/viridentity.h +virIdentityElevateCurrent; virIdentityEnsureSystemToken; virIdentityGetCurrent; virIdentityGetGroupName; @@ -2412,6 +2413,7 @@ virIdentityGetUserName; virIdentityGetX509DName; virIdentityNew; virIdentityNewCopy; +virIdentityRestoreHelper; virIdentitySetCurrent; virIdentitySetGroupName; virIdentitySetParameters; diff --git a/src/util/viridentity.c b/src/util/viridentity.c index 01edabf2d7..2e3fcc5add 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -154,6 +154,53 @@ int virIdentitySetCurrent(virIdentity *ident) } +/** + * virIdentityElevateCurrent: + * + * Set the new identity to be associated with this thread, + * to an elevated copy of the current identity. The old + * current identity is returned and should be released by + * the caller when no longer required. + * + * Returns the previous identity, or NULL on error + */ +virIdentity *virIdentityElevateCurrent(void) +{ + g_autoptr(virIdentity) ident = virIdentityGetCurrent(); + const char *token; + int rc; + + if (!ident) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No current identity to elevate")); + return NULL; + } + + if ((rc = virIdentityGetSystemToken(ident, &token)) < 0) + return NULL; + + if (rc == 0) { + g_autoptr(virIdentity) identel = virIdentityNewCopy(ident); + + if (virIdentitySetSystemToken(identel, systemToken) < 0) + return NULL; + + if (virIdentitySetCurrent(identel) < 0) + return NULL; + } + + return g_steal_pointer(&ident); +} + + +void virIdentityRestoreHelper(virIdentity **identptr) +{ + virIdentity *ident = *identptr; + + if (ident != NULL) + virIdentitySetCurrent(ident); +} + #define TOKEN_BYTES 16 #define TOKEN_STRLEN (TOKEN_BYTES * 2) diff --git a/src/util/viridentity.h b/src/util/viridentity.h index 512bca286d..848e5b2056 100644 --- a/src/util/viridentity.h +++ b/src/util/viridentity.h @@ -27,8 +27,13 @@ #define VIR_TYPE_IDENTITY vir_identity_get_type() G_DECLARE_FINAL_TYPE(virIdentity, vir_identity, VIR, IDENTITY, GObject); +#define VIR_IDENTITY_AUTORESTORE __attribute__((cleanup(virIdentityRestoreHelper))) + virIdentity *virIdentityGetCurrent(void); int virIdentitySetCurrent(virIdentity *ident); +virIdentity *virIdentityElevateCurrent(void); + +void virIdentityRestoreHelper(virIdentity **identptr); virIdentity *virIdentityGetSystem(void); -- 2.31.1

This is essentially a way to determine if the current identity is that of another libvirt daemon. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 1 + src/util/viridentity.c | 28 ++++++++++++++++++++++++++++ src/util/viridentity.h | 1 + 3 files changed, 30 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2ea950c5cd..1df4b8cfe8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2411,6 +2411,7 @@ virIdentityGetUNIXGroupID; virIdentityGetUNIXUserID; virIdentityGetUserName; virIdentityGetX509DName; +virIdentityIsCurrentElevated; virIdentityNew; virIdentityNewCopy; virIdentityRestoreHelper; diff --git a/src/util/viridentity.c b/src/util/viridentity.c index 2e3fcc5add..e7e5c31241 100644 --- a/src/util/viridentity.c +++ b/src/util/viridentity.c @@ -366,6 +366,34 @@ virIdentity *virIdentityGetSystem(void) } +/** + * virIdentityIsCurrentElevated: + * + * Determine if the current identity has elevated privileges. + * This indicates that it was invoked on behalf of the + * user by a libvirt daemon. + * + * Returns: true if elevated + */ +int virIdentityIsCurrentElevated(void) +{ + g_autoptr(virIdentity) current = virIdentityGetCurrent(); + const char *currentToken = NULL; + int rv; + + if (!current) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No current identity")); + return -1; + } + + rv = virIdentityGetSystemToken(current, ¤tToken); + if (rv <= 0) + return rv; + + return STREQ_NULLABLE(currentToken, systemToken); +} + /** * virIdentityNew: * diff --git a/src/util/viridentity.h b/src/util/viridentity.h index 848e5b2056..6da6d0c557 100644 --- a/src/util/viridentity.h +++ b/src/util/viridentity.h @@ -35,6 +35,7 @@ virIdentity *virIdentityElevateCurrent(void); void virIdentityRestoreHelper(virIdentity **identptr); +int virIdentityIsCurrentElevated(void); virIdentity *virIdentityGetSystem(void); virIdentity *virIdentityNew(void); -- 2.31.1

The drivers can all call virGetConnectXXX to open a connection to a secondary driver. For example, when creating a encrypted storage volume, the storage driver has to open a secret driver connection, or when starting a guest, the QEMU driver has to open the network driver to lookup a virtual network. When using monolithic libvirtd, the connection has the same effective identity as the client, since everything is still in the same process. When using the modular daemons, however, the remote daemon sees the identity of the calling daemon. This is a mistake as it results in the modular daemons seeing the client with elevated privileges. We need to pass on the current identity explicitly when opening the secondary drivers. This is the same thing that is done by daemon RPC dispatcher code when it is directly forwarding top level API calls from virtproxyd and other daemons. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/driver.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/driver.c b/src/driver.c index f8022d2522..227bb56e48 100644 --- a/src/driver.c +++ b/src/driver.c @@ -33,6 +33,8 @@ #include "virstring.h" #include "virthread.h" #include "virutil.h" +#include "viridentity.h" +#include "datatypes.h" #include "configmake.h" VIR_LOG_INIT("driver"); @@ -136,6 +138,7 @@ static virConnectPtr virGetConnectGeneric(virThreadLocal *threadPtr, const char *name) { virConnectPtr conn; + virErrorPtr saved; if (virConnectCacheInitialize() < 0) return NULL; @@ -153,8 +156,32 @@ virGetConnectGeneric(virThreadLocal *threadPtr, const char *name) conn = virConnectOpen(uri); VIR_DEBUG("Opened new %s connection %p", name, conn); + if (!conn) + return NULL; + + if (conn->driver->connectSetIdentity != NULL) { + g_autoptr(virIdentity) ident = NULL; + virTypedParameterPtr identparams = NULL; + int nidentparams = 0; + + VIR_DEBUG("Attempting to delegate current identity"); + if (!(ident = virIdentityGetCurrent())) + goto error; + + if (virIdentityGetParameters(ident, &identparams, &nidentparams) < 0) + goto error; + + if (virConnectSetIdentity(conn, identparams, nidentparams, 0) < 0) + goto error; + } } return conn; + + error: + saved = virSaveLastError(); + virConnectClose(conn); + virSetError(saved); + return NULL; } -- 2.31.1

On 5/12/21 9:33 AM, Daniel P. Berrangé wrote:
The drivers can all call virGetConnectXXX to open a connection to a secondary driver. For example, when creating a encrypted storage volume, the storage driver has to open a secret driver connection, or when starting a guest, the QEMU driver has to open the network driver to lookup a virtual network.
When using monolithic libvirtd, the connection has the same effective identity as the client, since everything is still in the same process. When using the modular daemons, however, the remote daemon sees the identity of the calling daemon. This is a mistake as it results in the modular daemons seeing the client with elevated privileges.
We need to pass on the current identity explicitly when opening the secondary drivers. This is the same thing that is done by daemon RPC dispatcher code when it is directly forwarding top level API calls from virtproxyd and other daemons.
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/driver.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
diff --git a/src/driver.c b/src/driver.c index f8022d2522..227bb56e48 100644 --- a/src/driver.c +++ b/src/driver.c @@ -33,6 +33,8 @@ #include "virstring.h" #include "virthread.h" #include "virutil.h" +#include "viridentity.h" +#include "datatypes.h" #include "configmake.h"
VIR_LOG_INIT("driver"); @@ -136,6 +138,7 @@ static virConnectPtr virGetConnectGeneric(virThreadLocal *threadPtr, const char *name) { virConnectPtr conn; + virErrorPtr saved;
if (virConnectCacheInitialize() < 0) return NULL; @@ -153,8 +156,32 @@ virGetConnectGeneric(virThreadLocal *threadPtr, const char *name)
conn = virConnectOpen(uri); VIR_DEBUG("Opened new %s connection %p", name, conn); + if (!conn) + return NULL; + + if (conn->driver->connectSetIdentity != NULL) { + g_autoptr(virIdentity) ident = NULL; + virTypedParameterPtr identparams = NULL; + int nidentparams = 0; + + VIR_DEBUG("Attempting to delegate current identity"); + if (!(ident = virIdentityGetCurrent())) + goto error; + + if (virIdentityGetParameters(ident, &identparams, &nidentparams) < 0) + goto error; + + if (virConnectSetIdentity(conn, identparams, nidentparams, 0) < 0) + goto error; + } } return conn; + + error: + saved = virSaveLastError(); + virConnectClose(conn); + virSetError(saved);
Coverity complains about leak here Need a virFreeError(saved); John
+ return NULL; }

When fetching the value of a private secret, we need to use an elevated identity otherwise the secret driver will deny access. When using the modular daemons, the elevated identity needs to be active before the secret driver connection is opened, and it will apply to all APIs calls made on that conncetion. When using the monolithic daemon, the identity at time of opening the connection is ignored, and the elevated identity needs to be active precisely at the time the virSecretGetValue API call is made. After acquiring the secret value, the elevated identity should be cleared. This sounds complex, but is fairly straightfoward with the automatic cleanup callbacks. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libxl/libxl_conf.c | 5 +++++ src/qemu/qemu_domain.c | 11 ++++++++++- src/qemu/qemu_tpm.c | 5 +++++ src/storage/storage_backend_iscsi.c | 5 +++++ src/storage/storage_backend_iscsi_direct.c | 5 +++++ src/storage/storage_backend_rbd.c | 5 +++++ src/storage/storage_util.c | 5 +++++ tests/qemuxml2argvmock.c | 9 +++++++++ tests/qemuxml2argvtest.c | 6 ++++++ 9 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c index 4de2158bea..e33297a9ba 100644 --- a/src/libxl/libxl_conf.c +++ b/src/libxl/libxl_conf.c @@ -31,6 +31,7 @@ #include "datatypes.h" #include "virconf.h" #include "virfile.h" +#include "viridentity.h" #include "virstring.h" #include "viralloc.h" #include "viruuid.h" @@ -1001,6 +1002,10 @@ libxlMakeNetworkDiskSrc(virStorageSource *src, char **srcstr) if (src->auth && src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD) { g_autofree uint8_t *secret = NULL; size_t secretlen = 0; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); + + if (!oldident) + goto cleanup; username = src->auth->username; if (!(conn = virConnectOpen("xen:///system"))) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index fe56d17486..10641846b3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -41,6 +41,7 @@ #include "viralloc.h" #include "virlog.h" #include "virerror.h" +#include "viridentity.h" #include "cpu/cpu.h" #include "viruuid.h" #include "virfile.h" @@ -1116,9 +1117,13 @@ qemuDomainSecretPlainSetup(qemuDomainSecretInfo *secinfo, const char *username, virSecretLookupTypeDef *seclookupdef) { + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); g_autoptr(virConnect) conn = virGetConnectSecret(); int ret = -1; + if (!oldident) + return -1; + if (!conn) return -1; @@ -1213,11 +1218,15 @@ qemuDomainSecretAESSetupFromSecret(qemuDomainObjPrivate *priv, const char *username, virSecretLookupTypeDef *seclookupdef) { - g_autoptr(virConnect) conn = virGetConnectSecret(); qemuDomainSecretInfo *secinfo; g_autofree char *alias = qemuAliasForSecret(srcalias, secretuse); g_autofree uint8_t *secret = NULL; size_t secretlen = 0; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); + g_autoptr(virConnect) conn = virGetConnectSecret(); + + if (!oldident) + return NULL; if (!conn) return NULL; diff --git a/src/qemu/qemu_tpm.c b/src/qemu/qemu_tpm.c index 9ae7e5f94b..477a26dc69 100644 --- a/src/qemu/qemu_tpm.c +++ b/src/qemu/qemu_tpm.c @@ -33,6 +33,7 @@ #include "vircommand.h" #include "viralloc.h" #include "virkmod.h" +#include "viridentity.h" #include "virlog.h" #include "virutil.h" #include "viruuid.h" @@ -366,6 +367,10 @@ qemuTPMSetupEncryption(const unsigned char *secretuuid, virSecretLookupTypeDef seclookupdef = { .type = VIR_SECRET_LOOKUP_TYPE_UUID, }; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); + + if (!oldident) + return -1; conn = virGetConnectSecret(); if (!conn) diff --git a/src/storage/storage_backend_iscsi.c b/src/storage/storage_backend_iscsi.c index 67e59e856c..ed17ed11a6 100644 --- a/src/storage/storage_backend_iscsi.c +++ b/src/storage/storage_backend_iscsi.c @@ -34,6 +34,7 @@ #include "virerror.h" #include "virfile.h" #include "viriscsi.h" +#include "viridentity.h" #include "virlog.h" #include "virobject.h" #include "virstring.h" @@ -263,6 +264,7 @@ virStorageBackendISCSISetAuth(const char *portal, virStorageAuthDef *authdef = source->auth; int ret = -1; virConnectPtr conn = NULL; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = NULL; if (!authdef || authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) return 0; @@ -275,6 +277,9 @@ virStorageBackendISCSISetAuth(const char *portal, return -1; } + if (!(oldident = virIdentityElevateCurrent())) + return -1; + conn = virGetConnectSecret(); if (!conn) return -1; diff --git a/src/storage/storage_backend_iscsi_direct.c b/src/storage/storage_backend_iscsi_direct.c index cb5b39baf4..0bff1882b9 100644 --- a/src/storage/storage_backend_iscsi_direct.c +++ b/src/storage/storage_backend_iscsi_direct.c @@ -29,6 +29,7 @@ #include "storage_util.h" #include "viralloc.h" #include "virerror.h" +#include "viridentity.h" #include "virlog.h" #include "virobject.h" #include "virstring.h" @@ -94,6 +95,7 @@ virStorageBackendISCSIDirectSetAuth(struct iscsi_context *iscsi, virStorageAuthDef *authdef = source->auth; int ret = -1; virConnectPtr conn = NULL; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = NULL; if (!authdef || authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) return 0; @@ -107,6 +109,9 @@ virStorageBackendISCSIDirectSetAuth(struct iscsi_context *iscsi, return ret; } + if (!(oldident = virIdentityElevateCurrent())) + return -1; + if (!(conn = virGetConnectSecret())) return ret; diff --git a/src/storage/storage_backend_rbd.c b/src/storage/storage_backend_rbd.c index 9fbb2464d1..ce3ab11dd6 100644 --- a/src/storage/storage_backend_rbd.c +++ b/src/storage/storage_backend_rbd.c @@ -27,6 +27,7 @@ #include "storage_backend_rbd.h" #include "storage_conf.h" #include "viralloc.h" +#include "viridentity.h" #include "virlog.h" #include "viruuid.h" #include "virstring.h" @@ -196,6 +197,7 @@ virStorageBackendRBDOpenRADOSConn(virStorageBackendRBDState *ptr, g_autofree char *mon_buff = NULL; if (authdef) { + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = NULL; g_autofree char *rados_key = NULL; int rc; @@ -206,6 +208,9 @@ virStorageBackendRBDOpenRADOSConn(virStorageBackendRBDState *ptr, goto cleanup; } + if (!(oldident = virIdentityElevateCurrent())) + goto cleanup; + conn = virGetConnectSecret(); if (!conn) return -1; diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c index 7efadc2197..2b0d08c65d 100644 --- a/src/storage/storage_util.c +++ b/src/storage/storage_util.c @@ -68,6 +68,7 @@ #include "storage_source_conf.h" #include "virlog.h" #include "virfile.h" +#include "viridentity.h" #include "virjson.h" #include "virqemu.h" #include "virstring.h" @@ -1265,6 +1266,7 @@ storageBackendCreateQemuImgSecretPath(virStoragePoolObj *pool, size_t secretlen = 0; virConnectPtr conn = NULL; VIR_AUTOCLOSE fd = -1; + VIR_IDENTITY_AUTORESTORE virIdentity *oldident = NULL; if (!enc) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -1279,6 +1281,9 @@ storageBackendCreateQemuImgSecretPath(virStoragePoolObj *pool, return NULL; } + if (!(oldident = virIdentityElevateCurrent())) + return NULL; + conn = virGetConnectSecret(); if (!conn) return NULL; diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c index 77a0814c08..2265492f1e 100644 --- a/tests/qemuxml2argvmock.c +++ b/tests/qemuxml2argvmock.c @@ -18,10 +18,13 @@ #include <config.h> +#define LIBVIRT_VIRIDENTITYPRIV_H_ALLOW + #include "internal.h" #include "viralloc.h" #include "vircommand.h" #include "vircrypto.h" +#include "viridentitypriv.h" #include "virmock.h" #include "virlog.h" #include "virnetdev.h" @@ -292,3 +295,9 @@ qemuInterfaceVDPAConnect(virDomainNetDef *net G_GNUC_UNUSED) abort(); return 1732; } + +char * +virIdentityEnsureSystemToken(void) +{ + return g_strdup("3de80bcbf22d4833897f1638e01be9b2"); +} diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index a9dafe226e..a93d21d61a 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -11,6 +11,7 @@ # include "internal.h" # include "viralloc.h" +# include "viridentity.h" # include "qemu/qemu_alias.h" # include "qemu/qemu_capabilities.h" # include "qemu/qemu_command.h" @@ -650,6 +651,7 @@ testCompareXMLToArgv(const void *data) xmlNodePtr root; g_autofree char *archstr = NULL; virArch arch = VIR_ARCH_NONE; + g_autoptr(virIdentity) sysident = virIdentityGetSystem(); if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) qemuTestSetHostArch(&driver, info->arch); @@ -670,6 +672,9 @@ testCompareXMLToArgv(const void *data) virSetConnectSecret(conn); virSetConnectStorage(conn); + if (virIdentitySetCurrent(sysident) < 0) + goto cleanup; + if (testCheckExclusiveFlags(info->flags) < 0) goto cleanup; @@ -809,6 +814,7 @@ testCompareXMLToArgv(const void *data) VIR_FREE(log); virDomainChrSourceDefClear(&monitor_chr); virObjectUnref(vm); + virIdentitySetCurrent(NULL); virSetConnectSecret(NULL); virSetConnectStorage(NULL); if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) -- 2.31.1

A secret can be marked with the "private" attribute. The intent was that it is not possible for any libvirt client to be able to read the secret value, it would only be accesible from within libvirtd. eg the QEMU driver can read the value to launch a guest. With the modular daemons, the QEMU, storage and secret drivers are all running in separate daemons. The QEMU and storage drivers thus appear to be normal libvirt client's from the POV of the secret driver, and thus they are not able to read a private secret. This is unhelpful. With the previous patches that introduced a "system token" to the identity object, we can now distinguish APIs invoked by libvirt daemons from those invoked by client applications. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/driver-secret.h | 9 +-------- src/libvirt-secret.c | 2 +- src/remote/remote_driver.c | 8 +------- src/secret/secret_driver.c | 34 +++++++++++++++++++++++++++------- src/util/virsecret.c | 3 +-- tests/qemuxml2argvtest.c | 3 +-- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/driver-secret.h b/src/driver-secret.h index eb6e82478c..1d21f62bb3 100644 --- a/src/driver-secret.h +++ b/src/driver-secret.h @@ -24,12 +24,6 @@ # error "Don't include this file directly, only use driver.h" #endif -enum { - /* This getValue call is inside libvirt, override the "private" flag. - This flag cannot be set by outside callers. */ - VIR_SECRET_GET_VALUE_INTERNAL_CALL = 1 << 0, -}; - typedef virSecretPtr (*virDrvSecretLookupByUUID)(virConnectPtr conn, const unsigned char *uuid); @@ -57,8 +51,7 @@ typedef int typedef unsigned char * (*virDrvSecretGetValue)(virSecretPtr secret, size_t *value_size, - unsigned int flags, - unsigned int internalFlags); + unsigned int flags); typedef int (*virDrvSecretUndefine)(virSecretPtr secret); diff --git a/src/libvirt-secret.c b/src/libvirt-secret.c index 75d40f53dc..a427805c7a 100644 --- a/src/libvirt-secret.c +++ b/src/libvirt-secret.c @@ -585,7 +585,7 @@ virSecretGetValue(virSecretPtr secret, size_t *value_size, unsigned int flags) if (conn->secretDriver != NULL && conn->secretDriver->secretGetValue != NULL) { unsigned char *ret; - ret = conn->secretDriver->secretGetValue(secret, value_size, flags, 0); + ret = conn->secretDriver->secretGetValue(secret, value_size, flags); if (ret == NULL) goto error; return ret; diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 0c72d69933..eed99af127 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -5382,7 +5382,7 @@ remoteDomainBuildQemuMonitorEvent(virNetClientProgram *prog G_GNUC_UNUSED, static unsigned char * remoteSecretGetValue(virSecretPtr secret, size_t *value_size, - unsigned int flags, unsigned int internalFlags) + unsigned int flags) { unsigned char *rv = NULL; remote_secret_get_value_args args; @@ -5391,12 +5391,6 @@ remoteSecretGetValue(virSecretPtr secret, size_t *value_size, remoteDriverLock(priv); - /* internalFlags intentionally do not go over the wire */ - if (internalFlags) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no internalFlags support")); - goto done; - } - make_nonnull_secret(&args.secret, secret); args.flags = flags; diff --git a/src/secret/secret_driver.c b/src/secret/secret_driver.c index 6ea8cc8ce9..d2175de8ed 100644 --- a/src/secret/secret_driver.c +++ b/src/secret/secret_driver.c @@ -36,6 +36,7 @@ #include "viruuid.h" #include "virerror.h" #include "virfile.h" +#include "viridentity.h" #include "virpidfile.h" #include "configmake.h" #include "virstring.h" @@ -352,8 +353,7 @@ secretSetValue(virSecretPtr secret, static unsigned char * secretGetValue(virSecretPtr secret, size_t *value_size, - unsigned int flags, - unsigned int internalFlags) + unsigned int flags) { unsigned char *ret = NULL; virSecretObj *obj; @@ -368,11 +368,31 @@ secretGetValue(virSecretPtr secret, if (virSecretGetValueEnsureACL(secret->conn, def) < 0) goto cleanup; - if ((internalFlags & VIR_SECRET_GET_VALUE_INTERNAL_CALL) == 0 && - def->isprivate) { - virReportError(VIR_ERR_INVALID_SECRET, "%s", - _("secret is private")); - goto cleanup; + /* + * For historical compat we want to deny access to + * private secrets, even if no ACL driver is + * present. + * + * We need to validate the identity requesting + * the secret value is running as the same user + * credentials as this driver. + * + * ie a non-root libvirt client should not be + * able to request the value from privileged + * libvirt driver. + * + * To apply restrictions to processes running under + * the same user account is out of scope. + */ + if (def->isprivate) { + int rv = virIdentityIsCurrentElevated(); + if (rv < 0) + goto cleanup; + if (rv == 0) { + virReportError(VIR_ERR_INVALID_SECRET, "%s", + _("secret is private")); + goto cleanup; + } } if (!(ret = virSecretObjGetValue(obj))) diff --git a/src/util/virsecret.c b/src/util/virsecret.c index 0695288229..604d900f77 100644 --- a/src/util/virsecret.c +++ b/src/util/virsecret.c @@ -174,8 +174,7 @@ virSecretGetSecretString(virConnectPtr conn, goto cleanup; } - *secret = conn->secretDriver->secretGetValue(sec, secret_size, 0, - VIR_SECRET_GET_VALUE_INTERNAL_CALL); + *secret = conn->secretDriver->secretGetValue(sec, secret_size, 0); if (!*secret) goto cleanup; diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index a93d21d61a..d5e59fe474 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -43,8 +43,7 @@ static virQEMUDriver driver; static unsigned char * fakeSecretGetValue(virSecretPtr obj G_GNUC_UNUSED, size_t *value_size, - unsigned int fakeflags G_GNUC_UNUSED, - unsigned int internalFlags G_GNUC_UNUSED) + unsigned int fakeflags G_GNUC_UNUSED) { char *secret; secret = g_strdup("AQCVn5hO6HzFAhAAq0NCv8jtJcIcE+HOBlMQ1A"); -- 2.31.1

On 5/12/21 3:33 PM, Daniel P. Berrangé wrote:
If you define a secret with private="yes", then libvirt won't let any client query the secret value after it is set. Only other libvirt drivers inside the daemon can query it by passing a special internal only flag to the virSecretGetValue API. The remote driver/daemon refuses to let this internal flag go over the wire preventing normal clients from using it
This doesn't work with the split daemons because the virSecretGetValue API done by virqemud / virtstoraged has to go over the wire to reach the virsecretd.
We need to come up with an alternative way to "prove" that the caller of virSecretGetValue is a libvirt daemon, as opposed to a general libvirt client.
Note with if only traditional POSIX DAC permissions are in effect then we could consider it pointless trying to restrict access to clients running the same user/group as the libvirt daemon. We ought to take into account that the client might be confined by SELinux though, so the "private secret" concept isn't entirely pointless.
Thus doing a simple uid of client == uid of daemon check is a bit too weak. The UID check might also not fly if the modular daemons are run inside containers with user namespaces, as the container for virtsecretd and virtqemud might have different user mappings in theory.
This series adds a concept of a "token" which is known only to the libvirt daemons. The first daemon to use it writes a random hex string to /var/run/libvirt/common/system.token. Other daemons can read and compare this. Unless a MAC system is present this is still largely security theatre, but that's not really worse than the historical behaviour.
When an API call is made the virIdentity by default reflects the identity of the UNIX process that initiated it.
When connecting to virtproxyd, the client apps' identity is forwarded to the next virtNNNNd daemon.
When libvirt drivers, however, initiate an API call we never set any identity. With monolithic libvirtd, they'd inherit the current client identity automagically since it was all in the same thread local. With modular daemons the othe driver would see the identity of the other libvirt daemon which is bad as this gives elevated privileges in the ACL check.
Thus we fix the code which drivers use to open a connection to other daemons, such that it applies the current caller's identity. It does this using an "elevated" identity though, which means, we have added in the system token. Thus the virtsecretd daemon getting the call virSecretGetValue sees the virIdentity reflecting the client application which originally called the virDomainCreate() API, but with the system token set. Thus virsecretd can see that the virSecretGetValue was invoked by another daemon, not a libvirt client app.
Changed in v3...
Properly mock the new APIs in test suite
Changed in v2...
We can't set the elevated identity only when opening the virConnect for the secret driver. This works for modular daemons, as the identity is passed to the virsecretd at time of opening and thus applies to the later virSecretGetValue call on that connection.
For monolithic daemon, the identity present at virConnectOpen is irrelevant. The virSecretGetValue call will just directly query the current thread's identity.
IOW, to work in both deployment scenarios we need to have the elevated identity set across both virConnectOpen and virSecretGetValue
Daniel P. Berrangé (10): util: add virRandomToken API util: introduce concept of a system token into identities util: generate a persistent system token util: set system token for system identity util: add API for copying identity objects util: helper to temporary elevate privileges of the current identity src: add API to determine if current identity is a system identity src: set identity when opening secondary drivers src: elevate current identity privilege when fetching secret secret: rework handling of private secrets
src/driver-secret.h | 9 +- src/driver.c | 27 +++ src/libvirt-secret.c | 2 +- src/libvirt_private.syms | 8 + src/libxl/libxl_conf.c | 5 + src/qemu/qemu_domain.c | 11 +- src/qemu/qemu_tpm.c | 5 + src/remote/remote_driver.c | 8 +- src/secret/secret_driver.c | 34 ++- src/storage/storage_backend_iscsi.c | 5 + src/storage/storage_backend_iscsi_direct.c | 5 + src/storage/storage_backend_rbd.c | 5 + src/storage/storage_util.c | 5 + src/util/viridentity.c | 244 ++++++++++++++++++++- src/util/viridentity.h | 11 + src/util/viridentitypriv.h | 30 +++ src/util/virrandom.c | 18 ++ src/util/virrandom.h | 1 + src/util/virsecret.c | 3 +- tests/qemuxml2argvmock.c | 9 + tests/qemuxml2argvtest.c | 9 +- tests/viridentitytest.c | 11 +- 22 files changed, 435 insertions(+), 30 deletions(-) create mode 100644 src/util/viridentitypriv.h
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal
participants (3)
-
Daniel P. Berrangé
-
John Ferlan
-
Michal Prívozník