Skip to content

Commit f8e3988

Browse files
committed
[UNDERTOW-1881] - Add a new exchange attribute for SSL/TLS protocol version
Add and register new ExchangeAttribute implementation Add support for AJP and TLS Add test case
1 parent a4720e3 commit f8e3988

File tree

4 files changed

+177
-1
lines changed

4 files changed

+177
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2024 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package io.undertow.attribute;
20+
21+
import io.undertow.server.HttpServerExchange;
22+
import io.undertow.server.SSLSessionInfo;
23+
import io.undertow.util.HeaderValues;
24+
25+
public class SslProtocolAttribute implements ExchangeAttribute {
26+
27+
public static final SslProtocolAttribute INSTANCE = new SslProtocolAttribute();
28+
29+
@Override
30+
public String readAttribute(HttpServerExchange exchange) {
31+
String sslProtocol = null;
32+
String transportProtocol = exchange.getConnection().getTransportProtocol();
33+
if ("ajp".equals(transportProtocol)) {
34+
// TODO: wrong
35+
HeaderValues headerValues = exchange.getRequestHeaders().get("AJP_SSL_PROTOCOL");
36+
if (headerValues != null && !headerValues.isEmpty()) {
37+
sslProtocol = headerValues.getFirst();
38+
}
39+
} else {
40+
SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo();
41+
if (ssl == null) {
42+
return null;
43+
}
44+
sslProtocol = ssl.getSSLSession().getProtocol();
45+
}
46+
47+
return sslProtocol;
48+
}
49+
50+
@Override
51+
public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
52+
throw new ReadOnlyAttributeException("SSL Protocol", newValue);
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return "%{SSL_PROTOCOL}";
58+
}
59+
60+
public static final class Builder implements ExchangeAttributeBuilder {
61+
62+
@Override
63+
public String name() {
64+
return "SSL Protocol";
65+
}
66+
67+
@Override
68+
public ExchangeAttribute build(final String token) {
69+
if (token.equals("%{SSL_PROTOCOL}")) {
70+
return INSTANCE;
71+
}
72+
return null;
73+
}
74+
75+
@Override
76+
public int priority() {
77+
return 0;
78+
}
79+
}
80+
}

core/src/main/java/io/undertow/server/handlers/accesslog/DefaultAccessLogReceiver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public void run() {
234234
* <p>
235235
* DO NOT USE THIS OUTSIDE OF A TEST
236236
*/
237-
void awaitWrittenForTest() throws InterruptedException {
237+
protected void awaitWrittenForTest() throws InterruptedException {
238238
while (!pendingMessages.isEmpty() || forceLogRotation) {
239239
Thread.sleep(10);
240240
}

core/src/main/resources/META-INF/services/io.undertow.attribute.ExchangeAttributeBuilder

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ io.undertow.attribute.PredicateContextAttribute$Builder
2323
io.undertow.attribute.QueryParameterAttribute$Builder
2424
io.undertow.attribute.SslClientCertAttribute$Builder
2525
io.undertow.attribute.SslCipherAttribute$Builder
26+
io.undertow.attribute.SslProtocolAttribute$Builder
2627
io.undertow.attribute.SslSessionIdAttribute$Builder
2728
io.undertow.attribute.ResponseTimeAttribute$Builder
2829
io.undertow.attribute.PathParameterAttribute$Builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2024 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package io.undertow.server.ssl;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.concurrent.Executor;
26+
import javax.net.ssl.SSLContext;
27+
28+
import io.undertow.server.handlers.accesslog.AccessLogFileTestCase;
29+
import io.undertow.server.handlers.accesslog.AccessLogHandler;
30+
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
31+
import io.undertow.testutils.DefaultServer;
32+
import io.undertow.testutils.HttpClientUtils;
33+
import io.undertow.testutils.TestHttpClient;
34+
import io.undertow.util.CompletionLatchHandler;
35+
import io.undertow.util.StatusCodes;
36+
import org.apache.http.HttpResponse;
37+
import org.apache.http.client.methods.HttpGet;
38+
import org.junit.Assert;
39+
import org.junit.Test;
40+
import org.junit.runner.RunWith;
41+
42+
@RunWith(DefaultServer.class)
43+
public class SslProtocolAttributeTestCase {
44+
private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
45+
46+
@Test
47+
public void testTlsRequestViaLogging() throws IOException, InterruptedException {
48+
logDirectory.toFile().mkdirs();
49+
Path logFileName = logDirectory.resolve("server1.log");
50+
File logFile = logFileName.toFile();
51+
logFile.createNewFile();
52+
logFile.deleteOnExit();
53+
54+
AccessLogReceiver logReceiver = new AccessLogReceiver(DefaultServer.getWorker(), logDirectory,
55+
"server1.", "log");
56+
57+
String formatString = "SSL Protocol is %{SSL_PROTOCOL}.";
58+
CompletionLatchHandler latchHandler= new CompletionLatchHandler(
59+
new AccessLogHandler(exchange -> exchange.getResponseSender().send("ping"),
60+
logReceiver, formatString,
61+
AccessLogFileTestCase.class.getClassLoader()));
62+
DefaultServer.setRootHandler(latchHandler);
63+
64+
try(TestHttpClient client = new TestHttpClient()) {
65+
DefaultServer.startSSLServer();
66+
SSLContext sslContext = DefaultServer.getClientSSLContext();
67+
client.setSSLContext(sslContext);
68+
69+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/path");
70+
HttpResponse result = client.execute(get);
71+
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
72+
Assert.assertEquals("ping", HttpClientUtils.readResponse(result));
73+
latchHandler.await();
74+
logReceiver.awaitWrittenForTest();
75+
Assert.assertEquals(formatString.replaceAll("%\\{SSL_PROTOCOL}", sslContext.getProtocol()) + System.lineSeparator(),
76+
new String(Files.readAllBytes(logFileName)));
77+
} finally {
78+
DefaultServer.stopSSLServer();
79+
}
80+
}
81+
82+
private static class AccessLogReceiver extends DefaultAccessLogReceiver {
83+
AccessLogReceiver(final Executor logWriteExecutor,
84+
final Path outputDirectory,
85+
final String logBaseName,
86+
final String logNameSuffix) {
87+
super(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, true);
88+
}
89+
90+
public void awaitWrittenForTest() throws InterruptedException {
91+
super.awaitWrittenForTest();
92+
}
93+
}
94+
95+
}

0 commit comments

Comments
 (0)