fix: oidc (#633)
* fix: oidc * revert: verbose oidc logs * fix: actually commit changes * chore: cleanup var name * chore: address pr feedback
This commit is contained in:
@@ -9,6 +9,8 @@ using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using k8s.Authentication;
|
||||
using k8s.Exceptions;
|
||||
using k8s.KubeConfigModels;
|
||||
using k8s.Models;
|
||||
using k8s.Tests.Mock;
|
||||
@@ -440,6 +442,87 @@ namespace k8s.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Oidc()
|
||||
{
|
||||
var clientId = "CLIENT_ID";
|
||||
var clientSecret = "CLIENT_SECRET";
|
||||
var idpIssuerUrl = "https://idp.issuer.url";
|
||||
var unexpiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjAsImV4cCI6MjAwMDAwMDAwMH0.8Ata5uKlrqYfeIaMwS91xVgVFHu7ntHx1sGN95i2Zho";
|
||||
var expiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjB9.f37LFpIw_XIS5TZt3wdtEjjyCNshYy03lOWpyDViRM0";
|
||||
var refreshToken = "REFRESH_TOKEN";
|
||||
|
||||
using (var server = new MockKubeApiServer(testOutput, cxt =>
|
||||
{
|
||||
var header = cxt.Request.Headers["Authorization"].FirstOrDefault();
|
||||
|
||||
var expect = new AuthenticationHeaderValue("Bearer", unexpiredIdToken).ToString();
|
||||
|
||||
if (header != expect)
|
||||
{
|
||||
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}))
|
||||
{
|
||||
{
|
||||
// use unexpired id token as bearer, do not attempt to refresh
|
||||
var client = new Kubernetes(new KubernetesClientConfiguration
|
||||
{
|
||||
Host = server.Uri.ToString(),
|
||||
AccessToken = unexpiredIdToken,
|
||||
TokenProvider = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, unexpiredIdToken, refreshToken),
|
||||
});
|
||||
|
||||
var listTask = ExecuteListPods(client);
|
||||
Assert.True(listTask.Response.IsSuccessStatusCode);
|
||||
Assert.Equal(1, listTask.Body.Items.Count);
|
||||
}
|
||||
|
||||
{
|
||||
// attempt to refresh id token when expired
|
||||
var client = new Kubernetes(new KubernetesClientConfiguration
|
||||
{
|
||||
Host = server.Uri.ToString(),
|
||||
AccessToken = expiredIdToken,
|
||||
TokenProvider = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, expiredIdToken, refreshToken),
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
PeelAggregate(() => ExecuteListPods(client));
|
||||
Assert.True(false, "should not be here");
|
||||
}
|
||||
catch (KubernetesClientException e)
|
||||
{
|
||||
Assert.StartsWith("Unable to refresh OIDC token.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// attempt to refresh id token when null
|
||||
var client = new Kubernetes(new KubernetesClientConfiguration
|
||||
{
|
||||
Host = server.Uri.ToString(),
|
||||
AccessToken = expiredIdToken,
|
||||
TokenProvider = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, null, refreshToken),
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
PeelAggregate(() => ExecuteListPods(client));
|
||||
Assert.True(false, "should not be here");
|
||||
}
|
||||
catch (KubernetesClientException e)
|
||||
{
|
||||
Assert.StartsWith("Unable to refresh OIDC token.", e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShouldThrowUnauthorized(Kubernetes client)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using k8s.Authentication;
|
||||
using k8s.Exceptions;
|
||||
using k8s.KubeConfigModels;
|
||||
using System;
|
||||
@@ -245,6 +246,18 @@ namespace k8s.Tests
|
||||
Assert.Equal("secret", cfg.Password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks oidc authentication provider information is read properly
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UserOidcAuthentication()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.user-oidc.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
|
||||
Assert.Equal("ID_TOKEN", cfg.AccessToken);
|
||||
Assert.IsType<OidcTokenProvider>(cfg.TokenProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a KubeConfigException is thrown when user cannot be found in users
|
||||
/// </summary>
|
||||
|
||||
57
tests/KubernetesClient.Tests/OidcAuthTests.cs
Normal file
57
tests/KubernetesClient.Tests/OidcAuthTests.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using k8s.Authentication;
|
||||
using k8s.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests
|
||||
{
|
||||
public class OidcAuthTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestOidcAuth()
|
||||
{
|
||||
var clientId = "CLIENT_ID";
|
||||
var clientSecret = "CLIENT_SECRET";
|
||||
var idpIssuerUrl = "https://idp.issuer.url";
|
||||
var unexpiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjAsImV4cCI6MjAwMDAwMDAwMH0.8Ata5uKlrqYfeIaMwS91xVgVFHu7ntHx1sGN95i2Zho";
|
||||
var expiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjB9.f37LFpIw_XIS5TZt3wdtEjjyCNshYy03lOWpyDViRM0";
|
||||
var refreshToken = "REFRESH_TOKEN";
|
||||
|
||||
// use unexpired id token as bearer, do not attempt to refresh
|
||||
var auth = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, unexpiredIdToken, refreshToken);
|
||||
var result = await auth.GetAuthenticationHeaderAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be(unexpiredIdToken);
|
||||
|
||||
try
|
||||
{
|
||||
// attempt to refresh id token when expired
|
||||
auth = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, expiredIdToken, refreshToken);
|
||||
result = await auth.GetAuthenticationHeaderAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be(expiredIdToken);
|
||||
Assert.True(false, "should not be here");
|
||||
}
|
||||
catch (KubernetesClientException e)
|
||||
{
|
||||
Assert.StartsWith("Unable to refresh OIDC token.", e.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// attempt to refresh id token when null
|
||||
auth = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, null, refreshToken);
|
||||
result = await auth.GetAuthenticationHeaderAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be(expiredIdToken);
|
||||
Assert.True(false, "should not be here");
|
||||
}
|
||||
catch (KubernetesClientException e)
|
||||
{
|
||||
Assert.StartsWith("Unable to refresh OIDC token.", e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using k8s.Authentication;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests
|
||||
{
|
||||
public class TokenFileAuthTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestToken()
|
||||
{
|
||||
var auth = new TokenFileAuth("assets/token1");
|
||||
@@ -20,7 +22,7 @@ namespace k8s.Tests
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be("token1");
|
||||
|
||||
auth.TokenExpiresAt = DateTime.UtcNow;
|
||||
auth.TokenExpiresAt = DateTime.UtcNow.AddSeconds(-1);
|
||||
result = await auth.GetAuthenticationHeaderAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be("token2");
|
||||
|
||||
28
tests/KubernetesClient.Tests/assets/kubeconfig.user-oidc.yml
Normal file
28
tests/KubernetesClient.Tests/assets/kubeconfig.user-oidc.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Sample file based on https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/
|
||||
# WARNING: File includes minor fixes
|
||||
---
|
||||
current-context: federal-context
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: assets/ca.crt
|
||||
server: https://horse.org:4443
|
||||
name: horse-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: horse-cluster
|
||||
namespace: chisel-ns
|
||||
user: green-user
|
||||
name: federal-context
|
||||
kind: Config
|
||||
users:
|
||||
- name: green-user
|
||||
user:
|
||||
auth-provider:
|
||||
config:
|
||||
client-id: CLIENT_ID
|
||||
client-secret: CLIENT_SECRET
|
||||
id-token: ID_TOKEN
|
||||
idp-issuer-url: IDP_ISSUER_URL
|
||||
refresh-token: REFRESH_TOKEN
|
||||
name: oidc
|
||||
Reference in New Issue
Block a user