Skip to content

Commit e0b317b

Browse files
committed
#19 - oc command line kube config file support
- added fallback to retrieve certificate from api server url in KubectlConfigReader.kt - added additional check on empty cert data in ClusterClientBuilder.kt
1 parent 0ea790e commit e0b317b

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

kuberig-core/src/main/kotlin/eu/rigeldev/kuberig/cluster/client/ClusterClientBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ClusterClientBuilder(private val flags: KubeRigFlags,
6565
sslContextBuilder.loadKeyMaterial(keyStore, keyStorePass.toCharArray())
6666
}
6767

68-
val sslcontext : SSLContext = if (certificateAuthorityData == null) {
68+
val sslcontext : SSLContext = if (certificateAuthorityData == null || certificateAuthorityData == "") {
6969
when {
7070
flags.trustAllSSL -> sslContextBuilder
7171
.loadTrustMaterial(null, TrustAllStrategy())

kuberig-core/src/main/kotlin/eu/rigeldev/kuberig/kubectl/KubectlConfigReader.kt

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,30 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator
1010
import com.jayway.jsonpath.JsonPath
1111
import eu.rigeldev.kuberig.core.generation.yaml.ByteArrayDeserializer
1212
import eu.rigeldev.kuberig.core.generation.yaml.ByteArraySerializer
13+
import org.apache.http.conn.ssl.TrustStrategy
14+
import org.apache.http.ssl.SSLContexts
1315
import org.bouncycastle.jce.provider.BouncyCastleProvider
1416
import org.bouncycastle.openssl.PEMKeyPair
1517
import org.bouncycastle.openssl.PEMParser
1618
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
19+
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
1720
import org.json.JSONObject
1821
import org.slf4j.LoggerFactory
1922
import java.io.File
2023
import java.io.IOException
24+
import java.io.StringWriter
25+
import java.net.URL
2126
import java.security.KeyStore
2227
import java.security.Security
2328
import java.security.cert.CertificateFactory
2429
import java.security.cert.X509Certificate
2530
import java.time.Instant
2631
import java.util.*
2732
import java.util.concurrent.TimeUnit
33+
import javax.net.ssl.SSLException
34+
import javax.net.ssl.SSLSocket
35+
import javax.net.ssl.TrustManagerFactory
36+
import javax.net.ssl.X509TrustManager
2837

2938

3039
/**
@@ -182,18 +191,17 @@ class KubectlConfigReader {
182191
if (currentNode.get("name").textValue() == clusterName) {
183192
val clusterNode = currentNode.get("cluster")
184193

194+
val server = clusterNode.get("server").textValue()
195+
185196
val certificateAuthorityData = if (clusterNode.has("certificate-authority")) {
186197
File(clusterNode.get("certificate-authority").textValue()).readText()
187198
} else if (clusterNode.has("certificate-authority-data")) {
188199
String(Base64.getDecoder().decode(clusterNode.get("certificate-authority-data").textValue()))
189200
} else {
190-
""
201+
retrieveCertificateAuthorityDataFromServer(server)
191202
}
192203

193-
clusterDetail = ClusterDetail(
194-
certificateAuthorityData,
195-
clusterNode.get("server").textValue()
196-
)
204+
clusterDetail = ClusterDetail(certificateAuthorityData, server)
197205
}
198206

199207
currentIndex++
@@ -308,6 +316,66 @@ class KubectlConfigReader {
308316
}
309317
}
310318

319+
/**
320+
* Not completely happy with this part as it does not yet go up the certificate chain.
321+
*/
322+
private fun retrieveCertificateAuthorityDataFromServer(url: String): String {
323+
println("Failing back to retrieve certificate from $url, we may not get the CA but only the server certificate in this case.")
324+
325+
var result : String? = null
326+
327+
val tm = SavingTrustManager()
328+
329+
val context = SSLContexts.custom()
330+
.loadTrustMaterial(null, tm)
331+
.build()
332+
333+
val urlObject = URL(url)
334+
335+
val factory = context.socketFactory
336+
val socket = factory.createSocket(urlObject.host, urlObject.port) as SSLSocket
337+
socket.soTimeout = 10000
338+
try {
339+
socket.use {
340+
it.startHandshake()
341+
}
342+
}
343+
catch (e: SSLException) {
344+
// ignore
345+
}
346+
347+
if (tm.chain == null) {
348+
println("Could not obtain server certificate chain")
349+
} else {
350+
val certList = tm.chain!!.asList()
351+
if (certList.isNotEmpty()) {
352+
// We should improve here and actually detect what the most top level certificate is.
353+
val certificate = certList[0]
354+
println("Using ${certificate.subjectDN}")
355+
println("Issued by ${certificate.issuerDN}")
356+
val stringWriter = StringWriter()
357+
JcaPEMWriter(stringWriter).use { pemWriter ->
358+
pemWriter.writeObject(certificate)
359+
}
360+
result = stringWriter.toString()
361+
}
362+
}
363+
364+
check(result != null) { "Failed to retrieve Certificate from $url" }
365+
366+
return result
367+
}
368+
369+
}
370+
371+
class SavingTrustManager : TrustStrategy {
372+
373+
var chain: Array<out X509Certificate>? = null
374+
375+
override fun isTrusted(chain: Array<out X509Certificate>?, authType: String?): Boolean {
376+
this.chain = chain
377+
return false
378+
}
311379
}
312380

313381
data class ContextDetail(val clusterName: String, val userName: String, val namespace: String = "")

0 commit comments

Comments
 (0)