Skip to content
This repository has been archived by the owner on Feb 23, 2023. It is now read-only.

Failed to establish connection to Kafka with SASL authentication. #1416

Closed
robtha opened this issue Jan 2, 2022 · 22 comments
Closed

Failed to establish connection to Kafka with SASL authentication. #1416

robtha opened this issue Jan 2, 2022 · 22 comments
Assignees
Labels
for: external-project For an external project and not something we can fix type: compatibility Native image compatibility issue

Comments

@robtha
Copy link

robtha commented Jan 2, 2022

Connecting to Kafka fails with the following exception (see full stack trace below)

javax.security.sasl.SaslException: Cannot get userid/password

I am using the following configuration settings:

spring.kafka.properties.sasl.mechanism=PLAIN
spring.kafka.properties.bootstrap.servers=pkc-123456.eu-central-1.aws.confluent.cloud:9092
spring.kafka.properties.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username='test' password='test';
spring.kafka.properties.security.protocol=SASL_SSL

Environment:

  • Java 17
  • Spring Boot 2.6.2
  • Spring Native 0.11.1
  • GraalVM CE 21.3.0
java.io.IOException: Channel could not be created for socket java.nio.channels.SocketChannel[closed]
        at org.apache.kafka.common.network.Selector.buildAndAttachKafkaChannel(Selector.java:348) ~[na:na]
        at org.apache.kafka.common.network.Selector.registerChannel(Selector.java:329) ~[na:na]
        at org.apache.kafka.common.network.Selector.connect(Selector.java:256) ~[na:na]
        at org.apache.kafka.clients.NetworkClient.initiateConnect(NetworkClient.java:977) ~[na:na]
        at org.apache.kafka.clients.NetworkClient.ready(NetworkClient.java:301) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.sendEligibleCalls(KafkaAdminClient.java:1117) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.processRequests(KafkaAdminClient.java:1377) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.run(KafkaAdminClient.java:1320) ~[na:na]
        at java.lang.Thread.run(Thread.java:833) ~[na:na]
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:596) ~[na:na]
        at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192) ~[na:na]
Caused by: org.apache.kafka.common.KafkaException: org.apache.kafka.common.errors.SaslAuthenticationException: Failed to configure SaslClientAuthenticator
        at org.apache.kafka.common.network.SaslChannelBuilder.buildChannel(SaslChannelBuilder.java:240) ~[kafka-test:na]
        at org.apache.kafka.common.network.Selector.buildAndAttachKafkaChannel(Selector.java:338) ~[na:na]
        ... 10 common frames omitted
Caused by: org.apache.kafka.common.errors.SaslAuthenticationException: Failed to configure SaslClientAuthenticator
Caused by: org.apache.kafka.common.errors.SaslAuthenticationException: Failed to create SaslClient with mechanism PLAIN
Caused by: javax.security.sasl.SaslException: Cannot get userid/password
        at com.sun.security.sasl.ClientFactoryImpl.getUserInfo(ClientFactoryImpl.java:159) ~[kafka-test:na]
        at com.sun.security.sasl.ClientFactoryImpl.createSaslClient(ClientFactoryImpl.java:96) ~[kafka-test:na]
        at javax.security.sasl.Sasl.createSaslClient(Sasl.java:434) ~[kafka-test:na]
        at org.apache.kafka.common.security.authenticator.SaslClientAuthenticator.lambda$createSaslClient$0(SaslClientAuthenticator.java:219) ~[na:na]
        at java.security.AccessController.doPrivileged(AccessController.java:150) ~[na:na]
        at javax.security.auth.Subject.doAs(Subject.java:439) ~[na:na]
        at org.apache.kafka.common.security.authenticator.SaslClientAuthenticator.createSaslClient(SaslClientAuthenticator.java:215) ~[na:na]
        at org.apache.kafka.common.security.authenticator.SaslClientAuthenticator.<init>(SaslClientAuthenticator.java:206) ~[na:na]
        at org.apache.kafka.common.network.SaslChannelBuilder.buildClientAuthenticator(SaslChannelBuilder.java:286) ~[kafka-test:na]
        at org.apache.kafka.common.network.SaslChannelBuilder.lambda$buildChannel$1(SaslChannelBuilder.java:228) ~[kafka-test:na]
        at org.apache.kafka.common.network.KafkaChannel.<init>(KafkaChannel.java:143) ~[na:na]
        at org.apache.kafka.common.network.SaslChannelBuilder.buildChannel(SaslChannelBuilder.java:236) ~[kafka-test:na]
        at org.apache.kafka.common.network.Selector.buildAndAttachKafkaChannel(Selector.java:338) ~[na:na]
        at org.apache.kafka.common.network.Selector.registerChannel(Selector.java:329) ~[na:na]
        at org.apache.kafka.common.network.Selector.connect(Selector.java:256) ~[na:na]
        at org.apache.kafka.clients.NetworkClient.initiateConnect(NetworkClient.java:977) ~[na:na]
        at org.apache.kafka.clients.NetworkClient.ready(NetworkClient.java:301) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.sendEligibleCalls(KafkaAdminClient.java:1117) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.processRequests(KafkaAdminClient.java:1377) ~[na:na]
        at org.apache.kafka.clients.admin.KafkaAdminClient$AdminClientRunnable.run(KafkaAdminClient.java:1320) ~[na:na]
        at java.lang.Thread.run(Thread.java:833) ~[na:na]
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:596) ~[na:na]
        at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192) ~[na:na]
