Skip to content

Commit

Permalink
Initial Check-in
Browse files Browse the repository at this point in the history
  • Loading branch information
kibblewhite committed May 23, 2022
1 parent e345915 commit c29f714
Show file tree
Hide file tree
Showing 40 changed files with 1,664 additions and 1 deletion.
39 changes: 39 additions & 0 deletions Client/Client.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>HashiVaultCs</AssemblyName>
<RootNamespace>HashiVaultCs</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FormatWith" Version="3.0.1" />
</ItemGroup>

<ItemGroup>
<Compile Update="Resources\HttpVaultClient\Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Resources\Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Resources\HttpVaultClient\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions Client/Clients/ApiUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace HashiVaultCs.Clients;

public static class ApiUrl
{
public const string AuthUserpassLogin = "/v1/auth/userpass/login/{username}";
public const string AuthApproleRoleId = "/v1/auth/approle/role/{rolename}/role-id";
public const string AuthApproleSecretId = "/v1/auth/approle/role/{rolename}/secret-id";
public const string AuthApproleLogin = "/v1/auth/approle/login";
public const string SecretsEngineDataPath = "/v1/{engine}/data/{path}";
}
43 changes: 43 additions & 0 deletions Client/Clients/Auth/ApproleClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using HashiVaultCs.Interfaces.Auth;
using HashiVaultCs.Models.Requests.Auth.Approle;

namespace HashiVaultCs.Clients.Auth;

public sealed class ApproleClient : MustInitialiseHttpVaultHeadersAndHostAbstraction<HttpVaultHeaders>, IApproleClient
{
public ApproleClient(HttpVaultHeaders vault_headers, string base_address) : base(vault_headers, base_address) { }

public async Task<Secret> LoginAsync(Login data, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default)
{
Uri request_uri = new(_base_uri, ApiUrl.AuthApproleLogin);
HttpVaultClient http_vault_client = new(HttpMethod.Post, _vault_headers, headers, request_uri, data);
JsonDocument response = await http_vault_client.SendAsync(cancellationToken);
return response.Deserialize<Secret>() ?? new Secret();
}

public async Task<Secret> RoleIdAsync(string rolename, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default)
{
string relative_url = ApiUrl.AuthApproleRoleId.FormatWith(new
{
rolename
});
Uri request_uri = new(_base_uri, relative_url);

HttpVaultClient http_vault_client = new(HttpMethod.Get, _vault_headers, headers, request_uri);
JsonDocument response = await http_vault_client.SendAsync(cancellationToken);
return response.Deserialize<Secret>() ?? new Secret();
}

public async Task<Secret> SecretIdAsync(string rolename, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default)
{
string relative_url = ApiUrl.AuthApproleSecretId.FormatWith(new
{
rolename
});
Uri request_uri = new(_base_uri, relative_url);

HttpVaultClient http_vault_client = new(HttpMethod.Post, _vault_headers, headers, request_uri, new { });
JsonDocument response = await http_vault_client.SendAsync(cancellationToken);
return response.Deserialize<Secret>() ?? new Secret();
}
}
22 changes: 22 additions & 0 deletions Client/Clients/Auth/UserpassClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using HashiVaultCs.Interfaces.Auth;
using HashiVaultCs.Models.Requests.Auth.Userpass;

namespace HashiVaultCs.Clients.Auth;

public sealed class UserpassClient : MustInitialiseHttpVaultHeadersAndHostAbstraction<HttpVaultHeaders>, IUserpassClient
{
public UserpassClient(HttpVaultHeaders vault_headers, string base_address) : base(vault_headers, base_address) { }

public async Task<Secret> LoginAsync(string username, Login data, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default)
{
string relative_url = ApiUrl.AuthUserpassLogin.FormatWith(new
{
username
});
Uri request_uri = new(_base_uri, relative_url);

HttpVaultClient http_vault_client = new(HttpMethod.Post, _vault_headers, headers, request_uri, data);
JsonDocument response = await http_vault_client.SendAsync(cancellationToken);
return response.Deserialize<Secret>() ?? new Secret(); // todo (2022-05-23|kibble): What to return when Deserialize() fails, this method shouldn't return empty secrets...
}
}
14 changes: 14 additions & 0 deletions Client/Clients/MustInitialiseHttpVaultHeadersAndHostAbstraction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using HashiVaultCs.Interfaces;

