Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic update of TLS version for Jetty client #11922

Open
MohammadNC opened this issue Jun 14, 2024 · 3 comments
Open

Dynamic update of TLS version for Jetty client #11922

MohammadNC opened this issue Jun 14, 2024 · 3 comments

Comments

@MohammadNC
Copy link

MohammadNC commented Jun 14, 2024

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.

@MohammadNC
Copy link
Author

MohammadNC commented Jun 14, 2024

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.

image

@joakime
Copy link
Contributor

joakime commented Jun 14, 2024

Jetty 11 is now at End of Community Support, you should be using Jetty 12 at this point in time.

For commercial support of Jetty 11, see above listed issues.

@sbordet
Copy link
Contributor

sbordet commented Jul 28, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants