Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnsupportedOperationException in TlsConfigUtils when setting ALPN false #46652

Open
amusarra opened this issue Mar 6, 2025 · 3 comments
Open
Labels
area/reactive-messaging area/tls TLS Registry related issues and PR

Comments

@amusarra
Copy link
Contributor

amusarra commented Mar 6, 2025

Description

The application is using the smallrye-amqp connector configured to use SSL/TLS connection through the TLS Registry configuration, as also reported in the Quarkus documentation.

# Configure the AMQP connection properties.
# Hostname, IP address, and port of the server RabbitMQ is running on
amqp-host=localhost
amqp-port=5671
amqp-use-ssl=true

# Username and password for the server RabbitMQ is running on
amqp-username=quarkus
amqp-password=quarkus

quarkus.tls.rabbitmq-tls-config.trust-store.pem.certs=certs/ca.crt
quarkus.tls.rabbitmq-tls-config.alpn=false

I encountered a java.lang.UnsupportedOperationException when using the TlsConfigUtils class to configure TLS options in my Quarkus project. The error occurs because the setUseAlpn method is called unconditionally, even when ALPN is disabled in the configuration.

I am currently blocked due to this incorrect behavior and cannot find a way to avoid this situation from the documentation.

Steps to Reproduce

  1. Set quarkus.tls.rabbitmq-tls-config.alpn=false in application.properties.
  2. Run the application.
  3. Observe the java.lang.UnsupportedOperationException in the logs.

Expected Behavior

The application should not call setUseAlpn when ALPN is disabled in the configuration, avoiding the UnsupportedOperationException.

Actual Behavior

The application throws a java.lang.UnsupportedOperationException because setUseAlpn is called unconditionally.

Image

Environment:

Quarkus version: 3.19.1
Java version: OpenJDK Runtime Environment Corretto-23.0.2.7.1 (build 23.0.2+7-FR)
Operating System: macOS 15.3.1 (24D70) (24.3.0 Darwin Kernel Version 24.3.0)

Additional Context

The issue occurs in the TlsConfigUtils class, which is part of the core of Quarkus. Here is the relevant code snippet:

