jetty.project
jetty.project copied to clipboard
Dynamic update of TLS version for Jetty client
jetty Version=11.0.17
Java Version = 17
Question I am using jetty as a client to send traffic by using the https with TLSv1.2 or TLSv1.3 version. Here my ask is that need to update the Jetty TLS version without impacting the existing Connections. let's say for Server1 there exists connection and after that I want to update the TLS version dynamically so, that next request to server2 should use the new connection with the latest TLS configuration, but existing connection should remain as is and allow traffic with old TLS config.
below code snippet get the webclient.
@Configuration
public class JettyClientConfig {
private static final Logger logger = LogManager.getLogger(JettyClientConfig.class.getName());
private static org.eclipse.jetty.client.HttpClient httpsClient;
public WebClient getWebClient() throws IOException {
String extUrl = "http://localhost:9090/dest";
ClientHttpConnector httpConnector = new JettyClientHttpConnector(getHttpClient());
return WebClient.builder().clientConnector(httpConnector).baseUrl(extUrl).build();
}
// Used for Egress side HTTP over TLS Client
public org.eclipse.jetty.client.HttpClient getHttpClient() throws IOException {
SslContextFactory sslContextFactory = new SslContextFactory.Client(true) {
@Override
public void customize(SSLEngine sslEngine) {
sslEngine.setSSLParameters(customize(sslEngine.getSSLParameters()));
if (logger.isInfoEnabled()) {
logger.info("Jetty-H2-Client: SSLEngine: {}", sslEngine);
}
}
};
ClientConnector clientConnector = new ClientConnector() {
protected void configure(SelectableChannel selectable) throws IOException {
super.configure(selectable);
if (selectable instanceof NetworkChannel) {
NetworkChannel channel = (NetworkChannel)selectable;
channel.setOption(java.net.StandardSocketOptions.SO_KEEPALIVE,
tcpConfigOptionProvider.getTcpKeepalive().getEnable());
// Set keepalive parameters only if it is enabled
if(tcpConfigOptionProvider.getTcpKeepalive().getEnable()) {
channel.setOption(jdk.net.ExtendedSocketOptions.TCP_KEEPIDLE,
Integer.parseInt(StringUtils.chop(tcpConfigOptionProvider.getTcpKeepalive().getTime())));
channel.setOption(jdk.net.ExtendedSocketOptions.TCP_KEEPINTERVAL,
Integer.parseInt(StringUtils.chop(tcpConfigOptionProvider.getTcpKeepalive().getInterval())));
channel.setOption(jdk.net.ExtendedSocketOptions.TCP_KEEPCOUNT,
tcpConfigOptionProvider.getTcpKeepalive().getProbes());
}
tcpKeepaliveChannelCofigDetails(channel);
}
}
protected void connectFailed(Throwable failure, Map<String, Object> context) {
if (logger.isInfoEnabled()) {
logger.info("Jetty-H2-Client: ClientConnector:: connectFailed() context {}",
context.get(REMOTE_SOCKE_INET_ADDRESS));
}
super.connectFailed(failure, context);
}
public void connect(SocketAddress address, Map<String, Object> context) {
if (logger.isInfoEnabled()) {
logger.info("Jetty-H2-Client: Connecting to {}", address);
}
if (context != null) {
context.put(REMOTE_SOCKE_INET_ADDRESS, address);
}
super.connect(address, context);
}
};
clientConnector.setSslContextFactory((SslContextFactory.Client) sslContextFactory);
sslContextFactory.setEndpointIdentificationAlgorithm(null);
updateTlsVersionAndCiphers(sslContextFactory);
HTTP2Client http2Client = new HTTP2Client(clientConnector);
// HTTP2Client http2Client = new HTTP2Client();
http2Client.setMaxConcurrentPushedStreams(JCMaxConcurrentPushedStreams);
org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2 transport = new org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2(
http2Client);
transport.setUseALPN(true);
org.eclipse.jetty.client.HttpClient httpClient = new org.eclipse.jetty.client.HttpClient(
transport) {
@Override
protected void doStart() throws Exception {
super.doStart();
}
@Override
public Origin createOrigin(HttpRequest request, Origin.Protocol protocol)
{
String scheme = request.getScheme();
if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme) &&
!HttpScheme.WS.is(scheme) && !HttpScheme.WSS.is(scheme))
throw new IllegalArgumentException("Invalid protocol " + scheme);
scheme = scheme.toLowerCase(Locale.ENGLISH);
String host = request.getHost();
host = host.toLowerCase(Locale.ENGLISH);
List<HttpCookie> cookies = request.getCookies();
if (logger.isInfoEnabled()) {
logger.info("Jetty-H2-Client: cookies found in request: {}", cookies);
}
String ip = getIpFromCookies(cookies);
String hostName = null;
if(StringUtils.isNotBlank(ip)) {
hostName = host;
host = ip;
}
/**
* Overriding the implementation from jetty client- end
*/
int port = request.getPort();
port = normalizePort(scheme, port);
return new Origin(scheme, host, port, request.getTag(), protocol, hostName);
}
private String getIpFromCookies(List<HttpCookie> cookies) {
String ip = "";
if(!cookies.isEmpty()) {
Iterator<HttpCookie> itr = cookies.iterator();
while(itr.hasNext()) {
HttpCookie httpCookie = itr.next();
if(httpCookie.getName().equals(CommonConstants.CUSTOM_SOURCE)) {
ip = httpCookie.getValue();
logger.info("Jetty-H2-Client: cookie found: {}", ip);
itr.remove();
break;
}
}
}
return ip;
}
};
httpClient.setIdleTimeout(JCidleTimeout);
httpClient.setMaxRequestsQueuedPerDestination(JCmaxRequestsQueuedPerDestination);
httpClient.setMaxConnectionsPerDestination(JCmaxConnectionsPerDestination);
httpClient.setUserAgentField(null);
httpClient.setRemoveIdleDestinations(true);
httpClient.setConnectTimeout(commonJCconnectTimeout);
// Add SslHandshakeListener
httpClient.addBean(new SslHandshakeListener() {
@Override
public void handshakeSucceeded(Event event) {
logger.debug("Handshake is success");
}
@Override
public void handshakeFailed(Event event, Throwable failure) {
logger.debug("Handshake is Failed");
}
});
try {
httpClient.start();
} catch (Exception e) {
logger.error("exception during client start: {}", e);
}
setHttpsclient(httpClient);
return httpClient;
}
public void updateTlsVersionAndCiphers(SslContextFactory sslContextFactory) {
try {
TLSConfigurationDataObject ciphersConfigDataObject = TLSConfigurationDataObject.getInstance();
TLSConfigurationWrapper tlsCiphersConfigWrapper = ciphersConfigDataObject
.getTlsDataByInterface("JETTY_CLIENT_DATA");
String[] ciphers = null;
String tlsVersion= "";
if (ObjectUtils.isNotEmpty(tlsCiphersConfigWrapper)) {
TLSConfigurationData tlsCiphersConfigData = tlsCiphersConfigWrapper.getTlsConfigData();
tlsVersion = tlsCiphersConfigData.getTlsVersion();
if ("TLSv1.3".equals(tlsVersion)) {
ciphers = tlsCiphersConfigData.getTls13Ciphers().toArray(new String[0]);
} else if ("TLSv1.2".equals(tlsVersion)) {
ciphers = tlsCiphersConfigData.getTls12Ciphers().toArray(new String[0]);
} else {
ciphers = Stream.concat(tlsCiphersConfigData.getTls12Ciphers().stream(),
tlsCiphersConfigData.getTls13Ciphers().stream()).toList().toArray(new String[0]);
}
} else {
tlsVersion = "TLSv1.3,TLSv.12";
ciphers = Http2SecurityUtil.CIPHERS.toArray(new String[0]);
}
sslContextFactory.setIncludeCipherSuites(ciphers);
sslContextFactory.setIncludeProtocols(tlsVersion.split(","));
sslContextFactory.setSslContext(getSSLContext(tlsVersion));
} catch (Exception e) {
logger.error("Excepiton occured :{}", e.getMessage());
}
}
private SSLContext getSSLContext(String tlsVersion) throws Exception {
SSLContext sslContext = "TLSv1.2".equals(tlsVersion) ?
SSLContext.getInstance("TLSv1.2") :
SSLContext.getInstance("TLSv1.3");
sslContext.init(new KeyManager[] { reloadableX509KeyManager },
new TrustManager[] { reloadableX509TrustManager }, null);
return sslContext;
}
public static void setHttpsclient(org.eclipse.jetty.client.HttpClient httpsClient) {
httpsClient = httpsClient;
}
}
I have tried some code changes to update the tls version but it is not working.
org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2 transport =(org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2) jettyClientConfig.getHttpsclient().getTransport();
HTTP2Client hTTP2Client = transport.getHTTP2Client();
ClientConnector clientConnector = hTTP2Client.getClientConnector();
SslContextFactory sslContextFactory = new SslContextFactory.Client(true) {
@Override
public void customize(SSLEngine sslEngine) {
sslEngine.setSSLParameters(customize(sslEngine.getSSLParameters()));
}
};
jettyClientConfig.updateTlsVersionAndCiphers(sslContextFactory);
clientConnector.setSslContextFactory((SslContextFactory.Client) sslContextFactory);
Kindly please suggest how to update the TLS version dynamically without disturbing the existing connection.
Hi Team,
trying one more approach to update the tls version dynamically.
org.eclipse.jetty.client.HttpClient httpsClient = JettyClientConfiguration.getHttpsclient();
SslContextFactory sslContextFactory = httpsClient.getSslContextFactory();
sslContextFactory.setIncludeProtocols(tlsVersion.split(","));
String[] ciphers;
if (CommonConstants.getTlsVersionOneDotThree().equals(tlsVersion)) {
ciphers = tlsConfigData.getTls13Ciphers().toArray(new String[0]);
} else if (CommonConstants.getTlsVersionOneDotTwo().equals(tlsVersion)) {
ciphers = tlsConfigData.getTls12Ciphers().toArray(new String[0]);
} else {
ciphers = Stream.concat(tlsConfigData.getTls12Ciphers().stream(),
tlsConfigData.getTls13Ciphers().stream()).toList().toArray(new String[0]);
}
sslContextFactory.setIncludeCipherSuites(ciphers);
try {
httpsClient.stop();
httpsClient.start();
} catch (Exception e) {
logger.error("Exception occurred while on https stop start");
}
but in this case directly existing TCP connection is terminated without GOAWAY.
Jetty 11 is now at End of Community Support, you should be using Jetty 12 at this point in time.
- #10485
- #7958
For commercial support of Jetty 11, see above listed issues.
It is the server that decides what TLS version to use, and cannot be forced by the client.
You can always use 2 different HttpClient, configured differently, where one HttpClient is used to talk to server1, and the other HttpClient is used to talk to server2.
Closing, unsupported release.