quarkusio / quarkus

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

Native build fails with signed jars containing Quarkus resources to be augmented #35906

Closed galderz closed 2 months ago

galderz commented 1 year ago

Building native applications which depend on signed jars that contain Quarkus resources for which bytecode is generated (e.g. JAX-RS resources) will fail. This is the case in this discussion. The error looks like this:

Caused by: java.lang.SecurityException:
class "com.ibm.bamoe.ilmt.quarkus.pamoe.PAMOESwidTagGen"'s signer information
does not match signer information of other classes in the same package

The error is caused because PAMOESwidTagGen is a JAX-RS resource, located inside a signed jar. When Quarkus augments that, it generates a PAMOESwidTagGen_Bean class, which is stored inside non-signed generated-bytecode.jar. During native image process, the standard classloading encounters that both of this classes are in the same package com.ibm.bamoe.ilmt.quarkus.pamoe, but one is inside a signed jar and the other is an unsigned jar and hence it fails this integrity check.

The standalone reproducer attached to this issue demonstrates the issue native image encounters with just a java class that does the following:

class reproducer {
    public static void main(String[] args) throws ClassNotFoundException
    {
        System.out.println(Class.forName("org.acme.service.GreetingService_Bean"));
        System.out.println(Class.forName("org.acme.service.GreetingService"));
    }
}

Compiling it and running it with the jars within the bundle reproduces it:

$ ❯ ./reproduce.sh
+ javac reproducer.java
+ java -cp .:io.quarkus.arc.arc-3.3.2.jar:jakarta.enterprise.jakarta.enterprise.cdi-api-4.0.1.jar:org.acme.service-1.0.0-SNAPSHOT.jar:generated-bytecode.jar reproducer
class org.acme.service.GreetingService_Bean
Exception in thread "main" java.lang.SecurityException: class "org.acme.service.GreetingService"'s signer information does not match signer information of other classes in the same package
    at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1163)
    at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:907)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1015)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:375)
    at reproducer.main(reproducer.java:5)

This issue does not happen in either test, dev, or production mode, because they run custom classloaders. The way the java.security.CodeSource is created means that any certiticates that a jar might contained are ignored:

        this.protectionDomain = new ProtectionDomain(new CodeSource(url, (Certificate[]) null), null, runnerClassLoader, null);

This is not a security problem because Quarkus does not support running with a SecurityManager.

So, the main issue here is really trying to figure out how to fix native builds in this scenario. There are 3 ways to do this:

  1. Generate bytecode in a different package. This wouldn't be the default configuration, but could be something the user decides to activate optionally. This would break package private and/or protected access though.
  2. Rebundle signed jars so that the jar that is passed onto the native-image process does not contain certificates. This requires exploration on how the Certificate[] that is passed onto CodeSource is extracted out of a jar, and use that information to decide how to reshape the signed jars.
  3. State that signed jars containing augmented Quarkus resources are not supported.

Irrespective of the chose solution, the Security Guide needs to be updated to make it clear that:

  1. Quarkus does not support running with a SecurityManager.
  2. Acknowledge that Quarkus uses a custom classloader in all test, dev and production modes, and this classloader ignores the certificates inside jar files.
galderz commented 1 year ago

One way to reproduce this with Quarkus production JVM mode is to build the project with -Dquarkus.package.type=legacy-jar. When you do that, Quarkus fails to start in the scenario above:

/Users/g/opt/java-17u-dev-fastdebug/bin/java -jar http-endpoint/target/http-endpoint-1.0.0-SNAPSHOT-runner.jar
Exception in thread "main" java.lang.ExceptionInInitializerError
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:70)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
    at io.quarkus.runner.GeneratedMain.main(Unknown Source)
Caused by: java.lang.RuntimeException: Failed to start quarkus
    at io.quarkus.runner.ApplicationImpl.<clinit>(Unknown Source)
    ... 9 more
Caused by: java.lang.SecurityException: class "org.acme.service.GreetingService"'s signer information does not match signer information of other classes in the same package
    at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1163)
    at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:907)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1015)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at org.acme.service.GreetingService_Bean.<init>(Unknown Source)
    at io.quarkus.arc.setup.Default_ComponentsProvider.addBeans1(Unknown Source)
    at io.quarkus.arc.setup.Default_ComponentsProvider.getComponents(Unknown Source)
    at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:125)
    at io.quarkus.arc.Arc.initialize(Arc.java:39)
    at io.quarkus.arc.runtime.ArcRecorder.initContainer(ArcRecorder.java:47)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy_0(Unknown Source)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy(Unknown Source)
    ... 10 more
rob-spoor commented 1 year ago

I can confirm that legacy JARs also have this issue. However, since Quarkus 3.x legacy JAR is enforced if you use quarkus-azure-functions-http, which means we cannot switch back to uber JAR which never had this issue.

rob-spoor commented 1 year ago

I've been able to work around this issue by removing the signature files from the JAR file using the Maven antrun plugin:

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>remove-jar-signatures</id>
            <phase>package</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <zip destfile="tmp.jar">
                  <zipfileset src="<original-jar>">
                    <exclude name="META-INF/MSFTSIG.*" />
                  </zipfileset>
                </zip>
                <move file="tmp.jar" tofile="<original-jar>" />
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
gastaldi commented 6 months ago

This should be fixed with #40001. Can you confirm @galderz ?

zakkak commented 2 months ago

@galderz I don't see any way to reproduce this in a quarkus native build, could you please confirm whether this is now fixed or not?

Furthermore even if #40001 fixed the issue we still need to address the following:

Irrespective of the chose solution, the Security Guide needs to be updated to make it clear that:

Quarkus does not support running with a SecurityManager. Acknowledge that Quarkus uses a custom classloader in all test, dev and production modes, and this classloader ignores the certificates inside jar files.

galderz commented 2 months ago

I've not been able to run https://github.com/baldimir/dmn-quarkus-example-bamoe with latest Quarkus. I get this issue:

[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:build (default) on project kogito-quarkus-examples: Failed to build quarkus application: Failed to bootstrap application in NORMAL mode: Failed to resolve artifact io.quarkus:quarkus-resteasy-reactive-spi-deployment:jar:999-SNAPSHOT: The following artifacts could not be resolved: io.quarkus:quarkus-resteasy-reactive-spi-deployment:jar:999-SNAPSHOT (absent): io.quarkus:quarkus-resteasy-reactive-spi-deployment:jar:999-SNAPSHOT was not found in https://repository.jboss.org/nexus/content/groups/public/ during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of jboss-public-repository-group has elapsed or updates are forced -> [Help 1]

dependency:tree does not show where quarkus-resteasy-reactive-spi-deployment is coming from, but seems something in that project needs updating as well as upping the Quarkus version.

I'll post a comment in the original discussion and ask the user to upgrade their app. Let's close this.

galderz commented 2 months ago

Furthermore even if #40001 fixed the issue we still need to address the following:

Just send a PR?

zakkak commented 2 months ago

Furthermore even if #40001 fixed the issue we still need to address the following:

Just send a PR?

I would have if I felt comfortable doing so, but I don't feel knowledgeable enough to do so properly. I created https://github.com/quarkusio/quarkus/issues/42978 instead.