Repository: quarkusio/quarkus
Issue: Invalid warning on quarkus.vault.secret-config-kv-path."prefix" property

**Describe the bug**
Using property `secret-config-kv-path."prefix"` works as expected, but prints an invalid warning at startup:
```
WARN  [io.qua.config] (main) Unrecognized configuration key "quarkus.vault.secret-config-kv-path.singer" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo
```

**Expected behavior**
There should be no warning

**To Reproduce**
Execute IT `VaultMultiPathConfigITCase` in the core project

**Additional context**
I am not sure if this is something that is specific to the vault configuration, because I don't see the same behavior on `ConfiguredBeanTest` that is using `ConfiguredBeanTest` with property `public Map<String, List<String>> stringListMap` with values:
```
# And list form
quarkus.rt.all-values.string-list-map.key1=value1,value2,value3
quarkus.rt.all-values.string-list-map.key2=value4,value5
quarkus.rt.all-values.string-list-map.key3=value6
```


Changed files:
extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java
extensions/vault/runtime/src/test/java/io/quarkus/vault/runtime/config/VaultConfigSourceTest.java
extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java

diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java
index b79e35062b2..b82e6010fa9 100644
--- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java
+++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java
@@ -16,6 +16,7 @@
 import static java.lang.Integer.parseInt;
 import static java.util.Collections.emptyMap;
 import static java.util.Optional.empty;
+import static java.util.regex.Pattern.compile;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toMap;
 
