On Thu, Nov 27, 2025 at 12:52:30PM +0530, Arun Menon via Devel wrote:
> A new configuration file called secrets.conf is introduced to
> let the user configure the path to the secrets encryption key.
> This key will be used to encrypt/decrypt the secrets in libvirt.
> 
> By default the path is set to the runtime directory
> /run/libvirt/secrets, and it is commented in the config file.
> After parsing the file, the virtsecretd driver checks if an
> encryption key is present in the path and is valid.
> 
> By default, if no encryption key is present in the path, then
> the service will by default use the encryption key stored in the
> CREDENTIALS_DIRECTORY.
> 
> Add logic to parse the encryption key file and store the key.
> It also checks for the encrypt_data attribute in the config file.
> The encryption and decryption logic will be added in the subsequent patches.
> 
> Signed-off-by: Arun Menon <[email protected]>
> ---
>  libvirt.spec.in                        |   3 +
>  po/POTFILES                            |   1 +
>  src/conf/meson.build                   |   1 +
>  src/conf/secret_config.c               | 177 +++++++++++++++++++++++++
>  src/conf/secret_config.h               |  38 ++++++
>  src/libvirt_private.syms               |   2 +
>  src/secret/libvirt_secrets.aug         |  40 ++++++
>  src/secret/meson.build                 |  18 +++
>  src/secret/secrets.conf.in             |  12 ++
>  src/secret/test_libvirt_secrets.aug.in |   6 +
>  10 files changed, 298 insertions(+)
>  create mode 100644 src/conf/secret_config.c
>  create mode 100644 src/conf/secret_config.h
>  create mode 100644 src/secret/libvirt_secrets.aug
>  create mode 100644 src/secret/secrets.conf.in
>  create mode 100644 src/secret/test_libvirt_secrets.aug.in
> 
> diff --git a/libvirt.spec.in b/libvirt.spec.in
> index dba8a71311..01ecf7e7ca 100644
> --- a/libvirt.spec.in
> +++ b/libvirt.spec.in
> @@ -2240,6 +2240,9 @@ exit 0
>  %config(noreplace) %{_sysconfdir}/libvirt/virtsecretd.conf
>  %{_datadir}/augeas/lenses/virtsecretd.aug
>  %{_datadir}/augeas/lenses/tests/test_virtsecretd.aug
> +%{_datadir}/augeas/lenses/libvirt_secrets.aug
> +%{_datadir}/augeas/lenses/tests/test_libvirt_secrets.aug
> +%config(noreplace) %{_sysconfdir}/libvirt/secrets.conf
>  %{_unitdir}/virtsecretd.service
>  %{_unitdir}/virt-secret-init-encryption.service
>  %{_unitdir}/virtsecretd.socket
> diff --git a/po/POTFILES b/po/POTFILES
> index f0aad35c8c..a64e4b2d87 100644
> --- a/po/POTFILES
> +++ b/po/POTFILES
> @@ -53,6 +53,7 @@ src/conf/nwfilter_conf.c
>  src/conf/nwfilter_params.c
>  src/conf/object_event.c
>  src/conf/secret_conf.c
> +src/conf/secret_config.c
>  src/conf/snapshot_conf.c
>  src/conf/storage_adapter_conf.c
>  src/conf/storage_conf.c
> diff --git a/src/conf/meson.build b/src/conf/meson.build
> index 5116c23fe3..9c51e99107 100644
> --- a/src/conf/meson.build
> +++ b/src/conf/meson.build
> @@ -68,6 +68,7 @@ interface_conf_sources = [
>  
>  secret_conf_sources = [
>    'secret_conf.c',
> +  'secret_config.c',
>    'virsecretobj.c',
>  ]
>  
> diff --git a/src/conf/secret_config.c b/src/conf/secret_config.c
> new file mode 100644
> index 0000000000..5bc0b24380
> --- /dev/null
> +++ b/src/conf/secret_config.c


> +static int
> +virSecretLoadDaemonConfig(virSecretDaemonConfig *cfg,
> +                          const char *filename)
> +{
> +    g_autoptr(virConf) conf = NULL;
> +    /* Encrypt secrets by default unless the configuration sets it otherwise 
> */
> +    cfg->encrypt_data = 1;

We can't do that, as it'll break back compat for unprivileged
daemons and non-systemd hosts. It must only be set to 1 if
an explicit path was configured in the config (regardless of
whether it exists on disk), or the implict systemd credential
exists on disk

> +static int virGetSecretsEncryptionKey(virSecretDaemonConfig *cfg,
> +                                    uint8_t **secrets_encryption_key, size_t 
> *secrets_encryption_keylen)
> +{
> +    VIR_AUTOCLOSE fd = -1;
> +    ssize_t encryption_key_length;
> +
> +    if (!virFileExists(cfg->secretsEncryptionKeyPath)) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, _("Secrets key file '%1$s' 
> does not exist"),
> +                       cfg->secretsEncryptionKeyPath);
> +        return -1;
> +    }
> +
> +    if ((fd = open(cfg->secretsEncryptionKeyPath, O_RDONLY)) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot open secrets key 
> file '%1$s'"),
> +                       cfg->secretsEncryptionKeyPath);
> +        return -1;
> +    }
> +
> +    *secrets_encryption_key = g_new0(uint8_t, 
> VIR_SECRETS_ENCRYPTION_KEY_LEN);
> +
> +    if ((encryption_key_length = saferead(fd, *secrets_encryption_key, 
> VIR_SECRETS_ENCRYPTION_KEY_LEN)) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot read secrets key 
> file '%1$s'"),
> +                       cfg->secretsEncryptionKeyPath);
> +        return -1;
> +    }

