Skip to content

Commit

Permalink
[UNDERTOW-1881] - Add a new exchange attribute for SSL/TLS protocol v…
Browse files Browse the repository at this point in the history
…ersion

Add and register new ExchangeAttribute implementation
Add support for AJP and TLS
Add test case
  • Loading branch information
jasondlee committed Dec 4, 2024
1 parent a4720e3 commit f8e3988
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
80 changes: 80 additions & 0 deletions core/src/main/java/io/undertow/attribute/SslProtocolAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2024 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.undertow.attribute;

import io.undertow.server.HttpServerExchange;
import io.undertow.server.SSLSessionInfo;
import io.undertow.util.HeaderValues;

public class SslProtocolAttribute implements ExchangeAttribute {

public static final SslProtocolAttribute INSTANCE = new SslProtocolAttribute();

@Override
public String readAttribute(HttpServerExchange exchange) {
String sslProtocol = null;
String transportProtocol = exchange.getConnection().getTransportProtocol();
if ("ajp".equals(transportProtocol)) {
// TODO: wrong
HeaderValues headerValues = exchange.getRequestHeaders().get("AJP_SSL_PROTOCOL");
if (headerValues != null && !headerValues.isEmpty()) {
sslProtocol = headerValues.getFirst();
}
} else {
SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo();
if (ssl == null) {
return null;
}
sslProtocol = ssl.getSSLSession().getProtocol();
}

return sslProtocol;
}

@Override
public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
throw new ReadOnlyAttributeException("SSL Protocol", newValue);
}

@Override
public String toString() {
return "%{SSL_PROTOCOL}";
}

public static final class Builder implements ExchangeAttributeBuilder {

@Override
public String name() {
return "SSL Protocol";
}

@Override
public ExchangeAttribute build(final String token) {
if (token.equals("%{SSL_PROTOCOL}")) {
return INSTANCE;
}
return null;
}

@Override
public int priority() {
return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public void run() {
* <p>
* DO NOT USE THIS OUTSIDE OF A TEST
*/
void awaitWrittenForTest() throws InterruptedException {
protected void awaitWrittenForTest() throws InterruptedException {
while (!pendingMessages.isEmpty() || forceLogRotation) {
Thread.sleep(10);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ io.undertow.attribute.PredicateContextAttribute$Builder
io.undertow.attribute.QueryParameterAttribute$Builder
io.undertow.attribute.SslClientCertAttribute$Builder
io.undertow.attribute.SslCipherAttribute$Builder
io.undertow.attribute.SslProtocolAttribute$Builder
io.undertow.attribute.SslSessionIdAttribute$Builder
io.undertow.attribute.ResponseTimeAttribute$Builder
io.undertow.attribute.PathParameterAttribute$Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2024 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.server.ssl;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.Executor;
import javax.net.ssl.SSLContext;

import io.undertow.server.handlers.accesslog.AccessLogFileTestCase;
import io.undertow.server.handlers.accesslog.AccessLogHandler;
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.HttpClientUtils;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.CompletionLatchHandler;
import io.undertow.util.StatusCodes;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(DefaultServer.class)
public class SslProtocolAttributeTestCase {
private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"));

@Test
public void testTlsRequestViaLogging() throws IOException, InterruptedException {
logDirectory.toFile().mkdirs();
Path logFileName = logDirectory.resolve("server1.log");
File logFile = logFileName.toFile();
logFile.createNewFile();
logFile.deleteOnExit();

AccessLogReceiver logReceiver = new AccessLogReceiver(DefaultServer.getWorker(), logDirectory,
"server1.", "log");

String formatString = "SSL Protocol is %{SSL_PROTOCOL}.";
CompletionLatchHandler latchHandler= new CompletionLatchHandler(
new AccessLogHandler(exchange -> exchange.getResponseSender().send("ping"),
logReceiver, formatString,
AccessLogFileTestCase.class.getClassLoader()));
DefaultServer.setRootHandler(latchHandler);

try(TestHttpClient client = new TestHttpClient()) {
DefaultServer.startSSLServer();
SSLContext sslContext = DefaultServer.getClientSSLContext();
client.setSSLContext(sslContext);

HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/path");
HttpResponse result = client.execute(get);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
Assert.assertEquals("ping", HttpClientUtils.readResponse(result));
latchHandler.await();
logReceiver.awaitWrittenForTest();
Assert.assertEquals(formatString.replaceAll("%\\{SSL_PROTOCOL}", sslContext.getProtocol()) + System.lineSeparator(),
new String(Files.readAllBytes(logFileName)));
} finally {
DefaultServer.stopSSLServer();
}
}

private static class AccessLogReceiver extends DefaultAccessLogReceiver {
AccessLogReceiver(final Executor logWriteExecutor,
final Path outputDirectory,
final String logBaseName,
final String logNameSuffix) {
super(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, true);
}

public void awaitWrittenForTest() throws InterruptedException {
super.awaitWrittenForTest();
}
}

}

0 comments on commit f8e3988

Please sign in to comment.