public static void configure(TCPSSLOptions options, TlsConfiguration configuration) {
    options.setSsl(true);
    if (configuration.getTrustStoreOptions() != null) {
        options.setTrustOptions(configuration.getTrustStoreOptions());
    }

    if (configuration.getKeyStoreOptions() != null) {
        options.setKeyCertOptions(configuration.getKeyStoreOptions());
    }

    SSLOptions sslOptions = configuration.getSSLOptions();
    if (sslOptions != null) {
        options.setSslHandshakeTimeout(sslOptions.getSslHandshakeTimeout());
        options.setSslHandshakeTimeoutUnit(sslOptions.getSslHandshakeTimeoutUnit());
        for (String suite : sslOptions.getEnabledCipherSuites()) {
            options.addEnabledCipherSuite(suite);
        }
        for (Buffer buffer : sslOptions.getCrlValues()) {
            options.addCrlValue(buffer);
        }
        options.setEnabledSecureTransportProtocols(sslOptions.getEnabledSecureTransportProtocols());
        options.setUseAlpn(sslOptions.isUseAlpn());
    }
}
SRMSG00230: Unable to create the publisher or subscriber during initialization: java.lang.UnsupportedOperationException
	at io.vertx.proton.ProtonClientOptions.setUseAlpn(ProtonClientOptions.java:304)
	at io.vertx.proton.ProtonClientOptions.setUseAlpn(ProtonClientOptions.java:43)
	at io.quarkus.tls.runtime.config.TlsConfigUtils.configure(TlsConfigUtils.java:81)
	at io.quarkus.tls.runtime.config.TlsConfigUtils.configure(TlsConfigUtils.java:92)
	at io.quarkus.tls.runtime.config.TlsConfigUtils.configure(TlsConfigUtils.java:105)
	at io.quarkus.smallrye.reactivemessaging.amqp.runtime.AmqpClientConfigCustomizer.customize(AmqpClientConfigCustomizer.java:32)
	at io.quarkus.smallrye.reactivemessaging.amqp.runtime.AmqpClientConfigCustomizer.customize(AmqpClientConfigCustomizer.java:17)
	at io.quarkus.smallrye.reactivemessaging.amqp.runtime.AmqpClientConfigCustomizer_ClientProxy.customize(Unknown Source)
	at io.smallrye.reactive.messaging.providers.helpers.ConfigUtils.customize(ConfigUtils.java:21)
	at io.smallrye.reactive.messaging.amqp.AmqpClientHelper.createClient(AmqpClientHelper.java:35)
	at io.smallrye.reactive.messaging.amqp.AmqpConnector.getPublisher(AmqpConnector.java:224)
	at io.smallrye.reactive.messaging.amqp.AmqpConnector_Subclass.getPublisher$$superforward(Unknown Source)
	at io.smallrye.reactive.messaging.amqp.AmqpConnector_Subclass$$function$$7.apply(Unknown Source)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:73)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext$NextAroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:97)
	at io.quarkus.smallrye.reactivemessaging.runtime.devmode.DevModeSupportConnectorFactoryInterceptor.intercept(DevModeSupportConnectorFactoryInterceptor.java:53)
	at io.quarkus.smallrye.reactivemessaging.runtime.devmode.DevModeSupportConnectorFactoryInterceptor_Bean.intercept(Unknown Source)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:70)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:62)
	at io.quarkus.smallrye.reactivemessaging.runtime.DuplicatedContextConnectorFactoryInterceptor.intercept(DuplicatedContextConnectorFactoryInterceptor.java:32)
	at io.quarkus.smallrye.reactivemessaging.runtime.DuplicatedContextConnectorFactoryInterceptor_Bean.intercept(Unknown Source)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:30)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:27)
	at io.smallrye.reactive.messaging.amqp.AmqpConnector_Subclass.getPublisher(Unknown Source)
	at io.smallrye.reactive.messaging.amqp.AmqpConnector_ClientProxy.getPublisher(Unknown Source)
	at io.smallrye.reactive.messaging.providers.impl.ConfiguredChannelFactory.createPublisher(ConfiguredChannelFactory.java:208)
	at io.smallrye.reactive.messaging.providers.impl.ConfiguredChannelFactory.register(ConfiguredChannelFactory.java:146)
	at io.smallrye.reactive.messaging.providers.impl.ConfiguredChannelFactory.initialize(ConfiguredChannelFactory.java:115)
	at io.smallrye.reactive.messaging.providers.impl.ConfiguredChannelFactory_ClientProxy.initialize(Unknown Source)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:807)
	at io.smallrye.reactive.messaging.providers.extension.MediatorManager.start(MediatorManager.java:250)
	at io.smallrye.reactive.messaging.providers.extension.MediatorManager_ClientProxy.start(Unknown Source)
	at io.quarkus.smallrye.reactivemessaging.runtime.SmallRyeReactiveMessagingLifecycle.onApplicationStart(SmallRyeReactiveMessagingLifecycle.java:53)
	at io.quarkus.smallrye.reactivemessaging.runtime.SmallRyeReactiveMessagingLifecycle_Observer_onApplicationStart_qTrMuLFyQ1IvGfeSxRVitl6CCBQ.notify(Unknown Source)
	at io.quarkus.arc.impl.EventImpl$Notifier.notifyObservers(EventImpl.java:365)
	at io.quarkus.arc.impl.EventImpl$Notifier.notify(EventImpl.java:347)
	at io.quarkus.arc.impl.EventImpl.fire(EventImpl.java:81)
	at io.quarkus.arc.runtime.ArcRecorder.fireLifecycleEvent(ArcRecorder.java:163)
	at io.quarkus.arc.runtime.ArcRecorder.handleLifecycleEvents(ArcRecorder.java:114)
	at io.quarkus.runner.recorded.LifecycleEventsBuildStep$startupEvent1144526294.deploy_0(Unknown Source)
	at io.quarkus.runner.recorded.LifecycleEventsBuildStep$startupEvent1144526294.deploy(Unknown Source)
	at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
	at io.quarkus.runtime.Application.start(Application.java:101)
	at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:121)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:77)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:48)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:137)
	at io.quarkus.runner.GeneratedMain.main(Unknown Source)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116)
	at java.base/java.lang.Thread.run(Thread.java:1575)

