Hi Peter,
Thank you for the review.

On Thu, Nov 27, 2025 at 04:09:07PM +0100, Peter Krempa wrote:
> On Thu, Nov 27, 2025 at 12:52:30 +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/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
> 
> src/conf/ is meant for common XML infra. This is the config of the
> secret driver so it ought to be in src/secret/secret_conf.c
Sure, I will move it.
> 
> 
> > @@ -0,0 +1,177 @@
> > +/*
> > + * secret_config.c: secrets.conf config file handling
> > + *
> > + * Copyright (C) 2025 Red Hat, Inc.
> > + * SPDX-License-Identifier: LGPL-2.1-or-later
> > + */
> 
> The SPDX line is enough, drop the rest.
Yes.
> 
> > +
> > +#include <config.h>
> > +#include <fcntl.h>
> > +#include "configmake.h"
> > +#include "datatypes.h"
> > +#include "virlog.h"
> > +#include "virerror.h"
> > +#include "virfile.h"
> > +#include "virutil.h"
> > +#include "secret_config.h"
> > +
> > +
> > +#define VIR_FROM_THIS VIR_FROM_CONF
> 
> VIR_FROM_SECRET
Yes, I will change it
> 
> > +
> > +VIR_LOG_INIT("secret.secret_config");
> > +
> > +static virClass *virSecretDaemonConfigClass;
> > +static void virSecretDaemonConfigDispose(void *obj);
> > +
> > +static int
> > +virSecretConfigOnceInit(void)
> > +{
> > +    if (!VIR_CLASS_NEW(virSecretDaemonConfig, virClassForObject()))
> > +        return -1;
> > +
> > +    return 0;
> > +}
> > +
> > +
> > +VIR_ONCE_GLOBAL_INIT(virSecretConfig);
> > +
> > +
> > +int
> > +virSecretDaemonConfigFilePath(bool privileged, char **configfile)
> > +{
> > +    if (privileged) {
> > +        *configfile = g_strdup(SYSCONFDIR "/libvirt/secrets.conf");
> 
> Configs for drivers are named based on the uri. So this ought to be
> 'secret.conf'
I will change it.
> 
> 
> > +    } else {
> > +        g_autofree char *configdir = NULL;
> > +
> > +        configdir = virGetUserConfigDirectory();
> > +
> > +        *configfile = g_strdup_printf("%s/secrets.conf", configdir);
> 
> ditto
Yes.
> 
> 
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +
> > +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;
> 
> This is a boolean now.
Yes, I will not set it in the new version.
> 
> > +
> > +    if (virFileExists(filename)) {
> > +        conf = virConfReadFile(filename, 0);
> > +        if (!conf)
> > +            return -1;
> > +        if (virConfGetValueBool(conf, "encrypt_data", &cfg->encrypt_data) 
> > < 0) {
> > +            virReportError(VIR_ERR_CONF_SYNTAX,
> > +                           _("Failed to get encrypt_data from %1$s"),
> > +                           filename);
> > +            return -1;
> > +        }
> > +
> > +        if (virConfGetValueString(conf, "secrets_encryption_key",
> > +                                  &cfg->secretsEncryptionKeyPath) < 0) {
> > +            virReportError(VIR_ERR_CONF_SYNTAX,
> > +                           _("Failed to get secrets_encryption_key from 
> > %1$s"),
> > +                           filename);
> 
> virConfGetValue* functions already set an error; don't overwrite it.
> 
> 
> > +            return -1;
> > +        }
> > +    }
> > +    return 0;
> > +}
> > +
> > +
> > +static int virGetSecretsEncryptionKey(virSecretDaemonConfig *cfg,
> > +                                    uint8_t **secrets_encryption_key, 
> > size_t *secrets_encryption_keylen)
> 
> In v2 I've noted that this doesn't follow the coding style (just look at
> other functions. You changed the name but didn't fix the coding style
> 
> 
> So to be explicit:
> 
>  static int
>  virGetSecretsEncryptionKey(virSecretDaemonConfig *cfg,
>                             uint8_t **secrets_encryption_key,
>                             size_t *secrets_encryption_keylen)
> 
> 
Thank you, I was not sure about this one.
> > +{
> > +    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);
> 
> None of the errors in this function are VIR_ERR_INTERNAL_ERROR.
I think I need to create a new one called VIR_ERR_INVALID_ENCR_KEY_LENGTH
in include/libvirt/virterror.h if that is okay. I shall use it for the
key length check here.
> 
> 
> > +        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;
> > +    }
> > +    if (encryption_key_length != VIR_SECRETS_ENCRYPTION_KEY_LEN) {
> > +        virReportError(VIR_ERR_INTERNAL_ERROR, _("Secrets encryption key 
> > file %1$s must be 32 bytes"),
> 
> Use the VIR_SECRETS_ENCRYPTION_KEY_LEN constant instead of hardcoding 32
> in the error message
> 
> > +                       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);
> > +    }
> > +    VIR_DEBUG("Secrets encryption key path: %s", 
> > NULLSTR(cfg->secretsEncryptionKeyPath));
> > +
> > +    if (cfg->secretsEncryptionKeyPath && 
> > virFileExists(cfg->secretsEncryptionKeyPath)) {
> > +        if (virGetSecretsEncryptionKey(cfg, &cfg->secrets_encryption_key, 
> > &cfg->secretsKeyLen) < 0) {
> > +            return NULL;
> > +        }
> > +    }
> > +    if (cfg->encrypt_data == 1) {
> 
> Firstly; it's a bool now. Secondly you don't know if this was an
> explicit config from the user ....
> 
> > +        if (!cfg->secretsEncryptionKeyPath) {
> 
> ... where refusing startup would be appropriate, or an old config
> (without the config file) where you assumed that this is enabled  but no
> key is present.
> 
> Thus this error ought to be printed only when the user explicitly
> requested encryption not when we assumed it.
> 
> Since it's in a different fuinction you'll either need to remember if
> you've seen the user config, or convert the value to tristate.

Got it. I realize now we shouldn't force encryption.
I'll update the code to enable encrypt_data only if the systemd credentials 
exist or the path is in secret.conf.
I wasn't sure before because I thought the user was supposed to use that flag 
to manually toggle encryption.
> 
> 
> > +            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);
> 
> This ought to be securely disposed.
Yes, will do. Thanks
> 
> > +    g_free(cfg->secretsEncryptionKeyPath);
> > +}
> > 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
> 
> Just the SPDX line.
Sure.
> 
> 
> > + */
> > +
> > +#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.
> 
> It's now a bool so  
I think we should drop the boolean from the secret.conf config file,
because the user will not be able to toggle encryption on/off based on
this field. Encryption is on if the path is set or if systemd credentials
is set. This attribute can be used internally.
> 
> > +     */
> > +    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/libvirt_secrets.aug b/src/secret/libvirt_secrets.aug
> > new file mode 100644
> > index 0000000000..092cdef41f
> > --- /dev/null
> > +++ b/src/secret/libvirt_secrets.aug
> > @@ -0,0 +1,40 @@
> > +(* /etc/libvirt/secrets.conf *)
> > +
> > +module Libvirt_secrets =
> > +   autoload xfm
> > +
> > +   let eol   = del /[ \t]*\n/ "\n"
> > +   let value_sep   = del /[ \t]*=[ \t]*/  " = "
> > +   let indent = del /[ \t]*/ ""
> > +
> > +   let array_sep  = del /,[ \t\n]*/ ", "
> > +   let array_start = del /\[[ \t\n]*/ "[ "
> > +   let array_end = del /\]/ "]"
> > +
> > +   let str_val = del /\"/ "\"" . store /[^\"]*/ . del /\"/ "\""
> > +   let bool_val = store /0|1/
> > +   let int_val = store /[0-9]+/
> > +   let str_array_element = [ seq "el" . str_val ] . del /[ \t\n]*/ ""
> > +   let str_array_val = counter "el" . array_start . ( str_array_element . 
> > ( array_sep . str_array_element ) * ) ? . array_end
> > +
> > +   let str_entry       (kw:string) = [ key kw . value_sep . str_val ]
> > +   let bool_entry      (kw:string) = [ key kw . value_sep . bool_val ]
> > +   let int_entry      (kw:string) = [ key kw . value_sep . int_val ]
> > +   let str_array_entry (kw:string) = [ key kw . value_sep . str_array_val ]
> > +
> > +   let secrets_entry = str_entry "secrets_encryption_key"
> > +                     | bool_entry "encrypt_data"
> > +
> > +   (* Each entry in the config is one of the following three ... *)
> > +   let entry = secrets_entry
> > +   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ 
> > \t\n][^\n]*)?/ . del /\n/ "\n" ]
> > +   let empty = [ label "#empty" . eol ]
> > +
> > +   let record = indent . entry . eol
> > +
> > +   let lns = ( record | comment | empty ) *
> > +
> > +   let filter = incl "/etc/libvirt/secrets.conf"
> > +              . Util.stdexcl
> > +
> > +   let xfm = transform lns filter
> > diff --git a/src/secret/meson.build b/src/secret/meson.build
> > index c02d1064a9..cff0f0678d 100644
> > --- a/src/secret/meson.build
> > +++ b/src/secret/meson.build
> > @@ -27,6 +27,24 @@ if conf.has('WITH_SECRETS')
> >      ],
> >    }
> >  
> > +  secrets_conf = configure_file(
> > +    input: 'secrets.conf.in',
> > +    output: 'secrets.conf',
> > +    copy: true
> > +  )
> > +  virt_conf_files += secrets_conf
> > +
> > +  virt_aug_files += files('libvirt_secrets.aug')
> > +
> > +  virt_test_aug_files += {
> > +    'name': 'test_libvirt_secrets.aug',
> > +    'aug': files('test_libvirt_secrets.aug.in'),
> > +    'conf': files('secrets.conf.in'),
> > +    'test_name': 'libvirt_secrets',
> > +    'test_srcdir': meson.current_source_dir(),
> > +    'test_builddir': meson.current_build_dir(),
> > +  }
> > +
> >    virt_daemon_confs += {
> >      'name': 'virtsecretd',
> >    }
> > 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
> 
> 'secret.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.
> > +#encrypt_data = 1
> 

Regards,
Arun Menon

Reply via email to