Skip to content

Commit cb8612a

Browse files
authored
Use SaslSubjectBuilder in the SASL inspector and PLAIN terminator (kroxylicious#2904)
* kroxylicious-filters: Use SubjectBuilder in SASL inspector (and IT's terminators) * Add SubjectBuildingException * Factor out PrincipalAdderConf and Map * Log use of default subject builder Signed-off-by: Tom Bentley <tbentley@redhat.com>
1 parent 665ef2f commit cb8612a

File tree

13 files changed

+363
-82
lines changed

13 files changed

+363
-82
lines changed

kroxylicious-api/src/main/java/io/kroxylicious/proxy/authentication/SaslSubjectBuilder.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ public interface SaslSubjectBuilder {
2828

2929
/**
3030
* Returns an asynchronous result which completes with the {@code Subject} built
31-
* from the
32-
* @param context
33-
* @return
31+
* from the given {@code context}.
32+
* @param context The context of a successful SASL authentication.
33+
* @return The Subject. The returned stage should fail with an {@link SubjectBuildingException} if the builder was not able to
34+
* build a subject.
3435
*/
3536
CompletionStage<Subject> buildSaslSubject(SaslSubjectBuilder.Context context);
3637

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.authentication;
8+
9+
/**
10+
* An exception to be thrown if a {@link Subject} cannot be built.
11+
*/
12+
public class SubjectBuildingException extends RuntimeException {
13+
public SubjectBuildingException(String message) {
14+
super(message);
15+
}
16+
17+
public SubjectBuildingException(String message, Throwable cause) {
18+
super(message, cause);
19+
}
20+
}

kroxylicious-filters/kroxylicious-sasl-inspection/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
<artifactId>junit-jupiter-params</artifactId>
7272
<scope>test</scope>
7373
</dependency>
74+
<dependency>
75+
<groupId>io.kroxylicious</groupId>
76+
<artifactId>kroxylicious-runtime</artifactId>
77+
<scope>test</scope>
78+
</dependency>
7479
</dependencies>
7580

7681
</project>

kroxylicious-filters/kroxylicious-sasl-inspection/src/main/java/io/kroxylicious/filters/sasl/inspection/Config.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88

99
import java.util.Set;
1010

11+
import edu.umd.cs.findbugs.annotations.Nullable;
12+
1113
/**
12-
* Config for the Sasl Initiation Filter.
14+
* Configuration for the Sasl Initiation Filter.
1315
*
1416
* @param enabledMechanisms set of SASL mechanisms to enable. Refer to the mechanism by its
1517
* IANA registered name. If enabledMechanisms is null, the system will automatically enable
1618
* the mechanism from all {@link SaslObserverFactory}s with
1719
* {@link SaslObserverFactory#transmitsCredentialInCleartext()} values of false.
20+
* @param subjectBuilder The name of a plugin class implementing {@link io.kroxylicious.proxy.authentication.SaslSubjectBuilderService}
21+
* @param subjectBuilderConfig The configuration for the SaslSubjectBuilderService.
1822
*/
19-
public record Config(Set<String> enabledMechanisms) {}
23+
public record Config(@Nullable Set<String> enabledMechanisms,
24+
@Nullable String subjectBuilder,
25+
@Nullable Object subjectBuilderConfig) {}

kroxylicious-filters/kroxylicious-sasl-inspection/src/main/java/io/kroxylicious/filters/sasl/inspection/SaslInspection.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@
1010
import java.util.Map;
1111
import java.util.Objects;
1212
import java.util.Optional;
13+
import java.util.Set;
14+
import java.util.concurrent.CompletableFuture;
15+
import java.util.concurrent.CompletionStage;
1316
import java.util.function.Function;
1417
import java.util.function.Predicate;
1518
import java.util.stream.Collectors;
1619

20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import io.kroxylicious.proxy.authentication.SaslSubjectBuilder;
24+
import io.kroxylicious.proxy.authentication.SaslSubjectBuilderService;
25+
import io.kroxylicious.proxy.authentication.Subject;
26+
import io.kroxylicious.proxy.authentication.User;
1727
import io.kroxylicious.proxy.filter.Filter;
1828
import io.kroxylicious.proxy.filter.FilterFactory;
1929
import io.kroxylicious.proxy.filter.FilterFactoryContext;
@@ -30,23 +40,49 @@
3040
@Plugin(configType = Config.class)
3141
public class SaslInspection implements FilterFactory<Config, Void> {
3242

43+
private static final Logger LOGGER = LoggerFactory.getLogger(SaslInspection.class);
44+
45+
public static final SaslSubjectBuilder DEFAULT_SUBJECT_BUILDER = new SaslSubjectBuilder() {
46+
@Override
47+
public CompletionStage<Subject> buildSaslSubject(Context context) {
48+
return CompletableFuture.completedStage(new Subject(Set.of(new User(context.clientSaslContext().authorizationId()))));
49+
}
50+
};
3351
private @Nullable Map<String, SaslObserverFactory> observerFactoryMap;
52+
private @Nullable SaslSubjectBuilder subjectBuilder;
3453

3554
@Override
3655
public Void initialize(FilterFactoryContext context,
3756
@Nullable Config config)
3857
throws PluginConfigurationException {
3958
observerFactoryMap = buildEnabledObserverFactoryMap(context, config);
59+
subjectBuilder = buildSubjectBuilder(context, config);
4060
return null;
4161
}
4262

63+
@NonNull
64+
private static SaslSubjectBuilder buildSubjectBuilder(FilterFactoryContext context,
65+
@Nullable Config config) {
66+
if (config == null || config.subjectBuilder() == null) {
67+
LOGGER.debug("No `subjectBuilder` configured. The default SaslSubjectBuilder will be used.");
68+
return DEFAULT_SUBJECT_BUILDER;
69+
}
70+
else {
71+
SaslSubjectBuilderService subjectBuilderFactory = context.pluginInstance(SaslSubjectBuilderService.class, config.subjectBuilder());
72+
subjectBuilderFactory.initialize(config.subjectBuilderConfig());
73+
return subjectBuilderFactory.build();
74+
}
75+
}
76+
4377
@Override
44-
public Filter createFilter(FilterFactoryContext context, Void unused) {
78+
public Filter createFilter(FilterFactoryContext context, @Nullable Void unused) {
4579
Objects.requireNonNull(observerFactoryMap);
46-
return new SaslInspectionFilter(observerFactoryMap);
80+
Objects.requireNonNull(subjectBuilder);
81+
return new SaslInspectionFilter(observerFactoryMap, subjectBuilder);
4782
}
4883

49-
private Map<String, SaslObserverFactory> buildEnabledObserverFactoryMap(FilterFactoryContext context, @Nullable Config config) {
84+
private Map<String, SaslObserverFactory> buildEnabledObserverFactoryMap(FilterFactoryContext context,
85+
@Nullable Config config) {
5086
var allNames = context.pluginImplementationNames(SaslObserverFactory.class);
5187
var allMap = allNames
5288
.stream()

kroxylicious-filters/kroxylicious-sasl-inspection/src/main/java/io/kroxylicious/filters/sasl/inspection/SaslInspectionFilter.java

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.ArrayList;
1010
import java.util.Map;
1111
import java.util.Objects;
12+
import java.util.Optional;
1213
import java.util.concurrent.CompletionStage;
1314

1415
import javax.security.sasl.SaslException;
@@ -24,6 +25,10 @@
2425
import org.slf4j.Logger;
2526
import org.slf4j.LoggerFactory;
2627

28+
import io.kroxylicious.proxy.authentication.ClientSaslContext;
29+
import io.kroxylicious.proxy.authentication.SaslSubjectBuilder;
30+
import io.kroxylicious.proxy.authentication.Subject;
31+
import io.kroxylicious.proxy.authentication.SubjectBuildingException;
2732
import io.kroxylicious.proxy.filter.FilterContext;
2833
import io.kroxylicious.proxy.filter.RequestFilterResult;
2934
import io.kroxylicious.proxy.filter.ResponseFilterResult;
@@ -32,7 +37,9 @@
3237
import io.kroxylicious.proxy.filter.SaslHandshakeRequestFilter;
3338
import io.kroxylicious.proxy.filter.SaslHandshakeResponseFilter;
3439
import io.kroxylicious.proxy.tag.VisibleForTesting;
40+
import io.kroxylicious.proxy.tls.ClientTlsContext;
3541

42+
import edu.umd.cs.findbugs.annotations.NonNull;
3643
import edu.umd.cs.findbugs.annotations.Nullable;
3744

3845
/**
@@ -57,12 +64,14 @@ class SaslInspectionFilter
5764
static final String PROBE_UPSTREAM = PROBING_SASL_OBSERVER.mechanismName();
5865

5966
private final Map<String, SaslObserverFactory> observerFactoryMap;
67+
private final SaslSubjectBuilder subjectBuilder;
6068

6169
private State currentState = State.start();
6270

63-
SaslInspectionFilter(Map<String, SaslObserverFactory> mechanismFactories) {
64-
Objects.requireNonNull(mechanismFactories, "mechanismFactories");
65-
this.observerFactoryMap = mechanismFactories;
71+
SaslInspectionFilter(Map<String, SaslObserverFactory> mechanismFactories,
72+
SaslSubjectBuilder subjectBuilder) {
73+
this.observerFactoryMap = Objects.requireNonNull(mechanismFactories, "mechanismFactories");
74+
this.subjectBuilder = Objects.requireNonNull(subjectBuilder, "subjectBuilder");
6675
}
6776

6877
@Override
@@ -253,7 +262,6 @@ private CompletionStage<ResponseFilterResult> processSuccessfulAuthenticateRespo
253262
// Ordinarily, we never expect to be here. The sasl negotiation ought to be yielded a authorizationId before
254263
// the negotiation is finished.
255264
return closeConnectionWithResponse(header, response.setErrorCode(Errors.ILLEGAL_SASL_STATE.code()), context);
256-
257265
}
258266

259267
var expiredCredential = state.saslObserver().zeroLengthSessionImpliesAuthnFailure() && response.sessionLifetimeMs() == 0;
@@ -267,19 +275,66 @@ private CompletionStage<ResponseFilterResult> processSuccessfulAuthenticateRespo
267275
context.clientSaslAuthenticationFailure(state.saslObserver().mechanismName(), authorizationIdFromClient, new SaslException("expired credential"));
268276
}
269277
else {
270-
LOGGER.atInfo()
271-
.setMessage("Server accepts SASL credentials for client on channel {}, announcing that client has authorizationId {}")
272-
.addArgument(context::channelDescriptor)
273-
.addArgument(authorizationIdFromClient)
274-
.log();
275-
context.clientSaslAuthenticationSuccess(saslObserver.mechanismName(), authorizationIdFromClient);
276-
278+
return buildSubjectAndAnnounce(header, response, context, state, saslObserver, authorizationIdFromClient);
277279
}
278280
}
279281
currentState = state.nextState(saslObserver.isFinished());
280282
return context.forwardResponse(header, response);
281283
}
282284

285+
@NonNull
286+
private CompletionStage<ResponseFilterResult> buildSubjectAndAnnounce(ResponseHeaderData header,
287+
SaslAuthenticateResponseData response,
288+
FilterContext context,
289+
State.AwaitingAuthenticateResponse state,
290+
SaslObserver saslObserver,
291+
String authorizationIdFromClient) {
292+
CompletionStage<Subject> subjectCompletionStage = subjectBuilder.buildSaslSubject(new SaslSubjectBuilder.Context() {
293+
@Override
294+
public Optional<ClientTlsContext> clientTlsContext() {
295+
return context.clientTlsContext();
296+
}
297+
298+
@Override
299+
public ClientSaslContext clientSaslContext() {
300+
return new ClientSaslContext() {
301+
@Override
302+
public String mechanismName() {
303+
return saslObserver.mechanismName();
304+
}
305+
306+
@Override
307+
public String authorizationId() {
308+
return authorizationIdFromClient;
309+
}
310+
};
311+
}
312+
});
313+
314+
return subjectCompletionStage.thenCompose(subject -> {
315+
LOGGER.atInfo()
316+
.setMessage("Server accepts SASL credentials for client on channel {}, announcing that client has authorizationId {}")
317+
.addArgument(context::channelDescriptor)
318+
.addArgument(authorizationIdFromClient)
319+
.log();
320+
context.clientSaslAuthenticationSuccess(saslObserver.mechanismName(),
321+
subject);
322+
currentState = state.nextState(saslObserver.isFinished());
323+
return context.forwardResponse(header, response);
324+
}).exceptionallyCompose(throwable -> {
325+
Exception e;
326+
if (throwable instanceof SubjectBuildingException exception) {
327+
e = exception;
328+
}
329+
else {
330+
e = new SubjectBuildingException("SaslSubjectBuilder " + subjectBuilder.getClass() + " threw an unexpected exception", throwable);
331+
}
332+
context.clientSaslAuthenticationFailure(saslObserver.mechanismName(),
333+
authorizationIdFromClient, e);
334+
return closeConnectionWithResponse(header, response.setErrorCode(Errors.ILLEGAL_SASL_STATE.code()), context);
335+
});
336+
}
337+
283338
private CompletionStage<ResponseFilterResult> processFailedAuthentication(ResponseHeaderData header,
284339
SaslAuthenticateResponseData response,
285340
FilterContext context,

0 commit comments

Comments
 (0)