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:
Jarrett Confrey
2021-05-24 09:33:39 -07:00
committed by GitHub
parent 3ce35a74bf
commit ac0f43b576
7 changed files with 213 additions and 7 deletions

View File

@@ -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

View File

@@ -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>

View 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);
}
}
}
}

View File

@@ -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");

View 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