Skip to content

Commit

Permalink
Sketch out mechanism for configuring WebSocket authentication from au…
Browse files Browse the repository at this point in the history
…th strategy

#108
#136
  • Loading branch information
tintoy committed Aug 10, 2022
1 parent 3c6a94b commit ac0f420
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 5 deletions.
39 changes: 38 additions & 1 deletion src/KubeClient.Extensions.WebSockets/K8sWebSocketOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace KubeClient.Extensions.WebSockets
/// Options for connecting to Kubernetes web sockets.
/// </summary>
public class K8sWebSocketOptions
: IClientAuthenticationConfig
{
/// <summary>
/// The default size (in bytes) for WebSocket send / receive buffers.
Expand Down Expand Up @@ -68,6 +69,40 @@ public K8sWebSocketOptions()
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(5);

/// <summary>
/// Add a client certificate for authentication to the Kubernetes API.
/// </summary>
/// <param name="certificate">
/// An <see cref="X509Certificate2"/> representing the client certificate to use.
/// </param>
void IClientAuthenticationConfig.AddClientCertificate(X509Certificate2 certificate)
{
if (certificate == null)
throw new ArgumentNullException(nameof(certificate));

ClientCertificates.Add(certificate);
}

/// <summary>
/// Configure the HTTP "Authorization" header for authentication to the Kubernetes API.
/// </summary>
/// <param name="scheme">
/// The authentication scheme (e.g. "Basic", "Bearer", etc).
/// </param>
/// <param name="value">
/// The authentication value.
/// </param>
void IClientAuthenticationConfig.SetAuthorizationHeader(string scheme, string value)
{
if (String.IsNullOrWhiteSpace(scheme))
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(scheme)}.", nameof(scheme));

if (value == null)
throw new ArgumentNullException(nameof(value));

RequestHeaders[HttpKnownHeaderNames.Authorization] = $"{scheme} ${value}";
}

/// <summary>
/// Create <see cref="K8sWebSocketOptions"/> using the client's authentication settings.
/// </summary>
Expand Down Expand Up @@ -103,7 +138,9 @@ public static K8sWebSocketOptions FromClientOptions(KubeClientOptions clientOpti

var socketOptions = new K8sWebSocketOptions();

// TODO: Expose functionality for obtaining access token via KubeAuthStrategy and call it from here, rather than using the special-case credential configuration logic below.
// NOTE: Not all authentication strategies are supported yet.
if (clientOptions.AuthStrategy != null)
clientOptions.AuthStrategy.Configure(socketOptions);

switch (clientOptions.AuthStrategy)
{
Expand Down
17 changes: 17 additions & 0 deletions src/KubeClient/Authentication/BasicAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ public override ClientBuilder Configure(ClientBuilder clientBuilder)
);
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

clientAuthenticationConfig.SetAuthorizationHeader(
scheme: "Basic",
value: BasicAuthenticationHandler.EncodeHeaderValue(UserName, Password)
);
}

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/KubeClient/Authentication/BearerTokenAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ public override ClientBuilder Configure(ClientBuilder clientBuilder)
);
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

Validate();

clientAuthenticationConfig.SetAuthorizationHeader(
scheme: "Bearer",
value: Token
);
}

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/KubeClient/Authentication/BearerTokenProviderAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ public override ClientBuilder Configure(ClientBuilder clientBuilder)
);
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

throw new NotImplementedException(); // TODO: Figure out how to implement this (especially given that we would need to do it synchronously).
}

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions src/KubeClient/Authentication/CertificateAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ public override ClientBuilder Configure(ClientBuilder clientBuilder)
return clientBuilder.WithClientCertificate(Certificate);
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

Validate();

clientAuthenticationConfig.AddClientCertificate(Certificate);
}

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/KubeClient/Authentication/CredentialPluginAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ public override ClientBuilder Configure(ClientBuilder clientBuilder)
);
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

throw new NotImplementedException(); // TODO: Figure out how to implement this (especially given that we would need to do it synchronously).
}

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions src/KubeClient/Authentication/IClientAuthenticationConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

namespace KubeClient.Authentication
{
/// <summary>
/// Represents configuration for client authentication to the Kubernetes API.
/// </summary>
public interface IClientAuthenticationConfig
{
/// <summary>
/// Add a client certificate for authentication to the Kubernetes API.
/// </summary>
/// <param name="certificate">
/// An <see cref="X509Certificate2"/> representing the client certificate to use.
/// </param>
void AddClientCertificate(X509Certificate2 certificate);

/// <summary>
/// Configure the HTTP "Authorization" header for authentication to the Kubernetes API.
/// </summary>
/// <param name="scheme">
/// The authentication scheme (e.g. "Basic", "Bearer", etc).
/// </param>
/// <param name="value">
/// The authentication value.
/// </param>
void SetAuthorizationHeader(string scheme, string value);
}
}
25 changes: 24 additions & 1 deletion src/KubeClient/Authentication/NoneAuthStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using HTTPlease;
using System;
using System.Net.Http;

namespace KubeClient.Authentication
Expand Down Expand Up @@ -41,7 +42,29 @@ public override void Validate()
/// <returns>
/// The configured <see cref="ClientBuilder"/>.
/// </returns>
public override ClientBuilder Configure(ClientBuilder clientBuilder) => clientBuilder;
public override ClientBuilder Configure(ClientBuilder clientBuilder)
{
if (clientBuilder == null)
throw new ArgumentNullException(nameof(clientBuilder));

Validate();

return clientBuilder;
}

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public override void Configure(IClientAuthenticationConfig clientAuthenticationConfig)
{
if (clientAuthenticationConfig == null)
throw new ArgumentNullException(nameof(clientAuthenticationConfig));

Validate();
}

/// <summary>
/// Create a deep clone of the authentication strategy.
Expand Down
8 changes: 8 additions & 0 deletions src/KubeClient/KubeAuthStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public abstract class KubeAuthStrategy
/// </returns>
public abstract ClientBuilder Configure(ClientBuilder clientBuilder);

/// <summary>
/// Configure client authentication.
/// </summary>
/// <param name="clientAuthenticationConfig">
/// The client authentication configuration.
/// </param>
public abstract void Configure(IClientAuthenticationConfig clientAuthenticationConfig);

/// <summary>
/// Create a deep clone of the authentication strategy.
/// </summary>
Expand Down
28 changes: 25 additions & 3 deletions src/KubeClient/MessageHandlers/BasicAuthenticationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public class BasicAuthenticationHandler : DelegatingHandler
/// <param name="password">The password to use</param>
public BasicAuthenticationHandler(string username, string password)
{
if(String.IsNullOrEmpty(username))
if (String.IsNullOrEmpty(username))
throw new ArgumentNullException(nameof(username));
if(String.IsNullOrEmpty(password))
if (String.IsNullOrEmpty(password))
throw new ArgumentNullException(nameof(password));

_encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password));
_encoded = EncodeHeaderValue(username, password);
}

/// <inheritdoc />
Expand All @@ -36,5 +36,27 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", _encoded);
return base.SendAsync(request, cancellationToken);
}

/// <summary>
/// Encode a username and password to use as a Basic authentication header value.
/// </summary>
/// <param name="username">
/// The username to use.
/// </param>
/// <param name="password">
/// The password to use.
/// </param>
/// <returns>
/// The encoded header value.
/// </returns>
public static string EncodeHeaderValue(string username, string password)
{
if (String.IsNullOrEmpty(username))
throw new ArgumentNullException(nameof(username));
if (String.IsNullOrEmpty(password))
throw new ArgumentNullException(nameof(password));

return Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password));
}
}
}

0 comments on commit ac0f420

Please sign in to comment.