OIDC support (#544)
* add minimal oidc support * add OidcTokenProvider * add null check for accessToken * deal with missing client-secret in config * fix formatting, typos * remove commented line * trigger github actions to check for non-deterministic test behavior * Update src/KubernetesClient/Authentication/OidcTokenProvider.cs Co-authored-by: Boshi Lian <farmer1992@gmail.com> * Update src/KubernetesClient/Authentication/OidcTokenProvider.cs Co-authored-by: Boshi Lian <farmer1992@gmail.com> * cleanup * add CA1723 to exceptions * remove exception for CA1723, add CA1724 instead Co-authored-by: Boshi Lian <farmer1992@gmail.com>
This commit is contained in:
@@ -300,6 +300,7 @@
|
|||||||
<Rule Id="CS1591" Action="None" />
|
<Rule Id="CS1591" Action="None" />
|
||||||
<Rule Id="CS1573" Action="None" />
|
<Rule Id="CS1573" Action="None" />
|
||||||
<Rule Id="CS1574" Action="None" />
|
<Rule Id="CS1574" Action="None" />
|
||||||
|
<!-- Rename k8s.Extensions type to mitigate conflict with Microsoft.Extensions namespace https://github.com/kubernetes-client/csharp/pull/544#issuecomment-759230655-->
|
||||||
|
<Rule Id="CA1724" Action="None" />
|
||||||
</Rules>
|
</Rules>
|
||||||
</RuleSet>
|
</RuleSet>
|
||||||
|
|||||||
65
src/KubernetesClient/Authentication/OidcTokenProvider.cs
Normal file
65
src/KubernetesClient/Authentication/OidcTokenProvider.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Rest;
|
||||||
|
using IdentityModel.OidcClient;
|
||||||
|
using k8s.Exceptions;
|
||||||
|
|
||||||
|
namespace k8s.Authentication
|
||||||
|
{
|
||||||
|
public class OidcTokenProvider : ITokenProvider
|
||||||
|
{
|
||||||
|
private OidcClient _oidcClient;
|
||||||
|
private string _idToken;
|
||||||
|
private string _refreshToken;
|
||||||
|
private string _accessToken;
|
||||||
|
private DateTime _expiry;
|
||||||
|
|
||||||
|
public OidcTokenProvider(string clientId, string clientSecret, string idpIssuerUrl, string idToken, string refreshToken)
|
||||||
|
{
|
||||||
|
_idToken = idToken;
|
||||||
|
_refreshToken = refreshToken;
|
||||||
|
_oidcClient = getClient(clientId, clientSecret, idpIssuerUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_expiry == null || _accessToken == null || DateTime.UtcNow.AddSeconds(30) > _expiry)
|
||||||
|
{
|
||||||
|
await RefreshToken().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AuthenticationHeaderValue("Bearer", _accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OidcClient getClient(string clientId, string clientSecret, string idpIssuerUrl)
|
||||||
|
{
|
||||||
|
OidcClientOptions options = new OidcClientOptions
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
ClientSecret = clientSecret ?? "",
|
||||||
|
Authority = idpIssuerUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new OidcClient(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshToken()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result =
|
||||||
|
await _oidcClient.RefreshTokenAsync(_refreshToken).ConfigureAwait(false);
|
||||||
|
_accessToken = result.AccessToken;
|
||||||
|
_idToken = result.IdentityToken;
|
||||||
|
_refreshToken = result.RefreshToken;
|
||||||
|
_expiry = result.AccessTokenExpiration;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new KubernetesClientException($"Unable to refresh OIDC token. \n {e.Message}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
<PackageReference Include="YamlDotNet" Version="8.1.2" />
|
<PackageReference Include="YamlDotNet" Version="8.1.2" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||||
|
<PackageReference Include="IdentityModel.OidcClient" Version="3.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace k8s
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If multiple kubeconfig files are specified in the KUBECONFIG environment variable,
|
/// If multiple kubeconfig files are specified in the KUBECONFIG environment variable,
|
||||||
/// merges the files, where first occurence wins. See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
/// merges the files, where first occurrence wins. See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>Instance of the<see cref="KubernetesClientConfiguration"/> class</returns>
|
/// <returns>Instance of the<see cref="KubernetesClientConfiguration"/> class</returns>
|
||||||
public static KubernetesClientConfiguration BuildDefaultConfig()
|
public static KubernetesClientConfiguration BuildDefaultConfig()
|
||||||
@@ -214,7 +214,7 @@ namespace k8s
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates and Intializes Client Configuration
|
/// Validates and Initializes Client Configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="k8SConfig">Kubernetes Configuration</param>
|
/// <param name="k8SConfig">Kubernetes Configuration</param>
|
||||||
/// <param name="currentContext">Current Context</param>
|
/// <param name="currentContext">Current Context</param>
|
||||||
@@ -346,7 +346,8 @@ namespace k8s
|
|||||||
if (userDetails.UserCredentials.AuthProvider != null)
|
if (userDetails.UserCredentials.AuthProvider != null)
|
||||||
{
|
{
|
||||||
if (userDetails.UserCredentials.AuthProvider.Config != null
|
if (userDetails.UserCredentials.AuthProvider.Config != null
|
||||||
&& userDetails.UserCredentials.AuthProvider.Config.ContainsKey("access-token"))
|
&& (userDetails.UserCredentials.AuthProvider.Config.ContainsKey("access-token")
|
||||||
|
|| userDetails.UserCredentials.AuthProvider.Config.ContainsKey("id-token")))
|
||||||
{
|
{
|
||||||
switch (userDetails.UserCredentials.AuthProvider.Name)
|
switch (userDetails.UserCredentials.AuthProvider.Name)
|
||||||
{
|
{
|
||||||
@@ -390,6 +391,29 @@ namespace k8s
|
|||||||
userCredentialsFound = true;
|
userCredentialsFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "oidc":
|
||||||
|
{
|
||||||
|
var config = userDetails.UserCredentials.AuthProvider.Config;
|
||||||
|
AccessToken = config["id-token"];
|
||||||
|
if (config.ContainsKey("client-id")
|
||||||
|
&& config.ContainsKey("idp-issuer-url")
|
||||||
|
&& config.ContainsKey("id-token")
|
||||||
|
&& config.ContainsKey("refresh-token"))
|
||||||
|
{
|
||||||
|
string clientId = config["client-id"];
|
||||||
|
string clientSecret = config.ContainsKey("client-secret") ? config["client-secret"] : null;
|
||||||
|
string idpIssuerUrl = config["idp-issuer-url"];
|
||||||
|
string idToken = config["id-token"];
|
||||||
|
string refreshToken = config["refresh-token"];
|
||||||
|
|
||||||
|
TokenProvider = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, idToken, refreshToken);
|
||||||
|
|
||||||
|
userCredentialsFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,7 +680,7 @@ namespace k8s
|
|||||||
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
|
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
|
||||||
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
|
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The kube config files will be merges into a single <see cref="K8SConfiguration"/>, where first occurence wins.
|
/// The kube config files will be merges into a single <see cref="K8SConfiguration"/>, where first occurrence wins.
|
||||||
/// See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
/// See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal static K8SConfiguration LoadKubeConfig(FileInfo[] kubeConfigs, bool useRelativePaths = true)
|
internal static K8SConfiguration LoadKubeConfig(FileInfo[] kubeConfigs, bool useRelativePaths = true)
|
||||||
@@ -672,7 +696,7 @@ namespace k8s
|
|||||||
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
|
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
|
||||||
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
|
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The kube config files will be merges into a single <see cref="K8SConfiguration"/>, where first occurence wins.
|
/// The kube config files will be merges into a single <see cref="K8SConfiguration"/>, where first occurrence wins.
|
||||||
/// See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
/// See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal static async Task<K8SConfiguration> LoadKubeConfigAsync(
|
internal static async Task<K8SConfiguration> LoadKubeConfigAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user