diff --git a/src/KubernetesClient/KubeConfigModels/AuthProvider.cs b/src/KubernetesClient/KubeConfigModels/AuthProvider.cs new file mode 100644 index 0000000..70f76cc --- /dev/null +++ b/src/KubernetesClient/KubeConfigModels/AuthProvider.cs @@ -0,0 +1,24 @@ +namespace k8s.KubeConfigModels +{ + using System.Collections.Generic; + using YamlDotNet.RepresentationModel; + using YamlDotNet.Serialization; + + /// + /// Contains information that describes identity information. This is use to tell the kubernetes cluster who you are. + /// + public class AuthProvider { + /// + /// Gets or sets the nickname for this auth provider. + /// + [YamlMember(Alias = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the configuration for this auth provider + /// + [YamlMember(Alias = "config")] + public Dictionary Config { get; set; } + + } +} \ No newline at end of file diff --git a/src/KubernetesClient/KubeConfigModels/UserCredentials.cs b/src/KubernetesClient/KubeConfigModels/UserCredentials.cs index d8f794d..37073bc 100644 --- a/src/KubernetesClient/KubeConfigModels/UserCredentials.cs +++ b/src/KubernetesClient/KubeConfigModels/UserCredentials.cs @@ -3,82 +3,82 @@ namespace k8s.KubeConfigModels using System.Collections.Generic; using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; - + /// /// Contains information that describes identity information. This is use to tell the kubernetes cluster who you are. /// public class UserCredentials - { + { /// /// Gets or sets PEM-encoded data from a client cert file for TLS. Overrides . /// [YamlMember(Alias = "client-certificate-data", ApplyNamingConventions = false)] public string ClientCertificateData { get; set; } - + /// /// Gets or sets the path to a client cert file for TLS. /// [YamlMember(Alias = "client-certificate", ApplyNamingConventions = false)] public string ClientCertificate { get; set; } - + /// /// Gets or sets PEM-encoded data from a client key file for TLS. Overrides . /// [YamlMember(Alias = "client-key-data", ApplyNamingConventions = false)] public string ClientKeyData { get; set; } - + /// /// Gets or sets the path to a client key file for TLS. /// [YamlMember(Alias = "client-key", ApplyNamingConventions = false)] public string ClientKey { get; set; } - + /// /// Gets or sets the bearer token for authentication to the kubernetes cluster. /// [YamlMember(Alias = "token")] public string Token { get; set; } - + /// /// Gets or sets the username to imperonate. The name matches the flag. - /// - [YamlMember(Alias = "as")] - public string Impersonate { get; set; } - + /// + [YamlMember(Alias = "as")] + public string Impersonate { get; set; } + /// /// Gets or sets the groups to imperonate. - /// - [YamlMember(Alias = "as-groups", ApplyNamingConventions = false)] - public IEnumerable ImpersonateGroups { get; set; } = new string[0]; - + /// + [YamlMember(Alias = "as-groups", ApplyNamingConventions = false)] + public IEnumerable ImpersonateGroups { get; set; } = new string[0]; + /// /// Gets or sets additional information for impersonated user. - /// - [YamlMember(Alias = "as-user-extra", ApplyNamingConventions = false)] - public Dictionary ImpersonateUserExtra { get; set; } = new Dictionary(); - + /// + [YamlMember(Alias = "as-user-extra", ApplyNamingConventions = false)] + public Dictionary ImpersonateUserExtra { get; set; } = new Dictionary(); + /// /// Gets or sets the username for basic authentication to the kubernetes cluster. /// [YamlMember(Alias = "username")] public string UserName { get; set; } - + /// /// Gets or sets the password for basic authentication to the kubernetes cluster. /// [YamlMember(Alias = "password")] public string Password { get; set; } - + /// /// Gets or sets custom authentication plugin for the kubernetes cluster. /// [YamlMember(Alias = "auth-provider", ApplyNamingConventions = false)] - public Dictionary AuthProvider { get; set; } - + public AuthProvider AuthProvider { get; set; } + /// /// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields. - /// - [YamlMember(Alias = "extensions")] + /// + [YamlMember(Alias = "extensions")] public IDictionary Extensions { get; set; } } } diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs index ecdd85d..41afcae 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs @@ -28,8 +28,8 @@ namespace k8s /// Initializes a new instance of the from config file /// /// kube api server endpoint - /// Explicit file path to kubeconfig. Set to null to use the default file path - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// Explicit file path to kubeconfig. Set to null to use the default file path + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. public static KubernetesClientConfiguration BuildConfigFromConfigFile(string kubeconfigPath = null, string currentContext = null, string masterUrl = null, bool useRelativePaths = true) @@ -42,8 +42,8 @@ namespace k8s /// /// Fileinfo of the kubeconfig, cannot be null /// override the context in config file, set null if do not want to override - /// override the kube api server endpoint, set null if do not want to override - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// override the kube api server endpoint, set null if do not want to override + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo kubeconfig, string currentContext = null, string masterUrl = null, bool useRelativePaths = true) @@ -239,6 +239,27 @@ namespace k8s userCredentialsFound = true; } + if (userDetails.UserCredentials.AuthProvider != null) { + if (userDetails.UserCredentials.AuthProvider.Name == "azure" && + userDetails.UserCredentials.AuthProvider.Config != null && + userDetails.UserCredentials.AuthProvider.Config.ContainsKey("access-token")) { + var config = userDetails.UserCredentials.AuthProvider.Config; + if (config.ContainsKey("expires-on")) { + var expires = DateTimeOffset.FromUnixTimeSeconds(Int32.Parse(config["expires-on"])); + 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; + } + } + if (!userCredentialsFound) { throw new KubeConfigException( @@ -246,11 +267,15 @@ namespace k8s } } + public static string RenewAzureToken(string tenantId, string clientId, string apiServerId, string refresh) { + throw new KubeConfigException("Refresh not supported."); + } + /// /// Loads entire Kube Config from default or explicit file path /// - /// Explicit file path to kubeconfig. Set to null to use the default file path - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// Explicit file path to kubeconfig. Set to null to use the default file path + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. /// Instance of the class public static async Task LoadKubeConfigAsync(string kubeconfigPath = null, bool useRelativePaths = true) @@ -263,8 +288,8 @@ namespace k8s /// /// Loads entire Kube Config from default or explicit file path /// - /// Explicit file path to kubeconfig. Set to null to use the default file path - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// Explicit file path to kubeconfig. Set to null to use the default file path + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. /// Instance of the class public static K8SConfiguration LoadKubeConfig(string kubeconfigPath = null, bool useRelativePaths = true) @@ -275,8 +300,8 @@ namespace k8s // /// Loads Kube Config /// - /// Kube config file contents - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// Kube config file contents + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. /// Instance of the class public static async Task LoadKubeConfigAsync(FileInfo kubeconfig, bool useRelativePaths = true) @@ -288,12 +313,12 @@ namespace k8s using (var stream = kubeconfig.OpenRead()) { - var config = await Yaml.LoadFromStreamAsync(stream); - - if (useRelativePaths) - { - config.FileName = kubeconfig.FullName; - } + var config = await Yaml.LoadFromStreamAsync(stream); + + if (useRelativePaths) + { + config.FileName = kubeconfig.FullName; + } return config; } @@ -302,8 +327,8 @@ namespace k8s /// /// Loads Kube Config /// - /// Kube config file contents - /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig + /// Kube config file contents + /// When , the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig /// file is located. When , the paths will be considered to be relative to the current working directory. /// Instance of the class public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig, bool useRelativePaths = true) diff --git a/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs b/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs index b8b1da7..400dec7 100755 --- a/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs +++ b/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs @@ -1,7 +1,7 @@ using System.IO; -using System.Linq; +using System.Linq; using k8s.Exceptions; -using k8s.KubeConfigModels; +using k8s.KubeConfigModels; using Xunit; namespace k8s.Tests @@ -272,8 +272,8 @@ namespace k8s.Tests public void DeletedConfigurationFile() { var assetFileInfo = new FileInfo("assets/kubeconfig.yml"); - var tempFileInfo = new FileInfo(Path.GetTempFileName()); - + var tempFileInfo = new FileInfo(Path.GetTempFileName()); + File.Copy(assetFileInfo.FullName, tempFileInfo.FullName, /* overwrite: */ true); KubernetesClientConfiguration config; @@ -321,125 +321,125 @@ namespace k8s.Tests { var filePath = "assets/kubeconfig.as-user-extra.yml"; - var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false); Assert.NotNull(cfg.Host); - } - + } + /// /// Ensures Kube config file is loaded from explicit file - /// - [Fact] - public void LoadKubeConfigExplicitFilePath() - { - var txt = File.ReadAllText("assets/kubeconfig.yml"); - var expectedCfg = Yaml.LoadFromString(txt); - - var cfg = KubernetesClientConfiguration.LoadKubeConfig("assets/kubeconfig.yml"); - - Assert.NotNull(cfg); - AssertConfigEqual(expectedCfg, cfg); - } - - [Fact] - public void LoadKubeConfigFileInfo() - { - var filePath = "assets/kubeconfig.yml"; - var txt = File.ReadAllText(filePath); - var expectedCfg = Yaml.LoadFromString(txt); - - var fileInfo = new FileInfo(filePath); - var cfg = KubernetesClientConfiguration.LoadKubeConfig(fileInfo); - - Assert.NotNull(cfg); - AssertConfigEqual(expectedCfg, cfg); - } - - [Fact] - public void LoadKubeConfigStream() - { - var filePath = "assets/kubeconfig.yml"; - var txt = File.ReadAllText(filePath); - var expectedCfg = Yaml.LoadFromString(txt); - - var fileInfo = new FileInfo(filePath); - K8SConfiguration cfg; - using (var stream = fileInfo.OpenRead()) - { - cfg = KubernetesClientConfiguration.LoadKubeConfig(stream); - } - - Assert.NotNull(cfg); - AssertConfigEqual(expectedCfg, cfg); - } - - private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual) - { - Assert.Equal(expected.ApiVersion, actual.ApiVersion); - Assert.Equal(expected.CurrentContext, actual.CurrentContext); - - foreach (var expectedContext in expected.Contexts) - { - // Will throw exception if not found - var actualContext = actual.Contexts.First(c => c.Name.Equals(expectedContext.Name)); - AssertContextEqual(expectedContext, actualContext); - } - - foreach (var expectedCluster in expected.Clusters) - { - // Will throw exception if not found - var actualCluster = actual.Clusters.First(c => c.Name.Equals(expectedCluster.Name)); - AssertClusterEqual(expectedCluster, actualCluster); - } - - foreach (var expectedUser in expected.Users) - { - // Will throw exception if not found - var actualUser = actual.Users.First(u => u.Name.Equals(expectedUser.Name)); - AssertUserEqual(expectedUser, actualUser); - } - } - - private void AssertContextEqual(Context expected, Context actual) - { - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Namespace, actual.Namespace); - Assert.Equal(expected.ContextDetails.Cluster, actual.ContextDetails.Cluster); - Assert.Equal(expected.ContextDetails.User, actual.ContextDetails.User); - Assert.Equal(expected.ContextDetails.Namespace, actual.ContextDetails.Namespace); - } - - private void AssertClusterEqual(Cluster expected, Cluster actual) - { - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.ClusterEndpoint.CertificateAuthority, actual.ClusterEndpoint.CertificateAuthority); - Assert.Equal(expected.ClusterEndpoint.CertificateAuthorityData, actual.ClusterEndpoint.CertificateAuthorityData); - Assert.Equal(expected.ClusterEndpoint.Server, actual.ClusterEndpoint.Server); - Assert.Equal(expected.ClusterEndpoint.SkipTlsVerify, actual.ClusterEndpoint.SkipTlsVerify); - } - - private void AssertUserEqual(User expected, User actual) - { - Assert.Equal(expected.Name, actual.Name); - - var expectedCreds = expected.UserCredentials; - var actualCreds = actual.UserCredentials; - - Assert.Equal(expectedCreds.ClientCertificateData, actualCreds.ClientCertificateData); - Assert.Equal(expectedCreds.ClientCertificate, actualCreds.ClientCertificate); - Assert.Equal(expectedCreds.ClientKeyData, actualCreds.ClientKeyData); - Assert.Equal(expectedCreds.ClientKey, actualCreds.ClientKey); - Assert.Equal(expectedCreds.Token, actualCreds.Token); - Assert.Equal(expectedCreds.Impersonate, actualCreds.Impersonate); - Assert.Equal(expectedCreds.UserName, actualCreds.UserName); - Assert.Equal(expectedCreds.Password, actualCreds.Password); - - Assert.True(expectedCreds.ImpersonateGroups.All(x => actualCreds.ImpersonateGroups.Contains(x))); - Assert.True(expectedCreds.ImpersonateUserExtra.All(x => actualCreds.ImpersonateUserExtra.Contains(x))); - - if (expectedCreds.AuthProvider != null) - { - Assert.True(expectedCreds.AuthProvider.All(x => actualCreds.AuthProvider.Contains(x))); - } + /// + [Fact] + public void LoadKubeConfigExplicitFilePath() + { + var txt = File.ReadAllText("assets/kubeconfig.yml"); + var expectedCfg = Yaml.LoadFromString(txt); + + var cfg = KubernetesClientConfiguration.LoadKubeConfig("assets/kubeconfig.yml"); + + Assert.NotNull(cfg); + AssertConfigEqual(expectedCfg, cfg); + } + + [Fact] + public void LoadKubeConfigFileInfo() + { + var filePath = "assets/kubeconfig.yml"; + var txt = File.ReadAllText(filePath); + var expectedCfg = Yaml.LoadFromString(txt); + + var fileInfo = new FileInfo(filePath); + var cfg = KubernetesClientConfiguration.LoadKubeConfig(fileInfo); + + Assert.NotNull(cfg); + AssertConfigEqual(expectedCfg, cfg); + } + + [Fact] + public void LoadKubeConfigStream() + { + var filePath = "assets/kubeconfig.yml"; + var txt = File.ReadAllText(filePath); + var expectedCfg = Yaml.LoadFromString(txt); + + var fileInfo = new FileInfo(filePath); + K8SConfiguration cfg; + using (var stream = fileInfo.OpenRead()) + { + cfg = KubernetesClientConfiguration.LoadKubeConfig(stream); + } + + Assert.NotNull(cfg); + AssertConfigEqual(expectedCfg, cfg); + } + + private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual) + { + Assert.Equal(expected.ApiVersion, actual.ApiVersion); + Assert.Equal(expected.CurrentContext, actual.CurrentContext); + + foreach (var expectedContext in expected.Contexts) + { + // Will throw exception if not found + var actualContext = actual.Contexts.First(c => c.Name.Equals(expectedContext.Name)); + AssertContextEqual(expectedContext, actualContext); + } + + foreach (var expectedCluster in expected.Clusters) + { + // Will throw exception if not found + var actualCluster = actual.Clusters.First(c => c.Name.Equals(expectedCluster.Name)); + AssertClusterEqual(expectedCluster, actualCluster); + } + + foreach (var expectedUser in expected.Users) + { + // Will throw exception if not found + var actualUser = actual.Users.First(u => u.Name.Equals(expectedUser.Name)); + AssertUserEqual(expectedUser, actualUser); + } + } + + private void AssertContextEqual(Context expected, Context actual) + { + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.Namespace, actual.Namespace); + Assert.Equal(expected.ContextDetails.Cluster, actual.ContextDetails.Cluster); + Assert.Equal(expected.ContextDetails.User, actual.ContextDetails.User); + Assert.Equal(expected.ContextDetails.Namespace, actual.ContextDetails.Namespace); + } + + private void AssertClusterEqual(Cluster expected, Cluster actual) + { + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.ClusterEndpoint.CertificateAuthority, actual.ClusterEndpoint.CertificateAuthority); + Assert.Equal(expected.ClusterEndpoint.CertificateAuthorityData, actual.ClusterEndpoint.CertificateAuthorityData); + Assert.Equal(expected.ClusterEndpoint.Server, actual.ClusterEndpoint.Server); + Assert.Equal(expected.ClusterEndpoint.SkipTlsVerify, actual.ClusterEndpoint.SkipTlsVerify); + } + + private void AssertUserEqual(User expected, User actual) + { + Assert.Equal(expected.Name, actual.Name); + + var expectedCreds = expected.UserCredentials; + var actualCreds = actual.UserCredentials; + + Assert.Equal(expectedCreds.ClientCertificateData, actualCreds.ClientCertificateData); + Assert.Equal(expectedCreds.ClientCertificate, actualCreds.ClientCertificate); + Assert.Equal(expectedCreds.ClientKeyData, actualCreds.ClientKeyData); + Assert.Equal(expectedCreds.ClientKey, actualCreds.ClientKey); + Assert.Equal(expectedCreds.Token, actualCreds.Token); + Assert.Equal(expectedCreds.Impersonate, actualCreds.Impersonate); + Assert.Equal(expectedCreds.UserName, actualCreds.UserName); + Assert.Equal(expectedCreds.Password, actualCreds.Password); + + Assert.True(expectedCreds.ImpersonateGroups.All(x => actualCreds.ImpersonateGroups.Contains(x))); + Assert.True(expectedCreds.ImpersonateUserExtra.All(x => actualCreds.ImpersonateUserExtra.Contains(x))); + + if (expectedCreds.AuthProvider != null) + { + Assert.True(expectedCreds.AuthProvider.Config.All(x => actualCreds.AuthProvider.Config.Contains(x))); + } } } }