spring-bootazureazure-active-directoryazure-eventhubazure-eventhub-client

spring boot auto-configure Azure EventHub handshake issue


I am writting an application using Java Spring Boot auto configure that tries to send a message to Azure EventHub using SPN for authentication without avail as i'm getting a cert issue. My code looks like this:

Environment:

Java 17
Spring Boot: solace-spring-boot-starter:2.0.0
Spring Azure: spring-cloud-azure-starter-integration-eventhubs:5.13.0

EventHubProducerService

package com.mysample.producer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.azure.messaging.eventhubs.EventData;
import com.azure.messaging.eventhubs.EventHubProducerClient;

import java.util.Collections;

@Service
public class EventHubProducerService {

    @Autowired
    private EventHubProducerClient producerClient;

    public void sendEventHubMessage(String message){
        producerClient.send(Collections.singletonList(new EventData(message)));
        log.info("message sent successfully to EventHub");
    }
}

application.yml

spring.cloud.azure:
  eventhubs:
    namespace: "<namespaceName>"
    eventHubName: "<eventHubName>"
  credential:
    client-id: "<SPN ClientId>"
    client-secret: "<SPN SecretValue>"
  profile:
    tenant-id: "<TenantId>"

The error is the following when sendEventHubMessage("Hello World") gets executed:

2024-07-11T14:57:45.612-04:00  WARN 9632 --- [ctor-http-nio-1] r.netty.http.client.HttpClientConnect    : [330ebfae, L:/<ip>:<port> - R:login.microsoftonline.com/<ip>:443] The connection observed an error

javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.handshakeException(ReferenceCountedOpenSslEngine.java:1927) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.wrap(ReferenceCountedOpenSslEngine.java:848) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at java.base/javax.net.ssl.SSLEngine.wrap(SSLEngine.java:564) ~[na:na]
        at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:1129) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.wrapNonAppData(SslHandler.java:973) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1509) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1347) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]        
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]  
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]     
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.111.Final.jar:4.1.111.Final] 
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]        
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) ~[na:na]
        at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) ~[na:na]
        at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:285) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:na]
        at io.netty.handler.ssl.EnhancingX509ExtendedTrustManager.checkServerTrusted(EnhancingX509ExtendedTrustManager.java:69) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback.verify(ReferenceCountedOpenSslClientContext.java:235) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier.verify(ReferenceCountedOpenSslContext.java:797) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.internal.tcnative.CertificateVerifierTask.runTask(CertificateVerifierTask.java:36) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:48) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:42) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.runAndResetNeedTask(ReferenceCountedOpenSslEngine.java:1533) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.access$700(ReferenceCountedOpenSslEngine.java:94) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine$TaskDecorator.run(ReferenceCountedOpenSslEngine.java:1505) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1649) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1495) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        ... 21 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
        at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
        at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
        at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ~[na:na]
        ... 36 common frames omitted

Any ideas? if i connect to EvenHub using a connection-string it works fine, only when i'm using SPN does it give me this error, i presume i have to get a cert somewhere in azure and import it to the java cacert?


Solution

  • Here, you need to change the above application.yml file format and also Additionally, needed slight adjustment as you can check that in the below steps.

    application.yml:

    spring:
      cloud:
        azure:
          credential:
            client-id: "<SPN ClientId>"
            client-secret: "<SPN SecretValue>"
            tenant-id: "<TenantId>"
          eventhubs:
            namespace: "<namespaceName>"
            producer:
              event-hub-name: "<eventHubName>"
    

    Check this below application that picks up the EventHub by a configuration class.

    import com.azure.identity.ClientSecretCredential;
    import com.azure.identity.ClientSecretCredentialBuilder;
    import com.azure.messaging.eventhubs.EventHubClientBuilder;
    import com.azure.messaging.eventhubs.EventHubProducerClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class EventHubConfig {
    
        @Value("${spring.cloud.azure.credential.client-id}")
        private String clientId;
    
        @Value("${spring.cloud.azure.credential.client-secret}")
        private String clientSecret;
    
        @Value("${spring.cloud.azure.credential.tenant-id}")
        private String tenantId;
    
        @Value("${spring.cloud.azure.eventhubs.namespace}")
        private String namespace;
    
        @Value("${spring.cloud.azure.eventhubs.producer.event-hub-name}")
        private String eventHubName;
    
        @Bean
        public EventHubProducerClient producerClient() {
            ClientSecretCredential credential = new ClientSecretCredentialBuilder()
                .clientId(clientId)
                .clientSecret(clientSecret)
                .tenantId(tenantId)
                .build();
    
            return new EventHubClientBuilder()
                .credential(namespace, eventHubName, credential)
                .buildProducerClient();
        }
    }
    

    Application tree:

    my-spring-boot-app/
    ├── src/
    │   ├── main/
    │   │   ├── java/
    │   │   │   ├── com/
    │   │   │   │   ├── mysample/
    │   │   │   │   │   ├── MySpringBootApplication.java
    │   │   │   │   │   ├── config/
    │   │   │   │   │   │   └── EventHubConfig.java
    │   │   │   │   │   ├── consumer/
    │   │   │   │   │   │   └── SomeClass.java
    │   │   │   │   │   ├── producer/
    │   │   │   │   │   │   └── EventHubProducerService.java
    │   │   ├── resources/
    │   │   │   ├── application.yml
    │   │   │   └── logback-spring.xml (optional, for logging configuration)
    ├── pom.xml
    

    Application runs successfully:

    enter image description here

    SPN setup:

    enter image description here

    EventHub:

    enter image description here