libvirt/src/secret/secret_driver.c
Daniel P. Berrangé 8f390ae310 secret: rework handling of private secrets
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>
2021-05-13 11:07:47 +01:00

737 lines
19 KiB
C

/*
* secret_driver.c: local driver for secret manipulation API
*
* Copyright (C) 2009-2016 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/>.
*/
#include <config.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "internal.h"
#include "datatypes.h"
#include "driver.h"
#include "virlog.h"
#include "viralloc.h"
#include "secret_conf.h"
#include "virsecretobj.h"
#include "secret_driver.h"
#include "virthread.h"
#include "viruuid.h"
#include "virerror.h"
#include "virfile.h"
#include "viridentity.h"
#include "virpidfile.h"
#include "configmake.h"
#include "virstring.h"
#include "viraccessapicheck.h"
#include "secret_event.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_SECRET
VIR_LOG_INIT("secret.secret_driver");
enum { SECRET_MAX_XML_FILE = 10*1024*1024 };
/* Internal driver state */
typedef struct _virSecretDriverState virSecretDriverState;
struct _virSecretDriverState {
virMutex lock;
bool privileged; /* readonly */
char *embeddedRoot; /* readonly */
int embeddedRefs;
virSecretObjList *secrets;
char *stateDir;
char *configDir;
/* pid file FD, ensures two copies of the driver can't use the same root */
int lockFD;
/* Immutable pointer, self-locking APIs */
virObjectEventState *secretEventState;
};
static virSecretDriverState *driver;
static void
secretDriverLock(void)
{
virMutexLock(&driver->lock);
}
static void
secretDriverUnlock(void)
{
virMutexUnlock(&driver->lock);
}
static virSecretObj *
secretObjFromSecret(virSecretPtr secret)
{
virSecretObj *obj;
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(secret->uuid, uuidstr);
if (!(obj = virSecretObjListFindByUUID(driver->secrets, uuidstr))) {
virReportError(VIR_ERR_NO_SECRET,
_("no secret with matching uuid '%s'"), uuidstr);
return NULL;
}
return obj;
}
/* Driver functions */
static int
secretConnectNumOfSecrets(virConnectPtr conn)
{
if (virConnectNumOfSecretsEnsureACL(conn) < 0)
return -1;
return virSecretObjListNumOfSecrets(driver->secrets,
virConnectNumOfSecretsCheckACL,
conn);
}
static int
secretConnectListSecrets(virConnectPtr conn,
char **uuids,
int maxuuids)
{
memset(uuids, 0, maxuuids * sizeof(*uuids));
if (virConnectListSecretsEnsureACL(conn) < 0)
return -1;
return virSecretObjListGetUUIDs(driver->secrets, uuids, maxuuids,
virConnectListSecretsCheckACL, conn);
}
static int
secretConnectListAllSecrets(virConnectPtr conn,
virSecretPtr **secrets,
unsigned int flags)
{
virCheckFlags(VIR_CONNECT_LIST_SECRETS_FILTERS_ALL, -1);
if (virConnectListAllSecretsEnsureACL(conn) < 0)
return -1;
return virSecretObjListExport(conn, driver->secrets, secrets,
virConnectListAllSecretsCheckACL,
flags);
}
static virSecretPtr
secretLookupByUUID(virConnectPtr conn,
const unsigned char *uuid)
{
virSecretPtr ret = NULL;
virSecretObj *obj;
virSecretDef *def;
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(uuid, uuidstr);
if (!(obj = virSecretObjListFindByUUID(driver->secrets, uuidstr))) {
virReportError(VIR_ERR_NO_SECRET,
_("no secret with matching uuid '%s'"), uuidstr);
goto cleanup;
}
def = virSecretObjGetDef(obj);
if (virSecretLookupByUUIDEnsureACL(conn, def) < 0)
goto cleanup;
ret = virGetSecret(conn,
def->uuid,
def->usage_type,
def->usage_id);
cleanup:
virSecretObjEndAPI(&obj);
return ret;
}
static virSecretPtr
secretLookupByUsage(virConnectPtr conn,
int usageType,
const char *usageID)
{
virSecretPtr ret = NULL;
virSecretObj *obj;
virSecretDef *def;
if (!(obj = virSecretObjListFindByUsage(driver->secrets,
usageType, usageID))) {
virReportError(VIR_ERR_NO_SECRET,
_("no secret with matching usage '%s'"), usageID);
goto cleanup;
}
def = virSecretObjGetDef(obj);
if (virSecretLookupByUsageEnsureACL(conn, def) < 0)
goto cleanup;
ret = virGetSecret(conn,
def->uuid,
def->usage_type,
def->usage_id);
cleanup:
virSecretObjEndAPI(&obj);
return ret;
}
static virSecretPtr
secretDefineXML(virConnectPtr conn,
const char *xml,
unsigned int flags)
{
virSecretPtr ret = NULL;
virSecretObj *obj = NULL;
virSecretDef *objDef;
virSecretDef *backup = NULL;
virSecretDef *def;
virObjectEvent *event = NULL;
virCheckFlags(0, NULL);
if (!(def = virSecretDefParseString(xml)))
return NULL;
if (virSecretDefineXMLEnsureACL(conn, def) < 0)
goto cleanup;
if (!(obj = virSecretObjListAdd(driver->secrets, def,
driver->configDir, &backup)))
goto cleanup;
objDef = g_steal_pointer(&def);
if (!objDef->isephemeral) {
if (backup && backup->isephemeral) {
if (virSecretObjSaveData(obj) < 0)
goto restore_backup;
}
if (virSecretObjSaveConfig(obj) < 0) {
if (backup && backup->isephemeral) {
/* Undo the virSecretObjSaveData() above; ignore errors */
virSecretObjDeleteData(obj);
}
goto restore_backup;
}
} else if (backup && !backup->isephemeral) {
if (virSecretObjDeleteConfig(obj) < 0)
goto restore_backup;
virSecretObjDeleteData(obj);
}
/* Saved successfully - drop old values */
virSecretDefFree(backup);
event = virSecretEventLifecycleNew(objDef->uuid,
objDef->usage_type,
objDef->usage_id,
VIR_SECRET_EVENT_DEFINED,
0);
ret = virGetSecret(conn,
objDef->uuid,
objDef->usage_type,
objDef->usage_id);
goto cleanup;
restore_backup:
/* If we have a backup, then secret was defined before, so just restore
* the backup; otherwise, this is a new secret, thus remove it. */
if (backup) {
virSecretObjSetDef(obj, backup);
def = g_steal_pointer(&objDef);
} else {
virSecretObjListRemove(driver->secrets, obj);
virObjectUnref(obj);
obj = NULL;
}
cleanup:
virSecretDefFree(def);
virSecretObjEndAPI(&obj);
virObjectEventStateQueue(driver->secretEventState, event);
return ret;
}
static char *
secretGetXMLDesc(virSecretPtr secret,
unsigned int flags)
{
char *ret = NULL;
virSecretObj *obj;
virSecretDef *def;
virCheckFlags(0, NULL);
if (!(obj = secretObjFromSecret(secret)))
goto cleanup;
def = virSecretObjGetDef(obj);
if (virSecretGetXMLDescEnsureACL(secret->conn, def) < 0)
goto cleanup;
ret = virSecretDefFormat(def);
cleanup:
virSecretObjEndAPI(&obj);
return ret;
}
static int
secretSetValue(virSecretPtr secret,
const unsigned char *value,
size_t value_size,
unsigned int flags)
{
int ret = -1;
virSecretObj *obj;
virSecretDef *def;
virObjectEvent *event = NULL;
virCheckFlags(0, -1);
if (!(obj = secretObjFromSecret(secret)))
goto cleanup;
def = virSecretObjGetDef(obj);
if (virSecretSetValueEnsureACL(secret->conn, def) < 0)
goto cleanup;
if (virSecretObjSetValue(obj, value, value_size) < 0)
goto cleanup;
event = virSecretEventValueChangedNew(def->uuid,
def->usage_type,
def->usage_id);
ret = 0;
cleanup:
virSecretObjEndAPI(&obj);
virObjectEventStateQueue(driver->secretEventState, event);
return ret;
}
static unsigned char *
secretGetValue(virSecretPtr secret,
size_t *value_size,
unsigned int flags)
{
unsigned char *ret = NULL;
virSecretObj *obj;
virSecretDef *def;
virCheckFlags(0, NULL);
if (!(obj = secretObjFromSecret(secret)))
goto cleanup;
def = virSecretObjGetDef(obj);
if (virSecretGetValueEnsureACL(secret->conn, def) < 0)
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)))
goto cleanup;
*value_size = virSecretObjGetValueSize(obj);
cleanup:
virSecretObjEndAPI(&obj);
return ret;
}
static int
secretUndefine(virSecretPtr secret)
{
int ret = -1;
virSecretObj *obj;
virSecretDef *def;
virObjectEvent *event = NULL;
if (!(obj = secretObjFromSecret(secret)))
goto cleanup;
def = virSecretObjGetDef(obj);
if (virSecretUndefineEnsureACL(secret->conn, def) < 0)
goto cleanup;
if (virSecretObjDeleteConfig(obj) < 0)
goto cleanup;
event = virSecretEventLifecycleNew(def->uuid,
def->usage_type,
def->usage_id,
VIR_SECRET_EVENT_UNDEFINED,
0);
virSecretObjDeleteData(obj);
virSecretObjListRemove(driver->secrets, obj);
virObjectUnref(obj);
obj = NULL;
ret = 0;
cleanup:
virSecretObjEndAPI(&obj);
virObjectEventStateQueue(driver->secretEventState, event);
return ret;
}
static int
secretStateCleanup(void)
{
if (!driver)
return -1;
secretDriverLock();
virObjectUnref(driver->secrets);
VIR_FREE(driver->configDir);
virObjectUnref(driver->secretEventState);
if (driver->lockFD != -1)
virPidFileRelease(driver->stateDir, "driver", driver->lockFD);
VIR_FREE(driver->stateDir);
secretDriverUnlock();
virMutexDestroy(&driver->lock);
VIR_FREE(driver);
return 0;
}
static int
secretStateInitialize(bool privileged,
const char *root,
virStateInhibitCallback callback G_GNUC_UNUSED,
void *opaque G_GNUC_UNUSED)
{
driver = g_new0(virSecretDriverState, 1);
driver->lockFD = -1;
if (virMutexInit(&driver->lock) < 0) {
VIR_FREE(driver);
return VIR_DRV_STATE_INIT_ERROR;
}
secretDriverLock();
driver->secretEventState = virObjectEventStateNew();
driver->privileged = privileged;
if (root) {
driver->embeddedRoot = g_strdup(root);
driver->configDir = g_strdup_printf("%s/etc/secrets", root);
driver->stateDir = g_strdup_printf("%s/run/secrets", root);
} else if (privileged) {
driver->configDir = g_strdup_printf("%s/libvirt/secrets", SYSCONFDIR);
driver->stateDir = g_strdup_printf("%s/libvirt/secrets", RUNSTATEDIR);
} else {
g_autofree char *rundir = NULL;
g_autofree char *cfgdir = NULL;
cfgdir = virGetUserConfigDirectory();
driver->configDir = g_strdup_printf("%s/secrets/", cfgdir);
rundir = virGetUserRuntimeDirectory();
driver->stateDir = g_strdup_printf("%s/secrets/run", rundir);
}
if (g_mkdir_with_parents(driver->configDir, S_IRWXU) < 0) {
virReportSystemError(errno, _("cannot create config directory '%s'"),
driver->configDir);
goto error;
}
if (g_mkdir_with_parents(driver->stateDir, S_IRWXU) < 0) {
virReportSystemError(errno, _("cannot create state directory '%s'"),
driver->stateDir);
goto error;
}
if ((driver->lockFD =
virPidFileAcquire(driver->stateDir, "driver", false, getpid())) < 0)
goto error;
if (!(driver->secrets = virSecretObjListNew()))
goto error;
if (virSecretLoadAllConfigs(driver->secrets, driver->configDir) < 0)
goto error;
secretDriverUnlock();
return VIR_DRV_STATE_INIT_COMPLETE;
error:
secretDriverUnlock();
secretStateCleanup();
return VIR_DRV_STATE_INIT_ERROR;
}
static int
secretStateReload(void)
{
if (!driver)
return -1;
secretDriverLock();
ignore_value(virSecretLoadAllConfigs(driver->secrets, driver->configDir));
secretDriverUnlock();
return 0;
}
static virDrvOpenStatus
secretConnectOpen(virConnectPtr conn,
virConnectAuthPtr auth G_GNUC_UNUSED,
virConf *conf G_GNUC_UNUSED,
unsigned int flags)
{
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
if (driver == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("secret state driver is not active"));
return VIR_DRV_OPEN_ERROR;
}
if (driver->embeddedRoot) {
const char *root = virURIGetParam(conn->uri, "root");
if (!root)
return VIR_DRV_OPEN_ERROR;
if (STRNEQ(conn->uri->path, "/embed")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("URI must be secret:///embed"));
return VIR_DRV_OPEN_ERROR;
}
if (STRNEQ(root, driver->embeddedRoot)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot open embedded driver at path '%s', "
"already open with path '%s'"),
root, driver->embeddedRoot);
return VIR_DRV_OPEN_ERROR;
}
} else {
if (!virConnectValidateURIPath(conn->uri->path,
"secret",
driver->privileged))
return VIR_DRV_OPEN_ERROR;
}
if (virConnectOpenEnsureACL(conn) < 0)
return VIR_DRV_OPEN_ERROR;
if (driver->embeddedRoot) {
secretDriverLock();
if (driver->embeddedRefs == 0)
virSetConnectSecret(conn);
driver->embeddedRefs++;
secretDriverUnlock();
}
return VIR_DRV_OPEN_SUCCESS;
}
static int secretConnectClose(virConnectPtr conn G_GNUC_UNUSED)
{
if (driver->embeddedRoot) {
secretDriverLock();
driver->embeddedRefs--;
if (driver->embeddedRefs == 0)
virSetConnectSecret(NULL);
secretDriverUnlock();
}
return 0;
}
static int secretConnectIsSecure(virConnectPtr conn G_GNUC_UNUSED)
{
/* Trivially secure, since always inside the daemon */
return 1;
}
static int secretConnectIsEncrypted(virConnectPtr conn G_GNUC_UNUSED)
{
/* Not encrypted, but remote driver takes care of that */
return 0;
}
static int secretConnectIsAlive(virConnectPtr conn G_GNUC_UNUSED)
{
return 1;
}
static int
secretConnectSecretEventRegisterAny(virConnectPtr conn,
virSecretPtr secret,
int eventID,
virConnectSecretEventGenericCallback callback,
void *opaque,
virFreeCallback freecb)
{
int callbackID = -1;
if (virConnectSecretEventRegisterAnyEnsureACL(conn) < 0)
return -1;
if (virSecretEventStateRegisterID(conn, driver->secretEventState,
secret, eventID, callback,
opaque, freecb, &callbackID) < 0)
callbackID = -1;
return callbackID;
}
static int
secretConnectSecretEventDeregisterAny(virConnectPtr conn,
int callbackID)
{
if (virConnectSecretEventDeregisterAnyEnsureACL(conn) < 0)
return -1;
if (virObjectEventStateDeregisterID(conn,
driver->secretEventState,
callbackID, true) < 0)
return -1;
return 0;
}
static virSecretDriver secretDriver = {
.name = "secret",
.connectNumOfSecrets = secretConnectNumOfSecrets, /* 0.7.1 */
.connectListSecrets = secretConnectListSecrets, /* 0.7.1 */
.connectListAllSecrets = secretConnectListAllSecrets, /* 0.10.2 */
.secretLookupByUUID = secretLookupByUUID, /* 0.7.1 */
.secretLookupByUsage = secretLookupByUsage, /* 0.7.1 */
.secretDefineXML = secretDefineXML, /* 0.7.1 */
.secretGetXMLDesc = secretGetXMLDesc, /* 0.7.1 */
.secretSetValue = secretSetValue, /* 0.7.1 */
.secretGetValue = secretGetValue, /* 0.7.1 */
.secretUndefine = secretUndefine, /* 0.7.1 */
.connectSecretEventRegisterAny = secretConnectSecretEventRegisterAny, /* 3.0.0 */
.connectSecretEventDeregisterAny = secretConnectSecretEventDeregisterAny, /* 3.0.0 */
};
static virHypervisorDriver secretHypervisorDriver = {
.name = "secret",
.connectOpen = secretConnectOpen, /* 4.1.0 */
.connectClose = secretConnectClose, /* 4.1.0 */
.connectIsEncrypted = secretConnectIsEncrypted, /* 4.1.0 */
.connectIsSecure = secretConnectIsSecure, /* 4.1.0 */
.connectIsAlive = secretConnectIsAlive, /* 4.1.0 */
};
static virConnectDriver secretConnectDriver = {
.localOnly = true,
.uriSchemes = (const char *[]){ "secret", NULL },
.embeddable = true,
.hypervisorDriver = &secretHypervisorDriver,
.secretDriver = &secretDriver,
};
static virStateDriver stateDriver = {
.name = "secret",
.stateInitialize = secretStateInitialize,
.stateCleanup = secretStateCleanup,
.stateReload = secretStateReload,
};
int
secretRegister(void)
{
if (virRegisterConnectDriver(&secretConnectDriver, false) < 0)
return -1;
if (virSetSharedSecretDriver(&secretDriver) < 0)
return -1;
if (virRegisterStateDriver(&stateDriver) < 0)
return -1;
return 0;
}