Caused by: javax.security.auth.callback.UnsupportedCallbackException: Could not login: the client is being asked for a password, but the Kafka client code does not currently support obtaining a password from the user.
        at org.apache.kafka.common.security.authenticator.SaslClientCallbackHandler.handle(SaslClientCallbackHandler.java:73) ~[kafka-test:na]
        at com.sun.security.sasl.ClientFactoryImpl.getUserInfo(ClientFactoryImpl.java:138) ~[kafka-test:na]
        ... 22 common frames omitted
@sdeleuze
Copy link
Contributor

sdeleuze commented Jan 3, 2022

Could you please share a reproducer and let us know if it works on the JVM without -DspringAot=true and with -DspringAot=true?

@sdeleuze sdeleuze added the status: waiting-for-feedback We need additional information before we can continue label Jan 3, 2022
@robtha
Copy link
Author

robtha commented Jan 6, 2022

The attached sample application is a minimalistic kafka application. It only creates a topic by the KafkaAdminClient.

I used confluent cloud kafka for my tests becauses it is the easiest way for me to connect to a properly SSL configured Kafka.

The application runs fine on the JVM either with or without -DspringAot=true

To solve some initialization errors I added some additional hints but in the end failed on establishing the connection.

kind regards

kafka-native.zip

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 6, 2022
@sdeleuze sdeleuze added this to the 0.11.2 milestone Jan 6, 2022
@sdeleuze sdeleuze added type: compatibility Native image compatibility issue and removed status: feedback-provided Feedback has been provided labels Jan 6, 2022
@sdeleuze
Copy link
Contributor

sdeleuze commented Jan 6, 2022

@garyrussell Could you please have a look?

@garyrussell
Copy link
Contributor

garyrussell commented Jan 6, 2022

I found some more classes that are instantiated in the kafka-clients with Utils.newInstance(), but this still doesn't work, even with SASL_PLAINTEXT...

@ResourceHint( isBundle = true, patterns = { "sun.security.util.Resources" } )
@TypeHint(
   types = {
           AbstractLogin.DefaultLoginCallbackHandler.class,
           DefaultLogin.class,
           PlainLoginModule.class,
           SaslClientCallbackHandler.class,
           DefaultLogin.class,
           DefaultSslEngineFactory.class,
           Subject.class
   },
   access = { TypeAccess.DECLARED_CONSTRUCTORS } )

