Skip to content

Commit 295c241

Browse files
committed
use IDEA certificate manager when connecting to the cluster (redhat-developer#600)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent 2c16a91 commit 295c241

File tree

4 files changed

+239
-7
lines changed

4 files changed

+239
-7
lines changed

Diff for: build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ dependencies {
6060
"io.fabric8:kubernetes-model-common:${kubernetesClientVersion}",
6161
"io.fabric8:openshift-client:${kubernetesClientVersion}",
6262
"io.fabric8:kubernetes-httpclient-okhttp:${kubernetesClientVersion}",
63-
"org.apache.commons:commons-lang3:3.12.0"
63+
"org.apache.commons:commons-lang3:3.12.0",
64+
"io.github.hakky54:sslcontext-kickstart:8.0.0"
6465
)
6566
testImplementation(
6667
"org.assertj:assertj-core:3.22.0",

Diff for: src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientAdapter.kt

+13-6
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@
1010
******************************************************************************/
1111
package com.redhat.devtools.intellij.kubernetes.model.client
1212

13-
import com.intellij.util.net.ssl.CertificateManager
13+
import com.redhat.devtools.intellij.kubernetes.model.client.ssl.IDEATrustManager
1414
import com.redhat.devtools.intellij.kubernetes.model.util.isUnauthorized
1515
import io.fabric8.kubernetes.client.Client
1616
import io.fabric8.kubernetes.client.Config
1717
import io.fabric8.kubernetes.client.KubernetesClient
1818
import io.fabric8.kubernetes.client.KubernetesClientBuilder
1919
import io.fabric8.kubernetes.client.KubernetesClientException
20+
import io.fabric8.kubernetes.client.http.HttpClient
2021
import io.fabric8.kubernetes.client.impl.AppsAPIGroupClient
2122
import io.fabric8.kubernetes.client.impl.BatchAPIGroupClient
2223
import io.fabric8.kubernetes.client.impl.NetworkAPIGroupClient
2324
import io.fabric8.kubernetes.client.impl.StorageAPIGroupClient
25+
import io.fabric8.kubernetes.client.internal.SSLUtils
2426
import io.fabric8.openshift.client.NamespacedOpenShiftClient
2527
import io.fabric8.openshift.client.OpenShiftClient
2628
import java.util.concurrent.ConcurrentHashMap
29+
import javax.net.ssl.X509ExtendedTrustManager
2730

2831
open class OSClientAdapter(client: OpenShiftClient, private val kubeClient: KubernetesClient) :
2932
ClientAdapter<OpenShiftClient>(client) {
@@ -55,14 +58,16 @@ abstract class ClientAdapter<C: KubernetesClient>(private val fabric8Client: C)
5558
companion object Factory {
5659
fun create(namespace: String? = null, context: String? = null): ClientAdapter<out KubernetesClient> {
5760
val config = Config.autoConfigure(context)
58-
setAcceptCertificates(config)
5961
return create(namespace, config)
6062
}
6163

6264
fun create(namespace: String? = null, config: Config): ClientAdapter<out KubernetesClient> {
6365
setNamespace(namespace, config)
6466
val kubeClient = KubernetesClientBuilder()
6567
.withConfig(config)
68+
.withHttpClientBuilderConsumer { builder ->
69+
setSslContext(builder, config)
70+
}
6671
.build()
6772
val osClient = kubeClient.adapt(NamespacedOpenShiftClient::class.java)
6873
val isOpenShift = isOpenShift(osClient)
@@ -73,10 +78,12 @@ abstract class ClientAdapter<C: KubernetesClient>(private val fabric8Client: C)
7378
}
7479
}
7580

76-
private fun setAcceptCertificates(config: Config) {
77-
val manager = CertificateManager.getInstance().state;
78-
config.isTrustCerts = manager.ACCEPT_AUTOMATICALLY
79-
config.isDisableHostnameVerification = manager.ACCEPT_AUTOMATICALLY
81+
private fun setSslContext(builder: HttpClient.Builder, config: Config) {
82+
val clientTrustManagers = SSLUtils.trustManagers(config)
83+
.filterIsInstance<X509ExtendedTrustManager>()
84+
.toTypedArray()
85+
val ideTrustManager = IDEATrustManager.configure(clientTrustManagers)
86+
builder.sslContext(SSLUtils.keyManagers(config), arrayOf(ideTrustManager))
8087
}
8188

8289
private fun isOpenShift(osClient: NamespacedOpenShiftClient): Boolean {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Based on nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager at https://github.com/Hakky54/sslcontext-kickstart
10+
* Red Hat, Inc. - initial API and implementation
11+
******************************************************************************/
12+
13+
package com.redhat.devtools.intellij.kubernetes.model.client.ssl
14+
15+
import java.net.Socket
16+
import java.security.cert.CertificateException
17+
import java.security.cert.X509Certificate
18+
import java.util.*
19+
import java.util.function.Consumer
20+
import javax.net.ssl.SSLEngine
21+
import javax.net.ssl.X509ExtendedTrustManager
22+
23+
class CompositeX509ExtendedTrustManager(trustManagers: List<X509ExtendedTrustManager>): X509ExtendedTrustManager() {
24+
25+
companion object {
26+
private const val CERTIFICATE_EXCEPTION_MESSAGE = "None of the TrustManagers trust this certificate chain"
27+
}
28+
29+
val innerTrustManagers: List<X509ExtendedTrustManager>
30+
private val acceptedIssuers: Array<X509Certificate>
31+
32+
init {
33+
innerTrustManagers = Collections.unmodifiableList(trustManagers)
34+
acceptedIssuers = trustManagers.stream()
35+
.map { obj: X509ExtendedTrustManager -> obj.acceptedIssuers }
36+
.flatMap { array: Array<X509Certificate> ->
37+
Arrays.stream(array)
38+
}
39+
.toArray { length -> arrayOfNulls<X509Certificate>(length) }
40+
}
41+
42+
override fun getAcceptedIssuers(): Array<X509Certificate> {
43+
return Arrays.copyOf(acceptedIssuers, acceptedIssuers.size)
44+
}
45+
46+
@Throws(CertificateException::class)
47+
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
48+
checkTrusted { trustManager: X509ExtendedTrustManager ->
49+
trustManager.checkClientTrusted(
50+
chain,
51+
authType
52+
)
53+
}
54+
}
55+
56+
@Throws(CertificateException::class)
57+
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket) {
58+
checkTrusted { trustManager: X509ExtendedTrustManager ->
59+
trustManager.checkClientTrusted(
60+
chain,
61+
authType,
62+
socket
63+
)
64+
}
65+
}
66+
67+
@Throws(CertificateException::class)
68+
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, sslEngine: SSLEngine) {
69+
checkTrusted { trustManager: X509ExtendedTrustManager ->
70+
trustManager.checkClientTrusted(
71+
chain,
72+
authType,
73+
sslEngine
74+
)
75+
}
76+
}
77+
78+
@Throws(CertificateException::class)
79+
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
80+
checkTrusted{ trustManager: X509ExtendedTrustManager ->
81+
trustManager.checkServerTrusted(
82+
chain,
83+
authType
84+
)
85+
}
86+
}
87+
88+
@Throws(CertificateException::class)
89+
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket) {
90+
checkTrusted{ trustManager: X509ExtendedTrustManager ->
91+
trustManager.checkServerTrusted(
92+
chain,
93+
authType,
94+
socket
95+
)
96+
}
97+
}
98+
99+
@Throws(CertificateException::class)
100+
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, sslEngine: SSLEngine) {
101+
checkTrusted { trustManager: X509ExtendedTrustManager ->
102+
trustManager.checkServerTrusted(
103+
chain,
104+
authType,
105+
sslEngine
106+
)
107+
}
108+
}
109+
110+
@Throws(CertificateException::class)
111+
private fun checkTrusted(consumer: (trustManager: X509ExtendedTrustManager) -> Unit) {
112+
val certificateExceptions: MutableList<CertificateException> = ArrayList()
113+
for (trustManager in innerTrustManagers) {
114+
try {
115+
consumer.invoke(trustManager)
116+
return
117+
} catch (e: CertificateException) {
118+
certificateExceptions.add(e)
119+
}
120+
}
121+
val certificateException = CertificateException(CERTIFICATE_EXCEPTION_MESSAGE)
122+
certificateExceptions.forEach(Consumer { exception: CertificateException? ->
123+
certificateException.addSuppressed(
124+
exception
125+
)
126+
})
127+
throw certificateException
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes.model.client.ssl
12+
13+
import com.intellij.openapi.diagnostic.logger
14+
import com.intellij.util.net.ssl.CertificateManager
15+
import com.intellij.util.net.ssl.ConfirmingTrustManager
16+
import javax.net.ssl.X509ExtendedTrustManager
17+
import javax.net.ssl.X509TrustManager
18+
import org.apache.commons.lang3.reflect.FieldUtils
19+
20+
object IDEATrustManager {
21+
22+
fun configure(clientTrustManagers: Array<X509ExtendedTrustManager>): ConfirmingTrustManager {
23+
val ideTrustManager = CertificateManager.getInstance().trustManager
24+
try {
25+
if (hasSystemManagerField()) {
26+
// < IC-2022.2
27+
setCompositeManager(clientTrustManagers, ideTrustManager)
28+
} else {
29+
// >= IC-2022.2
30+
addCompositeManager(clientTrustManagers, ideTrustManager)
31+
}
32+
} catch (e: RuntimeException) {
33+
logger<IDEATrustManager>().warn("Could not configure IDEA trust manager.", e)
34+
}
35+
return ideTrustManager
36+
}
37+
38+
/**
39+
* Returns `true` if [ConfirmingTrustManager] has a private field `mySystemManager`.
40+
* Returns `false` otherwise.
41+
* IDEA < IC-2022.2 manages a single [X509TrustManager] in a private field called `mySystemManager`.
42+
* IDEA >= IC-2022.2 manages a list of [X509TrustManager]s in a private list called `mySystemManagers`.
43+
*/
44+
private fun hasSystemManagerField(): Boolean {
45+
return FieldUtils.getDeclaredField(
46+
ConfirmingTrustManager::class.java, "mySystemManager", true) != null
47+
}
48+
49+
private fun setCompositeManager(
50+
clientTrustManagers: Array<X509ExtendedTrustManager>,
51+
ideTrustManager: ConfirmingTrustManager
52+
): Boolean {
53+
val systemManagerField = FieldUtils.getDeclaredField(
54+
ConfirmingTrustManager::class.java, "mySystemManager", true) ?: return false
55+
val systemManager = systemManagerField.get(ideTrustManager) as? X509ExtendedTrustManager ?: return false
56+
val compositeTrustManager = createCompositeTrustManager(systemManager, clientTrustManagers)
57+
systemManagerField.set(ideTrustManager, compositeTrustManager)
58+
return true
59+
}
60+
61+
private fun addCompositeManager(
62+
clientTrustManagers: Array<X509ExtendedTrustManager>,
63+
ideTrustManager: ConfirmingTrustManager
64+
) {
65+
val systemManagersField =
66+
FieldUtils.getDeclaredField(ideTrustManager::class.java, "mySystemManagers", true)
67+
val managers = systemManagersField.get(ideTrustManager) as? MutableList<X509TrustManager> ?: return
68+
val nonCompositeManagers = managers.filterNot { it is CompositeX509ExtendedTrustManager }
69+
val clientTrustManager = CompositeX509ExtendedTrustManager(clientTrustManagers.asList())
70+
managers.clear()
71+
managers.addAll(nonCompositeManagers)
72+
managers.add(clientTrustManager)
73+
}
74+
75+
private fun createCompositeTrustManager(
76+
systemManager: X509ExtendedTrustManager,
77+
clientTrustManagers: Array<X509ExtendedTrustManager>
78+
): X509ExtendedTrustManager {
79+
val compositeTrustManager = if (systemManager is CompositeX509ExtendedTrustManager) {
80+
// already patched CertificateManager, re-create composite manager
81+
CompositeX509ExtendedTrustManager(
82+
mutableListOf(systemManager.innerTrustManagers[0])
83+
.plus(clientTrustManagers)
84+
)
85+
} else {
86+
// 1st time we patch CertificateManager, create composite manager
87+
CompositeX509ExtendedTrustManager(
88+
mutableListOf(systemManager)
89+
.plus(clientTrustManagers)
90+
)
91+
}
92+
return compositeTrustManager
93+
}
94+
95+
}

0 commit comments

Comments
 (0)