Skip to content

Commit 6ee8a45

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

File tree

6 files changed

+308
-13
lines changed

6 files changed

+308
-13
lines changed

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ open class AllContexts(
7777
private val contextFactory: (ClientAdapter<out KubernetesClient>, IResourceModelObservable) -> IActiveContext<out HasMetadata, out KubernetesClient>? =
7878
IActiveContext.Factory::create,
7979
private val modelChange: IResourceModelObservable,
80-
private val clientFactory: (String?, String?) -> ClientAdapter<out KubernetesClient> = ClientAdapter.Factory::create
80+
private val clientFactory: (
81+
namespace: String?,
82+
context: String?
83+
) -> ClientAdapter<out KubernetesClient>
84+
= { namespace, context -> ClientAdapter.Factory.create(namespace, context) }
8185
) : IAllContexts {
8286

8387
init {

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ import java.io.IOException
3535
import java.io.OutputStream
3636
import java.util.concurrent.ConcurrentHashMap
3737

38-
open class ProcessWatches(private val clientFactory: (String?, String?) -> ClientAdapter<out KubernetesClient> = ClientAdapter.Factory::create) {
38+
open class ProcessWatches(
39+
private val clientFactory: (String?, String?) -> ClientAdapter<out KubernetesClient>
40+
= { namespace: String?, context: String? -> ClientAdapter.Factory.create(namespace, context) }
41+
) {
3942

4043
@Suppress("UNCHECKED_CAST")
4144
protected open val operators: Map<ResourceKind<out HasMetadata>, OperatorSpecs> = mapOf(

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

+32-10
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@
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
30+
import javax.net.ssl.X509TrustManager
2731

2832
open class OSClientAdapter(client: OpenShiftClient, private val kubeClient: KubernetesClient) :
2933
ClientAdapter<OpenShiftClient>(client) {
@@ -50,19 +54,31 @@ open class KubeClientAdapter(client: KubernetesClient) :
5054
}
5155
}
5256

53-
abstract class ClientAdapter<C: KubernetesClient>(private val fabric8Client: C) {
57+
abstract class ClientAdapter<C : KubernetesClient>(private val fabric8Client: C) {
5458

5559
companion object Factory {
56-
fun create(namespace: String? = null, context: String? = null): ClientAdapter<out KubernetesClient> {
60+
fun create(
61+
namespace: String? = null,
62+
context: String? = null,
63+
trustManagerProvider: ((toIntegrate: Array<out X509ExtendedTrustManager>) -> X509TrustManager)
64+
= IDEATrustManager()::configure
65+
): ClientAdapter<out KubernetesClient> {
5766
val config = Config.autoConfigure(context)
58-
setAcceptCertificates(config)
59-
return create(namespace, config)
67+
return create(namespace, config, trustManagerProvider)
6068
}
6169

62-
fun create(namespace: String? = null, config: Config): ClientAdapter<out KubernetesClient> {
70+
fun create(
71+
namespace: String? = null,
72+
config: Config,
73+
externalTrustManagerProvider: (toIntegrate: Array<out X509ExtendedTrustManager>) -> X509TrustManager
74+
= IDEATrustManager()::configure
75+
): ClientAdapter<out KubernetesClient> {
6376
setNamespace(namespace, config)
6477
val kubeClient = KubernetesClientBuilder()
6578
.withConfig(config)
79+
.withHttpClientBuilderConsumer { builder ->
80+
setSslContext(builder, config, externalTrustManagerProvider)
81+
}
6682
.build()
6783
val osClient = kubeClient.adapt(NamespacedOpenShiftClient::class.java)
6884
val isOpenShift = isOpenShift(osClient)
@@ -73,10 +89,16 @@ abstract class ClientAdapter<C: KubernetesClient>(private val fabric8Client: C)
7389
}
7490
}
7591

76-
private fun setAcceptCertificates(config: Config) {
77-
val manager = CertificateManager.getInstance().state;
78-
config.isTrustCerts = manager.ACCEPT_AUTOMATICALLY
79-
config.isDisableHostnameVerification = manager.ACCEPT_AUTOMATICALLY
92+
private fun setSslContext(
93+
builder: HttpClient.Builder,
94+
config: Config,
95+
externalTrustManagerProvider: (toIntegrate: Array<out X509ExtendedTrustManager>) -> X509TrustManager
96+
) {
97+
val clientTrustManagers = SSLUtils.trustManagers(config)
98+
.filterIsInstance<X509ExtendedTrustManager>()
99+
.toTypedArray()
100+
val externalTrustManager = externalTrustManagerProvider.invoke(clientTrustManagers)
101+
builder.sslContext(SSLUtils.keyManagers(config), arrayOf(externalTrustManager))
80102
}
81103

82104
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,113 @@
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+
class IDEATrustManager(private val trustManager: X509TrustManager = CertificateManager.getInstance().trustManager) {
21+
22+
fun configure(toIntegrate: Array<out X509ExtendedTrustManager>): X509TrustManager {
23+
try {
24+
if (hasSystemManagerField()) {
25+
// < IC-2022.2
26+
setCompositeManager(toIntegrate, trustManager)
27+
} else {
28+
// >= IC-2022.2
29+
addCompositeManager(toIntegrate, trustManager)
30+
}
31+
} catch (e: RuntimeException) {
32+
logger<IDEATrustManager>().warn("Could not configure IDEA trust manager.", e)
33+
}
34+
return trustManager
35+
}
36+
37+
/**
38+
* Returns `true` if [ConfirmingTrustManager] has a private field `mySystemManager`.
39+
* Returns `false` otherwise.
40+
* IDEA < IC-2022.2 manages a single [X509TrustManager] in a private field called `mySystemManager`.
41+
* IDEA >= IC-2022.2 manages a list of [X509TrustManager]s in a private list called `mySystemManagers`.
42+
*/
43+
private fun hasSystemManagerField(): Boolean {
44+
return FieldUtils.getDeclaredField(
45+
ConfirmingTrustManager::class.java, "mySystemManager", true) != null
46+
}
47+
48+
/**
49+
* Sets a [CompositeX509ExtendedTrustManager] with the given [X509TrustManager]s
50+
* to the given destination [X509TrustManager].
51+
* If a [CompositeX509ExtendedTrustManager] already exists, his first entry is taken and set to a new
52+
* [CompositeX509ExtendedTrustManager] that replaces the existing one.
53+
*
54+
* @param trustManagers the trust managers that should be set to the destination trust manager
55+
* @param destination the destination trust manager that should receive the trust managers
56+
* @return true if the operation worked
57+
*/
58+
private fun setCompositeManager(
59+
trustManagers: Array<out X509ExtendedTrustManager>,
60+
destination: X509TrustManager
61+
): Boolean {
62+
val systemManagerField = FieldUtils.getDeclaredField(
63+
ConfirmingTrustManager::class.java,
64+
"mySystemManager",
65+
true
66+
) ?: return false
67+
val systemManager = systemManagerField.get(destination) as? X509ExtendedTrustManager ?: return false
68+
val compositeTrustManager = createCompositeTrustManager(systemManager, trustManagers)
69+
systemManagerField.set(destination, compositeTrustManager)
70+
return true
71+
}
72+
73+
private fun createCompositeTrustManager(
74+
systemManager: X509ExtendedTrustManager,
75+
clientTrustManagers: Array<out X509ExtendedTrustManager>
76+
): X509ExtendedTrustManager {
77+
val trustManagers = if (systemManager is CompositeX509ExtendedTrustManager) {
78+
// already patched CertificateManager, re-create composite manager
79+
mutableListOf(systemManager.innerTrustManagers[0])
80+
.plus(clientTrustManagers)
81+
} else {
82+
// 1st time we patch CertificateManager, create composite manager
83+
mutableListOf(systemManager)
84+
.plus(clientTrustManagers)
85+
}
86+
return CompositeX509ExtendedTrustManager(trustManagers)
87+
}
88+
89+
/**
90+
* Adds a [CompositeX509ExtendedTrustManager] to the given destination [X509TrustManager].
91+
* If a [CompositeX509ExtendedTrustManager] already exists, it is replaced by a new [CompositeX509ExtendedTrustManager].
92+
*
93+
* @param trustManagers the trust managers that should be added to destination trust manager
94+
* @param destination the trust manager that should receive the given trust managers
95+
*/
96+
private fun addCompositeManager(
97+
trustManagers: Array<out X509ExtendedTrustManager>,
98+
destination: X509TrustManager
99+
): Boolean {
100+
val systemManagersField = FieldUtils.getDeclaredField(
101+
destination::class.java,
102+
"mySystemManagers",
103+
true
104+
) ?: return false
105+
val managers = systemManagersField.get(destination) as? MutableList<X509TrustManager> ?: return false
106+
val nonCompositeManagers = managers.filterNot { it is CompositeX509ExtendedTrustManager }
107+
val clientTrustManager = CompositeX509ExtendedTrustManager(trustManagers.asList())
108+
managers.clear()
109+
managers.addAll(nonCompositeManagers)
110+
managers.add(clientTrustManager)
111+
return true
112+
}
113+
}

0 commit comments

Comments
 (0)