namespace HashiVaultCs.Clients;

public abstract class MustInitialiseHttpVaultHeadersAndHostAbstraction<T> where T : IHttpVaultHeaders
{
protected readonly T _vault_headers;
protected readonly Uri _base_uri;
public MustInitialiseHttpVaultHeadersAndHostAbstraction(T vault_headers, string base_address)
{
_vault_headers = vault_headers;
_base_uri = new Uri(base_address);
}
}
22 changes: 22 additions & 0 deletions Client/Clients/Secrets/DataClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using HashiVaultCs.Interfaces.Secrets;

namespace HashiVaultCs.Clients.Secrets;

public sealed class DataClient : MustInitialiseHttpVaultHeadersAndHostAbstraction<HttpVaultHeaders>, IDataClient
{
public DataClient(HttpVaultHeaders vault_headers, string base_address) : base(vault_headers, base_address) { }

public async Task<Secret> GetAsync(string engine, string path, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default)
{
string relative_url = ApiUrl.SecretsEngineDataPath.FormatWith(new
{
engine,
path
});
Uri request_uri = new(_base_uri, relative_url);

HttpVaultClient http_vault_client = new(HttpMethod.Get, _vault_headers, headers, request_uri);
JsonDocument response = await http_vault_client.SendAsync(cancellationToken);
return response.Deserialize<Secret>() ?? new Secret();
}
}
35 changes: 35 additions & 0 deletions Client/Extentions/HttpRequestMessageExtentions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using HashiVaultCs;

namespace HashiVaultCs.Extentions;

public static class HttpRequestMessageExtentions
{
public static void AddVaultHttpHeaders(this HttpRequestMessage http_request_message, HttpVaultHeaders vault_headers)
{
ArgumentNullException.ThrowIfNull(vault_headers);

if (vault_headers.RequestHeaderPresent is true)
{
http_request_message.Headers.Remove(vault_headers.Request.Key);
http_request_message.Headers.Add(vault_headers.Request.Key, vault_headers.Request.Value);
}

if (vault_headers.TokenHeaderPresent is true)
{
http_request_message.Headers.Remove(vault_headers.Token.Key);
http_request_message.Headers.Add(vault_headers.Token.Key, vault_headers.Token.Value);
}

if (vault_headers.NamespaceHeaderPresent is true)
{
http_request_message.Headers.Remove(vault_headers.Namespace.Key);
http_request_message.Headers.Add(vault_headers.Namespace.Key, vault_headers.Namespace.Value);
}

if (vault_headers.WrapTimeToLiveHeaderPresent is true)
{
http_request_message.Headers.Remove(vault_headers.WrapTimeToLive.Key);
http_request_message.Headers.Add(vault_headers.WrapTimeToLive.Key, vault_headers.WrapTimeToLive.Value);
}
}
}
72 changes: 72 additions & 0 deletions Client/HttpVaultClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using HashiVaultCs.Interfaces;

namespace HashiVaultCs;

