This implementation stores the secrets in an unencrypted text file, for simplicity in implementation and debugging. (Symmetric encryption, e.g. using gpgme, will not be difficult to add. Because the TLS private key used by libvirtd is stored unencrypted, encrypting the secrets file does not currently provide much additional security.) --- include/libvirt/virterror.h | 1 + po/POTFILES.in | 1 + qemud/qemud.c | 3 + src/Makefile.am | 14 + src/libvirt_private.syms | 2 + src/secret_driver.c | 1077 +++++++++++++++++++++++++++++++++++++++++++ src/secret_driver.h | 28 ++ src/test.c | 21 + src/virterror.c | 6 + 9 files changed, 1153 insertions(+), 0 deletions(-) create mode 100644 src/secret_driver.c create mode 100644 src/secret_driver.h diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 64e0143..fc24251 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -168,6 +168,7 @@ typedef enum { VIR_ERR_INVALID_INTERFACE, /* invalid interface object */ VIR_ERR_MULTIPLE_INTERFACES, /* more than one matching interface found */ VIR_WAR_NO_SECRET, /* failed to start secret storage */ + VIR_ERR_NO_SECRET, /* secret not found */ } virErrorNumber; /** diff --git a/po/POTFILES.in b/po/POTFILES.in index 141f58b..cf0ec8d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -29,6 +29,7 @@ src/proxy_internal.c src/qemu_conf.c src/qemu_driver.c src/remote_internal.c +src/secret_driver.c src/security.c src/security_selinux.c src/storage_backend.c diff --git a/qemud/qemud.c b/qemud/qemud.c index 3e551ca..01426da 100644 --- a/qemud/qemud.c +++ b/qemud/qemud.c @@ -92,6 +92,7 @@ #ifdef WITH_NODE_DEVICES #include "node_device.h" #endif +#include "secret_driver.h" #endif @@ -819,6 +820,7 @@ static struct qemud_server *qemudInitialize(int sigread) { virDriverLoadModule("network"); virDriverLoadModule("storage"); virDriverLoadModule("nodedev"); + virDriverLoadModule("secret"); virDriverLoadModule("qemu"); virDriverLoadModule("lxc"); virDriverLoadModule("uml"); @@ -837,6 +839,7 @@ static struct qemud_server *qemudInitialize(int sigread) { (defined(HAVE_HAL) || defined(HAVE_DEVKIT)) nodedevRegister(); #endif + secretRegister(); #ifdef WITH_QEMU qemuRegister(); #endif diff --git a/src/Makefile.am b/src/Makefile.am index 9567490..ce33695 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -176,6 +176,9 @@ NETWORK_DRIVER_SOURCES = \ INTERFACE_DRIVER_SOURCES = \ interface_driver.h interface_driver.c +SECRET_DRIVER_SOURCES = \ + secret_driver.h secret_driver.c + # Storage backend specific impls STORAGE_DRIVER_SOURCES = \ storage_driver.h storage_driver.c \ @@ -448,6 +451,17 @@ endif libvirt_driver_interface_la_SOURCES = $(INTERFACE_DRIVER_SOURCES) endif +if WITH_DRIVER_MODULES +mod_LTLIBRARIES += libvirt_driver_secret.la +else +noinst_LTLIBRARIES += libvirt_driver_secret.la +libvirt_la_LIBADD += libvirt_driver_secret.la +endif +if WITH_DRIVER_MODULES +libvirt_driver_secret_la_LDFLAGS = -module -avoid-version +endif +libvirt_driver_secret_la_SOURCES = $(SECRET_DRIVER_SOURCES) + # Needed to keep automake quiet about conditionals libvirt_driver_storage_la_SOURCES = if WITH_STORAGE_DIR diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index bd63692..343a71e 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -285,6 +285,8 @@ qparam_get_query; qparam_query_parse; free_qparam_set; +# secret.h +secretRegister; # security.h virSecurityDriverVerify; diff --git a/src/secret_driver.c b/src/secret_driver.c new file mode 100644 index 0000000..07e4642 --- /dev/null +++ b/src/secret_driver.c @@ -0,0 +1,1077 @@ +/* + * secret_driver.c: local driver for secret manipulation API + * + * Copyright (C) 2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Red Hat Author: Miloslav TrmaÄ? <mitr@xxxxxxxxxx> + */ + +#include <config.h> + +#include <fcntl.h> +#include <stdbool.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "internal.h" +#include "base64.h" +#include "buf.h" +#include "datatypes.h" +#include "driver.h" +#include "memory.h" +#include "secret_driver.h" +#include "threads.h" +#include "util.h" +#include "uuid.h" +#include "virterror_internal.h" +#include "xml.h" + +#define VIR_FROM_THIS VIR_FROM_SECRET + +#define virSecretReportError(conn, code, fmt...) \ + virReportErrorHelper(conn, VIR_FROM_SECRET, code, __FILE__, \ + __FUNCTION__, __LINE__, fmt) + +#define secretLog(msg...) fprintf(stderr, msg) + +typedef struct _virSecret virSecret; +typedef virSecret *virSecretPtr; +struct _virSecret { + virSecretPtr next; + char *id; /* We generate UUIDs, but don't restrict user-chosen IDs */ + void *value; /* May be NULL */ + size_t value_size; + unsigned ephemeral : 1; + unsigned private : 1; + char *description, *volume; /* May be NULL */ +}; + +typedef struct _virSecretDriverState virSecretDriverState; +typedef virSecretDriverState *virSecretDriverStatePtr; +struct _virSecretDriverState { + virMutex lock; + virSecret *secrets; + char *filename; +}; + +static virSecretDriverStatePtr driverState; + +static void +secretDriverLock(virSecretDriverStatePtr driver) +{ + virMutexLock(&driver->lock); +} + +static void +secretDriverUnlock(virSecretDriverStatePtr driver) +{ + virMutexUnlock(&driver->lock); +} + +static virSecretPtr +listUnlink(virSecretPtr *pptr) +{ + virSecretPtr secret; + + secret = *pptr; + *pptr = secret->next; + return secret; +} + +static void +listInsert(virSecretPtr *pptr, virSecretPtr secret) +{ + secret->next = *pptr; + *pptr = secret; +} + +static void +secretFree(virSecretPtr secret) +{ + if (secret == NULL) + return; + + VIR_FREE(secret->id); + if (secret->value != NULL) { + memset(secret->value, 0, secret->value_size); + VIR_FREE(secret->value); + } + VIR_FREE(secret->description); + VIR_FREE(secret->volume); + VIR_FREE(secret); +} + +static virSecretPtr * +secretFind(virSecretDriverStatePtr driver, const char *secret_id) +{ + virSecretPtr *pptr, s; + + for (pptr = &driver->secrets; (s = *pptr) != NULL; pptr = &s->next) { + if (STREQ(s->id, secret_id)) + return pptr; + } + return NULL; +} + +static virSecretPtr +secretCreate(virConnectPtr conn, virSecretDriverStatePtr driver, + const char *secret_id) +{ + virSecretPtr secret = NULL; + + if (VIR_ALLOC(secret) < 0) + goto no_memory; + secret->id = strdup(secret_id); + if (secret->id == NULL) + goto no_memory; + listInsert(&driver->secrets, secret); + return secret; + + no_memory: + virReportOOMError(conn); + secretFree(secret); + return NULL; +} + +static virSecretPtr +secretFindOrCreate(virConnectPtr conn, virSecretDriverStatePtr driver, + const char *secret_id, bool *created_new) +{ + virSecretPtr *pptr, secret; + + pptr = secretFind(driver, secret_id); + if (pptr != NULL) { + if (created_new != NULL) + *created_new = false; + return *pptr; + } + + secret = secretCreate(conn, driver, secret_id); + if (secret != NULL && created_new != NULL) + *created_new = true; + return secret; +} + +/* The secret storage file format is intentionally simplistic, in order to + minimize the number of copies of unencrypted secrets in memory. */ + +static int +writeString(virConnectPtr conn, int fd, const char *s) +{ + int ret; + + ret = safewrite(fd, s, strlen(s)); + if (ret < 0) + virReportSystemError (conn, errno, "%s", + _("cannot write secrets file")); + return ret; +} + +static int +writeBase64Data(virConnectPtr conn, int fd, const char *field, + const void *data, size_t size) +{ + int ret = -1; + char *base64 = NULL; + + if (writeString(conn, fd, field) < 0 || writeString(conn, fd, " ") < 0) + goto cleanup; + + base64_encode_alloc(data, size, &base64); + if (base64 == NULL) { + virReportOOMError(conn); + goto cleanup; + } + if (writeString(conn, fd, base64) < 0 || writeString(conn, fd, "\n") < 0) + goto cleanup; + ret = 0; + + cleanup: + VIR_FREE(base64); + return ret; +} + +static int +writeSecret(virConnectPtr conn, int fd, const virSecret *secret) +{ + const char *s; + + if (writeBase64Data(conn, fd, "id", secret->id, strlen(secret->id)) < 0) + return -1; + + if (secret->ephemeral) + s = "ephemeral yes\n"; + else + s = "ephemeral no\n"; + if (writeString(conn, fd, s) < 0) + return -1; + + if (secret->private) + s = "private yes\n"; + else + s = "private no\n"; + if (writeString(conn, fd, s) < 0) + return -1; + + if (secret->value != NULL && + writeBase64Data(conn, fd, "value", secret->value, + secret->value_size) < 0) + return -1; + if (secret->description != NULL && + writeBase64Data(conn, fd, "description", secret->description, + strlen(secret->description)) < 0) + return -1; + if (secret->volume != NULL && + writeBase64Data(conn, fd, "volume", secret->volume, + strlen(secret->volume)) < 0) + return -1; + + return 0; +} + +static int +saveSecrets(virConnectPtr conn, virSecretDriverStatePtr driver) +{ + const virSecret *secret; + char *tmp_path = NULL; + int fd = -1, ret = -1; + + if (virAsprintf(&tmp_path, "%sXXXXXX", driver->filename) < 0) { + virReportOOMError(conn); + goto cleanup; + } + fd = mkstemp (tmp_path); + if (fd == -1) { + virReportSystemError (conn, errno, _("mkstemp(\"%s\") failed"), + tmp_path); + goto cleanup; + } + + for (secret = driver->secrets; secret != NULL; secret = secret->next) { + if (!secret->ephemeral && writeSecret(conn, fd, secret) < 0) + goto cleanup; + } + close(fd); + fd = -1; + if (rename(tmp_path, driver->filename) < 0) { + virReportSystemError (conn, errno, _("rename(%s, %s) failed"), + tmp_path, driver->filename); + goto cleanup; + } + tmp_path = NULL; + ret = 0; + + cleanup: + if (fd != -1) + close(fd); + if (tmp_path != NULL) { + unlink(tmp_path); + VIR_FREE(tmp_path); + } + return ret; +} + +static int +parseBase64String(virConnectPtr conn, const char *base64, char **string) +{ + char *tmp; + size_t size; + + if (!base64_decode_alloc(base64, strlen(base64), &tmp, &size)) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid format of base64 in secret storage")); + return -1; + } + if (tmp == NULL || VIR_ALLOC_N(*string, size + 1) < 0) { + virReportOOMError(conn); + return -1; + } + + memcpy(*string, tmp, size); + (*string)[size] = '\0'; + return 0; +} + +static int +parseKeyValue(virConnectPtr conn, virSecretPtr *list, virSecretPtr *secret, + const char *key, const char *value) +{ + virSecretPtr s; + + s = *secret; + if (s == NULL) { + if (VIR_ALLOC(s) < 0) + goto no_memory; + *secret = s; + } + if (STREQ(key, "id")) { + if (s->id != NULL) { + listInsert(list, s); + if (VIR_ALLOC(s) < 0) + goto no_memory; + *secret = s; + } + if (parseBase64String(conn, value, &s->id) < 0) + return -1; + } else if (STREQ(key, "ephemeral")) { + if (STREQ(value, "yes")) + s->ephemeral = 1; + else if (STREQ(value, "no")) + s->ephemeral = 0; + else + goto invalid; + } else if (STREQ(key, "private")) { + if (STREQ(value, "yes")) + s->private = 1; + else if (STREQ(value, "no")) + s->private = 0; + else + goto invalid; + } else if (STREQ(key, "value")) { + char *raw; + + if (s->value != NULL) + return -1; + if (!base64_decode_alloc(value, strlen(value), &raw, &s->value_size)) + goto invalid; + if (raw == NULL) + goto no_memory; + s->value = raw; + } else if (STREQ(key, "description")) { + if (s->description != NULL) + goto invalid; + if (parseBase64String(conn, value, &s->description) < 0) + return -1; + } else if (STREQ(key, "volume")) { + if (s->volume != NULL) + goto invalid; + if (parseBase64String(conn, value, &s->volume) < 0) + return -1; + } else + goto invalid; + + return 0; + + invalid: + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid format of secret storage")); + return -1; + + no_memory: + virReportOOMError(conn); + return -1; +} + +static int +loadSecrets(virConnectPtr conn, virSecretDriverStatePtr driver, + virSecretPtr *dest) +{ + int ret = -1, fd = -1; + struct stat st; + char *contents = NULL, *strtok_data = NULL, *strtok_first; + const char *key, *value; + virSecretPtr secret = NULL, list = NULL; + + if (stat(driver->filename, &st) < 0) { + if (errno == ENOENT) + return 0; + virReportSystemError (conn, errno, _("cannot stat '%s'"), + driver->filename); + goto cleanup; + } + if ((size_t)st.st_size != st.st_size || (size_t)(st.st_size + 1) == 0) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("secrets file does not fit in memory")); + goto cleanup; + } + + fd = open(driver->filename, O_RDONLY); + if (fd == -1) { + virReportSystemError (conn, errno, _("cannot open '%s'"), + driver->filename); + goto cleanup; + } + if (VIR_ALLOC_N(contents, st.st_size + 1) < 0) { + virReportOOMError(conn); + goto cleanup; + } + if (saferead(fd, contents, st.st_size) != st.st_size) { + virReportSystemError (conn, errno, _("cannot read '%s'"), + driver->filename); + goto cleanup; + } + close(fd); + fd = -1; + + strtok_first = contents; + while ((key = strtok_r(strtok_first, " ", &strtok_data)) != NULL) { + strtok_first = NULL; + value = strtok_r(strtok_first, "\n", &strtok_data); + if (value == NULL) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid format of secret storage")); + goto cleanup; + } + if (parseKeyValue(conn, &list, &secret, key, value) < 0) + goto cleanup; + } + if (secret != NULL) { + if (secret->id == NULL) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid format of secret storage")); + goto cleanup; + } + listInsert(&list, secret); + } + + /* The secrets were collected into "list" in reverse order. This happens + to reverse the order again, preserving the original order of secrets + in the file. */ + while (list != NULL) { + secret = listUnlink(&list); + listInsert(dest, secret); + } + secret = NULL; + + ret = 0; + goto cleanup; + + + cleanup: + secretFree(secret); + while (list != NULL) { + secret = listUnlink(&list); + secretFree(secret); + } + if (fd != -1) + close(fd); + if (contents != NULL) { + memset(contents, 0, st.st_size); + VIR_FREE(contents); + } + return ret; +} + +static virSecretPtr +secretXMLParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root) +{ + xmlXPathContextPtr ctxt = NULL; + virSecretPtr secret, ret = NULL; + char *prop; + + if (!xmlStrEqual(root->name, BAD_CAST "secret")) { + virSecretReportError(conn, VIR_ERR_XML_ERROR, "%s", + _("incorrect root element")); + goto cleanup; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(conn); + goto cleanup; + } + ctxt->node = root; + + if (VIR_ALLOC(secret) < 0) { + virReportOOMError(conn); + goto cleanup; + } + + prop = virXPathString(conn, "string(./@ephemeral)", ctxt); + if (prop != NULL && STREQ(prop, "yes")) + secret->ephemeral = 1; + VIR_FREE(prop); + + prop = virXPathString(conn, "string(./@private)", ctxt); + if (prop != NULL && STREQ(prop, "yes")) + secret->private = 1; + VIR_FREE(prop); + + secret->description = virXPathString(conn, "string(./description)", ctxt); + secret->volume = virXPathString(conn, "string(./volume)", ctxt); + + ret = secret; + secret = NULL; + + cleanup: + secretFree(secret); + xmlXPathFreeContext(ctxt); + return ret; +} + +/* Called from SAX on parsing errors in the XML. */ +static void +catchXMLError(void *ctx, const char *msg ATTRIBUTE_UNUSED, ...) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx; + + if (ctxt) { + virConnectPtr conn = ctxt->_private; + + if (virGetLastError() == NULL && + ctxt->lastError.level == XML_ERR_FATAL && + ctxt->lastError.message != NULL) { + virSecretReportError(conn, VIR_ERR_XML_DETAIL, _("at line %d: %s"), + ctxt->lastError.line, ctxt->lastError.message); + } + } +} + +static virSecretPtr +secretXMLParseString(virConnectPtr conn, const char *xmlStr) +{ + xmlParserCtxtPtr pctxt; + xmlDocPtr xml = NULL; + xmlNodePtr root; + virSecretPtr ret = NULL; + + pctxt = xmlNewParserCtxt(); + if (pctxt == NULL || pctxt->sax == NULL) + goto cleanup; + pctxt->sax->error = catchXMLError; + pctxt->_private = conn; + + xml = xmlCtxtReadDoc(pctxt, BAD_CAST xmlStr, "secret.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (xml == NULL) { + if (conn->err.code == VIR_ERR_NONE) + virSecretReportError(conn, VIR_ERR_XML_ERROR, "%s", + _("failed to parse xml document")); + goto cleanup; + } + + root = xmlDocGetRootElement(xml); + if (root == NULL) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("missing root element")); + goto cleanup; + } + + ret = secretXMLParseNode(conn, xml, root); + + cleanup: + xmlFreeDoc(xml); + xmlFreeParserCtxt(pctxt); + return ret; +} + +static char * +secretXMLFormat(virConnectPtr conn, const virSecret *secret) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *tmp; + + virBufferVSprintf(&buf, "<secret ephemeral='%s' private='%s'>\n", + secret->ephemeral ? "yes" : "no", + secret->private ? "yes" : "no"); + if (secret->description != NULL) + virBufferEscapeString(&buf, " <description>%s</description>\n", + secret->description); + if (secret->volume != NULL) + virBufferEscapeString(&buf, " <volume>%s</volume>\n", secret->volume); + virBufferAddLit(&buf, "</secret>\n"); + + if (virBufferError(&buf)) + goto no_memory; + + return virBufferContentAndReset(&buf); + + no_memory: + virReportOOMError(conn); + tmp = virBufferContentAndReset(&buf); + VIR_FREE(tmp); + return NULL; +} + +static virDrvOpenStatus +secretOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED, + int flags ATTRIBUTE_UNUSED) { + if (driverState == NULL) + return VIR_DRV_OPEN_DECLINED; + + conn->secretPrivateData = driverState; + return VIR_DRV_OPEN_SUCCESS; +} + +static int +secretClose(virConnectPtr conn) { + conn->secretPrivateData = NULL; + return 0; +} + +static char * +secretAllocateID(virConnectPtr conn) +{ + char *secret_id, *ret = NULL; + unsigned attempt; + virSecretDriverStatePtr driver = conn->secretPrivateData; + + if (VIR_ALLOC_N(secret_id, VIR_UUID_STRING_BUFLEN) < 0) { + virReportOOMError(conn); + return NULL; + } + + secretDriverLock(driver); + + for (attempt = 0; attempt < 65536; attempt++) { + unsigned char uuid[VIR_UUID_BUFLEN]; + + if (virUUIDGenerate(uuid) < 0) { + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to generate uuid")); + goto cleanup; + } + virUUIDFormat(uuid, secret_id); + if (secretFind(driver, secret_id) == NULL) { + virSecretPtr s; + + s = secretCreate(conn, driver, secret_id); + if (s != NULL) { + ret = secret_id; + secret_id = NULL; + } + goto cleanup; + } + } + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("too many conflicts when generating an uuid")); + + cleanup: + secretDriverUnlock(driver); + + VIR_FREE(secret_id); + return ret; +} + +static void +shallowCopyAttributes(virSecretPtr dest, const virSecret *src) +{ + dest->ephemeral = src->ephemeral; + dest->private = src->private; + dest->description = src->description; + dest->volume = src->volume; +} + +static int +secretSetXML(virConnectPtr conn, const char *secret_id, const char *xml) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + int ret = -1; + virSecret backup; + virSecretPtr secret, new_attrs; + bool secret_is_new; + + new_attrs = secretXMLParseString(conn, xml); + if (new_attrs == NULL) + return -1; + + secretDriverLock(driver); + + secret = secretFindOrCreate(conn, driver, secret_id, &secret_is_new); + if (secret == NULL) + goto cleanup; + + /* Save old values of the attributes */ + shallowCopyAttributes(&backup, secret); + + if (backup.private && !new_attrs->private) { + virSecretReportError(conn, VIR_ERR_OPERATION_DENIED, "%s", + virErrorMsg(VIR_ERR_OPERATION_DENIED, NULL)); + goto cleanup; + } + + shallowCopyAttributes(secret, new_attrs); + if (!new_attrs->ephemeral || !backup.ephemeral) { + if (saveSecrets(conn, driver) < 0) + goto restore_backup; + } + /* Saved succesfully - drop old values */ + new_attrs = NULL; + VIR_FREE(backup.description); + VIR_FREE(backup.volume); + + ret = 0; + goto cleanup; + + restore_backup: + /* Error - restore previous state and free new attributes */ + shallowCopyAttributes(secret, &backup); + if (secret_is_new) { + /* "secret" was added to the head of the list above */ + if (listUnlink(&driverState->secrets) != secret) + /* abort() instead? */ + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("list of secrets is inconsistent")); + else + secretFree(secret); + } + + cleanup: + secretFree(new_attrs); + secretDriverUnlock(driver); + + return ret; +} + +static char * +secretGetXML(virConnectPtr conn, const char *secret_id) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + char *ret = NULL; + virSecretPtr *pptr; + + secretDriverLock(driver); + + pptr = secretFind(driver, secret_id); + if (pptr == NULL) { + virSecretReportError(conn, VIR_ERR_NO_SECRET, + _("no secret with matching id '%s'"), secret_id); + goto cleanup; + } + + ret = secretXMLFormat(conn, *pptr); + + cleanup: + secretDriverUnlock(driver); + + return ret; +} + +static int +secretSetValue(virConnectPtr conn, const char *secret_id, const void *value, + size_t value_size) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + int ret = -1; + void *old_value; + unsigned char *new_value; + size_t old_value_size; + virSecretPtr secret; + bool secret_is_new; + + if (VIR_ALLOC_N(new_value, value_size) < 0) { + virReportOOMError(conn); + return -1; + } + + secretDriverLock(driver); + + secret = secretFindOrCreate(conn, driver, secret_id, &secret_is_new); + if (secret == NULL) + goto cleanup; + + old_value = secret->value; + old_value_size = secret->value_size; + + memcpy(new_value, value, value_size); + secret->value = new_value; + secret->value_size = value_size; + if (!secret->ephemeral) { + if (saveSecrets(conn, driver) < 0) + goto restore_backup; + } + /* Saved succesfully - drop old value */ + if (old_value != NULL) { + memset(old_value, 0, old_value_size); + VIR_FREE(old_value); + } + new_value = NULL; + + ret = 0; + goto cleanup; + + restore_backup: + /* Error - restore previous state and free new value */ + secret->value = old_value; + secret->value_size = old_value_size; + memset(new_value, 0, value_size); + if (secret_is_new) { + /* "secret" was added to the head of the list above */ + if (listUnlink(&driverState->secrets) != secret) + /* abort() instead? */ + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("list of secrets is inconsistent")); + else + secretFree(secret); + } + + cleanup: + secretDriverUnlock(driver); + + VIR_FREE(new_value); + + return ret; +} + +static void * +secretGetValue(virConnectPtr conn, const char *secret_id, size_t *secret_size, + bool libvirt_internal_call) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + unsigned char *ret = NULL; + virSecretPtr *pptr, secret; + + secretDriverLock(driver); + + pptr = secretFind(driver, secret_id); + if (pptr == NULL || (*pptr)->value == NULL) { + virSecretReportError(conn, VIR_ERR_NO_SECRET, + _("no secret with matching id '%s'"), secret_id); + goto cleanup; + } + secret = *pptr; + + if (!libvirt_internal_call && secret->private) { + virSecretReportError(conn, VIR_ERR_OPERATION_DENIED, "%s", + virErrorMsg(VIR_ERR_OPERATION_DENIED, NULL)); + goto cleanup; + } + + if (VIR_ALLOC_N(ret, secret->value_size) < 0) { + virReportOOMError(conn); + goto cleanup; + } + memcpy(ret, secret->value, secret->value_size); + *secret_size = secret->value_size; + + cleanup: + secretDriverUnlock(driver); + + return ret; +} + +static int +secretDelete(virConnectPtr conn, const char *secret_id) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + int ret = -1; + virSecretPtr *pptr, secret; + + secretDriverLock(driver); + + pptr = secretFind(driver, secret_id); + if (pptr == NULL) { + virSecretReportError(conn, VIR_ERR_NO_SECRET, + _("no secret with matching id '%s'"), secret_id); + goto cleanup; + } + + secret = listUnlink(pptr); + if (!secret->ephemeral) { + if (saveSecrets(conn, driver) < 0) + goto restore_backup; + } + secretFree(secret); + + ret = 0; + goto cleanup; + + restore_backup: + /* This may change the order of secrets in the list. We don't care. */ + listInsert(&driver->secrets, secret); + + cleanup: + secretDriverUnlock(driver); + + return ret; +} + +static int +secretNumOfSecrets(virConnectPtr conn) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + int i; + virSecretPtr secret; + + secretDriverLock(driver); + + i = 0; + for (secret = driver->secrets; secret != NULL; secret = secret->next) + i++; + + secretDriverUnlock(driver); + return i; +} + +static int +secretListSecrets(virConnectPtr conn, char **ids, int maxids) +{ + virSecretDriverStatePtr driver = conn->secretPrivateData; + int i; + virSecretPtr secret; + + memset(ids, 0, maxids * sizeof(*ids)); + + secretDriverLock(driver); + + i = 0; + for (secret = driver->secrets; secret != NULL; secret = secret->next) { + if (i == maxids) + break; + ids[i] = strdup(secret->id); + if (ids[i] == NULL) + goto cleanup; + i++; + } + + secretDriverUnlock(driver); + return i; + + cleanup: + secretDriverUnlock(driver); + + for (i = 0; i < maxids; i++) + VIR_FREE(ids[i]); + + return -1; +} + +static int +secretDriverCleanup(void) +{ + if (driverState == NULL) + return -1; + + secretDriverLock(driverState); + + while (driverState->secrets != NULL) { + virSecretPtr s; + + s = listUnlink(&driverState->secrets); + secretFree(s); + } + VIR_FREE(driverState->filename); + + secretDriverUnlock(driverState); + virMutexDestroy(&driverState->lock); + VIR_FREE(driverState); + + return 0; +} + +static int +secretDriverStartup(int privileged) +{ + char *base = NULL; + + if (VIR_ALLOC(driverState) < 0) + return -1; + + if (virMutexInit(&driverState->lock) < 0) { + VIR_FREE(driverState); + return -1; + } + secretDriverLock(driverState); + + if (privileged) { + base = strdup(SYSCONF_DIR "/libvirt"); + if (base == NULL) + goto out_of_memory; + } else { + uid_t uid = geteuid(); + char *userdir = virGetUserDirectory(NULL, uid); + + if (!userdir) + goto error; + + if (virAsprintf(&base, "%s/.libvirt", userdir) == -1) { + secretLog("out of memory in virAsprintf"); + VIR_FREE(userdir); + goto out_of_memory; + } + VIR_FREE(userdir); + } + if (virAsprintf(&driverState->filename, "%s/secrets", base) == -1) + goto out_of_memory; + VIR_FREE(base); + + if (loadSecrets(NULL, driverState, &driverState->secrets) < 0) + goto error; + + secretDriverUnlock(driverState); + return 0; + + out_of_memory: + secretLog("virSecretStartup: out of memory"); + error: + VIR_FREE(base); + secretDriverUnlock(driverState); + secretDriverCleanup(); + return -1; +} + +static int +secretDriverReload(void) +{ + virSecretPtr new_secrets = NULL; + + if (!driverState) + return -1; + + secretDriverLock(driverState); + + if (loadSecrets(NULL, driverState, &new_secrets) < 0) + goto end; + + /* Keep ephemeral secrets from current state. Discard non-ephemeral secrets + that were removed by the secrets file. */ + while (driverState->secrets != NULL) { + virSecretPtr s; + + s = listUnlink(&driverState->secrets); + if (s->ephemeral) + listInsert(&new_secrets, s); + else + secretFree(s); + } + driverState->secrets = new_secrets; + + end: + secretDriverUnlock(driverState); + return 0; +} + +static virSecretDriver secretDriver = { + .name = "secret", + .open = secretOpen, + .close = secretClose, + .allocateID = secretAllocateID, + .setXML = secretSetXML, + .getXML = secretGetXML, + .setValue = secretSetValue, + .getValue = secretGetValue, + .delete = secretDelete, + .numOfSecrets = secretNumOfSecrets, + .listSecrets = secretListSecrets +}; + +static virStateDriver stateDriver = { + .initialize = secretDriverStartup, + .cleanup = secretDriverCleanup, + .reload = secretDriverReload, + .active = NULL /* All persistent state is immediately saved to disk */ +}; + +int +secretRegister(void) +{ + virRegisterSecretDriver(&secretDriver); + virRegisterStateDriver(&stateDriver); + return 0; +} diff --git a/src/secret_driver.h b/src/secret_driver.h new file mode 100644 index 0000000..0d0b80a --- /dev/null +++ b/src/secret_driver.h @@ -0,0 +1,28 @@ +/* + * secret_driver.h: local driver for secret manipulation API + * + * Copyright (C) 2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Red Hat Author: Miloslav TrmaÄ? <mitr@xxxxxxxxxx> + */ + +#ifndef __VIR_SECRET_DRIVER_H__ +#define __VIR_SECRET_DRIVER_H__ + +int secretRegister(void); + +#endif /* __VIR_SECRET_DRIVER_H__ */ diff --git a/src/test.c b/src/test.c index 305f2c9..7c8f85b 100644 --- a/src/test.c +++ b/src/test.c @@ -4173,6 +4173,20 @@ static void testDomainEventQueue(testConnPtr driver, virEventUpdateTimeout(driver->domainEventTimer, 0); } +static virDrvOpenStatus testSecretOpen(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + int flags ATTRIBUTE_UNUSED) { + if (STRNEQ(conn->driver->name, "Test")) + return VIR_DRV_OPEN_DECLINED; + + conn->secretPrivateData = conn->privateData; + return VIR_DRV_OPEN_SUCCESS; +} + +static int testSecretClose(virConnectPtr conn) { + conn->secretPrivateData = NULL; + return 0; +} static virDriver testDriver = { VIR_DRV_TEST, @@ -4328,6 +4342,11 @@ static virDeviceMonitor testDevMonitor = { .close = testDevMonClose, }; +static virSecretDriver testSecretDriver = { + .name = "Test", + .open = testSecretOpen, + .close = testSecretClose, +}; /** @@ -4348,6 +4367,8 @@ testRegister(void) return -1; if (virRegisterDeviceMonitor(&testDevMonitor) < 0) return -1; + if (virRegisterSecretDriver(&testSecretDriver) < 0) + return -1; return 0; } diff --git a/src/virterror.c b/src/virterror.c index ba66238..137405a 100644 --- a/src/virterror.c +++ b/src/virterror.c @@ -1077,6 +1077,12 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("Failed to find a secret storage driver: %s"); break; + case VIR_ERR_NO_SECRET: + if (info == NULL) + errmsg = _("Secret not found"); + else + errmsg = _("Secret not found: %s"); + break; } return (errmsg); } -- 1.6.2.5 -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list