/* * virsecretobj.c: internal objects handling * * 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 * . */ #include #include #include #include #include "datatypes.h" #include "virsecretobj.h" #include "viralloc.h" #include "virerror.h" #include "virfile.h" #include "virhash.h" #include "virlog.h" #include "base64.h" #define VIR_FROM_THIS VIR_FROM_SECRET VIR_LOG_INIT("conf.virsecretobj"); static virClassPtr virSecretObjClass; static virClassPtr virSecretObjListClass; static void virSecretObjDispose(void *obj); static void virSecretObjListDispose(void *obj); struct _virSecretObjList { virObjectLockable parent; /* uuid string -> virSecretObj mapping * for O(1), lockless lookup-by-uuid */ virHashTable *objs; }; struct virSecretSearchData { int usageType; const char *usageID; }; static int virSecretObjOnceInit(void) { if (!(virSecretObjClass = virClassNew(virClassForObjectLockable(), "virSecretObj", sizeof(virSecretObj), virSecretObjDispose))) return -1; if (!(virSecretObjListClass = virClassNew(virClassForObjectLockable(), "virSecretObjList", sizeof(virSecretObjList), virSecretObjListDispose))) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virSecretObj) virSecretObjPtr virSecretObjNew(void) { virSecretObjPtr secret; if (virSecretObjInitialize() < 0) return NULL; if (!(secret = virObjectLockableNew(virSecretObjClass))) return NULL; return secret; } void virSecretObjEndAPI(virSecretObjPtr *secret) { if (!*secret) return; virObjectUnlock(*secret); virObjectUnref(*secret); *secret = NULL; } virSecretObjListPtr virSecretObjListNew(void) { virSecretObjListPtr secrets; if (virSecretObjInitialize() < 0) return NULL; if (!(secrets = virObjectLockableNew(virSecretObjListClass))) return NULL; if (!(secrets->objs = virHashCreate(50, virObjectFreeHashData))) { virObjectUnref(secrets); return NULL; } return secrets; } static void virSecretObjDispose(void *obj) { virSecretObjPtr secret = obj; virSecretDefFree(secret->def); if (secret->value) { /* Wipe before free to ensure we don't leave a secret on the heap */ memset(secret->value, 0, secret->value_size); VIR_FREE(secret->value); } VIR_FREE(secret->configFile); VIR_FREE(secret->base64File); } static void virSecretObjListDispose(void *obj) { virSecretObjListPtr secrets = obj; virHashFree(secrets->objs); } /** * virSecretObjFindByUUIDLocked: * @secrets: list of secret objects * @uuid: secret uuid to find * * This functions requires @secrets to be locked already! * * Returns: not locked, but ref'd secret object. */ virSecretObjPtr virSecretObjListFindByUUIDLocked(virSecretObjListPtr secrets, const unsigned char *uuid) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(uuid, uuidstr); return virObjectRef(virHashLookup(secrets->objs, uuidstr)); } /** * virSecretObjFindByUUID: * @secrets: list of secret objects * @uuid: secret uuid to find * * This function locks @secrets and finds the secret object which * corresponds to @uuid. * * Returns: locked and ref'd secret object. */ virSecretObjPtr virSecretObjListFindByUUID(virSecretObjListPtr secrets, const unsigned char *uuid) { virSecretObjPtr ret; virObjectLock(secrets); ret = virSecretObjListFindByUUIDLocked(secrets, uuid); virObjectUnlock(secrets); if (ret) virObjectLock(ret); return ret; } static int virSecretObjSearchName(const void *payload, const void *name ATTRIBUTE_UNUSED, const void *opaque) { virSecretObjPtr secret = (virSecretObjPtr) payload; struct virSecretSearchData *data = (struct virSecretSearchData *) opaque; int found = 0; virObjectLock(secret); if (secret->def->usage_type != data->usageType) goto cleanup; switch (data->usageType) { case VIR_SECRET_USAGE_TYPE_NONE: /* never match this */ break; case VIR_SECRET_USAGE_TYPE_VOLUME: if (STREQ(secret->def->usage.volume, data->usageID)) found = 1; break; case VIR_SECRET_USAGE_TYPE_CEPH: if (STREQ(secret->def->usage.ceph, data->usageID)) found = 1; break; case VIR_SECRET_USAGE_TYPE_ISCSI: if (STREQ(secret->def->usage.target, data->usageID)) found = 1; break; } cleanup: virObjectUnlock(secret); return found; } /** * virSecretObjFindByUsageLocked: * @secrets: list of secret objects * @usageType: secret usageType to find * @usageID: secret usage string * * This functions requires @secrets to be locked already! * * Returns: not locked, but ref'd secret object. */ virSecretObjPtr virSecretObjListFindByUsageLocked(virSecretObjListPtr secrets, int usageType, const char *usageID) { virSecretObjPtr ret = NULL; struct virSecretSearchData data = { .usageType = usageType, .usageID = usageID }; ret = virHashSearch(secrets->objs, virSecretObjSearchName, &data); if (ret) virObjectRef(ret); return ret; } /** * virSecretObjFindByUsage: * @secrets: list of secret objects * @usageType: secret usageType to find * @usageID: secret usage string * * This function locks @secrets and finds the secret object which * corresponds to @usageID of @usageType. * * Returns: locked and ref'd secret object. */ virSecretObjPtr virSecretObjListFindByUsage(virSecretObjListPtr secrets, int usageType, const char *usageID) { virSecretObjPtr ret; virObjectLock(secrets); ret = virSecretObjListFindByUsageLocked(secrets, usageType, usageID); virObjectUnlock(secrets); if (ret) virObjectLock(ret); return ret; } /* * virSecretObjListRemove: * @secrets: list of secret objects * @secret: a secret object * * Remove the object from the hash table. The caller must hold the lock * on the driver owning @secrets and must have also locked @secret to * ensure no one else is either waiting for @secret or still using it. */ void virSecretObjListRemove(virSecretObjListPtr secrets, virSecretObjPtr secret) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(secret->def->uuid, uuidstr); virObjectRef(secret); virObjectUnlock(secret); virObjectLock(secrets); virObjectLock(secret); virHashRemoveEntry(secrets->objs, uuidstr); virObjectUnlock(secret); virObjectUnref(secret); virObjectUnlock(secrets); } /* * virSecretObjListAddLocked: * @secrets: list of secret objects * @def: new secret definition * @configDir: directory to place secret config files * @oldDef: Former secret def (e.g. a reload path perhaps) * * Add the new def to the secret obj table hash * * This functions requires @secrets to be locked already! * * Returns pointer to secret or NULL if failure to add */ virSecretObjPtr virSecretObjListAddLocked(virSecretObjListPtr secrets, virSecretDefPtr def, const char *configDir, virSecretDefPtr *oldDef) { virSecretObjPtr secret; virSecretObjPtr ret = NULL; const char *newUsageID = virSecretUsageIDForDef(def); char uuidstr[VIR_UUID_STRING_BUFLEN]; char *configFile = NULL, *base64File = NULL; if (oldDef) *oldDef = NULL; /* Is there a secret already matching this UUID */ if ((secret = virSecretObjListFindByUUIDLocked(secrets, def->uuid))) { const char *oldUsageID; virObjectLock(secret); oldUsageID = virSecretUsageIDForDef(secret->def); if (STRNEQ(oldUsageID, newUsageID)) { virUUIDFormat(secret->def->uuid, uuidstr); virReportError(VIR_ERR_INTERNAL_ERROR, _("a secret with UUID %s is already defined for " "use with %s"), uuidstr, oldUsageID); goto cleanup; } if (secret->def->private && !def->private) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot change private flag on existing secret")); goto cleanup; } if (oldDef) *oldDef = secret->def; else virSecretDefFree(secret->def); secret->def = def; } else { /* No existing secret with same UUID, * try look for matching usage instead */ if ((secret = virSecretObjListFindByUsageLocked(secrets, def->usage_type, newUsageID))) { virObjectLock(secret); virUUIDFormat(secret->def->uuid, uuidstr); virReportError(VIR_ERR_INTERNAL_ERROR, _("a secret with UUID %s already defined for " "use with %s"), uuidstr, newUsageID); goto cleanup; } /* Generate the possible configFile and base64File strings * using the configDir, uuidstr, and appropriate suffix */ virUUIDFormat(def->uuid, uuidstr); if (!(configFile = virFileBuildPath(configDir, uuidstr, ".xml")) || !(base64File = virFileBuildPath(configDir, uuidstr, ".base64"))) goto cleanup; if (!(secret = virSecretObjNew())) goto cleanup; virObjectLock(secret); if (virHashAddEntry(secrets->objs, uuidstr, secret) < 0) goto cleanup; secret->def = def; secret->configFile = configFile; secret->base64File = base64File; configFile = NULL; base64File = NULL; virObjectRef(secret); } ret = secret; secret = NULL; cleanup: virSecretObjEndAPI(&secret); VIR_FREE(configFile); VIR_FREE(base64File); return ret; } virSecretObjPtr virSecretObjListAdd(virSecretObjListPtr secrets, virSecretDefPtr def, const char *configDir, virSecretDefPtr *oldDef) { virSecretObjPtr ret; virObjectLock(secrets); ret = virSecretObjListAddLocked(secrets, def, configDir, oldDef); virObjectUnlock(secrets); return ret; } struct virSecretObjListGetHelperData { virConnectPtr conn; virSecretObjListACLFilter filter; int got; char **uuids; int nuuids; bool error; }; static int virSecretObjListGetHelper(void *payload, const void *name ATTRIBUTE_UNUSED, void *opaque) { struct virSecretObjListGetHelperData *data = opaque; virSecretObjPtr obj = payload; if (data->error) return 0; if (data->nuuids >= 0 && data->got == data->nuuids) return 0; virObjectLock(obj); if (data->filter && !data->filter(data->conn, obj->def)) goto cleanup; if (data->uuids) { char *uuidstr; if (VIR_ALLOC_N(uuidstr, VIR_UUID_STRING_BUFLEN) < 0) goto cleanup; virUUIDFormat(obj->def->uuid, uuidstr); data->uuids[data->got] = uuidstr; } data->got++; cleanup: virObjectUnlock(obj); return 0; } int virSecretObjListNumOfSecrets(virSecretObjListPtr secrets, virSecretObjListACLFilter filter, virConnectPtr conn) { struct virSecretObjListGetHelperData data = { .conn = conn, .filter = filter, .got = 0, .uuids = NULL, .nuuids = -1, .error = false }; virObjectLock(secrets); virHashForEach(secrets->objs, virSecretObjListGetHelper, &data); virObjectUnlock(secrets); return data.got; } #define MATCH(FLAG) (flags & (FLAG)) static bool virSecretObjMatchFlags(virSecretObjPtr secret, unsigned int flags) { /* filter by whether it's ephemeral */ if (MATCH(VIR_CONNECT_LIST_SECRETS_FILTERS_EPHEMERAL) && !((MATCH(VIR_CONNECT_LIST_SECRETS_EPHEMERAL) && secret->def->ephemeral) || (MATCH(VIR_CONNECT_LIST_SECRETS_NO_EPHEMERAL) && !secret->def->ephemeral))) return false; /* filter by whether it's private */ if (MATCH(VIR_CONNECT_LIST_SECRETS_FILTERS_PRIVATE) && !((MATCH(VIR_CONNECT_LIST_SECRETS_PRIVATE) && secret->def->private) || (MATCH(VIR_CONNECT_LIST_SECRETS_NO_PRIVATE) && !secret->def->private))) return false; return true; } #undef MATCH struct virSecretObjListData { virConnectPtr conn; virSecretPtr *secrets; virSecretObjListACLFilter filter; unsigned int flags; int nsecrets; bool error; }; static int virSecretObjListPopulate(void *payload, const void *name ATTRIBUTE_UNUSED, void *opaque) { struct virSecretObjListData *data = opaque; virSecretObjPtr obj = payload; virSecretPtr secret = NULL; if (data->error) return 0; virObjectLock(obj); if (data->filter && !data->filter(data->conn, obj->def)) goto cleanup; if (!virSecretObjMatchFlags(obj, data->flags)) goto cleanup; if (!data->secrets) { data->nsecrets++; goto cleanup; } if (!(secret = virGetSecret(data->conn, obj->def->uuid, obj->def->usage_type, virSecretUsageIDForDef(obj->def)))) { data->error = true; goto cleanup; } data->secrets[data->nsecrets++] = secret; cleanup: virObjectUnlock(obj); return 0; } int virSecretObjListExport(virConnectPtr conn, virSecretObjListPtr secretobjs, virSecretPtr **secrets, virSecretObjListACLFilter filter, unsigned int flags) { int ret = -1; struct virSecretObjListData data = { .conn = conn, .secrets = NULL, .filter = filter, .flags = flags, .nsecrets = 0, .error = false }; virObjectLock(secretobjs); if (secrets && VIR_ALLOC_N(data.secrets, virHashSize(secretobjs->objs) + 1) < 0) goto cleanup; virHashForEach(secretobjs->objs, virSecretObjListPopulate, &data); if (data.error) goto cleanup; if (data.secrets) { /* trim the array to the final size */ ignore_value(VIR_REALLOC_N(data.secrets, data.nsecrets + 1)); *secrets = data.secrets; data.secrets = NULL; } ret = data.nsecrets; cleanup: virObjectUnlock(secretobjs); while (data.secrets && data.nsecrets) virObjectUnref(data.secrets[--data.nsecrets]); VIR_FREE(data.secrets); return ret; } int virSecretObjListGetUUIDs(virSecretObjListPtr secrets, char **uuids, int nuuids, virSecretObjListACLFilter filter, virConnectPtr conn) { int ret = -1; struct virSecretObjListGetHelperData data = { .conn = conn, .filter = filter, .got = 0, .uuids = uuids, .nuuids = nuuids, .error = false }; virObjectLock(secrets); virHashForEach(secrets->objs, virSecretObjListGetHelper, &data); virObjectUnlock(secrets); if (data.error) goto cleanup; ret = data.got; cleanup: if (ret < 0) { while (data.got) VIR_FREE(data.uuids[--data.got]); } return ret; } int virSecretObjDeleteConfig(virSecretObjPtr secret) { if (!secret->def->ephemeral && unlink(secret->configFile) < 0 && errno != ENOENT) { virReportSystemError(errno, _("cannot unlink '%s'"), secret->configFile); return -1; } return 0; } void virSecretObjDeleteData(virSecretObjPtr secret) { /* The configFile will already be removed, so secret won't be * loaded again if this fails */ (void)unlink(secret->base64File); } /* Permanent secret storage */ /* Secrets are stored in virSecretDriverStatePtr->configDir. Each secret has virSecretDef stored as XML in "$basename.xml". If a value of the secret is defined, it is stored as base64 (with no formatting) in "$basename.base64". "$basename" is in both cases the base64-encoded UUID. */ static int virSecretRewriteFile(int fd, void *opaque) { char *data = opaque; if (safewrite(fd, data, strlen(data)) < 0) return -1; return 0; } int virSecretObjSaveConfig(virSecretObjPtr secret) { char *xml = NULL; int ret = -1; if (!(xml = virSecretDefFormat(secret->def))) goto cleanup; if (virFileRewrite(secret->configFile, S_IRUSR | S_IWUSR, virSecretRewriteFile, xml) < 0) goto cleanup; ret = 0; cleanup: VIR_FREE(xml); return ret; } int virSecretObjSaveData(virSecretObjPtr secret) { char *base64 = NULL; int ret = -1; if (!secret->value) return 0; base64_encode_alloc((const char *)secret->value, secret->value_size, &base64); if (base64 == NULL) { virReportOOMError(); goto cleanup; } if (virFileRewrite(secret->base64File, S_IRUSR | S_IWUSR, virSecretRewriteFile, base64) < 0) goto cleanup; ret = 0; cleanup: VIR_FREE(base64); return ret; } static int virSecretLoadValidateUUID(virSecretDefPtr def, const char *file) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(def->uuid, uuidstr); if (!virFileMatchesNameSuffix(file, uuidstr, ".xml")) { virReportError(VIR_ERR_INTERNAL_ERROR, _(" does not match secret file name '%s'"), file); return -1; } return 0; } static int virSecretLoadValue(virSecretObjPtr secret) { int ret = -1, fd = -1; struct stat st; char *contents = NULL, *value = NULL; size_t value_size; if ((fd = open(secret->base64File, O_RDONLY)) == -1) { if (errno == ENOENT) { ret = 0; goto cleanup; } virReportSystemError(errno, _("cannot open '%s'"), secret->base64File); goto cleanup; } if (fstat(fd, &st) < 0) { virReportSystemError(errno, _("cannot stat '%s'"), secret->base64File); goto cleanup; } if ((size_t)st.st_size != st.st_size) { virReportError(VIR_ERR_INTERNAL_ERROR, _("'%s' file does not fit in memory"), secret->base64File); goto cleanup; } if (VIR_ALLOC_N(contents, st.st_size) < 0) goto cleanup; if (saferead(fd, contents, st.st_size) != st.st_size) { virReportSystemError(errno, _("cannot read '%s'"), secret->base64File); goto cleanup; } VIR_FORCE_CLOSE(fd); if (!base64_decode_alloc(contents, st.st_size, &value, &value_size)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid base64 in '%s'"), secret->base64File); goto cleanup; } if (value == NULL) goto cleanup; secret->value = (unsigned char *)value; value = NULL; secret->value_size = value_size; ret = 0; cleanup: if (value != NULL) { memset(value, 0, value_size); VIR_FREE(value); } if (contents != NULL) { memset(contents, 0, st.st_size); VIR_FREE(contents); } VIR_FORCE_CLOSE(fd); return ret; } static virSecretObjPtr virSecretLoad(virSecretObjListPtr secrets, const char *file, const char *path, const char *configDir) { virSecretDefPtr def = NULL; virSecretObjPtr secret = NULL, ret = NULL; if (!(def = virSecretDefParseFile(path))) goto cleanup; if (virSecretLoadValidateUUID(def, file) < 0) goto cleanup; if (!(secret = virSecretObjListAdd(secrets, def, configDir, NULL))) goto cleanup; def = NULL; if (virSecretLoadValue(secret) < 0) goto cleanup; ret = secret; secret = NULL; cleanup: if (secret) virSecretObjListRemove(secrets, secret); virSecretDefFree(def); return ret; } int virSecretLoadAllConfigs(virSecretObjListPtr secrets, const char *configDir) { DIR *dir = NULL; struct dirent *de; if (!(dir = opendir(configDir))) { if (errno == ENOENT) return 0; virReportSystemError(errno, _("cannot open '%s'"), configDir); return -1; } /* Ignore errors reported by readdir or other calls within the * loop (if any). It's better to keep the secrets we managed to find. */ while (virDirRead(dir, &de, NULL) > 0) { char *path; virSecretObjPtr secret; if (STREQ(de->d_name, ".") || STREQ(de->d_name, "..")) continue; if (!virFileHasSuffix(de->d_name, ".xml")) continue; if (!(path = virFileBuildPath(configDir, de->d_name, NULL))) continue; if (!(secret = virSecretLoad(secrets, de->d_name, path, configDir))) { virErrorPtr err = virGetLastError(); VIR_ERROR(_("Error reading secret: %s"), err != NULL ? err->message: _("unknown error")); virResetError(err); VIR_FREE(path); continue; } VIR_FREE(path); virSecretObjEndAPI(&secret); } closedir(dir); return 0; }