public sealed class HttpVaultClient : IHttpVaultClient
{
private const string _media_type = "application/json";
private readonly TimeSpan _http_client_timeout = TimeSpan.FromSeconds(6);
private readonly HttpClient _http_client;
private readonly HttpRequestMessage _http_request_message;
private readonly List<HttpMethod> _supported_http_methods_list = new() { HttpMethod.Get, HttpMethod.Post };

public HttpVaultClient(HttpMethod method, HttpVaultHeaders vault_headers, IReadOnlyDictionary<string, string> headers, Uri request_uri, object? content = null)
{
// Currently only HTTP Methods GET & POST is supported.
if (_supported_http_methods_list.Contains(method) is false)
{
throw new NotSupportedException($"{Resources.HttpVaultClient.Resource.MethodNotSupportedException}. {Resources.HttpVaultClient.Resource.SupportedHttpMethodsAre}: {string.Join(", ", _supported_http_methods_list)}");
}

// HTTP Headers should not be null, but can be empty (ImmutableDictionary<string, string>.Empty).
if (headers?.Any() is null)
{
throw new ArgumentNullException(nameof(headers), Resources.HttpVaultClient.Resource.HeadersArgumentNullException);
}

// Generate HTTP Request Message and set content
_http_request_message = new(method, request_uri)
{
Content = content is null
? null
: new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, _media_type)
};

// Build the HTTP Headers, both custom and Vault Specific HTTP Headers.
_http_request_message.AddVaultHttpHeaders(vault_headers);
foreach (KeyValuePair<string, string> kvp in headers)
{
_http_request_message.Headers.Remove(kvp.Key);
_http_request_message.Headers.Add(kvp.Key, kvp.Value);
}

HttpClientHandler handler = new();
_http_client = new(handler)
{
Timeout = _http_client_timeout
};

_http_client.DefaultRequestHeaders.Accept.Clear();
_http_client.DefaultRequestHeaders.Accept.Add(new(_media_type));
}

/// <summary>
/// This method will throw an exception if the HTTP response status is anything but a success.
/// </summary>
/// <remarks>Only valid JSON is returned from this method through the return of JsonDocument values.</remarks>
/// <returns></returns>
public async Task<JsonDocument> SendAsync(CancellationToken cancellationToken = default)
{
HttpResponseMessage http_response_message = await _http_client.SendAsync(_http_request_message, cancellationToken);
http_response_message.EnsureSuccessStatusCode();
string response_json = await http_response_message.Content.ReadAsStringAsync(cancellationToken);
JsonDocumentOptions jdo = new()
{
AllowTrailingCommas = false,
CommentHandling = JsonCommentHandling.Disallow,
MaxDepth = 32
};
JsonDocument document = JsonDocument.Parse(response_json, jdo);
return document;
}
}
9 changes: 9 additions & 0 deletions Client/HttpVaultHeaderKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace HashiVaultCs;

public static class HttpVaultHeaderKey
{
public const string Request = "X-Vault-Request";
public const string Token = "X-Vault-Token";
public const string Namespace = "X-Vault-Namespace";
public const string WrapTimeToLive = "X-Vault-Wrap-TTL";
}
69 changes: 69 additions & 0 deletions Client/HttpVaultHeaders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using HashiVaultCs.Interfaces;

namespace HashiVaultCs;

