quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.68k stars 2.65k forks source link

Quarkus requires the public key to be present at build time, preventing it from using keys generated at runtime #22536

Closed rafaelfnx closed 2 years ago

rafaelfnx commented 2 years ago

Describe the bug

After we developed our application using the SmallRye JWT extension, I started to adapt the application-prod.properties file, to use environment variables, as I want to launch the application as a native image in a docker container.

We are using containers to make the application easy to configure and as adaptable as possible, to avoid having to build everything again whenever we change hosting provider or change some simple configuration.

So I changed the following lines in the application-prod.properties file:

smallrye.jwt.sign.key.location=${APP_PRIVATE_KEY}
mp.jwt.verify.publickey.location=${APP_PUBLIC_KEY}

So that this way, regardless of the way and location we generate the cryptographic keys, we can start the application so that it reads the keys at runtime.

Expected behavior

I expected that the application would build normally, but right at the stage of building the uber jar, the build failed, I tried to build just the uber jar, without the native image, and still the error happened.

Command I used to build in native image mode:

mvn clean package -Pnative -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:21.3-java17

Command I used to build in uber jar mode:

mvn clean package -Dquarkus.package.type=uber-jar

Actual behavior

In both cases, native or jar, the error was the same:

[ERROR] Failed to execute goal io.quarkus.platform:quarkus-maven-plugin:2.6.1.Final:build (default) on project app: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR]         [error]: Build step io.quarkus.smallrye.jwt.deployment.SmallRyeJwtProcessor#registerNativeImageResources threw an exception: java.util.NoSuchElementException: SRCFG00011: Could not expand value APP_PUBLIC_KEY in property mp.jwt.verify.publickey.location
[ERROR]         at io.smallrye.config.ExpressionConfigSourceInterceptor.lambda$getValue$0(ExpressionConfigSourceInterceptor.java:63)
[ERROR]         at io.smallrye.common.expression.ExpressionNode.emit(ExpressionNode.java:22)
[ERROR]         at io.smallrye.common.expression.Expression.evaluateException(Expression.java:56)
[ERROR]         at io.smallrye.common.expression.Expression.evaluate(Expression.java:70)
[ERROR]         at io.smallrye.config.ExpressionConfigSourceInterceptor.getValue(ExpressionConfigSourceInterceptor.java:56)
[ERROR]         at io.smallrye.config.ExpressionConfigSourceInterceptor.getValue(ExpressionConfigSourceInterceptor.java:36)
[ERROR]         at io.smallrye.config.SmallRyeConfigSourceInterceptorContext.proceed(SmallRyeConfigSourceInterceptorContext.java:20)
[ERROR]         at io.smallrye.config.PropertyNamesConfigSourceInterceptor.getValue(PropertyNamesConfigSourceInterceptor.java:61)
[ERROR]         at io.smallrye.config.SmallRyeConfigSourceInterceptorContext.proceed(SmallRyeConfigSourceInterceptorContext.java:20)
[ERROR]         at io.smallrye.config.SmallRyeConfig.getConfigValue(SmallRyeConfig.java:305)
[ERROR]         at io.smallrye.config.SmallRyeConfig.getValue(SmallRyeConfig.java:223)
[ERROR]         at io.smallrye.config.SmallRyeConfig.getOptionalValue(SmallRyeConfig.java:322)
[ERROR]         at io.quarkus.smallrye.jwt.deployment.SmallRyeJwtProcessor.registerNativeImageResources(SmallRyeJwtProcessor.java:121)
[ERROR]         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR]         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
[ERROR]         at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR]         at java.base/java.lang.reflect.Method.invoke(Method.java:568)
[ERROR]         at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:887)
[ERROR]         at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
[ERROR]         at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
[ERROR]         at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
[ERROR]         at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
[ERROR]         at java.base/java.lang.Thread.run(Thread.java:833)
[ERROR]         at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :app

The error was caused because Quarkus was unable to expand the ${APP_PUBLIC_KEY} environment variable at build time even though it is a runtime variable.

How to Reproduce?

To reproduce the problem, just create a simple Quarkus application in version 2.6.1.Final, with the extension quarkus-smallrye-jwt, and in application.properties or application-prod.properties define the normal properties of SmallRye JWT, like quarkus.smallrye-jwt.enabled=true, but in the mp.jwt.verify.publickey.location property provide an environment variable in the template ${var}.

Quarkus will try to expand it to take the public key location and include it in the native image, even if you are not building a native image but a uber-jar, because if you provide the location of a local key at build time, it will show this on the output in both cases:

