Skip to content

Commit 9948376

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 6ae61c6 commit 9948376

File tree

4 files changed

+208
-1
lines changed

4 files changed

+208
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 javax.net.ssl.SSLSession;
22+
23+
import io.undertow.server.HttpServerExchange;
24+
import io.undertow.server.SSLSessionInfo;
25+
import io.undertow.util.HeaderValues;
26+
27+
public class SecureProtocolAttribute implements ExchangeAttribute {
28+
29+
public static final SecureProtocolAttribute INSTANCE = new SecureProtocolAttribute();
30+
31+
@Override
32+
public String readAttribute(HttpServerExchange exchange) {
33+
String secureProtocol = null;
34+
String transportProtocol = exchange.getConnection().getTransportProtocol();
35+
if ("ajp".equals(transportProtocol)) {
36+
// TODO: wrong
37+
HeaderValues headerValues = exchange.getRequestHeaders().get("AJP_SSL_PROTOCOL");
38+
if (headerValues != null && !headerValues.isEmpty()) {
39+
secureProtocol = headerValues.getFirst();
40+
}
41+
} else {
42+
SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo();
43+
if (ssl == null) {
44+
return null;
45+
}
46+
SSLSession session = ssl.getSSLSession();
47+
if (session != null) {
48+
secureProtocol = session.getProtocol();
49+
}
50+
}
51+
52+
return secureProtocol;
53+
}
54+
55+
@Override
56+
public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
57+
throw new ReadOnlyAttributeException("Secure Protocol", newValue);
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return "%{SECURE_PROTOCOL}";
63+
}
64+
65+
public static final class Builder implements ExchangeAttributeBuilder {
66+
67+
@Override
68+
public String name() {
69+
return "Secure Protocol";
70+
}
71+
72+
@Override
73+
public ExchangeAttribute build(final String token) {
74+
if (token.equals("%{SECURE_PROTOCOL}")) {
75+
return INSTANCE;
76+
}
77+
return null;
78+
}
79+
80+
@Override
81+
public int priority() {
82+
return 0;
83+
}
84+
}
85+
}

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.SecureProtocolAttribute$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,121 @@
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.BufferedWriter;
21+
import java.io.IOException;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.nio.file.Paths;
26+
import java.nio.file.StandardOpenOption;
27+
import java.util.concurrent.Executor;
28+
import javax.net.ssl.SSLContext;
29+
30+
import io.undertow.UndertowLogger;
31+
import io.undertow.attribute.ExchangeAttribute;
32+
import io.undertow.attribute.ExchangeAttributes;
33+
import io.undertow.attribute.SubstituteEmptyWrapper;
34+
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
35+
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
36+
import io.undertow.testutils.DefaultServer;
37+
import io.undertow.testutils.HttpClientUtils;
38+
import io.undertow.testutils.TestHttpClient;
39+
import io.undertow.util.CompletionLatchHandler;
40+
import io.undertow.util.StatusCodes;
41+
import org.apache.http.HttpResponse;
42+
import org.apache.http.client.methods.HttpGet;
43+
import org.junit.Assert;
44+
import org.junit.Test;
45+
import org.junit.runner.RunWith;
46+
47+
@RunWith(DefaultServer.class)
48+
public class SecureProtocolAttributeTestCase {
49+
private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "logs");
50+
51+
@Test
52+
public void testTlsRequestViaLogging() throws IOException, InterruptedException {
53+
LocalAccessLogReceiver logReceiver
54+
= new LocalAccessLogReceiver(DefaultServer.getWorker(), logDirectory, "server", ".log");
55+
56+
final String formatString = "Secure Protocol is %{SECURE_PROTOCOL}.";
57+
CompletionLatchHandler latchHandler = new CompletionLatchHandler(
58+
exchange -> {
59+
ExchangeAttribute tokens = ExchangeAttributes.parser(SecureProtocolAttributeTestCase.class.getClassLoader(),
60+
new SubstituteEmptyWrapper("-")).parse(formatString);
61+
exchange.getResponseSender().send(tokens.readAttribute(exchange));
62+
});
63+
64+
DefaultServer.setRootHandler(latchHandler);
65+
66+
try (TestHttpClient client = new TestHttpClient()) {
67+
DefaultServer.startSSLServer();
68+
SSLContext sslContext = DefaultServer.getClientSSLContext();
69+
client.setSSLContext(sslContext);
70+
71+
HttpResponse result = client.execute(new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/path"));
72+
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
73+
74+
String response = HttpClientUtils.readResponse(result);
75+
Assert.assertEquals(
76+
formatString.replaceAll("%\\{SECURE_PROTOCOL}",
77+
"false".equals(System.getProperty("test.ajp")) ? sslContext.getProtocol() : "-"),
78+
response);
79+
} finally {
80+
DefaultServer.stopSSLServer();
81+
}
82+
}
83+
84+
private static class SimpleAccessLogReceiver implements AccessLogReceiver {
85+
private final BufferedWriter writer;
86+
87+
SimpleAccessLogReceiver(Path logFile) {
88+
try {
89+
writer = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND,
90+
StandardOpenOption.CREATE);
91+
} catch (IOException e) {
92+
throw new RuntimeException(e);
93+
}
94+
}
95+
96+
@Override
97+
public void logMessage(String message) {
98+
try {
99+
writer.write(message);
100+
writer.newLine();
101+
writer.flush();
102+
} catch (IOException e) {
103+
UndertowLogger.ROOT_LOGGER.errorWritingAccessLog(e);
104+
}
105+
}
106+
}
107+
108+
private static class LocalAccessLogReceiver extends DefaultAccessLogReceiver {
109+
LocalAccessLogReceiver(final Executor logWriteExecutor,
110+
final Path outputDirectory,
111+
final String logBaseName,
112+
final String logNameSuffix) {
113+
super(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, true);
114+
}
115+
116+
public void awaitWrittenForTest() throws InterruptedException {
117+
super.awaitWrittenForTest();
118+
}
119+
}
120+
121+
}

0 commit comments

Comments
 (0)