From 1269c410f65039f3a6907aa42f538c67664c8b7d Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Thu, 20 Feb 2020 11:20:09 +0200 Subject: [PATCH 01/12] Adding the user credentials exec abillity new file: src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs new file: src/KubernetesClient/KubeConfigModels/ExternalExecution.cs modified: src/KubernetesClient/KubeConfigModels/UserCredentials.cs modified: src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs --- .../ExecCredentialResponse.cs | 15 ++ .../KubeConfigModels/ExternalExecution.cs | 26 +++ .../KubeConfigModels/UserCredentials.cs | 6 + ...ubernetesClientConfiguration.ConfigFile.cs | 173 +++++++++++++----- 4 files changed, 173 insertions(+), 47 deletions(-) create mode 100644 src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs create mode 100644 src/KubernetesClient/KubeConfigModels/ExternalExecution.cs diff --git a/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs new file mode 100644 index 000000000..5f10cff32 --- /dev/null +++ b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace k8s.KubeConfigModels +{ + public class ExecCredentialResponse + { + [JsonProperty("apiVersion")] + public string ApiVersion { get; set; } + [JsonProperty("kind")] + public string Kind { get; set; } + [JsonProperty("status")] + public Dictionary Status { get; set; } + } +} diff --git a/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs b/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs new file mode 100644 index 000000000..67a116bbe --- /dev/null +++ b/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using YamlDotNet.Serialization; + +namespace k8s.KubeConfigModels +{ + public class ExternalExecution + { + [YamlMember(Alias = "apiVersion")] + public string ApiVersion { get; set; } + /// + /// The command to execute. Required. + /// + [YamlMember(Alias = "command")] + public string Command { get; set; } + /// + /// Environment variables to set when executing the plugin. Optional. + /// + [YamlMember(Alias = "env")] + public Dictionary EnvironmentVariables { get; set; } + /// + /// Arguments to pass when executing the plugin. Optional. + /// + [YamlMember(Alias = "args")] + public List Arguments { get; set; } + } +} diff --git a/src/KubernetesClient/KubeConfigModels/UserCredentials.cs b/src/KubernetesClient/KubeConfigModels/UserCredentials.cs index c34f37467..7ab580e58 100644 --- a/src/KubernetesClient/KubeConfigModels/UserCredentials.cs +++ b/src/KubernetesClient/KubeConfigModels/UserCredentials.cs @@ -80,5 +80,11 @@ public class UserCredentials /// [YamlMember(Alias = "extensions")] public IDictionary Extensions { get; set; } + + /// + /// Gets or sets external command and its arguments to receive user credentials + /// + [YamlMember(Alias = "exec")] + public ExternalExecution ExternalExecution { get; set; } } } diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index a1e9b2d31..e712fd423 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using k8s.Exceptions; using k8s.KubeConfigModels; +using Newtonsoft.Json; namespace k8s { @@ -28,15 +31,19 @@ public partial class KubernetesClientConfiguration /// /// Initializes a new instance of the from config file /// - public static KubernetesClientConfiguration BuildDefaultConfig() { + public static KubernetesClientConfiguration BuildDefaultConfig() + { var kubeconfig = Environment.GetEnvironmentVariable("KUBECONFIG"); - if (kubeconfig != null) { + if (kubeconfig != null) + { return BuildConfigFromConfigFile(kubeconfigPath: kubeconfig); } - if (File.Exists(KubeConfigDefaultLocation)) { + if (File.Exists(KubeConfigDefaultLocation)) + { return BuildConfigFromConfigFile(kubeconfigPath: KubeConfigDefaultLocation); } - if (IsInCluster()) { + if (IsInCluster()) + { return InClusterConfig(); } var config = new KubernetesClientConfiguration(); @@ -214,7 +221,7 @@ private void SetClusterDetails(K8SConfiguration k8SConfig, Context activeContext Host = clusterDetails.ClusterEndpoint.Server; SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify; - if(!Uri.TryCreate(Host, UriKind.Absolute, out Uri uri)) + if (!Uri.TryCreate(Host, UriKind.Absolute, out Uri uri)) { throw new KubeConfigException($"Bad server host URL `{Host}` (cannot be parsed)"); } @@ -294,65 +301,79 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) switch (userDetails.UserCredentials.AuthProvider.Name) { case "azure": - { - var config = userDetails.UserCredentials.AuthProvider.Config; - if (config.ContainsKey("expires-on")) { - var expiresOn = Int32.Parse(config["expires-on"]); - DateTimeOffset expires; - #if NET452 + var config = userDetails.UserCredentials.AuthProvider.Config; + if (config.ContainsKey("expires-on")) + { + var expiresOn = Int32.Parse(config["expires-on"]); + DateTimeOffset expires; +#if NET452 var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); expires = epoch.AddSeconds(expiresOn); - #else - expires = DateTimeOffset.FromUnixTimeSeconds(expiresOn); - #endif +#else + expires = DateTimeOffset.FromUnixTimeSeconds(expiresOn); +#endif - if (DateTimeOffset.Compare(expires - , DateTimeOffset.Now) - <= 0) - { - var tenantId = config["tenant-id"]; - var clientId = config["client-id"]; - var apiServerId = config["apiserver-id"]; - var refresh = config["refresh-token"]; - var newToken = RenewAzureToken(tenantId - , clientId - , apiServerId - , refresh); - config["access-token"] = newToken; + if (DateTimeOffset.Compare(expires + , DateTimeOffset.Now) + <= 0) + { + var tenantId = config["tenant-id"]; + var clientId = config["client-id"]; + var apiServerId = config["apiserver-id"]; + var refresh = config["refresh-token"]; + var newToken = RenewAzureToken(tenantId + , clientId + , apiServerId + , refresh); + config["access-token"] = newToken; + } } - } - AccessToken = config["access-token"]; - userCredentialsFound = true; - break; - } + AccessToken = config["access-token"]; + userCredentialsFound = true; + break; + } case "gcp": - { - var config = userDetails.UserCredentials.AuthProvider.Config; - const string keyExpire = "expiry"; - if (config.ContainsKey(keyExpire)) { - if (DateTimeOffset.TryParse(config[keyExpire] - , out DateTimeOffset expires)) + var config = userDetails.UserCredentials.AuthProvider.Config; + const string keyExpire = "expiry"; + if (config.ContainsKey(keyExpire)) { - if (DateTimeOffset.Compare(expires - , DateTimeOffset.Now) - <= 0) + if (DateTimeOffset.TryParse(config[keyExpire] + , out DateTimeOffset expires)) { - throw new KubeConfigException("Refresh not supported."); + if (DateTimeOffset.Compare(expires + , DateTimeOffset.Now) + <= 0) + { + throw new KubeConfigException("Refresh not supported."); + } } } - } - AccessToken = config["access-token"]; - userCredentialsFound = true; - break; - } + AccessToken = config["access-token"]; + userCredentialsFound = true; + break; + } } } } + if (userDetails.UserCredentials.ExternalExecution != null) + { + if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command)) + throw new KubeConfigException( + "External command execution to receive user credentials must include a command to execute"); + if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.ApiVersion)) + throw new KubeConfigException("External command execution missing ApiVersion key"); + + var token = ExecuteExternalCommand(userDetails.UserCredentials.ExternalExecution); + AccessToken = token; + + userCredentialsFound = true; + } + if (!userCredentialsFound) { throw new KubeConfigException( @@ -365,6 +386,64 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap throw new KubeConfigException("Refresh not supported."); } + /// + /// Implementation of the proposal for out-of-tree client + /// authentication providers as described here -- + /// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md + /// Took inspiration from python exec_provider.py -- + /// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py + /// + /// The external command execution configuration + /// The token received from the external commmand execution + public static string ExecuteExternalCommand(ExternalExecution config) + { + var execInfo = new Dictionary + { + {"apiVersion", config.ApiVersion}, + {"kind", "ExecCredential"}, + {"spec", new Dictionary + { + {"interactive", Environment.UserInteractive} + }} + }; + + var process = new Process(); + + process.StartInfo.Environment.Add("KUBERNETES_EXEC_INFO", + JsonConvert.SerializeObject(execInfo)); + foreach (var configEnvironmentVariableKey in config.EnvironmentVariables.Keys) + process.StartInfo.Environment.Add(configEnvironmentVariableKey, + config.EnvironmentVariables[configEnvironmentVariableKey]); + + process.StartInfo.FileName = config.Command; + process.StartInfo.Arguments = string.Join(" ", config.Arguments); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + process.Start(); + process.WaitForExit(); + + var stderr = process.StandardOutput.ReadToEnd(); + if (string.IsNullOrWhiteSpace(stderr) == false) + throw new KubeConfigException($"external exec failed due to: {stderr}"); + + var stdout = process.StandardOutput.ReadToEnd(); + try + { + var responseObject = JsonConvert.DeserializeObject(stdout); + if (responseObject.ApiVersion != config.ApiVersion) + throw new KubeConfigException($"external exec failed because api version {responseObject.ApiVersion} does not match {config.ApiVersion}"); + return responseObject.Status["token"]; + } + catch (JsonSerializationException ex) + { + throw new KubeConfigException($"external exec failed due to failed deserialization process: {ex}"); + } + + } + /// /// Loads entire Kube Config from default or explicit file path /// From fbe92c1b5419475042e996777361b44d4efb6912 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Thu, 20 Feb 2020 14:31:50 +0200 Subject: [PATCH 02/12] Fixed a few issues with the process spawning and some null references issues --- ...ubernetesClientConfiguration.ConfigFile.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index e712fd423..04d9fe95d 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -400,7 +400,7 @@ public static string ExecuteExternalCommand(ExternalExecution config) var execInfo = new Dictionary { {"apiVersion", config.ApiVersion}, - {"kind", "ExecCredential"}, + {"kind", "ExecCredentials"}, {"spec", new Dictionary { {"interactive", Environment.UserInteractive} @@ -411,36 +411,52 @@ public static string ExecuteExternalCommand(ExternalExecution config) process.StartInfo.Environment.Add("KUBERNETES_EXEC_INFO", JsonConvert.SerializeObject(execInfo)); - foreach (var configEnvironmentVariableKey in config.EnvironmentVariables.Keys) - process.StartInfo.Environment.Add(configEnvironmentVariableKey, - config.EnvironmentVariables[configEnvironmentVariableKey]); + + if (config.EnvironmentVariables != null) + foreach (var configEnvironmentVariableKey in config.EnvironmentVariables.Keys) + process.StartInfo.Environment.Add(key: configEnvironmentVariableKey, + value: config.EnvironmentVariables[configEnvironmentVariableKey]); process.StartInfo.FileName = config.Command; process.StartInfo.Arguments = string.Join(" ", config.Arguments); process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - process.Start(); - process.WaitForExit(); + try + { + process.Start(); + } + catch (Exception ex) + { + throw new KubeConfigException($"external exec failed due to: {ex.Message}"); + } + var stdout = process.StandardOutput.ReadToEnd(); var stderr = process.StandardOutput.ReadToEnd(); if (string.IsNullOrWhiteSpace(stderr) == false) throw new KubeConfigException($"external exec failed due to: {stderr}"); - var stdout = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + try { var responseObject = JsonConvert.DeserializeObject(stdout); - if (responseObject.ApiVersion != config.ApiVersion) - throw new KubeConfigException($"external exec failed because api version {responseObject.ApiVersion} does not match {config.ApiVersion}"); + if (responseObject == null || responseObject.ApiVersion != config.ApiVersion) + throw new KubeConfigException( + $"external exec failed because api version {responseObject.ApiVersion} does not match {config.ApiVersion}"); return responseObject.Status["token"]; } catch (JsonSerializationException ex) { throw new KubeConfigException($"external exec failed due to failed deserialization process: {ex}"); } + catch (Exception ex) + { + throw new KubeConfigException($"external exec failed due to uncaught exception: {ex}"); + } + + } From 887fa41989bc6ce12b7d37b095a65aa553f3dd04 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Thu, 20 Feb 2020 14:50:58 +0200 Subject: [PATCH 03/12] Removed unused import that caused the build to fail (Mail) --- src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index 04d9fe95d..0329ab3c7 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; From 8eb021b6f295a15f3947264181d97bcd0a35f73f Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Thu, 20 Feb 2020 16:35:28 +0200 Subject: [PATCH 04/12] Added preprocessor directive that will disable out-of-tree client authentication in case it is not a asp.net core app --- .../KubernetesClientConfiguration.ConfigFile.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index 0329ab3c7..2299a5add 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -1,6 +1,9 @@ using System; +#if NETCOREAPP +using Newtonsoft.Json; using System.Collections.Generic; using System.Diagnostics; +#endif using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -8,7 +11,7 @@ using System.Threading.Tasks; using k8s.Exceptions; using k8s.KubeConfigModels; -using Newtonsoft.Json; + namespace k8s { @@ -359,6 +362,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) } } +#if NETCOREAPP if (userDetails.UserCredentials.ExternalExecution != null) { if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command)) @@ -372,6 +376,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) userCredentialsFound = true; } +#endif if (!userCredentialsFound) { @@ -385,6 +390,7 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap throw new KubeConfigException("Refresh not supported."); } +#if NETCOREAPP /// /// Implementation of the proposal for out-of-tree client /// authentication providers as described here -- @@ -458,6 +464,7 @@ public static string ExecuteExternalCommand(ExternalExecution config) } +#endif /// /// Loads entire Kube Config from default or explicit file path From 8ac68da22c8838e027185ce5bb3e6f0ec630b06b Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Mon, 24 Feb 2020 20:16:56 +0200 Subject: [PATCH 05/12] Added tests to the new external execution (out-of-tree client authentication) extension --- ...ubernetesClientConfiguration.ConfigFile.cs | 5 +- tests/KubernetesClient.Tests/AuthTests.cs | 94 ++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index 2299a5add..83b27cc0f 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -159,7 +159,7 @@ private static KubernetesClientConfiguration GetKubernetesClientConfiguration(st var k8SConfiguration = new KubernetesClientConfiguration(); currentContext = currentContext ?? k8SConfig.CurrentContext; - // only init context if context if set + // only init context if context is set if (currentContext != null) { k8SConfiguration.InitializeContext(k8SConfig, currentContext); @@ -423,7 +423,8 @@ public static string ExecuteExternalCommand(ExternalExecution config) value: config.EnvironmentVariables[configEnvironmentVariableKey]); process.StartInfo.FileName = config.Command; - process.StartInfo.Arguments = string.Join(" ", config.Arguments); + if (config.Arguments != null) + process.StartInfo.Arguments = string.Join(" ", config.Arguments); process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.UseShellExecute = false; diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 22e3f03ab..43426fb4d 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -9,11 +9,13 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using k8s.KubeConfigModels; using k8s.Models; using k8s.Tests.Mock; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Rest; +using Newtonsoft.Json; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; @@ -277,9 +279,49 @@ public void Cert() } } } - #endif // NETCOREAPP2_1 +#if NETCOREAPP // Requires NET Core Process (Diagnostics) Functionality + [Fact] + public void ExternalToken() + { + const string token = "testingtoken"; + const string name = "testing_irrelevant"; + + using (var server = new MockKubeApiServer(testOutput, cxt => + { + var header = cxt.Request.Headers["Authorization"].FirstOrDefault(); + + var expect = new AuthenticationHeaderValue("Bearer", token).ToString(); + + if (header != expect) + { + cxt.Response.StatusCode = (int) HttpStatusCode.Unauthorized; + return Task.FromResult(false); + } + + return Task.FromResult(true); + })) + { + { + var kubernetesConfig = GetK8SConfiguration(server.Uri.ToString(), token, name); + var clientConfig = KubernetesClientConfiguration.BuildConfigFromConfigObject(kubernetesConfig, name); + var client = new Kubernetes(clientConfig); + var listTask = ExecuteListPods(client); + Assert.True(listTask.Response.IsSuccessStatusCode); + Assert.Equal(1, listTask.Body.Items.Count); + } + { + var kubernetesConfig = GetK8SConfiguration(server.Uri.ToString(), "wrong token", name); + var clientConfig = KubernetesClientConfiguration.BuildConfigFromConfigObject(kubernetesConfig, name); + var client = new Kubernetes(clientConfig); + var listTask = ExecuteListPods(client); + Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode); + } + } + } +#endif // NETCOREAPP + [Fact] public void Token() { @@ -371,5 +413,55 @@ private X509Certificate2 OpenCertificateStore(Stream stream) return certificate; } + + private K8SConfiguration GetK8SConfiguration(string serverUri, string token, string name) + { + const string username = "testinguser"; + + var contexts = new List + { + new Context {Name = name, ContextDetails = new ContextDetails {Cluster = name, User = username}} + }; + + var responseObject = new ExecCredentialResponse + { + ApiVersion = "testingversion", Status = new Dictionary {{"token", token}} + }; + + { + var clusters = new List + { + new Cluster + { + Name = name, + ClusterEndpoint = new ClusterEndpoint {SkipTlsVerify = true, Server = serverUri} + } + }; + + var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "echo"; + var arguments = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? ($"/c echo {JsonConvert.SerializeObject(responseObject)}").Split(" ") + : new[] {JsonConvert.SerializeObject(responseObject)}; + + var users = new List + { + new User + { + Name = username, + UserCredentials = new UserCredentials + { + ExternalExecution = new ExternalExecution + { + ApiVersion = "testingversion", + Command = command, + Arguments = arguments.ToList() + } + } + } + }; + var kubernetesConfig = new K8SConfiguration {Clusters = clusters, Users = users, Contexts = contexts}; + return kubernetesConfig; + } + } } } From 5b69353f6e59c487ec84adc8b6d91407bc519871 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Tue, 25 Feb 2020 10:31:19 +0200 Subject: [PATCH 06/12] Trying to fix failing tests that fail apparently due to the preprocessor symbol --- .../KubernetesClientConfiguration.ConfigFile.cs | 2 +- tests/KubernetesClient.Tests/AuthTests.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index 83b27cc0f..e3858ac18 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -362,7 +362,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) } } -#if NETCOREAPP +#if NETCOREAPP2_1 if (userDetails.UserCredentials.ExternalExecution != null) { if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command)) diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 43426fb4d..79e3f7521 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -170,8 +170,7 @@ public void BasicAuth() } } -#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket functionality in .NET Core 2.1 or newer. - +#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket & Diagnostics functionality in .NET Core 2.1 or newer. [Fact] public void Cert() { @@ -279,9 +278,7 @@ public void Cert() } } } -#endif // NETCOREAPP2_1 -#if NETCOREAPP // Requires NET Core Process (Diagnostics) Functionality [Fact] public void ExternalToken() { @@ -320,7 +317,7 @@ public void ExternalToken() } } } -#endif // NETCOREAPP +#endif // NETCOREAPP2_1 [Fact] public void Token() From 93cd4d824f51860253fbac02d6d06d9a24768eaa Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Tue, 25 Feb 2020 11:42:08 +0200 Subject: [PATCH 07/12] Trying to fix failing macos tests --- tests/KubernetesClient.Tests/AuthTests.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 79e3f7521..0c586e205 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Rest; -using Newtonsoft.Json; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; @@ -420,10 +419,7 @@ private K8SConfiguration GetK8SConfiguration(string serverUri, string token, str new Context {Name = name, ContextDetails = new ContextDetails {Cluster = name, User = username}} }; - var responseObject = new ExecCredentialResponse - { - ApiVersion = "testingversion", Status = new Dictionary {{"token", token}} - }; + var responseJson = $"{{\"apiVersion\": \"testingversion\", \"status\": {{\"token\": \"{token}\"}}}}"; { var clusters = new List @@ -437,8 +433,8 @@ private K8SConfiguration GetK8SConfiguration(string serverUri, string token, str var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "echo"; var arguments = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? ($"/c echo {JsonConvert.SerializeObject(responseObject)}").Split(" ") - : new[] {JsonConvert.SerializeObject(responseObject)}; + ? ($"/c echo {responseJson}").Split(" ") + : new[] {responseJson}; var users = new List { From c7bacf61332bd401cd8361d20285577d852e0ab4 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Tue, 25 Feb 2020 13:26:29 +0200 Subject: [PATCH 08/12] Added the -n (do not output trailing newline) and the -E options to the echo command in OSX --- tests/KubernetesClient.Tests/AuthTests.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 0c586e205..cf1878bd3 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -432,9 +432,15 @@ private K8SConfiguration GetK8SConfiguration(string serverUri, string token, str }; var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "echo"; - var arguments = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? ($"/c echo {responseJson}").Split(" ") - : new[] {responseJson}; + + string[] arguments; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + arguments = ($"/c echo {responseJson}").Split(" "); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + arguments = new[] {responseJson}; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + arguments = new[] {"-nE", responseJson}; + var users = new List { From 77e18f2c2db9d16d3dba2b133fdc2c3ff747025e Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Tue, 25 Feb 2020 13:31:50 +0200 Subject: [PATCH 09/12] initializing arguments variable --- tests/KubernetesClient.Tests/AuthTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index cf1878bd3..5823e5cce 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -433,7 +433,7 @@ private K8SConfiguration GetK8SConfiguration(string serverUri, string token, str var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "echo"; - string[] arguments; + var arguments = new string[] { }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) arguments = ($"/c echo {responseJson}").Split(" "); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) From 5b40f28209a49f4fd3dfc714d27730586b896b45 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Wed, 26 Feb 2020 10:26:42 +0200 Subject: [PATCH 10/12] Changes according to tg123 comments Changed OSX testing command to printf to try and solve the JSON parsing errors --- .../KubeConfigModels/ExecCredentialResponse.cs | 2 +- .../KubeConfigModels/ExternalExecution.cs | 4 ++-- .../KubernetesClientConfiguration.ConfigFile.cs | 7 ++++--- tests/KubernetesClient.Tests/AuthTests.cs | 12 +++++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs index 5f10cff32..0c353fb7d 100644 --- a/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs +++ b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs @@ -10,6 +10,6 @@ public class ExecCredentialResponse [JsonProperty("kind")] public string Kind { get; set; } [JsonProperty("status")] - public Dictionary Status { get; set; } + public IDictionary Status { get; set; } } } diff --git a/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs b/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs index 67a116bbe..899f519ee 100644 --- a/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs +++ b/src/KubernetesClient/KubeConfigModels/ExternalExecution.cs @@ -16,11 +16,11 @@ public class ExternalExecution /// Environment variables to set when executing the plugin. Optional. /// [YamlMember(Alias = "env")] - public Dictionary EnvironmentVariables { get; set; } + public IDictionary EnvironmentVariables { get; set; } /// /// Arguments to pass when executing the plugin. Optional. /// [YamlMember(Alias = "args")] - public List Arguments { get; set; } + public IList Arguments { get; set; } } } diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index e3858ac18..d48fe371c 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -362,7 +362,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) } } -#if NETCOREAPP2_1 +#if NETSTANDARD if (userDetails.UserCredentials.ExternalExecution != null) { if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command)) @@ -390,7 +390,7 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap throw new KubeConfigException("Refresh not supported."); } -#if NETCOREAPP +#if NETSTANDARD /// /// Implementation of the proposal for out-of-tree client /// authentication providers as described here -- @@ -443,7 +443,8 @@ public static string ExecuteExternalCommand(ExternalExecution config) if (string.IsNullOrWhiteSpace(stderr) == false) throw new KubeConfigException($"external exec failed due to: {stderr}"); - process.WaitForExit(); + // Wait for a maximum of 5 seconds, if a response takes longer probably something went wrong... + process.WaitForExit(5); try { diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 5823e5cce..6e8f7f588 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -169,7 +169,7 @@ public void BasicAuth() } } -#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket & Diagnostics functionality in .NET Core 2.1 or newer. +#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket in .NET Core 2.1 or newer. [Fact] public void Cert() { @@ -278,6 +278,8 @@ public void Cert() } } +#endif // NETCOREAPP2_1 +#if NETSTANDARD [Fact] public void ExternalToken() { @@ -316,7 +318,7 @@ public void ExternalToken() } } } -#endif // NETCOREAPP2_1 +#endif // NETSTANDARD [Fact] public void Token() @@ -432,14 +434,14 @@ private K8SConfiguration GetK8SConfiguration(string serverUri, string token, str }; var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "echo"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + command = "printf"; var arguments = new string[] { }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) arguments = ($"/c echo {responseJson}").Split(" "); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) arguments = new[] {responseJson}; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - arguments = new[] {"-nE", responseJson}; var users = new List From 70026f29b48f90f71feedfc8d2eba2b29cd880d0 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Wed, 26 Feb 2020 10:33:55 +0200 Subject: [PATCH 11/12] Added missing references --- .../KubernetesClientConfiguration.ConfigFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index d48fe371c..80aeb4e1f 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -1,5 +1,5 @@ using System; -#if NETCOREAPP +#if NETSTANDARD using Newtonsoft.Json; using System.Collections.Generic; using System.Diagnostics; From b240d8788cb47e5483550041c633d0c456324528 Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Wed, 26 Feb 2020 10:41:46 +0200 Subject: [PATCH 12/12] Environment.UserInteractive and Process applies to .NET Standard >= 2.0 according to Microsoft documentation --- .../KubernetesClientConfiguration.ConfigFile.cs | 6 +++--- tests/KubernetesClient.Tests/AuthTests.cs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index 80aeb4e1f..74a150931 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -1,5 +1,5 @@ using System; -#if NETSTANDARD +#if NETSTANDARD2_0 using Newtonsoft.Json; using System.Collections.Generic; using System.Diagnostics; @@ -362,7 +362,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) } } -#if NETSTANDARD +#if NETSTANDARD2_0 if (userDetails.UserCredentials.ExternalExecution != null) { if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command)) @@ -390,7 +390,7 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap throw new KubeConfigException("Refresh not supported."); } -#if NETSTANDARD +#if NETSTANDARD2_0 /// /// Implementation of the proposal for out-of-tree client /// authentication providers as described here -- diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 6e8f7f588..d71a42a3a 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -279,7 +279,8 @@ public void Cert() } #endif // NETCOREAPP2_1 -#if NETSTANDARD + +#if NETSTANDARD2_0 [Fact] public void ExternalToken() { @@ -318,7 +319,7 @@ public void ExternalToken() } } } -#endif // NETSTANDARD +#endif // NETSTANDARD2_0 [Fact] public void Token()