[INFO] [io.quarkus.smallrye.jwt.deployment.SmallRyeJwtProcessor] Adding publicKey.pem to native image

Output of uname -a or ver

Microsoft Windows [versão 10.0.19043.1415]

Output of java -version

openjdk 17.0.1 2021-10-19 OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05) OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

Quarkus 2.6.1.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.8.1

Additional information

Exploring the io.quarkus.smallrye.jwt.deploymen.SmallRyeJwtProcessor class, which appears in the log when it finds the key and adds it to the native image, specifically in the method that causes the error if it can't find the key at build time, the registerNativeImageResources method, I found this:

    /**
     * If the configuration specified a deployment local key resource, register it in native mode
     *
     * @return NativeImageResourceBuildItem
     */
    @BuildStep
    NativeImageResourceBuildItem registerNativeImageResources() {
        final Config config = ConfigProvider.getConfig();
        Optional<String> publicKeyLocationOpt = config.getOptionalValue("mp.jwt.verify.publickey.location", String.class);
        if (publicKeyLocationOpt.isPresent()) {
            final String publicKeyLocation = publicKeyLocationOpt.get();
            if (publicKeyLocation.indexOf(':') < 0 || publicKeyLocation.startsWith("classpath:")) {
                log.infof("Adding %s to native image", publicKeyLocation);
                return new NativeImageResourceBuildItem(publicKeyLocation);
            }
        }
        return null;
    }

All this method does is look specifically for the mp.jwt.verify.publickey.location setting to include the public key in the native image at build time, it's the root of my problem.

I really don't think it should exist , because if I have a local private key and public key pair to be included in the native image I'm still required to include the private key manually with:

quarkus.native.resources.includes=/path/to/privateKey.pem

Because this method searches exclusively for the public key only, being useful only when another service is responsible for creating the tokens and I only need the public key.

But the problem is that it forces all other cases to have the public key at build time. This is not necessary as I tested it by removing the mp.jwt.verify.publickey.location from my application-prod.properties, and providing it only at the time of running the application with a command line argument -Dmp.jwt.verify.publickey.location=/path/to/publicKey.pem, doing that, it worked perfectly, and it loaded the public key at runtime, but it seems like a hack and not ideal.

I suggest removing this method as it would be clearer that both keys (or just the public key if you don't have the private one locally) were added to the native image using quarkus.native.resources.includes, rather than just the public key being added by a secret method with unexpected behavior that was not documented at https://quarkus.io/guides/security-jwt.

Or that at least there is a way to disable it for cases like this, and so that it doesn't become mandatory to have the public key at build time.

sberyozkin commented 2 years ago

@rafaelfnx Thanks for this detailed analysis. I'm not sure right now what is the best way forward as removing this method will break those applications which indirectly depend on it in native image - the typical MP JWT case is that indeed, the tokens have been creates elsewhere, so the public key is picked up.

Perhaps we should have a property (I think you also suggest something similar), enabled by default, to automatically include the keys identified by the standard MP JWT properties in native image. In your case you'd disable it, for example, quarkus.smallrye-jwt.add-keys=true/false. Can it work for you ?

rafaelfnx commented 2 years ago

Hello @sberyozkin!

Perhaps we should have a property (I think you also suggest something similar), enabled by default, to automatically include the keys identified by the standard MP JWT properties in native image. In your case you'd disable it, for example, quarkus.smallrye-jwt.add-keys=true/false. Can it work for you ?

It works perfectly! I think even this setting makes it clearer that Quarkus will automatically add the key to the native image as it wasn't so clear. I think it looks good this way!

As an additional suggestion, since Quarkus checks for a public key to add the native image, it would make sense for it to also check the smallrye.jwt.sign.key.location property to add the private key as well, if there is one locally, covering all cases evenly.

But regardless of this additional suggestion, I leave my vote for your suggestion, from the property quarkus.smallrye-jwt.add-keys=true/false. I don't know how it works from now on, I imagine this is like a proposal that maybe will be welcomed by the community that maintains Quarkus or not, but anyway, thank you so much @sberyozkin!

rafaelfnx commented 2 years ago

Hi @sberyozkin! This topic has been idle since the new year, can we then continue? As I said before, your property suggestion quarkus.smallrye-jwt.add-keys=true/false would really help me! But I don't know how to proceed now, should I make an improvement proposal?

And I would also like to just try to take my further suggestion further from smallrye.jwt.sign.key.location to add the private key to the native image by default too.