@@ -52,12 +53,13 @@ public class VaultConfigSource implements ConfigSource {
 
     private static final Logger log = Logger.getLogger(VaultConfigSource.class);
 
-    private static final String PROPERTY_PREFIX = "quarkus.vault.";
-    public static final Pattern CREDENTIALS_PATTERN = Pattern.compile("^quarkus\\\\.vault\\\\.credentials-provider\\\\.([^.]+)\\\\.");
-    public static final Pattern TRANSIT_KEY_PATTERN = Pattern.compile("^quarkus\\\\.vault\\\\.transit.key\\\\.([^.]+)\\\\.");
-    public static final Pattern SECRET_CONFIG_KV_PATH_PATTERN = Pattern
-            .compile("^quarkus\\\\.vault\\\\.secret-config-kv-path\\\\.(?:([^.]+)|(?:\\"([^\\"]+)\\"))$");
-    public static final Pattern EXPANSION_PATTERN = Pattern.compile("\\\\$\\\\{([^}]+)\\\\}");
+    static final String PROPERTY_PREFIX = "quarkus.vault.";
+    public static final Pattern CREDENTIALS_PATTERN = compile("^quarkus\\\\.vault\\\\.credentials-provider\\\\.([^.]+)\\\\.");
+    public static final Pattern TRANSIT_KEY_PATTERN = compile("^quarkus\\\\.vault\\\\.transit.key\\\\.([^.]+)\\\\.");
+    public static final String SECRET_CONFIG_KV_PREFIX_PATHS = "secret-config-kv-path";
+    public static final Pattern SECRET_CONFIG_KV_PREFIX_PATH_PATTERN = compile(
+            "^quarkus\\\\.vault\\\\." + SECRET_CONFIG_KV_PREFIX_PATHS + "\\\\.(?:([^.]+)|(?:\\"([^\\"]+)\\"))$");
+    public static final Pattern EXPANSION_PATTERN = compile("\\\\$\\\\{([^}]+)\\\\}");
 
     private AtomicReference<VaultCacheEntry<Map<String, String>>> cache = new AtomicReference<>(null);
     private AtomicReference<VaultRuntimeConfig> serverConfig = new AtomicReference<>(null);
@@ -117,13 +119,10 @@ private Map<String, String> getSecretConfig() {
 
         try {
             // default kv paths
-            if (serverConfig.secretConfigKvPath.isPresent()) {
-                fetchSecrets(serverConfig.secretConfigKvPath.get(), null, properties);
-            }
+            serverConfig.secretConfigKvPath.ifPresent(strings -> fetchSecrets(strings, null, properties));
 
             // prefixed kv paths
-            serverConfig.secretConfigKvPrefixPath.entrySet()
-                    .forEach(entry -> fetchSecrets(entry.getValue(), entry.getKey(), properties));
+            serverConfig.secretConfigKvPathPrefix.forEach((key, value) -> fetchSecrets(value.paths, key, properties));
 
             log.debug("loaded " + properties.size() + " properties from vault");
         } catch (RuntimeException e) {
@@ -252,7 +251,7 @@ private VaultRuntimeConfig loadRuntimeConfig() {
 
         serverConfig.credentialsProvider = createCredentialProviderConfigParser().getConfig();
         serverConfig.transit.key = createTransitKeyConfigParser().getConfig();
-        serverConfig.secretConfigKvPrefixPath = getSecretConfigKvPrefixPaths();
+        serverConfig.secretConfigKvPathPrefix = getSecretConfigKvPrefixPaths();
 
         return serverConfig;
     }
@@ -362,7 +361,7 @@ protected String getBaseProperty(String propertyName, String defaultValue) {
                 .orElse(defaultValue);
     }
 
-    private Map<String, List<String>> getSecretConfigKvPrefixPaths() {
+    private Map<String, VaultRuntimeConfig.KvPathConfig> getSecretConfigKvPrefixPaths() {
 
         return getConfigSourceStream()
                 .flatMap(configSource -> configSource.getPropertyNames().stream())
@@ -370,7 +369,8 @@ private Map<String, List<String>> getSecretConfigKvPrefixPaths() {
                 .filter(Objects::nonNull)
                 .distinct()
                 .map(this::createNameSecretConfigKvPrefixPathPair)
-                .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));
+                .collect(toMap(SimpleEntry::getKey,
+                        kvStore -> new VaultRuntimeConfig.KvPathConfig(kvStore.getValue())));
     }
 
     private Stream<ConfigSource> getConfigSourceStream() {
@@ -394,12 +394,12 @@ private SimpleEntry<String, List<String>> createNameSecretConfigKvPrefixPathPair
     }
 
     private String getSecretConfigKvPrefixPathName(String propertyName) {
-        Matcher matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher(propertyName);
+        Matcher matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(propertyName);
         return matcher.matches() ? (matcher.group(1) != null ? matcher.group(1) : matcher.group(2)) : null;
     }
 
     private List<String> getSecretConfigKvPrefixPath(String prefixName) {
-        return getOptionalListProperty("secret-config-kv-path." + prefixName).get();
+        return getOptionalListProperty(SECRET_CONFIG_KV_PREFIX_PATHS + "." + prefixName).get();
     }
 
 }
diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java
index 130d42b2527..f3d6015c2a1 100644
--- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java
+++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java
@@ -8,11 +8,14 @@
 
 import java.net.URL;
 import java.time.Duration;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
+import io.quarkus.runtime.annotations.ConfigDocMapKey;
 import io.quarkus.runtime.annotations.ConfigDocSection;
+import io.quarkus.runtime.annotations.ConfigGroup;
 import io.quarkus.runtime.annotations.ConfigItem;
 import io.quarkus.runtime.annotations.ConfigPhase;
 import io.quarkus.runtime.annotations.ConfigRoot;
@@ -23,7 +26,6 @@ public class VaultRuntimeConfig {
 
     public static final String DEFAULT_KUBERNETES_JWT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
     public static final String DEFAULT_KV_SECRET_ENGINE_MOUNT_PATH = "secret";
-    public static final String KV_SECRET_ENGINE_VERSION_V1 = "1";
     public static final String KV_SECRET_ENGINE_VERSION_V2 = "2";
     public static final String DEFAULT_RENEW_GRACE_PERIOD = "1H";
     public static final String DEFAULT_SECRET_CONFIG_CACHE_PERIOD = "10M";
@@ -35,9 +37,9 @@ public class VaultRuntimeConfig {
 
     /**
      * Vault server url.
-     * <p>
+     *
      * Example: https://localhost:8200
-     * <p>
+     *
      * See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
      * the full Vault url gets built.
      *
@@ -55,7 +57,7 @@ public class VaultRuntimeConfig {
 
     /**
      * Renew grace period duration.
-     * <p>
+     *
      * This value if used to extend a lease before it expires its ttl, or recreate a new lease before the current
      * lease reaches its max_ttl.
      * By default Vault leaseDuration is equal to 7 days (ie: 168h or 604800s).
@@ -75,7 +77,7 @@ public class VaultRuntimeConfig {
 
     /**
      * Vault config source cache period.
-     * <p>
+     *
      * Properties fetched from vault as MP config will be kept in a cache, and will not be fetched from vault
      * again until the expiration of that period.
      * This property is ignored if `secret-config-kv-path` is not set.
@@ -89,20 +91,20 @@ public class VaultRuntimeConfig {
     /**
      * List of comma separated vault paths in kv store,
      * where all properties will be available as MP config properties **as-is**, with no prefix.
-     * <p>
+     *
      * For instance, if vault contains property `foo`, it will be made available to the
      * quarkus application as `@ConfigProperty(name = "foo") String foo;`
-     * <p>
+     *
      * If 2 paths contain the same property, the last path will win.
-     * <p>
+     *
      * For instance if
-     * <p>
+     *
      * * `secret/base-config` contains `foo=bar` and
      * * `secret/myapp/config` contains `foo=myappbar`, then
-     * <p>
+     *
      * `@ConfigProperty(name = "foo") String foo` will have value `myappbar`
      * with application properties `quarkus.vault.secret-config-kv-path=base-config,myapp/config`
-     * <p>
+     *
      * See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
      * the full Vault url gets built.
      *
@@ -112,32 +114,17 @@ public class VaultRuntimeConfig {
     @ConfigItem
     public Optional<List<String>> secretConfigKvPath;
 
-    // @formatter:off
     /**
-     * List of comma separated vault paths in kv store,
-     * where all properties will be available as **prefixed** MP config properties.
-     * <p>
-     * For instance if the application properties contains
-     * `quarkus.vault.secret-config-kv-path.myprefix=config`, and
-     * vault path `secret/config` contains `foo=bar`, then `myprefix.foo`
-     * will be available in the MP config.
-     * <p>
-     * If the same property is available in 2 different paths for the same prefix, the last one
-     * will win.
-     * <p>
-     * See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
-     * the full Vault url gets built.
-     *
-     * @asciidoclet
+     * KV store paths configuration.
      */
-    // @formatter:on
-    @ConfigItem(name = "secret-config-kv-path.\\"prefix\\"")
-    public Map<String, List<String>> secretConfigKvPrefixPath;
+    @ConfigItem(name = "secret-config-kv-path")
+    @ConfigDocMapKey("prefix")
+    public Map<String, KvPathConfig> secretConfigKvPathPrefix;
 
     /**
      * Used to hide confidential infos, for logging in particular.
      * Possible values are:
-     * <p>
+     *
      * * low: display all secrets.
      * * medium: display only usernames and lease ids (ie: passwords and tokens are masked).
      * * high: hide lease ids and dynamic credentials username.
@@ -149,7 +136,7 @@ public class VaultRuntimeConfig {
 
     /**
      * Kv secret engine version.
-     * <p>
+     *
      * see https://www.vaultproject.io/docs/secrets/kv/index.html
      *
      * @asciidoclet
@@ -159,24 +146,24 @@ public class VaultRuntimeConfig {
 
     /**
      * KV secret engine path.
-     * <p>
+     *
      * This value is used when building the url path in the KV secret engine programmatic access
      * (i.e. `VaultKVSecretEngine`) and the vault config source (i.e. fetching configuration properties from Vault).
-     * <p>
+     *
      * For a v2 KV secret engine (default - see `kv-secret-engine-version property`)
      * the full url is built from the expression `<url>/v1/</kv-secret-engine-mount-path>/data/...`.
-     * <p>
+     *
      * With property `quarkus.vault.url=https://localhost:8200`, the following call
      * `vaultKVSecretEngine.readSecret("foo/bar")` would lead eventually to a `GET` on Vault with the following
      * url: `https://localhost:8200/v1/secret/data/foo/bar`.
-     * <p>
+     *
      * With a KV secret engine v1, the url changes to: `<url>/v1/</kv-secret-engine-mount-path>/...`.
-     * <p>
+     *
      * The same logic is applied to the Vault config source. With `quarkus.vault.secret-config-kv-path=config/myapp`
      * The secret properties would be fetched from Vault using a `GET` on
      * `https://localhost:8200/v1/secret/data/config/myapp` for a KV secret engine v2 (or
      * `https://localhost:8200/v1/secret/config/myapp` for a KV secret engine v1).
-     * <p>
+     *
      * see https://www.vaultproject.io/docs/secrets/kv/index.html
      *
      * @asciidoclet
@@ -205,10 +192,10 @@ public class VaultRuntimeConfig {
 
     /**
      * List of named credentials providers, such as: `quarkus.vault.credentials-provider.foo.kv-path=mypath`
-     * <p>
+     *
      * This defines a credentials provider `foo` returning key `password` from vault path `mypath`.
      * Once defined, this provider can be used in credentials consumers, such as the Agroal connection pool.
-     * <p>
+     *
      * Example: `quarkus.datasource.credentials-provider=foo`
      *
      * @asciidoclet
@@ -271,4 +258,43 @@ public String toString() {
                 '}';
     }
 
+    @ConfigGroup
+    public static class KvPathConfig {
+        // @formatter:off
+        /**
+         * List of comma separated vault paths in kv store,
+         * where all properties will be available as **prefixed** MP config properties.
+         *
+         * For instance if the application properties contains
+         * `quarkus.vault.secret-config-kv-path.myprefix=config`, and
+         * vault path `secret/config` contains `foo=bar`, then `myprefix.foo`
+         * will be available in the MP config.
+         *
+         * If the same property is available in 2 different paths for the same prefix, the last one
+         * will win.
+         *
+         * See also the documentation for the `quarkus.vault.kv-secret-engine-mount-path` property for some insights on how
+         * the full Vault url gets built.
+         *
+         * @asciidoclet
+         */
+        // @formatter:on
+        @ConfigItem(name = ConfigItem.PARENT)
+        List<String> paths;
+
+        public KvPathConfig(List<String> paths) {
+            this.paths = paths;
+        }
+
+        public KvPathConfig() {
+            paths = Collections.emptyList();
+        }
+
+        @Override
+        public String toString() {
+            return "SecretConfigKvPathConfig{" +
+                    "paths=" + paths +
+                    '}';
+        }
+    }
 }
diff --git a/extensions/vault/runtime/src/test/java/io/quarkus/vault/runtime/config/VaultConfigSourceTest.java b/extensions/vault/runtime/src/test/java/io/quarkus/vault/runtime/config/VaultConfigSourceTest.java
index f4e8f5a2675..47ff8245c44 100644
--- a/extensions/vault/runtime/src/test/java/io/quarkus/vault/runtime/config/VaultConfigSourceTest.java
+++ b/extensions/vault/runtime/src/test/java/io/quarkus/vault/runtime/config/VaultConfigSourceTest.java
@@ -1,6 +1,8 @@
 package io.quarkus.vault.runtime.config;
 
-import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PATH_PATTERN;
+import static io.quarkus.vault.runtime.config.VaultConfigSource.PROPERTY_PREFIX;
+import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PREFIX_PATHS;
+import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PREFIX_PATH_PATTERN;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -10,16 +12,18 @@
 
 public class VaultConfigSourceTest {
 
+    private static final String PROPERTY = PROPERTY_PREFIX + SECRET_CONFIG_KV_PREFIX_PATHS;
+
     @Test
     void secretConfigKvPathPattern() {
 
         Matcher matcher;
 
-        matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher("quarkus.vault.secret-config-kv-path.hello");
+        matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(PROPERTY + ".hello");
         assertTrue(matcher.matches());
         assertEquals("hello", matcher.group(1));
 
-        matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher("quarkus.vault.secret-config-kv-path.\\"mp.jwt.verify\\"");
+        matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(PROPERTY + ".\\"mp.jwt.verify\\"");
         assertTrue(matcher.matches());
         assertEquals("mp.jwt.verify", matcher.group(2));
     }