Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 69 additions & 44 deletions hashicorp-vault-cagateway/Client/HashicorpVaultClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Asn1.X509;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Keyfactor.Extensions.CAPlugin.HashicorpVault
Expand All @@ -25,15 +27,16 @@ namespace Keyfactor.Extensions.CAPlugin.HashicorpVault
/// </summary>
public class HashicorpVaultClient
{
private VaultHttp _vaultHttp { get; set; }
private static readonly ILogger logger = LogHandler.GetClassLogger<HashicorpVaultClient>();
private HashicorpVaultCAConfig _caConfig { get; set; }
private HashicorpVaultCATemplateConfig _templateConfig { get; set; }
private readonly ILogger logger;

public HashicorpVaultClient(HashicorpVaultCAConfig caConfig, HashicorpVaultCATemplateConfig templateConfig = null)
{
logger = LogHandler.GetClassLogger<HashicorpVaultClient>();
logger.MethodEntry();

SetClientValuesFromConfigs(caConfig, templateConfig);

_caConfig = caConfig;
_templateConfig = templateConfig;
logger.MethodExit();
}

Expand All @@ -48,8 +51,9 @@ public async Task<SignResponse> SignCSR(string csr, string subject, Dictionary<s
string commonName = null, organization = null, orgUnit = null;

logger.LogTrace($"SAN values: ");
foreach (var key in san.Keys) {
logger.LogTrace($"{key}: {string.Join(",", san[key])}");
foreach (var key in san.Keys)
{
logger.LogTrace($"{key}: {string.Join(",", san[key])}");
}

if (san.ContainsKey("dnsname"))
Expand Down Expand Up @@ -90,7 +94,7 @@ public async Task<SignResponse> SignCSR(string csr, string subject, Dictionary<s
}
else
{
throw new Exception("No Common Name or DNS SAN provided, unable to enroll");
throw new Exception("no Common Name or DNS SAN provided, unable to enroll");
}
}

Expand All @@ -101,16 +105,17 @@ public async Task<SignResponse> SignCSR(string csr, string subject, Dictionary<s
Format = "pem_bundle",
CSR = csr
};
var _vaultHttp = ConfigureNewVaultClient();

logger.LogTrace($"sending request to vault..");
logger.LogTrace($"serialized request: {JsonSerializer.Serialize(request)}");
response = await _vaultHttp.PostAsync<WrappedResponse<SignResponse>>($"sign/{roleName}", request);
logger.LogTrace($"got a response from vault..");

if (response.Warnings?.Count > 0) { logger.LogTrace($"the response contained warnings: {string.Join(", ", response.Warnings)}"); }
logger.LogTrace($"serialized SignResponse: {JsonSerializer.Serialize(response.Data)}");

logger.LogTrace($"serialized SignResponse: {JsonSerializer.Serialize(response.Data)}");

return response.Data;
}
catch (Exception ex)
Expand All @@ -131,6 +136,7 @@ public async Task<CertResponse> GetCertificate(string certSerial)

try
{
var _vaultHttp = ConfigureNewVaultClient();
var response = await _vaultHttp.GetAsync<CertResponse>($"cert/{certSerial}");
logger.LogTrace($"successfully received a response for certificate with serial number: {certSerial}");
return response;
Expand All @@ -151,7 +157,8 @@ public async Task<RevokeResponse> RevokeCertificate(string serial)
logger.MethodEntry();
logger.LogTrace($"making request to revoke cert with serial: {serial}");
try
{
{
var _vaultHttp = ConfigureNewVaultClient();
var response = await _vaultHttp.PostAsync<RevokeResponse>("revoke", new RevokeRequest(serial));
logger.LogTrace($"successfully revoked cert with serial {serial}, revocation time: {response.RevocationTime}");
return response;
Expand All @@ -170,11 +177,12 @@ public async Task<bool> PingServer()
logger.LogTrace($"performing a system health check request to Vault");
try
{
var _vaultHttp = ConfigureNewVaultClient();
var res = await _vaultHttp.HealthCheckAsync();
logger.LogTrace($"-- Vault health check response --");
logger.LogTrace($"Vault version : {res.VaultVersion}");
logger.LogTrace($"sealed? : {res.Sealed}");
logger.LogTrace($"initialized? : {res.Initialized}");
logger.LogTrace($"initialized? : {res.Initialized}");
return true;
}
catch (Exception ex)
Expand All @@ -192,18 +200,30 @@ public async Task<bool> PingServer()
/// Retreives all serial numbers for issued certificates
/// </summary>
/// <returns>a list of the certificate serial number strings</returns>
public async Task<List<string>> GetAllCertSerialNumbers()
public async Task<List<string>> GetAllCertSerialNumbers(CancellationToken token)
{
var certSerials = new List<string>();
if (token.IsCancellationRequested)
{
logger.LogWarning($"cancelation was requested; stopping task");
return certSerials;
}
logger.MethodEntry();
var keys = new List<string>();
try
{
var _vaultHttp = ConfigureNewVaultClient();
var res = await _vaultHttp.GetAsync<WrappedResponse<KeyedList>>("certs/?list=true");
return res.Data.Entries;
var serials = res.Data?.Entries;
if (serials == null || serials.Count == 0)
{
return certSerials;
}
logger.LogTrace($"got {res.Data?.Entries?.Count} serial numbers from {_caConfig.Host} namespace: {_caConfig.Namespace}, mount-point: {_caConfig.MountPoint}");
return serials;
}
catch (Exception ex)
{
logger.LogError($"there was an error retreiving the certificate keys: {ex.Message}");
logger.LogError($"there was an error retreiving the certificate keys from {_caConfig.Host} using namespace: {_templateConfig.Namespace ?? _caConfig.Namespace} and mount-point: {_caConfig.MountPoint}. Error: {ex.Message}");
throw;
}
finally { logger.MethodExit(); }
Expand All @@ -215,6 +235,7 @@ private async Task<List<string>> GetRevokedSerialNumbers()
var keys = new List<string>();
try
{
var _vaultHttp = ConfigureNewVaultClient();
var res = await _vaultHttp.GetAsync<KeyedList>("certs/revoked");
keys = res.Entries;
}
Expand All @@ -233,6 +254,7 @@ public async Task<List<string>> GetRoleNamesAsync()
var roleNames = new List<string>();
try
{
var _vaultHttp = ConfigureNewVaultClient();
logger.LogTrace("getting the role names as a wrapped keyed-list response..");
var response = await _vaultHttp.GetAsync<WrappedResponse<KeyedList>>("roles/?list=true");
logger.LogTrace($"received {response.Data?.Entries?.Count} role names (or product IDs)");
Expand All @@ -243,44 +265,47 @@ public async Task<List<string>> GetRoleNamesAsync()
logger.LogError($"There was a problem retreiving the PKI role names: {LogHandler.FlattenException(ex)}");
throw;
}
finally { logger.MethodExit(); }
finally { logger.MethodExit(); }
}

private void SetClientValuesFromConfigs(HashicorpVaultCAConfig caConfig, HashicorpVaultCATemplateConfig templateConfig)
private VaultHttp ConfigureNewVaultClient()
{
logger.MethodEntry();
try
{
var hostUrl = _caConfig.Host; // host url and authentication details come from the CA config
logger.LogTrace($"set value for Host url: {hostUrl}");

var hostUrl = caConfig.Host; // host url and authentication details come from the CA config
var token = caConfig.Token;
var nameSpace = string.IsNullOrEmpty(templateConfig?.Namespace) ? caConfig.Namespace : templateConfig.Namespace; // Namespace comes from templateconfig if available, otherwise defaults to caConfig; can be null
var mountPoint = string.IsNullOrEmpty(templateConfig?.MountPoint) ? caConfig.MountPoint : templateConfig.MountPoint; // Mountpoint comes from templateconfig if available, otherwise defaults to caConfig; if null, uses "pki" (Vault Default)
mountPoint = mountPoint ?? "pki"; // using the vault default PKI secrets engine mount point if not present in config

logger.LogTrace($"set value for Host url: {hostUrl}");
logger.LogTrace($"set value for authentication token: {token ?? "(not defined)"}");
logger.LogTrace($"set value for Namespace: {nameSpace ?? "(not defined)"}");
logger.LogTrace($"set value for Mountpoint: {mountPoint}");
var token = _caConfig.Token;
logger.LogTrace($"set value for authentication token: {token ?? "(not defined)"}");

// _certAuthInfo = caConfig?.ClientCertificate;
// logger.LogTrace($"set value for Certificate authentication; thumbprint: {_certAuthInfo?.Thumbprint ?? "(missing) - using token authentication"}");
var nameSpace = string.IsNullOrEmpty(_templateConfig?.Namespace) ? _caConfig.Namespace : _templateConfig.Namespace; // Namespace comes from templateconfig if available, otherwise defaults to caConfig; can be null
logger.LogTrace($"set value for Namespace: {nameSpace ?? "(not defined)"}");

//if (_token == null && _certAuthInfo == null)
//{
// throw new MissingFieldException("Either an authentication token or certificate to use for authentication into Vault must be provided.");
//}
var mountPoint = string.IsNullOrEmpty(_templateConfig?.MountPoint) ? _caConfig.MountPoint : _templateConfig.MountPoint; // Mountpoint comes from templateconfig if available, otherwise defaults to caConfig; if null, uses "pki" (Vault Default)
mountPoint = mountPoint ?? "pki"; // using the vault default PKI secrets engine mount point if not present in config
logger.LogTrace($"set value for Mountpoint: {mountPoint}");

_vaultHttp = new VaultHttp(hostUrl, mountPoint, token, nameSpace);

logger.MethodExit();
}
// _certAuthInfo = caConfig?.ClientCertificate;
// logger.LogTrace($"set value for Certificate authentication; thumbprint: {_certAuthInfo?.Thumbprint ?? "(missing) - using token authentication"}");

private static string ConvertSerialToTrackingId(string serialNumber)
{
// vault returns certificate serial formatted thusly: 17:67:16:b0:b9:45:58:c0:3a:29:e3:cb:d6:98:33:7a:a6:3b:66:c1
// we cannot use the ':' character as part of our internal tracking id, but Vault requests can work with either ':' or '-'
// so we convert from colon-separated pairs to hyphen separated pairs.
//if (_token == null && _certAuthInfo == null)
//{
// throw new MissingFieldException("Either an authentication token or certificate to use for authentication into Vault must be provided.");
//}

return serialNumber.Replace(":", "-");
return new VaultHttp(hostUrl, mountPoint, token, nameSpace);
}
catch (Exception ex)
{
logger.LogError($"error when creating new vault client: {LogHandler.FlattenException(ex)}");
throw;
}
finally
{
logger.MethodExit();
}
}
}
}
Loading