Skip to content
Draft
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
28 changes: 28 additions & 0 deletions src/KubernetesClient.Classic/CertUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace k8s
{
Expand Down Expand Up @@ -39,6 +40,33 @@ public static X509Certificate2Collection LoadPemFileCert(string file)
return certCollection;
}

/// <summary>
/// Load pem encoded certificates from text
/// </summary>
/// <param name="pemText">PEM encoded certificate text</param>
/// <returns>List of x509 instances.</returns>
public static X509Certificate2Collection LoadFromPemText(string pemText)
{
var certCollection = new X509Certificate2Collection();
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(pemText)))
{
var certs = new X509CertificateParser().ReadCertificates(stream);

// Convert BouncyCastle X509Certificates to the .NET cryptography implementation and add
// it to the certificate collection
//
foreach (Org.BouncyCastle.X509.X509Certificate cert in certs)
{
// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
string nullPassword = null;
certCollection.Add(new X509Certificate2(cert.GetEncoded(), nullPassword));
}
}

return certCollection;
}

/// <summary>
/// Generates pfx from client configuration
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/KubernetesClient/CertUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public static X509Certificate2Collection LoadPemFileCert(string file)
return certCollection;
}

/// <summary>
/// Load pem encoded certificates from text
/// </summary>
/// <param name="pemText">PEM encoded certificate text</param>
/// <returns>List of x509 instances.</returns>
public static X509Certificate2Collection LoadFromPemText(string pemText)
{
var certCollection = new X509Certificate2Collection();
certCollection.ImportFromPem(pemText);
return certCollection;
}

/// <summary>
/// Generates pfx from client configuration
/// </summary>
Expand Down
21 changes: 4 additions & 17 deletions src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace k8s
Expand Down Expand Up @@ -308,26 +307,14 @@ private void SetClusterDetails(K8SConfiguration k8SConfig, Context activeContext
if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthorityData))
{
var data = clusterDetails.ClusterEndpoint.CertificateAuthorityData;
#if NET9_0_OR_GREATER
SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificate(Convert.FromBase64String(data)));
#else
string nullPassword = null;
// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
SslCaCerts = new X509Certificate2Collection(new X509Certificate2(Convert.FromBase64String(data), nullPassword));
#endif
var pemText = Encoding.UTF8.GetString(Convert.FromBase64String(data));
SslCaCerts = CertUtils.LoadFromPemText(pemText);
}
else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority))
{
#if NET9_0_OR_GREATER
SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificateFromFile(GetFullPath(
SslCaCerts = CertUtils.LoadPemFileCert(GetFullPath(
k8SConfig,
clusterDetails.ClusterEndpoint.CertificateAuthority)));
#else
SslCaCerts = new X509Certificate2Collection(new X509Certificate2(GetFullPath(
k8SConfig,
clusterDetails.ClusterEndpoint.CertificateAuthority)));
#endif
clusterDetails.ClusterEndpoint.CertificateAuthority));
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -822,5 +822,31 @@ public void LoadInClusterNamespace()
Assert.Equal("some namespace", config.Namespace);
}
}

