This is an automated email from the ASF dual-hosted git repository. smolnar82 pushed a commit to branch knox_idf in repository https://gitbox.apache.org/repos/asf/knox.git
commit 01c04c2da79e330ccfae3437bdc171d3e36a63ba Author: Sandor Molnar <[email protected]> AuthorDate: Fri Jun 19 17:46:10 2026 +0200 KNOX-3356: Allow Cloudera Manager service discovery over cleartext HTTP (#1272) (cherry picked from commit c3f55243fba023261ae91b80b5534aaa460ed114) --- .../ClouderaManagerServiceDiscoveryMessages.java | 3 +++ .../topology/discovery/cm/DiscoveryApiClient.java | 14 ++++++++++ .../cm/ClouderaManagerServiceDiscoveryTest.java | 30 +++++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryMessages.java b/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryMessages.java index d025fdf05..30155c4d9 100644 --- a/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryMessages.java +++ b/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryMessages.java @@ -280,6 +280,9 @@ public interface ClouderaManagerServiceDiscoveryMessages { @Message(level = MessageLevel.ERROR, text = "Failed to configure truststore") void failedToConfigureTruststore(); + @Message(level = MessageLevel.DEBUG, text = "Skipping SSL configuration for cleartext CM discovery address {0}") + void skippingSslConfigurationForCleartextAddress(String address); + @Message(level = MessageLevel.DEBUG, text = "Looking up cluster services from service discovery repository...") void lookupClusterServicesFromRepository(); diff --git a/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/DiscoveryApiClient.java b/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/DiscoveryApiClient.java index f74203b61..27e240ca5 100644 --- a/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/DiscoveryApiClient.java +++ b/gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/DiscoveryApiClient.java @@ -20,6 +20,7 @@ import java.security.KeyStore; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; @@ -155,6 +156,11 @@ public class DiscoveryApiClient extends ApiClient { return (address.endsWith("/") ? address + apiPath : address + "/" + apiPath); } + private boolean isSecure() { + final String basePath = getBasePath(); + return basePath != null && basePath.toLowerCase(Locale.ROOT).startsWith("https:"); + } + private void configureInterceptors(List<Interceptor> interceptors) { final OkHttpClient.Builder builder = getHttpClient().newBuilder(); interceptors.forEach(builder::addInterceptor); @@ -172,6 +178,14 @@ public class DiscoveryApiClient extends ApiClient { } private void configureSsl(GatewayConfig gatewayConfig, KeyStore trustStore) { + // The CM discovery endpoint may be plain HTTP (e.g. CM TLS not enabled). In that case the + // TLS-only ConnectionSpec below would make OkHttp reject the connection with + // "CLEARTEXT communication not enabled for client". Only configure TLS for HTTPS addresses. + if (!isSecure()) { + log.skippingSslConfigurationForCleartextAddress(getBasePath()); + return; + } + final SSLContext truststoreSSLContext = TruststoreSSLContextUtils.getTruststoreSSLContext(trustStore); final X509TrustManager trustManager = TruststoreSSLContextUtils.getTrustManager(trustStore); diff --git a/gateway-discovery-cm/src/test/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryTest.java b/gateway-discovery-cm/src/test/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryTest.java index adaaaf35a..9437913ad 100644 --- a/gateway-discovery-cm/src/test/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryTest.java +++ b/gateway-discovery-cm/src/test/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryTest.java @@ -91,6 +91,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class ClouderaManagerServiceDiscoveryTest { private static final String DISCOVERY_URL = "http://localhost:1234"; + private static final String DISCOVERY_URL_TLS = "https://localhost:1234"; private static final String DISCOVERY_USER = "discoveryUser"; private static final String CLUSTER_NAME = "Cluster 1"; private static final String DISCOVERY_PASSWORD_ALIAS = "discovery.alias"; @@ -164,7 +165,7 @@ public class ClouderaManagerServiceDiscoveryTest { EasyMock.expect(gwConf.getClouderaManagerClientSSLProtocols()).andReturn(Set.of(cmClientTlsVersion)).anyTimes(); EasyMock.replay(gwConf); - ServiceDiscoveryConfig sdConfig = createMockDiscoveryConfig(DISCOVERY_URL, DISCOVERY_USER, CLUSTER_NAME); + ServiceDiscoveryConfig sdConfig = createMockDiscoveryConfig(DISCOVERY_URL_TLS, DISCOVERY_USER, CLUSTER_NAME); AliasService aliasService = EasyMock.createNiceMock(AliasService.class); EasyMock.replay(aliasService); @@ -195,6 +196,33 @@ public class ClouderaManagerServiceDiscoveryTest { assertTrue(containsTlsVersion(connectionSpecs.get(0).tlsVersions(), cmClientTlsVersion)); } + /** + * KNOX-3353: when the CM discovery address is plain HTTP (CM TLS not enabled), the client must + * not be locked to a TLS-only ConnectionSpec, otherwise OkHttp rejects the request with + * "CLEARTEXT communication not enabled for client". The default specs (which include CLEARTEXT) + * should be left in place. + */ + @Test + public void testApiClientAllowsCleartextForHttpDiscoveryAddress() { + GatewayConfig gwConf = EasyMock.createNiceMock(GatewayConfig.class); + EasyMock.expect(gwConf.getClouderaManagerServiceDiscoveryApiVersion()).andReturn(GatewayConfig.DEFAULT_CLOUDERA_MANAGER_SERVICE_DISCOVERY_API_VERSION).anyTimes(); + EasyMock.replay(gwConf); + + ServiceDiscoveryConfig sdConfig = createMockDiscoveryConfig(DISCOVERY_URL, DISCOVERY_USER, CLUSTER_NAME); + + AliasService aliasService = EasyMock.createNiceMock(AliasService.class); + EasyMock.replay(aliasService); + + KeyStore trustStore = EasyMock.createNiceMock(KeyStore.class); + EasyMock.replay(trustStore); + + ApiClient apiClient = new TestDiscoveryApiClient(gwConf, sdConfig, aliasService, trustStore); + + final List<ConnectionSpec> connectionSpecs = apiClient.getHttpClient().connectionSpecs(); + assertTrue("HTTP discovery address should keep a CLEARTEXT-capable connection spec.", + connectionSpecs.stream().anyMatch(spec -> spec.equals(ConnectionSpec.CLEARTEXT))); + } + private boolean containsCipherSuite(List<CipherSuite> cipherSuites, String cipherSuiteNameToCheck) { return cipherSuites.stream().anyMatch(cipherSuite -> cipherSuite.javaName().equals(cipherSuiteNameToCheck)); }