Here you can be found the example code https://github.com/amusarra/quarkus-quickstarts/tree/main/amqp-quickstart/amqp-quickstart-producer

Thank you for your assistance

Copy link

quarkus-bot bot commented Mar 6, 2025

/cc @radcortez (config)

@amusarra
Copy link
Contributor Author

amusarra commented Mar 10, 2025

Hi @radcortez
To unlock this error state, I tried to work around the issue by implementing a ClientCustomizer component with a high priority to change the behavior of TlsConfigUtils to avoid calling the setUseAlpn() method.

package org.acme.amqp.runtime.client;

// ...

@ApplicationScoped
@Priority(1)
public class AmqpClientConfigFixAlpnCustomizer implements ClientCustomizer<AmqpClientOptions> {

  private static final Logger log = Logger.getLogger(AmqpClientConfigFixAlpnCustomizer.class);

  @Inject
  TlsConfigurationRegistry tlsRegistry;

  @Override
  public AmqpClientOptions customize(String channel, Config channelConfig, AmqpClientOptions options) {
    Optional<String> tlsConfigName = channelConfig.getOptionalValue("tls-configuration-name", String.class);
    if (tlsConfigName.isPresent()) {
      String tlsConfig = tlsConfigName.get();
      Optional<TlsConfiguration> maybeTlsConfig = tlsRegistry.get(tlsConfig);
      if (maybeTlsConfig.isPresent()) {
        //Use the custom TlsConfigUtils
        TlsConfigUtils.configure(options, maybeTlsConfig.get()); // Use your custom class
        log.debugf("Configured RabbitMQOptions for channel %s with TLS configuration %s", channel, tlsConfig);
      }
    }
    return options;
  }
}
package org.acme.amqp.tls.runtime.config;

// ...
public class TlsConfigUtils {

  public static void configure(TCPSSLOptions options, TlsConfiguration configuration) {
    options.setSsl(true);
    if (configuration.getTrustStoreOptions() != null) {
      options.setTrustOptions(configuration.getTrustStoreOptions());
    }
      // ...
      options.setEnabledSecureTransportProtocols(sslOptions.getEnabledSecureTransportProtocols());
      
      // Avoid call setUseAlpn
      if (sslOptions.isUseAlpn()) {
        options.setUseAlpn(true);
      }
    }
  }
}

With this change to my example project, I managed to avoid the blocking exception but I get yet another error which I report below.