I'm not sure we need a virFileExists check separate from open. And indeed
open+saferead could be replaced with virFileReadAll, following by a data
length check.

> +    if (encryption_key_length != VIR_SECRETS_ENCRYPTION_KEY_LEN) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, _("Secrets encryption key 
> file %1$s must be 32 bytes"),
> +                       cfg->secretsEncryptionKeyPath);
> +        return -1;
> +    }
> +
> +    *secrets_encryption_keylen = (size_t)encryption_key_length;
> +    return 0;
> +}
> +
> +
> +virSecretDaemonConfig *
> +virSecretDaemonConfigNew(bool privileged)
> +{
> +    g_autoptr(virSecretDaemonConfig) cfg = NULL;
> +    g_autofree char *configdir = NULL;
> +    g_autofree char *configfile = NULL;
> +    const char *credentials_directory;
> +
> +    if (virSecretConfigInitialize() < 0)
> +        return NULL;
> +
> +    if (!(cfg = virObjectNew(virSecretDaemonConfigClass)))
> +        return NULL;
> +
> +    cfg->secretsEncryptionKeyPath = NULL;
> +
> +    if (virSecretDaemonConfigFilePath(privileged, &configfile) < 0)
> +        return NULL;
> +
> +    if (virSecretLoadDaemonConfig(cfg, configfile) < 0)
> +        return NULL;
> +
> +    credentials_directory = getenv("CREDENTIALS_DIRECTORY");
> +
> +    if (!cfg->secretsEncryptionKeyPath && credentials_directory) {
> +        cfg->secretsEncryptionKeyPath = 
> g_strdup_printf("%s/secrets-encryption-key",
> +                                                        
> credentials_directory);

This must have

   if (!virFileExists(cfg->secretsEncryptionKeyPath))
       g_clear_pointer(cfg->secretsEncryptionKeyPath, g_free);

so that we don't assume the systemd credential exists, while still
always honouring an explicitly cnofigured path.


> +    }

There here have

   cfg->encrypt_data = cfg->secretsEncryptionKeyPath != NULL;

> +    VIR_DEBUG("Secrets encryption key path: %s", 
> NULLSTR(cfg->secretsEncryptionKeyPath));
> +
> +    if (cfg->secretsEncryptionKeyPath && 
> virFileExists(cfg->secretsEncryptionKeyPath)) {

This virFileExists check is undesirable here. We do a virFileExists check
for a systemd credential earlier, but for a explicitly configured file
we must always honour it. What we should do is

      if (cfg->encrypt_data) {
          if (!cfg->secretsEncryptionKeyPath) {
             ...error...
          }
          if (virGetSecretsEncryptionKey(...)) {
              ....
          }
      }

> +        if (virGetSecretsEncryptionKey(cfg, &cfg->secrets_encryption_key, 
> &cfg->secretsKeyLen) < 0) {
> +            return NULL;
> +        }
> +    }
> +    if (cfg->encrypt_data == 1) {
> +        if (!cfg->secretsEncryptionKeyPath) {
> +            virReportError(VIR_ERR_CONF_SYNTAX,
> +                           _("secretsEncryptionKeyPath must be set if 
> encrypt_data is 1 in %1$s"),
> +                           configfile);
> +            return NULL;
> +        }
> +    }
> +    return g_steal_pointer(&cfg);
> +}
> +
> +
> +static void
> +virSecretDaemonConfigDispose(void *obj)
> +{
> +    virSecretDaemonConfig *cfg = obj;
> +
> +    g_free(cfg->secrets_encryption_key);
> +    g_free(cfg->secretsEncryptionKeyPath);

Pick one naming style only for the struct fields, snakecase or not.

> +}
> diff --git a/src/conf/secret_config.h b/src/conf/secret_config.h
> new file mode 100644
> index 0000000000..4cc6589814
> --- /dev/null
> +++ b/src/conf/secret_config.h
> @@ -0,0 +1,38 @@
> +/*
> + * secret_config.h: secrets.conf config file handling
> + *
> + * Copyright (C) 2025 Red Hat, Inc.
> + * SPDX-License-Identifier: LGPL-2.1-or-later
> + */
> +
> +#pragma once
> +
> +#include "internal.h"
> +#include "virinhibitor.h"
> +#include "secret_event.h"
> +#define VIR_SECRETS_ENCRYPTION_KEY_LEN 32
> +
> +typedef struct _virSecretDaemonConfig virSecretDaemonConfig;
> +struct _virSecretDaemonConfig {
> +    virObject parent;
> +    /* secrets encryption key path from secrets.conf file */
> +    char *secretsEncryptionKeyPath;
> +
> +    /* Store the key to encrypt secrets on the disk */
> +    unsigned char *secrets_encryption_key;
> +
> +    size_t secretsKeyLen;
> +
> +    /* Indicates if the newly written secrets are encrypted or not.
> +     * 0 if not encrypted and 1 if encrypted.
> +     */
> +    bool encrypt_data;
> +};
> +
> +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virSecretDaemonConfig, virObjectUnref);
> +
> +int virSecretDaemonConfigFilePath(bool privileged, char **configfile);
> +virSecretDaemonConfig *virSecretDaemonConfigNew(bool privileged);
> +int virSecretDaemonConfigLoadFile(virSecretDaemonConfig *data,
> +                                  const char *filename,
> +                                  bool allow_missing);
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index 63a1ae4c70..cdf5426af6 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -1066,6 +1066,8 @@ virSecretDefParse;
>  virSecretUsageTypeFromString;
>  virSecretUsageTypeToString;
>  
> +# conf/secret_config.h
> +virSecretDaemonConfigNew;
>  
>  # conf/secret_event.h
>  virSecretEventLifecycleNew;

