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.)
Changes since the second submission:
- Update for the changed internal API
- Fix some memory leaks
- Use a clearer error message in secretGetValue()
- s/secret_id/uuid/g
- Use "unsigned char *" for secret value
---
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 | 1102 +++++++++++++++++++++++++++++++++++++++++++
src/secret_driver.h | 28 ++
src/test.c | 21 +
src/virterror.c | 5 +
9 files changed, 1177 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 62cad88..fa5cac4 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -169,6 +169,7 @@ typedef enum {
VIR_ERR_MULTIPLE_INTERFACES, /* more than one matching interface found */
VIR_WAR_NO_SECRET, /* failed to start secret storage */
VIR_ERR_INVALID_SECRET, /* invalid secret */
+ VIR_ERR_NO_SECRET, /* secret not found */
} virErrorNumber;
/**
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 66d3ebd..e9d388a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -30,6 +30,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 e657cf2..ec7c021 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
@@ -814,6 +815,7 @@ static struct qemud_server *qemudInitialize(int sigread) {
virDriverLoadModule("network");
virDriverLoadModule("storage");
virDriverLoadModule("nodedev");
+ virDriverLoadModule("secret");
virDriverLoadModule("qemu");
virDriverLoadModule("lxc");
virDriverLoadModule("uml");
@@ -832,6 +834,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 61f18e6..dd691be 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -295,6 +295,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..d9e638c
--- /dev/null
+++ b/src/secret_driver.c
@@ -0,0 +1,1102 @@
+/*
+ * 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(a)redhat.com>
+ */
+
+#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 _virSecretEntry virSecretEntry;
+typedef virSecretEntry *virSecretEntryPtr;
+struct _virSecretEntry {
+ virSecretEntryPtr next;
+ char *id; /* We generate UUIDs, but don't restrict user-chosen IDs */
+ unsigned char *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;
+ virSecretEntry *secrets;
+ char *filename;
+};
+
+static virSecretDriverStatePtr driverState;
+
+static void
+secretDriverLock(virSecretDriverStatePtr driver)
+{
+ virMutexLock(&driver->lock);
+}
+
+static void
+secretDriverUnlock(virSecretDriverStatePtr driver)
+{
+ virMutexUnlock(&driver->lock);
+}
+
+static virSecretEntryPtr
+listUnlink(virSecretEntryPtr *pptr)
+{
+ virSecretEntryPtr secret;
+
+ secret = *pptr;
+ *pptr = secret->next;
+ return secret;
+}
+
+static void
+listInsert(virSecretEntryPtr *pptr, virSecretEntryPtr secret)
+{
+ secret->next = *pptr;
+ *pptr = secret;
+}
+
+static void
+secretFree(virSecretEntryPtr 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 virSecretEntryPtr *
+secretFind(virSecretDriverStatePtr driver, const char *uuid)
+{
+ virSecretEntryPtr *pptr, s;
+
+ for (pptr = &driver->secrets; (s = *pptr) != NULL; pptr = &s->next) {
+ if (STREQ(s->id, uuid))
+ return pptr;
+ }
+ return NULL;
+}
+
+static virSecretEntryPtr
+secretCreate(virConnectPtr conn, virSecretDriverStatePtr driver,
+ const char *uuid)
+{
+ virSecretEntryPtr secret = NULL;
+
+ if (VIR_ALLOC(secret) < 0)
+ goto no_memory;
+ secret->id = strdup(uuid);
+ if (secret->id == NULL)
+ goto no_memory;
+ listInsert(&driver->secrets, secret);
+ return secret;
+
+ no_memory:
+ virReportOOMError(conn);
+ secretFree(secret);
+ return NULL;
+}
+
+static virSecretEntryPtr
+secretFindOrCreate(virConnectPtr conn, virSecretDriverStatePtr driver,
+ const char *uuid, bool *created_new)
+{
+ virSecretEntryPtr *pptr, secret;
+
+ pptr = secretFind(driver, uuid);
+ if (pptr != NULL) {
+ if (created_new != NULL)
+ *created_new = false;
+ return *pptr;
+ }
+
+ secret = secretCreate(conn, driver, uuid);
+ 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 virSecretEntry *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 virSecretEntry *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;
+ }
+ VIR_FREE(tmp_path);
+ 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);
+ VIR_FREE(tmp);
+ return -1;
+ }
+
+ memcpy(*string, tmp, size);
+ (*string)[size] = '\0';
+ VIR_FREE(tmp);
+ return 0;
+}
+
+static int
+parseKeyValue(virConnectPtr conn, virSecretEntryPtr *list,
+ virSecretEntryPtr *secret, const char *key, const char *value)
+{
+ virSecretEntryPtr 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 = (unsigned char *)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,
+ virSecretEntryPtr *dest)
+{
+ int ret = -1, fd = -1;
+ struct stat st;
+ char *contents = NULL, *strtok_data = NULL, *strtok_first;
+ const char *key, *value;
+ virSecretEntryPtr 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 virSecretEntryPtr
+secretXMLParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root)
+{
+ xmlXPathContextPtr ctxt = NULL;
+ virSecretEntryPtr secret = NULL, 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->id = virXPathString(conn, "string(./uuid)", ctxt);
+ 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 virSecretEntryPtr
+secretXMLParseString(virConnectPtr conn, const char *xmlStr)
+{
+ xmlParserCtxtPtr pctxt;
+ xmlDocPtr xml = NULL;
+ xmlNodePtr root;
+ virSecretEntryPtr 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 virSecretEntry *secret)
+{
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ char *tmp;
+
+ virBufferVSprintf(&buf, "<secret ephemeral='%s'
private='%s'>\n",
+ secret->ephemeral ? "yes" : "no",
+ secret->private ? "yes" : "no");
+ virBufferEscapeString(&buf, " <uuid>%s</uuid>\n",
secret->id);
+ 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 int
+secretNumOfSecrets(virConnectPtr conn)
+{
+ virSecretDriverStatePtr driver = conn->secretPrivateData;
+ int i;
+ virSecretEntryPtr 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 **uuids, int maxuuids)
+{
+ virSecretDriverStatePtr driver = conn->secretPrivateData;
+ int i;
+ virSecretEntryPtr secret;
+
+ memset(uuids, 0, maxuuids * sizeof(*uuids));
+
+ secretDriverLock(driver);
+
+ i = 0;
+ for (secret = driver->secrets; secret != NULL; secret = secret->next) {
+ if (i == maxuuids)
+ break;
+ uuids[i] = strdup(secret->id);
+ if (uuids[i] == NULL)
+ goto cleanup;
+ i++;
+ }
+
+ secretDriverUnlock(driver);
+ return i;
+
+ cleanup:
+ secretDriverUnlock(driver);
+
+ for (i = 0; i < maxuuids; i++)
+ VIR_FREE(uuids[i]);
+
+ return -1;
+}
+
+static virSecretPtr
+secretLookupByUUIDString(virConnectPtr conn, const char *uuid)
+{
+ virSecretDriverStatePtr driver = conn->secretPrivateData;
+ virSecretPtr ret = NULL;
+ virSecretEntryPtr *pptr;
+
+ secretDriverLock(driver);
+
+ pptr = secretFind(driver, uuid);
+ if (pptr == NULL) {
+ virSecretReportError(conn, VIR_ERR_NO_SECRET,
+ _("no secret with matching id '%s'"),
uuid);
+ goto cleanup;
+ }
+
+ ret = virGetSecret(conn, (*pptr)->id);
+
+cleanup:
+ secretDriverUnlock(driver);
+ return ret;
+}
+
+static char *
+secretGenerateUUID(virConnectPtr conn, virSecretDriverStatePtr driver)
+{
+ char *uuid = NULL;
+ unsigned attempt;
+
+ if (VIR_ALLOC_N(uuid, VIR_UUID_STRING_BUFLEN) < 0) {
+ virReportOOMError(conn);
+ goto error;
+ }
+
+ for (attempt = 0; attempt < 65536; attempt++) {
+ unsigned char uuid_data[VIR_UUID_BUFLEN];
+
+ if (virUUIDGenerate(uuid_data) < 0) {
+ virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
+ _("unable to generate uuid"));
+ goto error;
+ }
+ virUUIDFormat(uuid_data, uuid);
+ if (secretFind(driver, uuid) == NULL)
+ return uuid;
+ }
+ virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
+ _("too many conflicts when generating an uuid"));
+ goto error;
+
+error:
+ VIR_FREE(uuid);
+ return NULL;
+}
+
+static void
+shallowCopyAttributes(virSecretEntryPtr dest, const virSecretEntry *src)
+{
+ dest->id = src->id;
+ dest->ephemeral = src->ephemeral;
+ dest->private = src->private;
+ dest->description = src->description;
+ dest->volume = src->volume;
+}
+
+static virSecretPtr
+secretDefineXML(virConnectPtr conn, const char *xml)
+{
+ virSecretDriverStatePtr driver = conn->secretPrivateData;
+ virSecretPtr ret = NULL;
+ virSecretEntry backup;
+ virSecretEntryPtr secret, new_attrs;
+ bool secret_is_new;
+
+ new_attrs = secretXMLParseString(conn, xml);
+ if (new_attrs == NULL)
+ return NULL;
+
+ secretDriverLock(driver);
+
+ if (new_attrs->id != NULL)
+ secret = secretFindOrCreate(conn, driver, new_attrs->id,
+ &secret_is_new);
+ else {
+ new_attrs->id = secretGenerateUUID(conn, driver);
+ if (new_attrs->id == NULL)
+ goto cleanup;
+ secret = secretCreate(conn, driver, new_attrs->id);
+ secret_is_new = true;
+ }
+ 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 */
+ VIR_FREE(new_attrs);
+ VIR_FREE(backup.id);
+ VIR_FREE(backup.description);
+ VIR_FREE(backup.volume);
+
+ ret = virGetSecret(conn, secret->id);
+ 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 *
+secretGetXMLDesc(virSecretPtr obj)
+{
+ virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+ char *ret = NULL;
+ virSecretEntryPtr *pptr;
+
+ secretDriverLock(driver);
+
+ pptr = secretFind(driver, obj->uuid);
+ if (pptr == NULL) {
+ virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+ _("no secret with matching id '%s'"),
obj->uuid);
+ goto cleanup;
+ }
+
+ ret = secretXMLFormat(obj->conn, *pptr);
+
+ cleanup:
+ secretDriverUnlock(driver);
+
+ return ret;
+}
+
+static int
+secretSetValue(virSecretPtr obj, const unsigned char *value,
+ size_t value_size)
+{
+ virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+ int ret = -1;
+ unsigned char *old_value, *new_value;
+ size_t old_value_size;
+ virSecretEntryPtr secret, *pptr;
+
+ if (VIR_ALLOC_N(new_value, value_size) < 0) {
+ virReportOOMError(obj->conn);
+ return -1;
+ }
+
+ secretDriverLock(driver);
+
+ pptr = secretFind(driver, obj->uuid);
+ if (pptr == NULL) {
+ virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+ _("no secret with matching id '%s'"),
obj->uuid);
+ goto cleanup;
+ }
+ secret = *pptr;
+
+ 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(obj->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);
+
+ cleanup:
+ secretDriverUnlock(driver);
+
+ VIR_FREE(new_value);
+
+ return ret;
+}
+
+static unsigned char *
+secretGetValue(virSecretPtr obj, size_t *value_size,
+ bool libvirt_internal_call)
+{
+ virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+ unsigned char *ret = NULL;
+ virSecretEntryPtr *pptr, secret;
+
+ secretDriverLock(driver);
+
+ pptr = secretFind(driver, obj->uuid);
+ if (pptr == NULL) {
+ virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+ _("no secret with matching id '%s'"),
obj->uuid);
+ goto cleanup;
+ }
+ secret = *pptr;
+ if (secret->value == NULL) {
+ virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+ _("secret '%s' does not have a value"),
obj->uuid);
+ goto cleanup;
+ }
+
+ if (!libvirt_internal_call && secret->private) {
+ virSecretReportError(obj->conn, VIR_ERR_OPERATION_DENIED, "%s",
+ _("secret is private"));
+ goto cleanup;
+ }
+
+ if (VIR_ALLOC_N(ret, secret->value_size) < 0) {
+ virReportOOMError(obj->conn);
+ goto cleanup;
+ }
+ memcpy(ret, secret->value, secret->value_size);
+ *value_size = secret->value_size;
+
+ cleanup:
+ secretDriverUnlock(driver);
+
+ return ret;
+}
+
+static int
+secretUndefine(virSecretPtr obj)
+{
+ virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+ int ret = -1;
+ virSecretEntryPtr *pptr, secret;
+
+ secretDriverLock(driver);
+
+ pptr = secretFind(driver, obj->uuid);
+ if (pptr == NULL) {
+ virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+ _("no secret with matching id '%s'"),
obj->uuid);
+ goto cleanup;
+ }
+
+ secret = listUnlink(pptr);
+ if (!secret->ephemeral) {
+ if (saveSecrets(obj->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
+secretDriverCleanup(void)
+{
+ if (driverState == NULL)
+ return -1;
+
+ secretDriverLock(driverState);
+
+ while (driverState->secrets != NULL) {
+ virSecretEntryPtr 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)
+{
+ virSecretEntryPtr 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) {
+ virSecretEntryPtr 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,
+ .numOfSecrets = secretNumOfSecrets,
+ .listSecrets = secretListSecrets,
+ .lookupByUUIDString = secretLookupByUUIDString,
+ .defineXML = secretDefineXML,
+ .getXMLDesc = secretGetXMLDesc,
+ .setValue = secretSetValue,
+ .getValue = secretGetValue,
+ .undefine = secretUndefine
+};
+
+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(a)redhat.com>
+ */
+
+#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 2a3cdaf..77b295c 100644
--- a/src/virterror.c
+++ b/src/virterror.c
@@ -1082,6 +1082,11 @@ virErrorMsg(virErrorNumber error, const char *info)
errmsg = _("Invalid secret");
else
errmsg = _("Invalid secret: %s");
+ 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