2025-03-06 16:40:40,687 INFO  [io.sma.rea.mes.amqp] (Quarkus Main Thread) SRMSG16201: AMQP broker configured to localhost:5671 for channel quotes
2025-03-06 16:40:40,713 INFO  [io.sma.rea.mes.amqp] (Quarkus Main Thread) SRMSG16201: AMQP broker configured to localhost:5671 for channel quote-requests
2025-03-06 16:40:40,731 INFO  [io.sma.rea.mes.amqp] (Quarkus Main Thread) SRMSG16212: Establishing connection with AMQP broker
2025-03-06 16:40:50,644 INFO  [io.quarkus] (Quarkus Main Thread) amqp-quickstart-producer 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.19.2) started in 11.185s. Listening on: http://localhost:8080
2025-03-06 16:40:50,645 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-03-06 16:40:50,645 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, messaging, messaging-amqp, rest, rest-jackson, smallrye-context-propagation, vertx]
2025-03-06 16:42:46,084 WARN  [io.net.cha.ChannelInitializer] (vert.x-eventloop-thread-0) Failed to initialize a channel. Closing: [id: 0x1a553353]: io.vertx.core.VertxException: java.lang.NullPointerException: Cannot read the array length because "tms" is null
        at io.vertx.core.net.impl.SslChannelProvider.sslClientContext(SslChannelProvider.java:85)
        at io.vertx.core.net.impl.SslChannelProvider.sslClientContext(SslChannelProvider.java:78)
        at io.vertx.core.net.impl.SslChannelProvider.createClientSslHandler(SslChannelProvider.java:135)
        at io.vertx.core.net.impl.ChannelProvider.initSSL(ChannelProvider.java:109)
        at io.vertx.core.net.impl.ChannelProvider.access$200(ChannelProvider.java:41)
        at io.vertx.core.net.impl.ChannelProvider$2.initChannel(ChannelProvider.java:149)
        at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:129)
        at io.netty.channel.ChannelInitializer.handlerAdded(ChannelInitializer.java:112)
        at io.netty.channel.AbstractChannelHandlerContext.callHandlerAdded(AbstractChannelHandlerContext.java:1130)
        at io.netty.channel.DefaultChannelPipeline.callHandlerAdded0(DefaultChannelPipeline.java:558)
        at io.netty.channel.DefaultChannelPipeline.access$100(DefaultChannelPipeline.java:45)
        at io.netty.channel.DefaultChannelPipeline$PendingHandlerAddedTask.execute(DefaultChannelPipeline.java:1410)
        at io.netty.channel.DefaultChannelPipeline.callHandlerAddedForAllHandlers(DefaultChannelPipeline.java:1064)
        at io.netty.channel.DefaultChannelPipeline.invokeHandlerAddedIfNeeded(DefaultChannelPipeline.java:599)
        at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:513)
        at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:479)
        at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:89)
        at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:83)
        at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:339)
        at io.netty.bootstrap.Bootstrap.doResolveAndConnect(Bootstrap.java:164)
        at io.netty.bootstrap.Bootstrap.connect(Bootstrap.java:148)
        at io.vertx.core.net.impl.ChannelProvider.handleConnect(ChannelProvider.java:152)
        at io.vertx.core.net.impl.ChannelProvider.connect(ChannelProvider.java:103)
        at io.vertx.core.net.impl.ChannelProvider.connect(ChannelProvider.java:89)
        at io.vertx.core.net.impl.NetClientImpl.connectInternal2(NetClientImpl.java:308)
        at io.vertx.core.net.impl.NetClientImpl.lambda$connectInternal$2(NetClientImpl.java:273)
        at io.vertx.core.impl.future.FutureImpl$4.onSuccess(FutureImpl.java:176)
        at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:66)
        at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:259)
        at io.vertx.core.impl.future.Mapping.onSuccess(Mapping.java:40)
        at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:66)
        at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:259)
        at io.vertx.core.impl.future.Mapping.onSuccess(Mapping.java:40)
        at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:66)
        at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:259)
        at io.vertx.core.impl.future.Mapping.onSuccess(Mapping.java:40)
        at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:66)
        at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:259)
        at io.vertx.core.impl.future.PromiseImpl.onSuccess(PromiseImpl.java:49)
        at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:66)
        at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:259)
        at io.vertx.core.impl.future.Composition$1.onSuccess(Composition.java:62)
        at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:60)
        at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.NullPointerException: Cannot read the array length because "tms" is null
        at io.quarkus.tls.runtime.keystores.ExpiryTrustOptions.getWrappedTrustManagers(ExpiryTrustOptions.java:87)
        at io.quarkus.tls.runtime.keystores.ExpiryTrustOptions$3.apply(ExpiryTrustOptions.java:81)
        at io.quarkus.tls.runtime.keystores.ExpiryTrustOptions$3.apply(ExpiryTrustOptions.java:77)
        at io.smallrye.mutiny.unchecked.UncheckedFunction.lambda$toFunction$0(UncheckedFunction.java:45)
        at io.vertx.core.net.impl.SslContextProvider.resolveTrustManagers(SslContextProvider.java:203)
        at io.vertx.core.net.impl.SslChannelProvider.sslContext(SslChannelProvider.java:93)
        at io.vertx.core.net.impl.SslChannelProvider.sslClientContext(SslChannelProvider.java:83)

Is it possible that the AMQP/TLS connection part was not tested correctly?

Thanks.

@radcortez
Copy link
Member

//cc @ozangunalp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/reactive-messaging area/tls TLS Registry related issues and PR
Projects
None yet
Development

No branches or pull requests

2 participants