Skip to content

Commit 65d4371

Browse files
Merge pull request #27 from brendandburns/creds
Remove the dependency on the openssl executable.
2 parents d68e940 + a5bd209 commit 65d4371

File tree

10 files changed

+194
-131
lines changed

10 files changed

+194
-131
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ language: csharp
22
sudo: required
33
matrix:
44
include:
5-
- dotnet: 1.0.4
5+
- dotnet: 2.0.0
66
mono: none
77
dist: trusty
88

99
script:
10-
- ./ci.sh
10+
- ./ci.sh

src/DotNetUtilities.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace k8s {
2+
using System;
3+
using System.Security.Cryptography;
4+
5+
using Org.BouncyCastle.Crypto.Parameters;
6+
using Org.BouncyCastle.Math;
7+
8+
// This class was derived from:
9+
// https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/DotNetUtilities.cs
10+
// Copyright (c) 2000 - 2017 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
11+
//
12+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13+
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15+
public class DotNetUtilities
16+
{
17+
public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey)
18+
{
19+
RSAParameters rp = new RSAParameters();
20+
rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
21+
rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
22+
rp.P = privKey.P.ToByteArrayUnsigned();
23+
rp.Q = privKey.Q.ToByteArrayUnsigned();
24+
rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
25+
rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
26+
rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
27+
rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
28+
return rp;
29+
}
30+
31+
private static byte[] ConvertRSAParametersField(BigInteger n, int size)
32+
{
33+
byte[] bs = n.ToByteArrayUnsigned();
34+
35+
if (bs.Length == size)
36+
return bs;
37+
38+
if (bs.Length > size)
39+
throw new ArgumentException("Specified size too small", "size");
40+
41+
byte[] padded = new byte[size];
42+
Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
43+
return padded;
44+
}
45+
}
46+
}

src/Kubernetes.Auth.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public Kubernetes(KubernetesClientConfiguration config)
4141
};
4242

4343
// set credentails for the kubernernet client
44-
this.SetCredentialsAsync(config, handler).Wait();
44+
this.SetCredentials(config, handler);
4545
this.InitializeHttpClient(handler);
4646
}
4747

