Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ data class KubeConfigNamedUser(
fun getByName(userName: String, config: KubeConfig?): KubeConfigNamedUser? {
return (config?.users ?: emptyList<Any>())
.mapNotNull {
it as? Map<String, Any>
it as? Map<*, *>
}
.firstOrNull { user ->
userName == user["name"]
Expand Down Expand Up @@ -224,11 +224,6 @@ data class KubeConfigNamedUser(
fun getUserTokenForCluster(clusterName: String, kubeConfig: KubeConfig): String? =
getUserForCluster(clusterName, kubeConfig)?.token

fun getUserClientCertForCluster(clusterName: String, kubeConfig: KubeConfig): Pair<CertificateSource?, CertificateSource?>? {
val user = getUserForCluster(clusterName, kubeConfig) ?: return null
return Pair(user.clientCertificate, user.clientKey)
}

fun isTokenAuth(kubeConfig: KubeConfig): Boolean {
return kubeConfig.credentials?.containsKey(KubeConfig.CRED_TOKEN_KEY) == true
}
Expand Down Expand Up @@ -275,6 +270,20 @@ data class KubeConfigUser(
password = map["password"] as? String
)
}

fun tokenOnly(token: String): KubeConfigUser =
KubeConfigUser(
token = token.trim(),
clientCertificate = null,
clientKey = null
)

fun clientCertOnly(cert: CertificateSource, key: CertificateSource): KubeConfigUser =
KubeConfigUser(
token = null,
clientCertificate = cert,
clientKey = key
)
}

fun toMap(): MutableMap<String, Any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package com.redhat.devtools.gateway.kubeconfig
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.util.text.UniqueNameGenerator
import com.redhat.devtools.gateway.auth.tls.CertificateSource
import com.redhat.devtools.gateway.auth.tls.PemUtils
import com.redhat.devtools.gateway.kubeconfig.KubeConfigUtils.path
import com.redhat.devtools.gateway.openshift.Utils
import io.kubernetes.client.persister.ConfigPersister
Expand All @@ -30,6 +31,12 @@ abstract class KubeConfigUpdate private constructor(
) {

companion object {
private val USER = arrayOf("user")
private val USER_TOKEN = USER + "token"
private val USER_CLIENT_CERT = USER + "client-certificate"
private val USER_CLIENT_KEY = USER + "client-key"
private val USER_CLIENT_CERT_DATA = USER + "client-certificate-data"
private val USER_CLIENT_KEY_DATA = USER + "client-key-data"
fun create(clusterName: String, clusterUrl: String, token: String): KubeConfigUpdate {
val allConfigs = KubeConfigUtils.getAllConfigs(KubeConfigUtils.getAllConfigFiles())
val context = KubeConfigNamedContext.getByClusterName(clusterName, allConfigs)
Expand Down Expand Up @@ -114,6 +121,29 @@ abstract class KubeConfigUpdate private constructor(
)
}

protected fun setTokenAuthentication(
namedUser: Any,
token: String
) {
Utils.setValue(namedUser, token, USER_TOKEN)
Utils.removeValue(namedUser, USER_CLIENT_CERT)
Utils.removeValue(namedUser, USER_CLIENT_KEY)
Utils.removeValue(namedUser, USER_CLIENT_CERT_DATA)
Utils.removeValue(namedUser, USER_CLIENT_KEY_DATA)
}

protected fun setClientCertificateAuthentication(
namedUser: Any,
clientCertPem: String,
clientKeyPem: String
) {
Utils.setValue(namedUser, PemUtils.toBase64(clientCertPem), USER_CLIENT_CERT_DATA)
Utils.setValue(namedUser, PemUtils.toBase64(clientKeyPem), USER_CLIENT_KEY_DATA)
Utils.removeValue(namedUser, USER_CLIENT_CERT)
Utils.removeValue(namedUser, USER_CLIENT_KEY)
Utils.removeValue(namedUser, USER_TOKEN)
}

protected data class ContextEntries(
val users: ArrayList<Any?>,
val clusters: ArrayList<Any?>,
Expand Down Expand Up @@ -210,17 +240,10 @@ abstract class KubeConfigUpdate private constructor(
config.users?.find { user ->
username == Utils.getValue(user, arrayOf("name"))
}?.apply {
Utils.setValue(this, token, arrayOf("user", "token"))

removeClientCerts(this)
setTokenAuthentication(this, token)
}
}

private fun removeClientCerts(namedUser: Any) {
Utils.removeValue(namedUser, arrayOf("user", "client-certificate-data"))
Utils.removeValue(namedUser, arrayOf("user", "client-key-data"))
}

}

class CreateContext(
Expand All @@ -234,7 +257,7 @@ abstract class KubeConfigUpdate private constructor(
override fun apply() {
val config = allConfigs.firstOrNull() ?: return
val user = KubeConfigNamedUser(
KubeConfigUser(authToken),
KubeConfigUser.tokenOnly(authToken),
uniqueUserName(allConfigs)
)
val entries = createContext(user, config.users, config.clusters, config.contexts)
Expand Down Expand Up @@ -268,16 +291,9 @@ abstract class KubeConfigUpdate private constructor(
config.users?.find { user ->
username == Utils.getValue(user, arrayOf("name"))
}?.apply {
Utils.setValue(this, clientCertPem, arrayOf("user", "client-certificate-data"))
Utils.setValue(this, clientKeyPem, arrayOf("user", "client-key-data"))

removeToken(this)
setClientCertificateAuthentication(this,clientCertPem,clientKeyPem)
}
}

private fun removeToken(namedUser: Any) {
Utils.removeValue(namedUser, arrayOf("user", "token"))
}
}

class CreateContextWithClientCert(
Expand All @@ -292,10 +308,9 @@ abstract class KubeConfigUpdate private constructor(
override fun apply() {
val config = allConfigs.firstOrNull() ?: return
val user = KubeConfigNamedUser(
KubeConfigUser(
token = null,
clientCertificate = CertificateSource.fromData(clientCertPem),
clientKey = CertificateSource.fromData(clientKeyPem)
KubeConfigUser.clientCertOnly(
CertificateSource.fromData(clientCertPem),
CertificateSource.fromData(clientKeyPem)
),
uniqueUserName(allConfigs)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ object KubeConfigTestHelpers {
}

/**
* Creates a user map for testing with both token and client certificates
* Creates a user map for testing with token, data-path, and file-path client certificate fields.
* Includes both file-path and data-path fields so tests can assert cleanup of each.
*/
fun createUserMapWithClientCert(
name: String,
Expand All @@ -128,6 +129,8 @@ object KubeConfigTestHelpers {
"name" to name,
"user" to mutableMapOf(
"token" to token,
"client-certificate" to "/path/to/cert",
"client-key" to "/path/to/key",
"client-certificate-data" to PemUtils.toBase64(clientCertPem),
"client-key-data" to PemUtils.toBase64(clientKeyPem)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,55 @@ class KubeConfigUpdateTest {
}
}

@Test
fun `#apply UpdateClientCert removes client-certificate and client-key file-path fields when setting client cert`() {
// given
val data = UpdateClientCertWithTokenTestData()
val existingUserMap = KubeConfigTestHelpers.createUserMapWithClientCert(
data.userName,
data.oldToken,
data.oldClientCertPem,
data.oldClientKeyPem
)
val existingClusterMap = KubeConfigTestHelpers.createClusterMap(data.clusterName, data.clusterUrl)
val existingContextMap = KubeConfigTestHelpers.createContextMap(data.contextName, data.clusterName, data.userName)
val config = KubeConfigTestHelpers.createMockKubeConfig(kubeConfigPath, existingUserMap, existingClusterMap, existingContextMap)
val allConfigs = listOf(config)
val mockContext = setupUpdateExistingContextMocks(data.clusterName, data.userName, data.contextName, allConfigs, config, null)

val update = KubeConfigUpdate.UpdateClientCert(
data.clusterName,
data.clusterUrl,
data.newClientCertPem,
data.newClientKeyPem,
mockContext,
allConfigs,
testPersisterFactory,
)

// when
update.apply()

// then - file-path cert fields should be removed, only data-path fields should remain
verify {
persisterFor(kubeConfigPath).save(
any(),
any(),
match { users ->
assertThat(users).hasSize(1)
verifyUserWithClientCert(
users[0] as Map<*, *>,
data.userName,
data.newClientCertPem,
data.newClientKeyPem
)
},
any(),
any(),
)
}
}

@Test
fun `#apply UpdateToken sets current context on primary config when no config has current context`() {
// given
Expand Down Expand Up @@ -575,6 +624,42 @@ class KubeConfigUpdateTest {
}
}

@Test
fun `#apply UpdateToken removes client-certificate and client-key file-path fields when setting a new token`() {
// given
val data = UpdateTokenWithClientCertTestData()
val existingUserMap = KubeConfigTestHelpers.createUserMapWithClientCert(
data.userName,
data.oldToken,
data.clientCertPem,
data.clientKeyPem
)
val existingClusterMap = KubeConfigTestHelpers.createClusterMap(data.clusterName, data.clusterUrl)
val existingContextMap = KubeConfigTestHelpers.createContextMap(data.contextName, data.clusterName, data.userName)
val config = KubeConfigTestHelpers.createMockKubeConfig(kubeConfigPath, existingUserMap, existingClusterMap, existingContextMap)
val allConfigs = listOf(config)
val mockContext = setupUpdateExistingContextMocks(data.clusterName, data.userName, data.contextName, allConfigs, config, null)

val update = KubeConfigUpdate.UpdateToken(data.clusterName, data.clusterUrl, data.newToken, mockContext, allConfigs, testPersisterFactory)

// when
update.apply()

// then - both file-path and data-path cert fields should be removed, only token remains
verify {
persisterFor(kubeConfigPath).save(
any(),
any(),
match { users ->
assertThat(users).hasSize(1)
verifyUserWithTokenOnly(users[0] as Map<*, *>, data.userName, data.newToken)
},
any(),
any(),
)
}
}

@Test
fun `#apply generates unique user name if user name already exists`() {
// given
Expand Down Expand Up @@ -1076,6 +1161,24 @@ class KubeConfigUpdateTest {
assertThat(cert).isEqualTo(PemUtils.toBase64(expectedCertPem))
assertThat(key).isEqualTo(PemUtils.toBase64(expectedKeyPem))
assertThat(Utils.getValue(user, arrayOf("user", "token"))).isNull()
assertThat(Utils.getValue(user, arrayOf("user", "client-certificate"))).isNull()
assertThat(Utils.getValue(user, arrayOf("user", "client-key"))).isNull()
return true
}

private fun verifyUserWithTokenOnly(
user: Map<*, *>,
expectedName: String,
expectedToken: String
): Boolean {
val name = Utils.getValue(user, arrayOf("name")) as String
val token = Utils.getValue(user, arrayOf("user", "token")) as String
assertThat(name).isEqualTo(expectedName)
assertThat(token).isEqualTo(expectedToken)
assertThat(Utils.getValue(user, arrayOf("user", "client-certificate"))).isNull()
assertThat(Utils.getValue(user, arrayOf("user", "client-key"))).isNull()
assertThat(Utils.getValue(user, arrayOf("user", "client-certificate-data"))).isNull()
assertThat(Utils.getValue(user, arrayOf("user", "client-key-data"))).isNull()
return true
}

Expand Down
Loading