Skip to content

Commit 8cb7cfb

Browse files
authored
SASL plain termination changes (ITs only) (kroxylicious#2776)
Signed-off-by: Tom Bentley <[email protected]>
1 parent 1adbc3d commit 8cb7cfb

File tree

4 files changed

+93
-21
lines changed

4 files changed

+93
-21
lines changed

kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/SaslPlainTerminatorIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ private static void doAThing(KafkaCluster cluster,
7474
NamedFilterDefinition saslTermination = new NamedFilterDefinitionBuilder(
7575
SaslPlainTermination.class.getName(),
7676
SaslPlainTermination.class.getName())
77+
.withConfig("userNameToPassword", Map.of("alice", "alice-secret"))
7778
.build();
7879
NamedFilterDefinition lawyer = new NamedFilterDefinitionBuilder(
7980
ClientTlsAwareLawyer.class.getName(),

kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/testplugins/SaslPlainTermination.java

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,93 @@
66

77
package io.kroxylicious.proxy.testplugins;
88

9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import javax.security.auth.callback.Callback;
13+
import javax.security.auth.callback.NameCallback;
14+
import javax.security.auth.callback.UnsupportedCallbackException;
15+
import javax.security.auth.login.AppConfigurationEntry;
16+
17+
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
18+
import org.apache.kafka.common.security.plain.PlainAuthenticateCallback;
19+
import org.apache.kafka.common.utils.Utils;
20+
921
import io.kroxylicious.proxy.filter.Filter;
1022
import io.kroxylicious.proxy.filter.FilterFactory;
1123
import io.kroxylicious.proxy.filter.FilterFactoryContext;
1224
import io.kroxylicious.proxy.plugin.Plugin;
1325
import io.kroxylicious.proxy.plugin.PluginConfigurationException;
26+
import io.kroxylicious.proxy.plugin.Plugins;
27+
28+
import edu.umd.cs.findbugs.annotations.Nullable;
1429

15-
@Plugin(configType = Void.class)
30+
@Plugin(configType = SaslPlainTerminationConfig.class)
1631
public class SaslPlainTermination
17-
implements FilterFactory<Void, Void> {
32+
implements FilterFactory<SaslPlainTerminationConfig, SaslPlainTermination.PasswordVerifier> {
33+
34+
interface PasswordVerifier extends AuthenticateCallbackHandler {
35+
}
36+
37+
/**
38+
* This password verifier is insecure in the sense that the user credentials are stored (and configured!) in plaintext.
39+
*/
40+
static class InsecurePasswordVerifier implements PasswordVerifier {
41+
private final Map<String, String> userNameToPassword;
42+
43+
InsecurePasswordVerifier(Map<String, String> userNameToPassword) {
44+
this.userNameToPassword = userNameToPassword;
45+
}
46+
47+
@Override
48+
public void configure(Map<String, ?> configs, String saslMechanism, List<AppConfigurationEntry> jaasConfigEntries) {
49+
if (!"PLAIN".equals(saslMechanism)) {
50+
throw new IllegalStateException("This verifier only supports PLAIN authentication");
51+
}
52+
}
53+
54+
@Override
55+
public void close() {
56+
57+
}
58+
59+
@Override
60+
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
61+
String username = null;
62+
for (Callback callback : callbacks) {
63+
if (callback instanceof NameCallback) {
64+
username = ((NameCallback) callback).getDefaultName();
65+
}
66+
else if (callback instanceof PlainAuthenticateCallback) {
67+
PlainAuthenticateCallback plainCallback = (PlainAuthenticateCallback) callback;
68+
boolean authenticated = authenticate(username, plainCallback.password());
69+
plainCallback.authenticated(authenticated);
70+
}
71+
else {
72+
throw new UnsupportedCallbackException(callback);
73+
}
74+
}
75+
}
76+
77+
protected boolean authenticate(@Nullable String username, char[] password) {
78+
if (username == null) {
79+
return false;
80+
}
81+
else {
82+
String expectedPassword = userNameToPassword.get(username);
83+
return expectedPassword != null && Utils.isEqualConstantTime(password, expectedPassword.toCharArray());
84+
}
85+
}
86+
}
1887

1988
@Override
20-
public Void initialize(FilterFactoryContext context, Void config) throws PluginConfigurationException {
21-
return null;
89+
public PasswordVerifier initialize(FilterFactoryContext context, SaslPlainTerminationConfig config) throws PluginConfigurationException {
90+
Plugins.requireConfig(this, config);
91+
return new InsecurePasswordVerifier(config.userNameToPassword());
2292
}
2393

2494
@Override
25-
public Filter createFilter(FilterFactoryContext context, Void initializationData) {
26-
return new SaslPlainTerminationFilter();
95+
public Filter createFilter(FilterFactoryContext context, PasswordVerifier passwordVerifier) {
96+
return new SaslPlainTerminationFilter(passwordVerifier);
2797
}
2898
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Kroxylicious Authors.
3+
*
4+
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
package io.kroxylicious.proxy.testplugins;
8+
9+
import java.util.Map;
10+
11+
public record SaslPlainTerminationConfig(Map<String, String> userNameToPassword) {}

kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/testplugins/SaslPlainTerminationFilter.java

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
package io.kroxylicious.proxy.testplugins;
88

99
import java.util.List;
10-
import java.util.Map;
1110
import java.util.concurrent.CompletionStage;
1211

13-
import javax.security.auth.login.AppConfigurationEntry;
1412
import javax.security.sasl.Sasl;
1513
import javax.security.sasl.SaslException;
1614
import javax.security.sasl.SaslServer;
@@ -25,15 +23,12 @@
2523
import org.apache.kafka.common.protocol.ApiMessage;
2624
import org.apache.kafka.common.protocol.Errors;
2725
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
28-
import org.apache.kafka.common.security.plain.PlainLoginModule;
29-
import org.apache.kafka.common.security.plain.internals.PlainServerCallbackHandler;
3026
import org.slf4j.Logger;
3127
import org.slf4j.LoggerFactory;
3228

3329
import io.kroxylicious.proxy.filter.FilterContext;
3430
import io.kroxylicious.proxy.filter.RequestFilter;
3531
import io.kroxylicious.proxy.filter.RequestFilterResult;
36-
import io.kroxylicious.proxy.internal.KafkaAuthnHandler;
3732

3833
/**
3934
* A minimal SASL termination filter supporting the {@code PLAIN} mechanism only.
@@ -47,17 +42,12 @@ public class SaslPlainTerminationFilter
4742

4843
private static final Logger LOGGER = LoggerFactory.getLogger(SaslPlainTerminationFilter.class);
4944

45+
private final SaslPlainTermination.PasswordVerifier passwordVerifier;
46+
5047
private SaslServer saslServer;
5148

52-
private AuthenticateCallbackHandler saslPlainCallbackHandler(String user,
53-
String password) {
54-
PlainServerCallbackHandler plainServerCallbackHandler = new PlainServerCallbackHandler();
55-
plainServerCallbackHandler.configure(Map.of(),
56-
KafkaAuthnHandler.SaslMechanism.PLAIN.mechanismName(),
57-
List.of(new AppConfigurationEntry(PlainLoginModule.class.getName(),
58-
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
59-
Map.of("user_" + user, password))));
60-
return plainServerCallbackHandler;
49+
public SaslPlainTerminationFilter(SaslPlainTermination.PasswordVerifier passwordVerifier) {
50+
this.passwordVerifier = passwordVerifier;
6151
}
6252

6353
CompletionStage<RequestFilterResult> onSaslHandshakeRequest(SaslHandshakeRequestData request,
@@ -66,7 +56,7 @@ CompletionStage<RequestFilterResult> onSaslHandshakeRequest(SaslHandshakeRequest
6656
AuthenticateCallbackHandler cbh = null;
6757
List<String> supportedMechanisms;
6858
if ("PLAIN".equals(request.mechanism())) {
69-
cbh = saslPlainCallbackHandler("alice", "alice-secret");
59+
cbh = this.passwordVerifier;
7060
errorCode = Errors.NONE;
7161
supportedMechanisms = List.of();
7262
}

0 commit comments

Comments
 (0)