In SaslCallbackHandler.handle()...

        Subject subject = Subject.getSubject(AccessController.getContext());
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                NameCallback nc = (NameCallback) callback;
                if (subject != null && !subject.getPublicCredentials(String.class).isEmpty()) {
                    nc.setName(subject.getPublicCredentials(String.class).iterator().next());
                } else
                    nc.setName(nc.getDefaultName());
            } else if (callback instanceof PasswordCallback) {
                if (subject != null && !subject.getPrivateCredentials(String.class).isEmpty()) { // <<<<<<<<<<<<<<<<<<<<<<<<<<
                    char[] password = subject.getPrivateCredentials(String.class).iterator().next().toCharArray();
                    ((PasswordCallback) callback).setPassword(password);
                } else {
                    String errorMessage = "Could not login: the client is being asked for a password, but the Kafka" +
                             " client code does not currently support obtaining a password from the user.";
                    throw new UnsupportedCallbackException(callback, errorMessage);
                }
...

If it was a reflection issue, I'd expect to see class not found exceptions, but it appears that either the Subject is null or the private credentials therein are empty (as indicated by <<<< above).

@garyrussell
Copy link
Contributor

garyrussell commented Jan 10, 2022

Looks like this is an issue with GraalVM.

I added some diagnostics to the kafka-clients code and determined that the required Provider (for SASL PLAIN) is not present in the native environment.

When Running on the JVM, I see these providers (and the required provider)...

Providers: [SUN version 17, SunRsaSign version 17, SunEC version 17, SunJSSE version 17, SunJCE version 17, SunJGSS version 17, SunSASL version 17, XMLDSig version 17, SunPCSC version 17, JdkLDAP version 17, JdkSASL version 17, Apple version 17, SunPKCS11 version 17, Simple SASL/PLAIN Server Provider version 1.0]
Required Provider: SunSASL version 17

When running the native image, I see

Providers: [SUN version 17, SunRsaSign version 17, SunEC version 17, SunJSSE version 17, SunJCE version 17, JdkLDAP version 17, Apple version 17, Simple SASL/PLAIN Server Provider version 1.0]
Required Provider: null

Notice the smaller list of Providers.

I found this oracle/graal#3664

@Hollerweger
Copy link

Maybe the provider can be manually registered via -H:AdditionalSecurityProviders
https://www.graalvm.org/reference-manual/native-image/JCASecurityServices/#custom-service-types

@garyrussell
Copy link
Contributor

garyrussell commented Jan 13, 2022

@Hollerweger Per the comments in that issue and https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#_enable_native_image_support - I tried adding

<BP_NATIVE_IMAGE_BUILD_ARGUMENTS>-H:AdditionalSecurityProviders=com.sun.security.sasl.Provider</BP_NATIVE_IMAGE_BUILD_ARGUMENTS>

to the pom, but it didn't help (same result).

@sdeleuze
Copy link
Contributor

I asked feedback to the GraalVM team, I will update this issue based on their feedback.

@sdeleuze
Copy link
Contributor

Could you try to annotate the application class with:

@NativeHint(options = {
        "-H:AdditionalSecurityProviders=com.sun.security.sasl.Provider",
        "-H:+TraceSecurityServices"
})

That allows the options to be taken in account with both Native Build Tools and Buildpacks, and when used with Native Build Tools that will generate a report of the security modules enabled in target/reports. I tried and with this flag I see Marked provider com.sun.security.sasl.Provider as used (without it is not).

I don't think I can test myself without a Kafka instance with SASL so please let me know how it goes as a comment.

@robtha
Copy link
Author

robtha commented Jan 15, 2022

I have tried with @NativeHint from above. Did not work, still getting

javax.security.auth.callback.UnsupportedCallbackException: Could not login: the client is being asked for a password, but the Kafka client code does not currently support obtaining a password from the user.

I have attached the report generated by TraceSecurityServices
security_services_20220115_094322.txt
.

@sdeleuze
Copy link
Contributor

JdkSASL - com.sun.security.sasl.gsskerb.JdkSASL is marked as not used, not sure if it is needed or not, but could you try to enable it as well?

@sdeleuze sdeleuze added the status: waiting-for-feedback We need additional information before we can continue label Jan 18, 2022
@robtha
Copy link
Author

robtha commented Jan 18, 2022

Extended NativeHint as requested - did not help

@NativeHint(options = {
   "-H:AdditionalSecurityProviders=com.sun.security.sasl.Provider,com.sun.security.sasl.gsskerb.JdkSASL",
   "-H:+TraceSecurityServices"
})

security_services_20220118_134222.txt

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 18, 2022
@garyrussell
Copy link
Contributor

garyrussell commented Jan 18, 2022

I don't think I can test myself without a Kafka instance with SASL so please let me know how it goes as a comment.

I am testing with SASL_PLAINTEXT and getting a slightly different error - it can't find the provider.

In @robtha 's case, the provider is located but when the callback is called, the Subject is not properly set up.

Adding the above hint solved the provider problem and I am now seeing the same error as @robtha

Caused by: javax.security.auth.callback.UnsupportedCallbackException: Could not login: the client is being asked for a password, but the Kafka client code does not currently support obtaining a password from the user.
	at org.apache.kafka.common.security.authenticator.SaslClientCallbackHandler.handle(SaslClientCallbackHandler.java:74) ~[kafka-native:na]

i.e. the Subject is not set up correctly.

I will add some more diagnostics to the kafka-clients to see if I can determine this issue.

@garyrussell
Copy link
Contributor

Peeling back the onion some more; I added these diagnostics to the SaslClientAuthenticator...

  SaslClient createSaslClient() {
      System.out.println("createSaslClient: " + subject);
      try {
          return Subject.doAs(subject, (PrivilegedExceptionAction<SaslClient>) () -> {
              System.out.println("doAs: " + subject);
              Subject subject2 = Subject.getSubject(AccessController.getContext());
              System.out.println(Thread.currentThread().getName() + ": " + subject2 + ", context: " +  AccessController.getContext()
                      + ", combiner: " + AccessController.getContext().getDomainCombiner());
              ...

When I run this in the JVM, I see

createSaslClient: Subject:
	Public Credential: admin
	Private Credential: admin-secret

doAs: Subject:
	Public Credential: admin
	Private Credential: admin-secret

kafka-admin-client-thread | adminclient-1: Subject:
	Public Credential: admin
	Private Credential: admin-secret
, context: java.security.AccessControlContext@35400565, combiner: javax.security.auth.SubjectDomainCombiner@28638073

So the subject is correctly populated. However, when I run the native code, I get

createSaslClient: Subject:
	Public Credential: admin
	Private Credential: admin-secret

doAs: Subject:
	Public Credential: admin
	Private Credential: admin-secret

kafka-admin-client-thread | adminclient-1: null, context: java.security.AccessControlContext@0, combiner: null

i.e. the Subject returned by Subject.getSubject() is null (which causes the error later). I believe that this is because the combiner is null.

The AccessorControllerContext is created in Subject.createContext().

    private static AccessControlContext createContext(final Subject subject,
                                        final AccessControlContext acc) {


        return java.security.AccessController.doPrivileged
            (new java.security.PrivilegedAction<>() {
            public AccessControlContext run() {
                if (subject == null) {
                    return new AccessControlContext(acc, null);
                } else {
                    return new AccessControlContext
                                        (acc,
                                        new SubjectDomainCombiner(subject));
            }
            }
        });
    }

which is called from the Subject.doAs() method.

I have confirmed that the subject is not null (passed back into the doAs lambda) so it is not clear how the combiner can be null.

That said, the identity hashCode of the context instance is suspicious (0) java.security.AccessControlContext@0 in the native image.

FYI, AccessController.getContext() calls this native method:

    /**
     * Returns the AccessControl context. i.e., it gets
     * the protection domains of all the callers on the stack,
     * starting at the first class with a non-null
     * ProtectionDomain.
     *
     * @return the access control context based on the current stack or
     *         null if there was only privileged system code.
     */

    @SuppressWarnings("removal")
    private static native AccessControlContext getStackAccessControlContext();

@sdeleuze sdeleuze modified the milestones: 0.11.2, 0.11.3 Jan 20, 2022
@sdeleuze
Copy link
Contributor

Could be related to oracle/graal#4279.

@sdeleuze sdeleuze added status: blocked An issue that's blocked on an external project change or another issue and removed status: feedback-provided Feedback has been provided labels Feb 4, 2022
@sdeleuze sdeleuze removed this from the 0.11.3 milestone Feb 4, 2022
@sdeleuze sdeleuze added this to the Backlog milestone Feb 4, 2022
@sdeleuze
Copy link
Contributor

sdeleuze commented Feb 4, 2022

Let's test again when the issue mentioned above will be fixed.

@robtha
Copy link
Author

robtha commented Feb 20, 2022

Tested successfully today using graalvm developer build 22.1 (22.1.0-dev-20220218_1947).

An additional ResourceHint was needed for displaying kafka.version

@ResourceHint( patterns = { "kafka.kafka-version.properties"} )

@sdeleuze
Copy link
Contributor

@robtha Thanks for your feedback, since that's on GraalVM side, I close this issue. Please provide a PR for the missing hint (should it be kafka/kafka-version.properties?) with the right trigger in KafkaHints.

@sdeleuze sdeleuze added for: external-project For an external project and not something we can fix and removed status: blocked An issue that's blocked on an external project change or another issue labels Mar 15, 2022
@sdeleuze sdeleuze removed this from the Backlog milestone Mar 15, 2022
@intumba
Copy link

intumba commented Oct 25, 2022

Hi,

I am facing this issue with Java 17 as noted above by the author of this ticket.
I have noticed that this feature works fine with Java 11 however the interest is on Java 17 for our project.

@sdeleuze or anyone here do you know if anyone on the for:external-project is looking at this issue?

@sdeleuze
Copy link
Contributor

sdeleuze commented Nov 2, 2022

@intumba Please check with Spring Boot 3 RC1 or snapshots if this use case work or not, you can create a new project on https://start.spring.io/#!platformVersion=3.0.0-RC1&dependencies=native or start from what we have in https://github.com/spring-projects/spring-aot-smoke-tests/tree/main/integration. Make sure to use GraalVM 22.3.

If it does not work, please raise an issue on https://github.com/spring-projects/spring-kafka.

@intumba
Copy link

intumba commented Nov 2, 2022

@sdeleuze

Thanks for the feedback you provided. I will act on this accordingly.

Thanks again.

@sshemirani
Copy link

Hi @intumba,

Did you find a solution for this?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
for: external-project For an external project and not something we can fix type: compatibility Native image compatibility issue
Development

No branches or pull requests

7 participants