public sealed class HttpVaultHeaders : IHttpVaultHeaders
{
public bool RequestHeaderPresent { get; private set; }
private KeyValuePair<string, string>? _request;
public KeyValuePair<string, string> Request => RequestHeaderPresent is false || _request is null
? throw new InvalidOperationException($"{Resources.Resource.VaultHttpHeadersInvalidOperationException}: {nameof(Request)}")
: _request ?? throw new NullReferenceException();

public bool TokenHeaderPresent { get; private set; }
private KeyValuePair<string, string>? _token;
public KeyValuePair<string, string> Token => TokenHeaderPresent is false || _token is null
? throw new InvalidOperationException($"{Resources.Resource.VaultHttpHeadersInvalidOperationException}: {nameof(Token)}")
: _token ?? throw new NullReferenceException();

public bool NamespaceHeaderPresent { get; private set; }
private KeyValuePair<string, string>? _namespace;
public KeyValuePair<string, string> Namespace => NamespaceHeaderPresent is false || _namespace is null
? throw new InvalidOperationException($"{Resources.Resource.VaultHttpHeadersInvalidOperationException}: {nameof(Namespace)}")
: _namespace ?? throw new NullReferenceException();

public bool WrapTimeToLiveHeaderPresent { get; private set; }
private KeyValuePair<string, string>? _wrapttl;
public KeyValuePair<string, string> WrapTimeToLive => WrapTimeToLiveHeaderPresent is false || _wrapttl is null
? throw new InvalidOperationException($"{Resources.Resource.VaultHttpHeadersInvalidOperationException}: {nameof(WrapTimeToLive)}")
: _wrapttl ?? throw new NullReferenceException();

HttpVaultHeaders IHttpVaultHeaders.Build(IReadOnlyDictionary<string, string> headers) => Build(headers);

public static HttpVaultHeaders Build(IReadOnlyDictionary<string, string> headers)
{
HttpVaultHeaders vault_http_headers = new();
foreach (KeyValuePair<string, string> kvp in headers)
{
switch (kvp.Key)
{
case HttpVaultHeaderKey.Request:
{
vault_http_headers._request = new KeyValuePair<string, string>(HttpVaultHeaderKey.Request, kvp.Value);
vault_http_headers.RequestHeaderPresent = true;
break;
}
case HttpVaultHeaderKey.Token:
{
vault_http_headers._token = new KeyValuePair<string, string>(HttpVaultHeaderKey.Token, kvp.Value);
vault_http_headers.TokenHeaderPresent = true;
break;
}
case HttpVaultHeaderKey.Namespace:
{
vault_http_headers._namespace = new KeyValuePair<string, string>(HttpVaultHeaderKey.Namespace, kvp.Value);
vault_http_headers.NamespaceHeaderPresent = true;
break;
}
case HttpVaultHeaderKey.WrapTimeToLive:
{
vault_http_headers._wrapttl = new KeyValuePair<string, string>(HttpVaultHeaderKey.WrapTimeToLive, kvp.Value);
vault_http_headers.WrapTimeToLiveHeaderPresent = true;
break;
}
}
}

return vault_http_headers;
}
}
10 changes: 10 additions & 0 deletions Client/Interfaces/Auth/IApproleClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using HashiVaultCs.Models.Requests.Auth.Approle;

namespace HashiVaultCs.Interfaces.Auth;

public interface IApproleClient : IVaultClient
{
Task<Secret> LoginAsync(Login data, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default);
Task<Secret> RoleIdAsync(string rolename, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default);
Task<Secret> SecretIdAsync(string rolename, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default);
}
8 changes: 8 additions & 0 deletions Client/Interfaces/Auth/IUserpassClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using HashiVaultCs.Models.Requests.Auth.Userpass;

namespace HashiVaultCs.Interfaces.Auth;

public interface IUserpassClient : IVaultClient
{
Task<Secret> LoginAsync(string username, Login data, IImmutableDictionary<string, string> headers, CancellationToken cancellationToken = default);
}
6 changes: 6 additions & 0 deletions Client/Interfaces/IHttpVaultClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HashiVaultCs.Interfaces;

public interface IHttpVaultClient
{
Task<JsonDocument> SendAsync(CancellationToken cancellationToken = default);
}
20 changes: 20 additions & 0 deletions Client/Interfaces/IHttpVaultHeaders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using HashiVaultCs;

namespace HashiVaultCs.Interfaces;

public interface IHttpVaultHeaders
{
bool RequestHeaderPresent { get; }
KeyValuePair<string, string> Request { get; }

bool TokenHeaderPresent { get; }
KeyValuePair<string, string> Token { get; }

bool NamespaceHeaderPresent { get; }
KeyValuePair<string, string> Namespace { get; }

bool WrapTimeToLiveHeaderPresent { get; }
KeyValuePair<string, string> WrapTimeToLive { get; }

HttpVaultHeaders Build(IReadOnlyDictionary<string, string> headers);
}
3 changes: 3 additions & 0 deletions Client/Interfaces/IVaultClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace HashiVaultCs.Interfaces;

public interface IVaultClient { }
Loading

0 comments on commit c29f714

Please sign in to comment.