jetty / jetty.project

Eclipse Jetty® - Web Container & Clients - supports HTTP/2, HTTP/1.1, HTTP/1.0, websocket, servlets, and more
https://eclipse.dev/jetty
Other
3.79k stars 1.9k forks source link

Dynamic update of TLS version for Jetty client #11922

Open MohammadNC opened 3 weeks ago

MohammadNC commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

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.