Skip to content

Commit ce086de

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 9ca3cb1 commit ce086de

File tree

4 files changed

+174
-1
lines changed

4 files changed

+174
-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,92 @@
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.security.NoSuchAlgorithmException;
26+
import java.util.concurrent.Executor;
27+
import javax.net.ssl.SSLContext;
28+
29+
import io.undertow.server.handlers.accesslog.AccessLogFileTestCase;
30+
import io.undertow.server.handlers.accesslog.AccessLogHandler;
31+
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
32+
import io.undertow.testutils.DefaultServer;
33+
import io.undertow.testutils.HttpClientUtils;
34+
import io.undertow.testutils.TestHttpClient;
35+
import io.undertow.util.CompletionLatchHandler;
36+
import io.undertow.util.StatusCodes;
37+
import org.apache.http.HttpResponse;
38+
import org.apache.http.client.methods.HttpGet;
39+
import org.junit.Assert;
40+
import org.junit.Test;
41+
import org.junit.runner.RunWith;
42+
43+
@RunWith(DefaultServer.class)
44+
public class SslProtocolAttributeTestCase {
45+
private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
46+
47+
@Test
48+
public void testTlsRequestViaLogging() throws IOException, InterruptedException, NoSuchAlgorithmException {
49+
Path logFileName = logDirectory.resolve("server1.log");
50+
File logFile = logFileName.toFile();
51+
logFile.deleteOnExit();
52+
53+
AccessLogReceiver logReceiver = new AccessLogReceiver(DefaultServer.getWorker(), logDirectory,
54+
"server1.", "log");
55+
56+
String formatString = "SSL Protocol is %{SSL_PROTOCOL}.";
57+
CompletionLatchHandler latchHandler= new CompletionLatchHandler(
58+
new AccessLogHandler(exchange -> exchange.getResponseSender().send("ping"),
59+
logReceiver, formatString,
60+
AccessLogFileTestCase.class.getClassLoader()));
61+
DefaultServer.setRootHandler(latchHandler);
62+
63+
try(TestHttpClient client = new TestHttpClient()) {
64+
DefaultServer.startSSLServer();
65+
SSLContext sslContext = DefaultServer.getClientSSLContext();
66+
client.setSSLContext(sslContext);
67+
68+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/path");
69+
HttpResponse result = client.execute(get);
70+
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
71+
Assert.assertEquals("ping", HttpClientUtils.readResponse(result));
72+
latchHandler.await();
73+
logReceiver.awaitWrittenForTest();
74+
Assert.assertEquals(formatString.replaceAll("%\\{SSL_PROTOCOL}", sslContext.getProtocol()) + System.lineSeparator(),
75+
new String(Files.readAllBytes(logFileName)));
76+
}
77+
}
78+
79+
private static class AccessLogReceiver extends DefaultAccessLogReceiver {
80+
public AccessLogReceiver(final Executor logWriteExecutor,
81+
final Path outputDirectory,
82+
final String logBaseName,
83+
final String logNameSuffix) {
84+
super(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, true);
85+
}
86+
87+
public void awaitWrittenForTest() throws InterruptedException {
88+
super.awaitWrittenForTest();
89+
}
90+
}
91+
92+
}

0 commit comments

Comments
 (0)