quarkiverse / quarkus-vault

Quarkus HashiCorp Vault extension
Apache License 2.0
19 stars 26 forks source link

[3.2.0]: Boolean entry in vault no more supported #189

Closed edelfour closed 1 year ago

edelfour commented 1 year ago

Hello, We are currently migrating to Quarkus 3.4.3 which uses quarkus-vault:3.2.0
In our vault, we have entries of boolean type, for example: "dev-mode":true
At the startup we encoutered the following exception:

   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR java.lang.RuntimeException: Failed to start quarkus
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runtime.Application.start(Application.java:101)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.runner.GeneratedMain.main(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR Caused by: java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class java.lang.String (java.lang.Boolean and java.lang.String are in module java.base of loader 'bootstrap')
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.vault.runtime.VaultKvManager.lambda$convert$0(VaultKvManager.java:58)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.Iterator.forEachRemaining(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.util.stream.ReferencePipeline.collect(Unknown Source)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.quarkus.vault.runtime.VaultKvManager.convert(VaultKvManager.java:58)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.context.impl.wrappers.SlowContextualFunction.apply(SlowContextualFunction.java:21)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:36)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.onItem(UniOnItemTransformToUni.java:60)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:43)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOperatorProcessor.onItem(UniOperatorProcessor.java:47)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOperatorProcessor.onItem(UniOperatorProcessor.java:47)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOperatorProcessor.onItem(UniOperatorProcessor.java:47)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOperatorProcessor.onItem(UniOperatorProcessor.java:47)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:43)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.operators.uni.UniFailOnTimeout$UniFailOnTimeoutProcessor.onItem(UniFailOnTimeout.java:69)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.vertx.AsyncResultUni.lambda$subscribe$1(AsyncResultUni.java:35)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.smallrye.mutiny.vertx.DelegatingHandler.handle(DelegatingHandler.java:25)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.handleDispatchResponse(HttpContext.java:397)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:384)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:362)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:329)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.dispatchResponse(HttpContext.java:291)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.ext.web.client.impl.HttpContext.lambda$null$7(HttpContext.java:507)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:277)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:259)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:43)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
   2023-10-28T21:09:54.61+0000 [APP/PROC/WEB/0] ERR at java.base/java.lang.Thread.run(Unknown Source)

The problems seems to come from convert method from VaultKvManager classe. The method do a String cast on value which throws the exception when it's a Boolean object.
Thanks in advance for your help.

edelfour commented 1 year ago

Hello @vsevel, @sberyozkin, @kdubb
By any chance, would you have time to have a look at this issue and the proposed fix ? Thanks in advance :-)

gsmet commented 1 year ago

@edelfour created a PR here: https://github.com/quarkiverse/quarkus-vault/pull/190

vsevel commented 1 year ago

Map<String, String> readSecret(String path) is a shortcut to read secrets that are maps of strings. for other situations we have Map<String, Object> readSecretJson(String path) would that fit your need? that feels strange converting those non string values to strings.

edelfour commented 1 year ago

Hello @vsevel We don't call those methods directly in our code. We just have the quarkus-vault dependency in our pom file and the vault properties in our application.properties file. If I understand well magic happens in io.quarkus.vault.runtime.config.VaultConfigSource which implements org.eclipse.microprofile.config.spi.ConfigSource and calls readSecret method. VaultConfigSource implements public Map<String, String> getProperties() and public String getValue(String propertyName) and that's the reason why in VaultConfigSource readSecret is called because ConfigSource wants property value as String.

vsevel commented 1 year ago

I see. so may be it is an issue with the VaultConfigSource, not the VaultKvManager. the issue I see with your implementation is that you are going to take the toString of any top level entry you are going to find in your secret. so if your value is a list or a map you are going to take the java toString of those objects. I do not know if people are expecting this. may be we should change VaultConfigSource.fetchSecrets() instead, with something like this:

    private Map<String, String> fetchSecrets(String path, String prefix) {

        Map<String, Object> secretJson = getVaultKVSecretEngine().readSecretJson(path).await().indefinitely();

        // ignore list and map, honor null, get as string scalar types
        Map<String, String> secret = secretJson.entrySet().stream()
                .filter(entry -> isScalar(entry.getValue()))
                .collect(toMap(entry -> entry.getKey(), entry -> toString(entry.getValue())));

        return prefixMap(secret, prefix);
    }

    private boolean isScalar(Object value) {
        return !(value instanceof List || value instanceof Map);
    }

    private String toString(Object value) {
        return value == null ? null : value.toString();
    }
edelfour commented 1 year ago

yes for sure :-) Perhaps, instead of ignoring List, List could be converted to a String with coma separated values as ConfigSource can convert this format to a List ?

vsevel commented 1 year ago

yes assuming this is not a list of list or a list of maps

edelfour commented 1 year ago

yes :-)
perhaps the documentation of quarkus vault also should mention that values of type list or map are not supported
if it's ok for you, I will close my PR as there is a better solution.
Any idea in which release the fix will be available ?

vsevel commented 1 year ago

if it's ok for you, I will close my PR as there is a better solution.

yes are you willing to develop the fix?

edelfour commented 1 year ago

If it can help, I can do the copy/paste :-) and the update of the tests

vsevel commented 1 year ago

that would be fantastic you can try implementing your suggestion to support scalar list as well (or we leave this for another time)

vsevel commented 1 year ago

for your tests, instead of using the WiremockProxy, you can add a true secret using a file such as secret.json, and push it into the vault from the VaultTestExtension using:

.withClasspathResourceMapping("config.json", "/tmp/config.json", READ_ONLY)
...
execVault(format("vault kv put %s/config-json @/tmp/config.json", SECRET_PATH_V1));

you would have to change the policy as well:

path "secret-v1/config-json" {
  capabilities = ["read"]
}

then add another quarkus.vault.secret-config-kv-path in application-vault.properties, beside the existing value config:

quarkus.vault.secret-config-kv-path=config,config-json

and you would probably have to do it for v2 as well:

execVault(format("vault kv put %s/config-json @/tmp/config.json", SECRET_PATH_V2));
...
edelfour commented 1 year ago

Thanks for the tips :-)

edelfour commented 1 year ago

Hello @vsevel A PR is ready for review following your recommendation. I did not manage the scalar list in this PR to avoid mixing fix and enhancement. I'll do another PR to manage the scalar list support.