> diff --git a/src/secret/secrets.conf.in b/src/secret/secrets.conf.in
> new file mode 100644
> index 0000000000..d998940140
> --- /dev/null
> +++ b/src/secret/secrets.conf.in
> @@ -0,0 +1,12 @@
> +#
> +# Configuration file for the secrets driver.
> +#
> +# The secret encryption key is used to override default encryption
> +# key path. The user can create an encryption key and set the 
> secret_encryption_key
> +# to the path on which it resides.
> +# The key must be 32-bytes long.
> +#secrets_encryption_key = "/run/libvirt/secrets/secret-encryption-key"
> +
> +# The encrypt_data setting is used to indicate if the encryption is on or 
> off.
> +# 0 indicates off and 1 indicates on. By default it is set to on.

 "By default it is on if  secrets_encryption_key is set to a non-NULL
  path, or if a systemd credential named "secrets-encryption-key"
  exists."

> +#encrypt_data = 1
> diff --git a/src/secret/test_libvirt_secrets.aug.in 
> b/src/secret/test_libvirt_secrets.aug.in
> new file mode 100644
> index 0000000000..1bb205e0f2
> --- /dev/null
> +++ b/src/secret/test_libvirt_secrets.aug.in
> @@ -0,0 +1,6 @@
> +module Test_libvirt_secrets =
> +  @CONFIG@
> +
> +   test Libvirt_secrets.lns get conf =
> +{ "secrets_encryption_key" = "/run/libvirt/secrets/secret-encryption-key" }
> +{ "encrypt_data" = "1" }
> -- 
> 2.51.1
> 

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 :|

Reply via email to