On Tue, Feb 13, 2024 at 05:16:05PM +0100, Michal Privoznik wrote:
> The systemd-cred offers a convenient way to talk to host's TPM.
> While its original intent is to be used in systemd unit files, it
> offers two additional commands: encrypt and decrypt that can be
> used independent of the rest of systemd. And these are the ones
> we need. They offer a convenient way to encrypt/decrypt plaintext
> using a key that's stored in host's TPM (when --with-key=tpm is
> passed).
This is one way we could do it, but what I don't like about
this is that it ties the secret driver to system. I think
it would be better to have an intermediate key, that is
protected by credentials and use that for encryption of
the individual secrets.
Roughly speaking....
* Make virtsecretd accept a '--encryption-key /some/path' option
* In ExecPreStart in virtsecretd.service, if no key already
exists, we dd from /dev/random enough bytes for a AES256 key,
and then use systemd-creds encrypt to store it
* In ExecStart we set the --encryption-key arg to point to
the credentials that systemd will decrypt
* The secret driver can now use this master key to
AES encrypt/decrypt individual secrets
The benefit of this is that only encryption of the intermediate
key is tied to the TPM.
If someone needs to move everything to a new host, then can copy
over all the secret data files, and then just re-seal the intermediate
key against the new machine's TPM.
The also lets people setup the --encryption-key for virtsecretd
with some arbitrary non-systemd mechanism. eg could use clevis/tang,
or $whatever.
The secret driver does not need to know about systemd at all,
all the credentials logic is done with a minimal tweak to the
.service file.
> In addition, there's 'has-tpm2' command which allows us to detect
> whether host and the OS have everything needed to utilize the
> TPM.
FWIW, systemd-creds does not require the use of a TPM2, it is
quite happy running without one, so libvirt should not really
describe this as TPM based, nor require a TPM. Without a TPM
systemd just generates a random key and stores it in /var.
That is less secure by default of course, but still reasonable,
as in theory you could way to inject the master key to /var in
some manner.
>
> Signed-off-by: Michal Privoznik <[email protected]>
> ---
> src/libvirt_private.syms | 3 +
> src/util/virsecret.c | 170 +++++++++++++++++++++++++++++++++++++++
> src/util/virsecret.h | 10 +++
> 3 files changed, 183 insertions(+)
>
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index 396bd80844..1d1bba915a 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -3362,6 +3362,9 @@ virSecretLookupDefClear;
> virSecretLookupDefCopy;
> virSecretLookupFormatSecret;
> virSecretLookupParseSecret;
> +virSecretTPMAvailable;
> +virSecretTPMDecrypt;
> +virSecretTPMEncrypt;
>
>
> # util/virsecureerase.h
> diff --git a/src/util/virsecret.c b/src/util/virsecret.c
> index 8a220a37ec..11aa146b6d 100644
> --- a/src/util/virsecret.c
> +++ b/src/util/virsecret.c
> @@ -23,10 +23,13 @@
>
> #include "datatypes.h"
> #include "viralloc.h"
> +#include "vircommand.h"
> #include "virerror.h"
> #include "virlog.h"
> #include "virsecret.h"
> +#include "virutil.h"
> #include "viruuid.h"
> +#include "virfile.h"
>
> #define VIR_FROM_THIS VIR_FROM_NONE
>
> @@ -176,3 +179,170 @@ virSecretGetSecretString(virConnectPtr conn,
>
> return 0;
> }
> +
> +
> +/**
> + * virSecretTPMAvailable:
> + *
> + * Checks whether systemd-creds is available and whether host supports
> + * TPM. Use this prior calling other virSecretTPM*() APIs.
> + *
> + * Returns: 1 in case TPM is available,
> + * 0 in case TPM is NOT available,
> + * -1 otherwise.
> + */
> +int
> +virSecretTPMAvailable(void)
> +{
> + g_autoptr(virCommand) cmd = NULL;
> + g_autofree char *out = NULL;
> + int exitstatus;
> + int rc;
> +
> + cmd = virCommandNewArgList("systemd-creds", "has-tpm2", NULL);
> +
> + virCommandSetOutputBuffer(cmd, &out);
> +
> + rc = virCommandRun(cmd, &exitstatus);
> +
> + if (rc < 0) {
> + /* systemd-creds not available */
> + return -1;
> + }
> +
> + if (!out || !*out) {
> + /* systemd-creds reported nothing. Ouch. */
> + return 0;
> + }
> +
> + /* systemd-creds can report one of these:
> + *
> + * yes - TPM is available and recognized by FW, kernel, etc.
> + * no - TPM is not available or not recognized
> + * partial - otherwise
> + */
> +
> + if (STRPREFIX(out, "yes\n"))
> + return 1;
> +
> + return 0;
> +}
> +
> +
> +/**
> + * virSecretTPMEncrypt:
> + * @name: credential name
> + * @value: credential value
> + * @value_size: size of @value
> + * @base64: encrypted @value, base64 encoded
> + *
> + * Encrypts given plaintext (@value) with a secret key automatically
> + * derived from the system's TPM2 chip. Ciphertext is base64 encoded and
> + * stored into @base64. Pass the same @name to virSecretTPMDecrypt().
> + *
> + * Returns: 0 on success,
> + * -1 otherwise (with error reported).
> + */
> +int
> +virSecretTPMEncrypt(const char *name,
> + unsigned const char *value,
> + size_t value_size,
> + char **base64)
> +{
> + g_autoptr(virCommand) cmd = NULL;
> + g_autofree char *errBuf = NULL;
> + int pipeFD[2] = { -1, -1 };
> + int ret = -1;
> +
> + if (virPipe(pipeFD) < 0)
> + return -1;
> +
> + if (virSetCloseExec(pipeFD[1]) < 0) {
> + virReportSystemError(errno, "%s",
> + _("Unable to set cloexec flag"));
> + goto cleanup;
> + }
> +
> + cmd = virCommandNewArgList("systemd-creds", "encrypt",
> + "--with-key=tpm2", NULL);
> + virCommandAddArgFormat(cmd, "--name=%s", name);
> + virCommandAddArgList(cmd, "-", "-", NULL);
> +
> + virCommandSetInputFD(cmd, pipeFD[0]);
> + virCommandSetOutputBuffer(cmd, base64);
> + virCommandSetErrorBuffer(cmd, &errBuf);
> + virCommandDoAsyncIO(cmd);
> +
> + if (virCommandRunAsync(cmd, NULL) < 0)
> + goto cleanup;
> +
> + if (safewrite(pipeFD[1], value, value_size) != value_size) {
> + virReportSystemError(errno, "%s",
> + _("Unable to pass secret value to
> systemd-cred"));
> + goto cleanup;
> + }
> +
> + if (VIR_CLOSE(pipeFD[1]) < 0) {
> + virReportSystemError(errno, "%s", _("unable to close pipe"));
> + goto cleanup;
> + }
> +
> + if (virCommandWait(cmd, NULL) < 0) {
> + if (errBuf && *errBuf) {
> + VIR_WARN("systemd-creds reported: %s", errBuf);
> + }
> + goto cleanup;
> + }
> +
> + ret = 0;
> + cleanup:
> + virCommandAbort(cmd);
> + VIR_FORCE_CLOSE(pipeFD[0]);
> + VIR_FORCE_CLOSE(pipeFD[1]);
> + return ret;
> +}
> +
> +
> +/**
> + * virSecretTPMDecrypt:
> + * @name: credential name
> + * @base64: encrypted @value, base64 encoded
> + * @value: credential value,
> + * @value_size: size of @value
> + *
> + * Decrypts given ciphertext, base64 encoded (@base64) and stores
> + * plaintext into @value and its size into @value_size.
> + *
> + * Returns: 0 on success,
> + * -1 otherwise (with error reported).
> + */
> +int
> +virSecretTPMDecrypt(const char *name,
> + const char *base64,
> + unsigned char **value,
> + size_t *value_size)
> +{
> + g_autoptr(virCommand) cmd = NULL;
> + g_autofree char *out = NULL;
> + g_autofree char *errBuf = NULL;
> +
> + cmd = virCommandNewArgList("systemd-creds", "decrypt",
> + "--transcode=base64", NULL);
> + virCommandAddArgFormat(cmd, "--name=%s", name);
> + virCommandAddArgList(cmd, "-", "-", NULL);
> +
> + virCommandSetInputBuffer(cmd, base64);
> + virCommandSetOutputBuffer(cmd, &out);
> + virCommandSetErrorBuffer(cmd, &errBuf);
> +
> + if (virCommandRun(cmd, NULL) < 0) {
> + if (errBuf && *errBuf) {
> + VIR_WARN("systemd-creds reported: %s", errBuf);
> + }
> + return -1;
> + }
> +
> + *value = g_base64_decode(out, value_size);
> +
> + return 0;
> +}
> diff --git a/src/util/virsecret.h b/src/util/virsecret.h
> index c803f0fe33..a5ca764bb7 100644
> --- a/src/util/virsecret.h
> +++ b/src/util/virsecret.h
> @@ -62,3 +62,13 @@ int virSecretGetSecretString(virConnectPtr conn,
> size_t *ret_secret_size)
> ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4)
> ATTRIBUTE_NONNULL(5) G_GNUC_WARN_UNUSED_RESULT;
> +
> +int virSecretTPMAvailable(void);
> +int virSecretTPMEncrypt(const char *name,
> + unsigned const char *value,
> + size_t value_size,
> + char **base64);
> +int virSecretTPMDecrypt(const char *name,
> + const char *base64,
> + unsigned char **value,
> + size_t *value_size);
> --
> 2.43.0
> _______________________________________________
> Devel mailing list -- [email protected]
> To unsubscribe send an email to [email protected]
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
_______________________________________________
Devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]