On Sun, Aug 16, 2009 at 10:48:00PM +0200, Miloslav Trmač wrote: > 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.) What if we change our mind in some time, would there be any obstacle to dynamically detecting things are not encrypted and switching to a crypted file transparently? I'm just trying to make sure a decision taken now won't become a obstacle on deployed instances if we ever revisit the issue. [...] > 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 @@ [...] > +#define virSecretReportError(conn, code, fmt...) \ > + virReportErrorHelper(conn, VIR_FROM_SECRET, code, __FILE__, \ > + __FUNCTION__, __LINE__, fmt) > + > +#define secretLog(msg...) fprintf(stderr, msg) argh, no please let's use the existing log mechanism and not push more fprintf based debugging #include "logging.h" and #define secretLog(level, msg, ...) \ virLogMessage(__FILE__, level, 0, msg, __VA_ARGS__) and use one of the virLogPriority values for level > +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 */ for structs I prefer one entry per line char *description; char *volume; > +}; cosmetic but in line with other code [...] > +static virSecretEntryPtr * > +secretFind(virSecretDriverStatePtr driver, const char *uuid) > +{ > + virSecretEntryPtr *pptr, s; > + > + for (pptr = &driver->secrets; (s = *pptr) != NULL; pptr = &s->next) { Urgh, a test with an affectation side effect in the loop guard, please clean this up, this is really hard to figure out > + if (STREQ(s->id, uuid)) > + return pptr; > + } > + return NULL; > +} > + > +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); where is base64_encode_alloc coming from ? [...] > +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); Hum even if unencrypted, we should make sure of the mode of the file beforehand... isn't there a safer equivalent (possibly made postable by gnulib ?) > + 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; > + } Hum, the whole set smells fishy, we are creating a temp file, without mode checked, then moving that file somewhere else. I would ratehr have internal APIsdeling with a dump to memory and then a single open, safe and directly to the driver->filename. > +static int > +parseBase64String(virConnectPtr conn, const char *base64, char **string) > +{ > + char *tmp; > + size_t size; > + > + if (!base64_decode_alloc(base64, strlen(base64), &tmp, &size)) { where is base64_decode_alloc coming from ? > + 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; here > + } else if (STREQ(key, "private")) { > + if (STREQ(value, "yes")) > + s->private = 1; > + else if (STREQ(value, "no")) > + s->private = 0; > + else > + goto invalid; and here, it's better to state why parsing failed rather than just let the user guess. We have the info let's give it back > + } else if (STREQ(key, "value")) { > +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; > + } stat()/open() introduces a small race, I think open() and then fdstat() is a bit cleaner, not a big deal though > + 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; contents[st.st_size] = 0; needed here. > + strtok_first = contents; [...] > +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; we should test for "no" and explicitely fail otherwise if (prop != NULL) { if (STREQ(prop, "yes")) { secret->ephemeral = 1; } else if (STREQ(prop, "no")) { secret->ephemeral = 0; } else { raise an error } VIR_FREE(prop); } > + VIR_FREE(prop); > + > + prop = virXPathString(conn, "string(./@private)", ctxt); > + if (prop != NULL && STREQ(prop, "yes")) > + secret->private = 1; > + VIR_FREE(prop); same here > + 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; > +} [...] [...] > + 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? */ no we can't abort() in the library > + virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", > + _("list of secrets is inconsistent")); > + else > + secretFree(secret); > + } > + > + cleanup: > + secretFree(new_attrs); > + 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; > + } I would be tempted to add an immutable flag to a secret, basically this would allow to set the value once but forbid ever changing it we would just test here that secret->value is NULL before allowing to set it if immutable. But it's easy to add later if needed. > + secret = *pptr; > + > + old_value = secret->value; > + old_value_size = secret->value_size; > + So there is a few points to check IMHO, also I would like to understand where the base64 encoding/decoding routines come from, actually I would prefer to have them in utils.[ch] and with function names matching the existing conventions. Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@xxxxxxxxxxxx | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/ -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list