Skip to content

Commit ecda15d

Browse files
SamBarkerk-wall
andauthored
Direct users to our docs when we suspect TLS on a plain socket.
* Expand TLS hint. Signed-off-by: Sam Barker <[email protected]> Co-authored-by: Keith Wall <[email protected]>
1 parent 2978733 commit ecda15d

File tree

7 files changed

+147
-5
lines changed

7 files changed

+147
-5
lines changed

kroxylicious-runtime/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
<properties>
2525
<libs.dir>libs</libs.dir>
26+
<release.version>${project.version}</release.version>
2627
</properties>
2728

2829
<name>Proxy runtime</name>
@@ -217,6 +218,7 @@
217218
<filtering>true</filtering>
218219
<includes>
219220
<include>META-INF/metadata.properties</include>
221+
<include>META-INF/stablelinks.properties</include>
220222
</includes>
221223
</resource>
222224
</resources>

kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/internal/ProxyChannelStateMachine.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.kroxylicious.proxy.internal.ProxyChannelState.Forwarding;
3131
import io.kroxylicious.proxy.internal.codec.FrameOversizedException;
3232
import io.kroxylicious.proxy.internal.util.Metrics;
33+
import io.kroxylicious.proxy.internal.util.StableKroxyliciousLinkGenerator;
3334
import io.kroxylicious.proxy.model.VirtualClusterModel;
3435
import io.kroxylicious.proxy.service.HostPort;
3536
import io.kroxylicious.proxy.tag.VisibleForTesting;
@@ -453,10 +454,15 @@ void onClientException(Throwable cause, boolean tlsEnabled) {
453454
ApiException errorCodeEx;
454455
if (cause instanceof DecoderException de
455456
&& de.getCause() instanceof FrameOversizedException e) {
456-
var tlsHint = tlsEnabled ? "" : " or an unexpected TLS handshake";
457+
String tlsHint;
458+
tlsHint = tlsEnabled
459+
? ""
460+
: " Possible unexpected TLS handshake? When connecting via TLS from your client, make sure to enable TLS for the Kroxylicious gateway ("
461+
+ StableKroxyliciousLinkGenerator.INSTANCE.errorLink(StableKroxyliciousLinkGenerator.CLIENT_TLS)
462+
+ ").";
457463
LOGGER.warn(
458464
"Received over-sized frame from the client, max frame size bytes {}, received frame size bytes {} "
459-
+ "(hint: are we decoding a Kafka frame, or something unexpected like an HTTP request{}?)",
465+
+ "(hint: {} Other possible causes are: an oversized Kafka frame, or something unexpected like an HTTP request.)",
460466
e.getMaxFrameSizeBytes(), e.getReceivedFrameSizeBytes(), tlsHint);
461467
errorCodeEx = Errors.INVALID_REQUEST.exception();
462468
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.internal.util;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.io.UncheckedIOException;
12+
import java.util.Map;
13+
import java.util.Properties;
14+
import java.util.function.Supplier;
15+
import java.util.stream.Collectors;
16+
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
public class StableKroxyliciousLinkGenerator {
21+
private static final Logger LOGGER = LoggerFactory.getLogger(StableKroxyliciousLinkGenerator.class);
22+
23+
public static final StableKroxyliciousLinkGenerator INSTANCE = new StableKroxyliciousLinkGenerator();
24+
25+
public static final String CLIENT_TLS = "clientTls";
26+
private final LinkInfo links;
27+
28+
StableKroxyliciousLinkGenerator() {
29+
this(() -> {
30+
LOGGER.info("loading links from: classpath:META-INF/stablelinks.properties");
31+
return StableKroxyliciousLinkGenerator.class.getClassLoader().getResourceAsStream("META-INF/stablelinks.properties");
32+
});
33+
}
34+
35+
StableKroxyliciousLinkGenerator(Supplier<InputStream> propLoader) {
36+
links = loadLinks(propLoader);
37+
}
38+
39+
public String errorLink(String slug) {
40+
return links.generateLink("errors", slug);
41+
}
42+
43+
private LinkInfo loadLinks(Supplier<InputStream> propLoader) {
44+
try (var resource = propLoader.get()) {
45+
if (resource != null) {
46+
Properties properties = new Properties();
47+
properties.load(resource);
48+
return new LinkInfo(properties);
49+
}
50+
}
51+
catch (IOException e) {
52+
throw new UncheckedIOException(e);
53+
}
54+
return new LinkInfo(Map.of());
55+
}
56+
57+
private record LinkInfo(Map<String, String> properties) {
58+
LinkInfo(Properties properties) {
59+
this(properties.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())));
60+
}
61+
62+
public String generateLink(String namespace, String slug) {
63+
String lookupKey = "%s.%s".formatted(namespace, slug);
64+
if (properties.containsKey(lookupKey)) {
65+
return properties.get(lookupKey);
66+
}
67+
else {
68+
throw new IllegalArgumentException("No link found for " + lookupKey);
69+
}
70+
}
71+
}
72+
}

kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/model/VirtualClusterModel.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.kroxylicious.proxy.config.tls.TrustOptions;
4040
import io.kroxylicious.proxy.config.tls.TrustProvider;
4141
import io.kroxylicious.proxy.internal.net.EndpointGateway;
42+
import io.kroxylicious.proxy.internal.util.StableKroxyliciousLinkGenerator;
4243
import io.kroxylicious.proxy.service.HostPort;
4344
import io.kroxylicious.proxy.service.NodeIdentificationStrategy;
4445
import io.kroxylicious.proxy.tag.VisibleForTesting;
@@ -376,8 +377,10 @@ public Optional<Tls> getTls() {
376377
private Optional<SslContext> buildDownstreamSslContext() {
377378
return tls.map(tlsConfiguration -> {
378379
if (tlsConfiguration.key() == null) {
379-
throw new IllegalConfigurationException(("Virtual cluster '%s', gateway '%s': 'tls' object is missing the mandatory attribute 'key'.")
380-
.formatted(virtualCluster.getClusterName(), name()));
380+
throw new IllegalConfigurationException(
381+
"Virtual cluster '%s', gateway '%s': 'tls' object is missing the mandatory attribute 'key'. See %s for details"
382+
.formatted(virtualCluster.getClusterName(), name(),
383+
StableKroxyliciousLinkGenerator.INSTANCE.errorLink(StableKroxyliciousLinkGenerator.CLIENT_TLS)));
381384
}
382385
try {
383386
var sslContextBuilder = Optional.of(tlsConfiguration.key()).map(NettyKeyProvider::new).map(NettyKeyProvider::forServer)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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+
errors.clientTls=https://kroxylicious.io/redirect/errors/${release.version}/clientTls

kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/ConfigParserTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,8 @@ void shouldRequireKeyIfDownstreamTlsObjectPresent() {
410410
assertThatThrownBy(() -> {
411411
configuration.virtualClusterModel(registry);
412412
}).isInstanceOf(IllegalConfigurationException.class)
413-
.hasMessage("Virtual cluster 'mycluster1', gateway 'default': 'tls' object is missing the mandatory attribute 'key'.");
413+
.hasMessageStartingWith("Virtual cluster 'mycluster1', gateway 'default': 'tls' object is missing the mandatory attribute 'key'.");
414+
// We can't assert the full message as the link will change with every release
414415
}
415416

416417
@Test
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.internal.util;
8+
9+
import java.io.ByteArrayInputStream;
10+
import java.nio.charset.StandardCharsets;
11+
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
17+
18+
class StableKroxyliciousLinkGeneratorTest {
19+
20+
private StableKroxyliciousLinkGenerator stableKroxyliciousLinkGenerator;
21+
22+
@BeforeEach
23+
void setUp() {
24+
stableKroxyliciousLinkGenerator = new StableKroxyliciousLinkGenerator(() -> new ByteArrayInputStream("""
25+
errors.clientTls=https://example.com/redirect/errors/
26+
alternative.clientTls=https://example.com/alternative
27+
""".getBytes(StandardCharsets.UTF_8)));
28+
}
29+
30+
@Test
31+
void shouldThrowExceptionIfSlugIsNotInGivenNamespace() {
32+
// Given
33+
34+
// When
35+
// Then
36+
assertThatThrownBy(() -> stableKroxyliciousLinkGenerator.errorLink("unknown"))
37+
.isInstanceOf(IllegalArgumentException.class)
38+
.hasMessageContaining("No link found for errors.unknown");
39+
}
40+
41+
@Test
42+
void shouldLoadLinkFromErrorNamespace() {
43+
// Given
44+
45+
// When
46+
String errorLink = stableKroxyliciousLinkGenerator.errorLink("clientTls");
47+
48+
// Then
49+
assertThat(errorLink).isEqualTo("https://example.com/redirect/errors/");
50+
}
51+
}

0 commit comments

Comments
 (0)