/// <summary>
/// Checks that multiple certificates are loaded from certificate-authority-data
/// </summary>
[Fact]
public void LoadMultipleCertificatesFromCertificateAuthorityData()
{
var fi = new FileInfo("assets/kubeconfig.multi-ca.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "multi-ca-context");

Assert.NotNull(cfg.SslCaCerts);
Assert.Equal(2, cfg.SslCaCerts.Count);
}

/// <summary>
/// Checks that multiple certificates are loaded from certificate-authority file
/// </summary>
[Fact]
public void LoadMultipleCertificatesFromCertificateAuthorityFile()
{
var fi = new FileInfo("assets/kubeconfig.multi-ca-file.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "multi-ca-file-context", useRelativePaths: false);

Assert.NotNull(cfg.SslCaCerts);
Assert.Equal(2, cfg.SslCaCerts.Count);
}
}
}
17 changes: 17 additions & 0 deletions tests/KubernetesClient.Tests/assets/kubeconfig.multi-ca-file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: assets/ca-bundle.crt
server: https://multi-ca-file-test.example.com:6443
name: multi-ca-file-cluster
contexts:
- context:
cluster: multi-ca-file-cluster
user: test-user
name: multi-ca-file-context
current-context: multi-ca-file-context
users:
- name: test-user
user:
token: test-token
17 changes: 17 additions & 0 deletions tests/KubernetesClient.Tests/assets/kubeconfig.multi-ca.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: LS0tIENvbW1lbnRzIHRvIG1ha2Ugc3VyZSB3ZSBzdGlsbCBwYXJzZSB0aGUgY2VydCBjb3JyZWN0bHkKCkludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZQoKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREVENDQWZXZ0F3SUJBZ0lDRUFFd0RRWUpLb1pJaHZjTkFRRUxCUUF3RlRFVE1CRUdBMVVFQXd3S2EzVmkKWlhKdVpYUmxjekFlRncweE9UQXpNRE14TnpBNE1EbGFGdzB5T1RBeU1qZ3hOekE0TURsYU1CWXhGREFTQmdOVgpCQU1NQzJWMFkyaGhibWN0ZFdJMU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCngxVHA3RGEzTmJqZEhtWWRZWi9HTnBDUkd2RkZhcDdFRzFwb2toZklMS1NiUHVzcWlPOXduS0RFNEFmZG4vWkUKQ1FWMFdod3RveDNqY3pCT0lSeStQNkZ2bFB5aEFwVXB5blZUd2dDaXVoVE0rdGhnT0RncGU2R1htVmxWSkd2dgpBb0x3N0NNbmRCNXNNczVISCtxQTJVMXE0VkZJL2NzcjMveWVLeldCaWszZFpWb2gwNHNJOVdUVkwrYmwvMVg1CjBkbDVxcnFrWWlEeDh5Y0FIeU9ubDhkaEpXK1JHbDY3SGlsaXVVZVNxNnZ3c2Z2OXJoM1RQOXdIVkYxUFhGSnAKV2ZYeTRXYkxtdWxkNXd4WG5RVk8yZzUxanFmcU45ZkQ4RkhJa2FlMUlrTy9QVVR1Y2xvTmxMaUZzcmFnUU9URApSVlNQK1RWM2dzaEFUQnMyTU1WWE13SURBUUFCbzJZd1pEQWRCZ05WSFE0RUZnUVUvM3c5QVIyY25FZXBXSDRFCjhhMXhMWkFuanlrd0h3WURWUjBqQkJnd0ZvQVVMcy9semN0OENHdlZkSWlxNHQ5VDRpZHU1T3d3RWdZRFZSMFQKQVFIL0JBZ3dCZ0VCL3dJQkFEQU9CZ05WSFE4QkFmOEVCQU1DQVlZd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBS3c3NDFWMXdzekl0aEhCVjhkdkN5UW95b3pCSnVBbzRJSGJpaUZtenVpUXV5c2hNY1grUXM5YStnNk9HNWQxClVid0ZmVWxxem1aUWNiY1IvSmM2d016M3dPNkhveTVwUzN3L0ZSMlVNR1IzOW85NS83WENrVElPd0NxYXU2UHcKZHBndmJuYWlxUEZQcUQzb2hkVXVWUmNYRzN2YTVBbUtUc1VuN20rbFIvOTMvcXB0dCtTVVZwNmp3bmJHY3dvQgpzM3UyWFh4NXMxTTd0cXFqM3RBRU9QQ0tsb2hTNm1RNFgzd3VsZ3BaMVhwSjBXVHZjdm9QWEV0QTU2azd2WDNhCjRFNng2NkxaQ0ZBMlpSLzVDT3Y1RDA1NUFocmloS0w4a2JBdXR4aGZBMjdTSi9NR293em1UVDdrVlFoYTNTdTMKYW9PWVpnY1V3dytTa1JTR1ZydGdNZ1E9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KClJvb3QgY2VydGlmaWNhdGUKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lKQUpwYjlpcktnMkpqTUEwR0NTcUdTSWIzRFFFQkN3VUFNQlV4RXpBUkJnTlYKQkFNTUNtdDFZbVZ5Ym1WMFpYTXdIaGNOTVRrd016QXpNREF5TVRJM1doY05Nemt3TWpJMk1EQXlNVEkzV2pBVgpNUk13RVFZRFZRUUREQXByZFdKbGNtNWxkR1Z6TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCCkNnS0NBUUVBdW9LK05hK0x5S0lpcG1mdEJ3NFkrWjE5Zzh5djZZK2RZdDZLbEJnMjRYSE5BMHYxY013dE9DREYKTWxtNHJzRDdKZDVVTzZ1Z2RrM2Z4RXZHR2hteldUWFNCUlVXY1RiU2NBTTQ5bUFMQkZrQ3ZOVFBLMnZWaGs3UAppbTJRUWw4YTV2all6OEhMS0pUYi9PKzBxK0trdHBkN1hUYVUyVTdaZWJpTFZzNWJ2TmJiM1pEdElqQUFSWTlTCmFsWjRoT3p1Vk5hU1g5TUJScVRXcTNIdUt3RGlWVFQzZGFuL0FCb1U4TmRlZFBmSWJ5WTQ4d2lRZ2pFWWI2NGcKM2dlWXBBckxRZWZmbzhmbWhVRVBSUi8xV3Jmdll2dm04c1Y4alQrcnF4SVRLSjVWbzVrcFpVcG9tRE90R1ZNUwpnR0FsZTZtY1RycWxyc0NGYzRnRlJSb0hpSDFPRFFJREFRQUJvMk13WVRBZEJnTlZIUTRFRmdRVUxzL2x6Y3Q4CkNHdlZkSWlxNHQ5VDRpZHU1T3d3SHdZRFZSMGpCQmd3Rm9BVUxzL2x6Y3Q4Q0d2VmRJaXE0dDlUNGlkdTVPd3cKRHdZRFZSMFRBUUgvQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FZWXdEUVlKS29aSWh2Y05BUUVMQlFBRApnZ0VCQUZNcTV6NE9vYUlocXgvaS9idHBWTFJuUURjcER3ZFV1ckUwaU5QejNiZ09JNVFpSWU5NW9Vd1hTRlFMCmNpRUN2T2JmTW1pVnV6K3A3TkQ1ZU5keFlSNGhscTFXMVBZY2dSUWd1c1hDQzRYZC9YQUdhTFpYekgzU0JybXAKYnM1c2Zva2hYS05jY3NuUXU1WWEzSlJrQUx4eFVKK0RjT24rdmk5Z21FQXppK25YYnlxVWpJaFNENW55Z0NsWAowYVNLYnZoVW1YeWFKcFVIMGk3ZFN4V1AzTHFDRGp0cnUvNWVqTnRCMDk3ZE5jeUY4anMzWXVrM2h3cXllZ1F4CkVMbjRjL1RLUEw5TDh2RTd0SmcvTTc4RFBBdlJDaXV3bDBIUWNhc0JFMkFYMHdkcFkwVWVYc05EeXpVZi8yV0YKZkhZNERudUJkZVZkSHRsMXlQbFhtUWtNb1FNPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://multi-ca-test.example.com:6443
name: multi-ca-cluster
contexts:
- context:
cluster: multi-ca-cluster
user: test-user
name: multi-ca-context
current-context: multi-ca-context
users:
- name: test-user
user:
token: test-token
Loading