Skip to content

Commit 248dc4f

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

File tree

7 files changed

+498
-13
lines changed

7 files changed

+498
-13
lines changed

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 {

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(

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
35+
.map { manager: X509ExtendedTrustManager -> manager.acceptedIssuers }
36+
.flatMap { acceptedIssuers: Array<X509Certificate>? ->
37+
acceptedIssuers?.asList() ?: emptyList()
38+
}
39+
.toTypedArray()
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,118 @@
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 java.lang.reflect.Field
17+
import javax.net.ssl.X509ExtendedTrustManager
18+
import javax.net.ssl.X509TrustManager
19+
import org.apache.commons.lang3.reflect.FieldUtils
20+
21+
class IDEATrustManager(private val trustManager: X509TrustManager = CertificateManager.getInstance().trustManager) {
22+
23+
fun configure(toAdd: Array<out X509ExtendedTrustManager>): X509TrustManager {
24+
try {
25+
if (hasSystemManagerField()) {
26+
// < IC-2022.2
27+
setCompositeManager(toAdd, trustManager)
28+
} else {
29+
// >= IC-2022.2
30+
addCompositeManager(toAdd, trustManager)
31+
}
32+
} catch (e: RuntimeException) {
33+
logger<IDEATrustManager>().warn("Could not configure IDEA trust manager.", e)
34+
}
35+
return trustManager
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+
* @return true if com.intellij.util.net.ssl.ConfirmingTrustManager has a field mySystemManager. False otherwise.
45+
*/
46+
private fun hasSystemManagerField(): Boolean {
47+
return getSystemManagerField() != null
48+
}
49+
50+
private fun getSystemManagerField(): Field? {
51+
return FieldUtils.getDeclaredField(
52+
trustManager::class.java,
53+
"mySystemManager",
54+
true
55+
)
56+
}
57+
58+
/**
59+
* Sets a [CompositeX509ExtendedTrustManager] with the given [X509TrustManager]s
60+
* to the given destination [X509TrustManager].
61+
* If a [CompositeX509ExtendedTrustManager] already exists, his first entry is taken and set to a new
62+
* [CompositeX509ExtendedTrustManager] that replaces the existing one.
63+
*
64+
* @param trustManagers the trust managers that should be set to the destination trust manager
65+
* @param destination the destination trust manager that should receive the trust managers
66+
* @return true if the operation worked
67+
*/
68+
private fun setCompositeManager(
69+
trustManagers: Array<out X509ExtendedTrustManager>,
70+
destination: X509TrustManager
71+
): Boolean {
72+
val systemManagerField = getSystemManagerField() ?: return false
73+
val systemManager = systemManagerField.get(destination) as? X509ExtendedTrustManager ?: return false
74+
val compositeTrustManager = createCompositeTrustManager(systemManager, trustManagers)
75+
systemManagerField.set(destination, compositeTrustManager)
76+
return true
77+
}
78+
79+
private fun createCompositeTrustManager(
80+
systemManager: X509ExtendedTrustManager,
81+
clientTrustManagers: Array<out X509ExtendedTrustManager>
82+
): X509ExtendedTrustManager {
83+
val trustManagers = if (systemManager is CompositeX509ExtendedTrustManager) {
84+
// already patched CertificateManager, take 1st entry in existing system manager
85+
mutableListOf(systemManager.innerTrustManagers[0])
86+
} else {
87+
// unpatched CertificateManager, take system manager
88+
mutableListOf(systemManager)
89+
}
90+
trustManagers.addAll(clientTrustManagers)
91+
return CompositeX509ExtendedTrustManager(trustManagers)
92+
}
93+
94+
/**
95+
* Adds a [CompositeX509ExtendedTrustManager] to the given destination [X509TrustManager].
96+
* If a [CompositeX509ExtendedTrustManager] already exists, it is replaced by a new [CompositeX509ExtendedTrustManager].
97+
*
98+
* @param trustManagers the trust managers that should be added to destination trust manager
99+
* @param destination the trust manager that should receive the given trust managers
100+
*/
101+
private fun addCompositeManager(
102+
trustManagers: Array<out X509ExtendedTrustManager>,
103+
destination: X509TrustManager
104+
): Boolean {
105+
val systemManagersField = FieldUtils.getDeclaredField(
106+
destination::class.java,
107+
"mySystemManagers",
108+
true
109+
) ?: return false
110+
val managers = systemManagersField.get(destination) as? MutableList<X509TrustManager> ?: return false
111+
val nonCompositeManagers = managers.filterNot { it is CompositeX509ExtendedTrustManager }
112+
val clientTrustManager = CompositeX509ExtendedTrustManager(trustManagers.asList())
113+
managers.clear()
114+
managers.addAll(nonCompositeManagers)
115+
managers.add(clientTrustManager)
116+
return true
117+
}
118+
}

0 commit comments

Comments
 (0)