@@ -53,7 +53,7 @@ public Kubernetes(KubernetesClientConfiguration config)
5353
/// <param name="config">k8s client configuration</param>
5454
/// <param name="handler">http client handler for the rest client</param>
5555
/// <returns>Task</returns>
56-
private async Task SetCredentialsAsync(KubernetesClientConfiguration config, HttpClientHandler handler)
56+
private void SetCredentials(KubernetesClientConfiguration config, HttpClientHandler handler)
5757
{
5858
// set the Credentails for token based auth
5959
if (!string.IsNullOrWhiteSpace(config.AccessToken))
@@ -70,9 +70,7 @@ private async Task SetCredentialsAsync(KubernetesClientConfiguration config, Htt
7070
(!string.IsNullOrWhiteSpace(config.ClientCertificateKey) ||
7171
!string.IsNullOrWhiteSpace(config.ClientKey)))
7272
{
73-
var pfxFilePath = await Utils.GeneratePfxAsync(config).ConfigureAwait(false);
74-
75-
var cert = new X509Certificate2(pfxFilePath, string.Empty, X509KeyStorageFlags.PersistKeySet);
73+
var cert = Utils.GeneratePfx(config);
7674
handler.ClientCertificates.Add(cert);
7775
}
7876
else

src/KubernetesClient.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>netstandard2.0</TargetFramework>
3+
<TargetFramework>netcoreapp2.0</TargetFramework>
44
</PropertyGroup>
55
<ItemGroup>
66
<Compile Remove="GlobalSuppressions.cs" />
77
</ItemGroup>
88
<ItemGroup>
9-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="1.1.0" />
9+
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.1.3" />
1010
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="3.0.3" />
1111
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
12-
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
1312
<PackageReference Include="YamlDotNet.NetCore" Version="1.0.0" />
1413
</ItemGroup>
15-
</Project>
14+
</Project>

src/Utils.cs

Lines changed: 88 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,88 @@
1-
namespace k8s
2-
{
3-
using k8s.Exceptions;
4-
using System;
5-
using System.ComponentModel;
6-
using System.Diagnostics;
7-
using System.IO;
8-
using System.Runtime.InteropServices;
9-
using System.Text;
10-
using System.Threading.Tasks;
11-
12-
public static class Utils
13-
{
14-
/// <summary>
15-
/// Encode string in base64 format.
16-
/// </summary>
17-
/// <param name="text">string to be encoded.</param>
18-
/// <returns>Encoded string.</returns>
19-
public static string Base64Encode(string text)
20-
{
21-
return Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
22-
}
23-
24-
/// <summary>
25-
/// Encode string in base64 format.
26-
/// </summary>
27-
/// <param name="text">string to be encoded.</param>
28-
/// <returns>Encoded string.</returns>
29-
public static string Base64Decode(string text)
30-
{
31-
return Encoding.UTF8.GetString(Convert.FromBase64String(text));
32-
}
33-
34-
/// <summary>
35-
/// Generates pfx from client configuration
36-
/// </summary>
37-
/// <param name="config">Kuberentes Client Configuration</param>
38-
/// <returns>Generated Pfx Path</returns>
39-
/// TODO: kabhishek8260 Remplace the method with X509 Certificate with private key(in dotnet 2.0)
40-
public static async Task<string> GeneratePfxAsync(KubernetesClientConfiguration config)
41-
{
42-
var userHomeDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
43-
Environment.GetEnvironmentVariable("USERPROFILE") :
44-
Environment.GetEnvironmentVariable("HOME");
45-
46-
var certDirPath = Path.Combine(userHomeDir, ".k8scerts");
47-
Directory.CreateDirectory(certDirPath);
48-
49-
var keyFilePath = "";
50-
var certFilePath = "";
51-
52-
var filePrefix = config.CurrentContext;
53-
var pfxFilePath = Path.Combine(certDirPath, filePrefix + "pfx");
54-
if (!string.IsNullOrWhiteSpace(config.ClientCertificateKey))
55-
{
56-
keyFilePath = Path.Combine(certDirPath, filePrefix + "key");
57-
using (FileStream fs = File.Create(keyFilePath))
58-
{
59-
byte[] info = Convert.FromBase64String(config.ClientCertificateKey);
60-
await fs.WriteAsync(info, 0, info.Length).ConfigureAwait(false);
61-
}
62-
}
63-
if (!string.IsNullOrWhiteSpace(config.ClientKey))
64-
{
65-
keyFilePath = config.ClientKey;
66-
}
67-
68-
if (!string.IsNullOrWhiteSpace(config.ClientCertificateData))
69-
{
70-
certFilePath = Path.Combine(certDirPath, filePrefix + "cert");
71-
72-
using (FileStream fs = File.Create(certFilePath))
73-
{
74-
byte[] info = Convert.FromBase64String(config.ClientCertificateData);
75-
await fs.WriteAsync(info, 0, info.Length).ConfigureAwait(false);
76-
}
77-
}
78-
if (!string.IsNullOrWhiteSpace(config.ClientCertificate))
79-
{
80-
certFilePath = config.ClientCertificate;
81-
}
82-
83-
var processStartInfo = new ProcessStartInfo
84-
{
85-
FileName = @"openssl",
86-
Arguments = $"pkcs12 -export -out {pfxFilePath} -inkey {keyFilePath} -in {certFilePath} -passout pass:",
87-
CreateNoWindow = true,
88-
RedirectStandardError = true,
89-
RedirectStandardOutput = true
90-
};
91-
92-
try
93-
{
94-
using (Process process = Process.Start(processStartInfo))
95-
{
96-
process.WaitForExit();
97-
if (process.ExitCode != 0)
98-
{
99-
throw new KubernetesClientException($"Failed to generate pfx file with openssl. ExitCode = {process.ExitCode}.");
100-
}
101-
}
102-
}
103-
catch (Win32Exception e)
104-
{
105-
throw new KubernetesClientException("Failed to generate pfx file with openssl.", e);
106-
}
107-
108-
return pfxFilePath;
109-
}
110-
}
111-
}
1+
namespace k8s
2+
{
3+
using System;
4+
using System.Diagnostics;
5+
using System.Globalization;
6+
using System.IO;
7+
using System.Runtime.InteropServices;
8+
using System.Security.Cryptography;
9+
using System.Security.Cryptography.X509Certificates;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
13+
using Org.BouncyCastle.Crypto;
14+
using Org.BouncyCastle.Crypto.Parameters;
15+
using Org.BouncyCastle.Security;
16+
using Org.BouncyCastle.OpenSsl;
17+
18+
public static class Utils
19+
{
20+
/// <summary>
21+
/// Encode string in base64 format.
22+
/// </summary>
23+
/// <param name="text">string to be encoded.</param>
24+
/// <returns>Encoded string.</returns>
25+
public static string Base64Encode(string text)
26+
{
27+
return Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
28+
}
29+
30+
/// <summary>
31+
/// Encode string in base64 format.
32+
/// </summary>
33+
/// <param name="text">string to be encoded.</param>
34+
/// <returns>Encoded string.</returns>
35+
public static string Base64Decode(string text)
36+
{
37+
return Encoding.UTF8.GetString(Convert.FromBase64String(text));
38+
}
39+
40+
/// <summary>
41+
/// Generates pfx from client configuration
42+
/// </summary>
43+
/// <param name="config">Kuberentes Client Configuration</param>
44+
/// <returns>Generated Pfx Path</returns>
45+
public static X509Certificate2 GeneratePfx(KubernetesClientConfiguration config)
46+
{
47+
var keyData = new byte[]{};
48+
var certData = new byte[]{};
49+
50+
var filePrefix = config.CurrentContext;
51+
if (!string.IsNullOrWhiteSpace(config.ClientCertificateKey))
52+
{
53+
keyData = Convert.FromBase64String(config.ClientCertificateKey);
54+
}
55+
if (!string.IsNullOrWhiteSpace(config.ClientKey))
56+
{
57+
keyData = File.ReadAllBytes(config.ClientKey);
58+
}
59+
60+
if (!string.IsNullOrWhiteSpace(config.ClientCertificateData))
61+
{
62+
certData = Convert.FromBase64String(config.ClientCertificateData);
63+
}
64+
if (!string.IsNullOrWhiteSpace(config.ClientCertificate))
65+
{
66+
certData = File.ReadAllBytes(config.ClientCertificate);
67+
}
68+
69+
var cert = new X509Certificate2(certData);
70+
return addPrivateKey(cert, keyData);
71+
}
72+
73+
public static X509Certificate2 addPrivateKey(X509Certificate2 cert, byte[] keyData)
74+
{
75+
using (var reader = new StreamReader(new MemoryStream(keyData)))
76+
{
77+
var obj = new PemReader(reader).ReadObject();
78+
if (obj is AsymmetricCipherKeyPair) {
79+
var cipherKey = (AsymmetricCipherKeyPair)obj;
80+
obj = cipherKey.Private;
81+
}
82+
var rsaKeyParams = (RsaPrivateCrtKeyParameters)obj;
83+
var rsaKey = RSA.Create(DotNetUtilities.ToRSAParameters(rsaKeyParams));
84+
return cert.CopyWithPrivateKey(rsaKey);
85+
}
86+
}
87+
}
88+
}

tests/UtilTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Xunit;
3+
using k8s;
4+
using System.IO;
5+
6+
namespace k8s.Tests
7+
{
8+
public class UtilsTests
9+
{
10+
/// <summary>
11+
/// This file contains a sample kubeconfig file
12+
/// </summary>
13+
private static readonly string kubeConfigFileName = "assets/kubeconfig.yml";
14+
15+
/// <summary>
16+
/// Checks that a certificate can be loaded from files.
17+
/// </summary>
18+
[Fact]
19+
public void LoadFromFiles()
20+
{
21+
var fi = new FileInfo(kubeConfigFileName);
22+
var cfg = new KubernetesClientConfiguration(fi, "federal-context");
23+
24+
// Just validate that this doesn't throw and private key is non-null
25+
var cert = Utils.GeneratePfx(cfg);
26+
Assert.NotNull(cert.PrivateKey);
27+
}
28+
29+
/// <summary>
30+
/// Checks that a certificate can be loaded from inline.
31+
/// </summary>
32+
[Fact]
33+
public void LoadFromInlineData()
34+
{
35+
var fi = new FileInfo(kubeConfigFileName);
36+
var cfg = new KubernetesClientConfiguration(fi, "victorian-context");
37+
38+
// Just validate that this doesn't throw and private key is non-null
39+
var cert = Utils.GeneratePfx(cfg);
40+
Assert.NotNull(cert.PrivateKey);
41+
}
42+
}
43+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlEUkRDQ0FpeWdBd0lCQWdJSUVIVm8zVzZXcnJjd0RRWUpLb1pJaHZjTkFRRUxCUUF3RXpFUk1BOEdBMVVFQXd3SVlXTnphemh6DQpZMkV3SGhjTk1UY3dOREU1TURBd01EQXdXaGNOTVRrd05ERTVNREF3TURBd1dqQVZNUk13RVFZRFZRUUREQXByZFdKbFkyOXVabWxuDQpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXVEZ0ttVUxsU0ZSTStCN0pNMWMxUEJqS25mWlZpL1d1DQpSUks2TE9xNHpzL1UxanUyN21TbE9HbWJxRWVYTlpCRVFCblYvVGptQXpqM0Zwd2VKbW5PdDY0TTFIeXZRUW8yTC9LS2JlOVlPbUxNDQpRWXZyeE9QeFRnZHVJeDYzTGxKSUJRZERqdVFhWUhBc0FqTllhbTB3UmQwZTNhVkUxeEJnVDA4QkVWZnVwZmUrUnpXUWhVemJXYkhDDQpydmU2b2s4aHQzRk9LYjNxRzhyR3UrYzN4bGM1MXNVa2ovMGhMK0xrZVBWOTBRYVVCRjF6czlDMVZGaVhoT1Z6WlRjQ0lWQThSTDNvDQpxRTdGUEwvczBNc0RsdkRoNkZ3ZFkvUWhwSUtRMkdKTHQ0ZGRobk5yYW5GbS9pM1RzQ3FJSWxieHl5TVRsT09YYytOUG9mZW9EUUcvDQpvQldjTVFJREFRQUJvNEdaTUlHV01FSUdBMVVkSXdRN01EbUFGSmcrY0p4ZHpaUzNabzdjajFjQXJFWjNxREh1b1Jla0ZUQVRNUkV3DQpEd1lEVlFRRERBaGhZM05yT0hOallZSUlGTjZKejlmRStOVXdIUVlEVlIwT0JCWUVGTmJxZURyeHRvbWZFYnd5bmFUaXp4WXVmeVRXDQpNQXdHQTFVZEV3RUIvd1FDTUFBd0RnWURWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWRKUVFNTUFvR0NDc0dBUVVGQndNQ01BMEdDU3FHDQpTSWIzRFFFQkN3VUFBNElCQVFCYkFKdjJDRzRnTDJtNlpiQmJKSEtqMVF5MGtmbUFJdWgyZHNISU5vUHZ4V0lqY3VXeHZlbUpRcndUDQpuUEZPeWFMY3VtNmVmcGZhWG04T2hxNWxjT3NldUtYaDVya2ZuRUp1VmNWYk9UNXY2aUU3TEY5eGMrelVlTkZDWjA1a1QwbU52eW9lDQpIVmhkYlljeG8yOXVwbGVqa3RqZXd0eGZBZjM2a25hQytOVGE3dVZyNVQ0aTd4MUNQMGkvcUY5ZVgwVHJqZ0diM09MTHVwcVhMSzd5DQpUWUllK3NCajNjOGZRNit6RUJDVFNib05DRlRldXpjOTMxclltc1VyZGtIdEp2SkI2aSthMG5iRjB4MnRYY3ZRbVJ6aUwzTGR0UG84DQptSWh0bDRRWXBQZG5Qdk1RWk1MMlZ4T0FqN3V0RnhNMW44OEpKV0NaTkNyUUdYZjBLSDFpdHhwNg0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ0K
1+
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlEUkRDQ0FpeWdBd0lCQWdJSUxzVmNxZ0pmOWdZd0RRWUpLb1pJaHZjTkFRRUxCUUF3RXpFUk1BOEdBMVVFQXd3SVlXTnphemh6DQpZMkV3SGhjTk1UY3dPVEl3TURBd01EQXdXaGNOTVRrd09USXdNREF3TURBd1dqQVZNUk13RVFZRFZRUUREQXByZFdKbFkyOXVabWxuDQpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJQVjB0Rmp3ZkxVL0p6M3RaUXRRblNSRG5aMGpLTGtjDQpRcnJ2a2pwbEhpVkFVOVNkRG9nT0VneUVWQjJoMU0xSWNYSVZYM2lCTGFHS04rSTl2dGZQSFQ2QmViTzVKNzFaOHRGMytJU1pNRzBLDQpzSWwzQS94VEJWKzlwRHVEU3pXTGNVTVoxbnhWV1Bma0paRkVTQTNCakxFT1NGWWYySUF4V0g2cjJDRHp0SDMySzVLbUNaLzR1NFFsDQpCT204YWoxVkFUbUZvK3BJaTNmUXVKcm1TblBSRGRlSW5HVGYwaEYyQ3oybzYrNU1TSzAxbURuaERyZVF5eTJHNXZXRnlWUjNxZ3pGDQpOMHFyaUEvSFRGcklSazdtbFZtaEpPM3A5cU0wRGdJc29XdHg0Ukdqc1lycTBmOTBnREhna1YvS0E3UEhZQlhheVVnQjhLcTUwS2xHDQpxczRyYVFJREFRQUJvNEdaTUlHV01FSUdBMVVkSXdRN01EbUFGS0FXL3JnbWJ4YUtWcUZoaFl4bEl5TEhQbEkyb1Jla0ZUQVRNUkV3DQpEd1lEVlFRRERBaGhZM05yT0hOallZSUlSbWp6U3h0U2d5TXdIUVlEVlIwT0JCWUVGSk5mZDNaZlJZZnlQdGJZbE1NUDYvKzcrU3dIDQpNQXdHQTFVZEV3RUIvd1FDTUFBd0RnWURWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWRKUVFNTUFvR0NDc0dBUVVGQndNQ01BMEdDU3FHDQpTSWIzRFFFQkN3VUFBNElCQVFCRVd0U1JMU0lTODhFUWpRdWp4bitQdmQ0OVNxSkdTUmRFNnlraUkwcXZ5RmY1c1lnV2FlYTdaME43DQpuK3k3SmlQQXQ1MEFkdGdvYTF1bWV4bG9VZE02WDJQYTB4RUpDaklGOFArcjhPOU41U0N5NVRXYTd0VTFrWkdoZHJFOERMS2RJbXk4DQpqblhnUlBjUHVlVnRkam50TFdUMGpETDI5YVg3ZUhLcllTMWQ2dkUyWXE3djZVNFNKN2JLU0NGbFNjS1h0VjhxUFVteXFlbXd5WHNBDQpVdEtBelRKS0l0UGw3eG5icXduVm5tY3Q0a0Q1VHZBUEFTdmZGTkR0eEU0WTU1ZHNYTERSVEc3NU5VK044bi94ZnBzcnlBZkQrUGZ6DQpNR05OWVhLb0hwbm93R1Z4V3UxRHZNY2kxQTl4STF6VnFsS2FpVTZSNm9qWmx6Q2xsVU54QUhzZA0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ0K

0 commit comments

Comments
 (0)