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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.0.2
* bug fix: _certDataReader is now initialized in the Initialize method

## 1.0.1
* added retrieval of roles associated with enrolled certificates via metadata for Vault Enterprise users

Expand Down
6 changes: 6 additions & 0 deletions hashicorp-vault-cagateway/APIProxy/WrappedResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Keyfactor.Extensions.CAPlugin.HashicorpVault.APIProxy
{
public class WrappedResponse<T>
{
[JsonPropertyName("request_id")]
public string RequestId { get; set; }

[JsonPropertyName("lease_id")]
public string LeaseId { get; set; }

Expand All @@ -30,6 +33,9 @@ public class WrappedResponse<T>
[JsonPropertyName("mount_point")]
public string MountPoint { get; set; }

[JsonPropertyName("mount_type")]
public string MountType { get; set; }

[JsonPropertyName("mount_running_plugin_version")]
public string PluginVersion { get; set; }

Expand Down
12 changes: 9 additions & 3 deletions hashicorp-vault-cagateway/Client/HashicorpVaultClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,13 @@ public async Task<CertResponse> GetCertificate(string certSerial)
try
{
var response = await _vaultHttp.GetAsync<CertResponse>($"cert/{certSerial}");

logger.LogTrace($"successfully received a response for certificate with serial number: {certSerial}");
logger.LogTrace($"--response data--");
logger.LogTrace($"cert string: {response.Certificate}");
logger.LogTrace($"revocation time: {response.RevocationTime}");


return response;
}
catch (Exception ex)
Expand Down Expand Up @@ -189,7 +195,7 @@ public async Task<bool> PingServer()
}

/// <summary>
/// Retreives all serial numbers for issued certificates
/// Retrieves all serial numbers for issued certificates
/// </summary>
/// <returns>a list of the certificate serial number strings</returns>
public async Task<List<string>> GetAllCertSerialNumbers()
Expand Down Expand Up @@ -247,7 +253,7 @@ public async Task<List<string>> GetRoleNamesAsync()
}

/// <summary>
/// Retreives the metadata for the certificate
/// Retrieves the metadata for the certificate
/// </summary>
/// <param name="certSerial"></param>
/// <returns></returns>
Expand Down Expand Up @@ -275,7 +281,7 @@ public async Task<MetadataResponse> GetCertMetadata(string certSerial)
}
catch (Exception ex)
{
logger.LogError($"an error occurred when attempting to retreive the certificate metadata: {ex.Message}");
logger.LogError($"an error occurred when attempting to retrieve the certificate metadata: {ex.Message}");
throw;
}
finally { logger.MethodExit(); }
Expand Down
35 changes: 23 additions & 12 deletions hashicorp-vault-cagateway/Client/VaultHttp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ public VaultHttp(string host, string mountPoint, string authToken, string nameSp

_serializerOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
RespectNullableAnnotations = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
PropertyNameCaseInsensitive = true,
PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
RespectNullableAnnotations = true,
PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace
};

var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1") { ThrowOnAnyError = true };
var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1") { ThrowOnAnyError = true };
_restClient = new RestClient(restClientOptions, configureSerialization: s => s.UseSystemTextJson(_serializerOptions));

_mountPoint = mountPoint.TrimStart('/').TrimEnd('/'); // remove leading and trailing slashes
Expand Down Expand Up @@ -70,18 +70,29 @@ public async Task<T> GetAsync<T>(string path, Dictionary<string, string> paramet
{
logger.MethodEntry();
logger.LogTrace($"preparing to send GET request to {path} with parameters {JsonSerializer.Serialize(parameters)}");
logger.LogTrace($"will attempt to deserialize the response into a {typeof(T)}");

try
{
var request = new RestRequest($"{_mountPoint}/{path}", Method.Get);
if (parameters != null) { request.AddJsonBody(parameters); }

var response = await _restClient.ExecuteGetAsync<T>(request);
if (parameters != null && parameters.Keys.Count > 0) { request.AddJsonBody(parameters); }
var response = await _restClient.ExecuteGetAsync(request);

logger.LogTrace($"raw response: {response.Content}");

logger.LogTrace($"response status: {response.StatusCode}");

logger.LogTrace($"response error msg: {response.ErrorMessage}");

response.ThrowIfError();
if (string.IsNullOrEmpty(response.Content)) throw new Exception(response.ErrorMessage ?? "no content returned from Vault");

return response.Data;
logger.LogTrace($"deserializing the response into a {typeof(T)}");

var deserialized = JsonSerializer.Deserialize<T>(response.Content, _serializerOptions);

logger.LogTrace($"successfully deserialized the response");

return deserialized;
}
catch (Exception ex)
{
Expand All @@ -108,8 +119,8 @@ public async Task<T> PostAsync<T>(string path, dynamic parameters = default)
var request = new RestRequest(resourcePath, Method.Post);
if (parameters != null)
{
string serializedParams = JsonSerializer.Serialize(parameters, _serializerOptions);
logger.LogTrace($"serialized parameters (from {parameters.GetType()?.Name}): {serializedParams}");
string serializedParams = JsonSerializer.Serialize(parameters);
logger.LogTrace($"deserialized parameters (from {parameters.GetType()?.Name}): {serializedParams}");
request.AddJsonBody(serializedParams);
}

Expand All @@ -127,7 +138,7 @@ public async Task<T> PostAsync<T>(string path, dynamic parameters = default)

if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
errorResponse = JsonSerializer.Deserialize<ErrorResponse>(response.Content!);
errorResponse = JsonSerializer.Deserialize<ErrorResponse>(response.Content ?? "no content");
string allErrors = "(Bad Request)";
if (errorResponse?.Errors.Count > 0)
{
Expand Down
52 changes: 34 additions & 18 deletions hashicorp-vault-cagateway/HashicorpVaultCAConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;

namespace Keyfactor.Extensions.CAPlugin.HashicorpVault
{
Expand Down Expand Up @@ -50,10 +51,22 @@ public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDa
{
logger.MethodEntry(LogLevel.Trace);
string rawConfig = JsonSerializer.Serialize(configProvider.CAConnectionData);
logger.LogTrace($"serialized config: {rawConfig}");
_caConfig = JsonSerializer.Deserialize<HashicorpVaultCAConfig>(rawConfig);
logger.MethodExit(LogLevel.Trace);
_client = new HashicorpVaultClient(_caConfig);
_certificateDataReader = certificateDataReader;

Assembly targetAssembly = typeof(HashicorpVaultCAConnector).Assembly;

// Get the AssemblyName object
AssemblyName assemblyName = targetAssembly?.GetName();

// Get the Version object
Version version = assemblyName?.Version;

logger.LogTrace($"-- {assemblyName?.Name ?? "unknown"} v{version} --");

logger.LogTrace($"serialized config: {rawConfig}");
}

/// <summary>
Expand Down Expand Up @@ -239,7 +252,7 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
}
catch (Exception ex)
{
logger.LogError($"failed to retreive serial numbers: {LogHandler.FlattenException(ex)}");
logger.LogError($"failed to retrieve serial numbers: {LogHandler.FlattenException(ex)}");
throw;
}

Expand All @@ -250,25 +263,25 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
CertResponse certFromVault = null;
var dbStatus = -1;

// first, retreive the details from Vault
// first, retrieve the details from Vault
try
{
logger.LogTrace($"Calling GetCertificate on our client, passing serial number: {certSerial}");
certFromVault = await _client.GetCertificate(certSerial);
}
catch (Exception ex)
{
logger.LogError($"Failed to retreive details for certificate with serial number {certSerial} from Vault. Errors: {LogHandler.FlattenException(ex)}");
logger.LogError($"Failed to retrieve details for certificate with serial number {certSerial} from Vault. Errors: {LogHandler.FlattenException(ex)}");
throw;
}
logger.LogTrace($"converting {certSerial} to database trackingId");

var trackingId = certSerial.Replace(":", "-"); // we store with '-'; hashi stores with ':'
var trackingId = certSerial.Replace(":", "-"); // we store with '-'; hashi stores with ':'

// then, check for an existing local entry
try
{
logger.LogTrace($"attempting to retreive status of cert with tracking id {trackingId} from the database");
logger.LogTrace($"attempting to retrieve status of cert with tracking id {trackingId} from the database");
dbStatus = await _certificateDataReader.GetStatusByRequestID(trackingId);
}
catch
Expand All @@ -280,29 +293,29 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
{
logger.LogTrace($"adding cert with serial {trackingId} to the database. fullsync is {fullSync}, and the certificate {(dbStatus == -1 ? "does not yet exist" : "already exists")} in the database.");

logger.LogTrace("attempting to retreive the role name (productId) from the certificate metadata, if available");
logger.LogTrace("attempting to retrieve the role name (productId) from the certificate metadata, if available");

var metaData = new MetadataResponse();

try
{
metaData = await _client.GetCertMetadata(certSerial);
}
catch (Exception)
catch (Exception)
{
logger.LogTrace("an error occurred when attempting to retreive the metadata, continuing..");
logger.LogTrace("an error occurred when attempting to retrieve the metadata, continuing..");
}

var newCert = new AnyCAPluginCertificate
{
CARequestID = trackingId,
Certificate = certFromVault.Certificate,
Status = certFromVault.RevocationTime != null ? (int)EndEntityStatus.REVOKED : (int)EndEntityStatus.GENERATED,
RevocationDate = certFromVault.RevocationTime,
RevocationDate = certFromVault.RevocationTime,
};

// if we were able to get the role name from metadata, we include it
if (!string.IsNullOrEmpty(metaData?.Role))
if (!string.IsNullOrEmpty(metaData?.Role))
{
newCert.ProductID = metaData.Role;
}
Expand Down Expand Up @@ -346,7 +359,7 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
/// </summary>
/// <param name="connectionInfo">The information used to connect to the CA.</param>
public async Task ValidateCAConnectionInfo(Dictionary<string, object> connectionInfo)
{
{
logger.MethodEntry();
logger.LogTrace(message: $"Validating CA connection info: {JsonSerializer.Serialize(connectionInfo)}");

Expand All @@ -372,7 +385,7 @@ public async Task ValidateCAConnectionInfo(Dictionary<string, object> connection

// make sure an authentication mechanism is defined (either certificate or token)
var token = connectionInfo[Constants.CAConfig.TOKEN] as string;

//var cert = connectionInfo[Constants.CAConfig.CLIENTCERT] as string;

var cert = string.Empty; // temporary until client cert auth into vault is implemented
Expand Down Expand Up @@ -422,12 +435,12 @@ public async Task ValidateCAConnectionInfo(Dictionary<string, object> connection

_client = new HashicorpVaultClient(config);

// attempt an authenticated request to retreive role names
// attempt an authenticated request to retrieve role names
try
{
logger.LogTrace("making an authenticated request to the Vault server to verify credentials (listing role names)..");
var roleNames = await _client.GetRoleNamesAsync();
logger.LogTrace($"successfule request: received a response containing {roleNames.Count} role names");
logger.LogTrace($"successful request: received a response containing {roleNames?.Count} role names");
}
catch (Exception ex)
{
Expand Down Expand Up @@ -465,7 +478,7 @@ public Task ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary<st
logger.LogError(LogHandler.FlattenException(ex));
throw;
}

// if any errors, throw
if (errors.Any())
{
Expand Down Expand Up @@ -570,7 +583,10 @@ public List<string> GetProductIds()
try
{
logger.LogTrace("requesting role names from vault..");
var roleNames = _client.GetRoleNamesAsync().Result;
var roleNames = _client.GetRoleNamesAsync().GetAwaiter().GetResult();
if (roleNames == null) {
throw new Exception("no role names returned, or deserialization failed.");
}
logger.LogTrace($"got {roleNames.Count} role names from vault:");
foreach (var name in roleNames)
{
Expand Down
10 changes: 0 additions & 10 deletions hashicorp-vault-cagateway/Properties/launchSettings.json

This file was deleted.

32 changes: 9 additions & 23 deletions hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,20 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.0" />
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.0.0" />
<PackageReference Include="Keyfactor.Logging" Version="1.1.2" />
<PackageReference Include="Keyfactor.PKI" Version="5.5.0" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.0" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.1.0" />
<PackageReference Include="Keyfactor.Logging" Version="1.3.0" />
<PackageReference Include="Keyfactor.PKI" Version="8.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="RestSharp" Version="113.0.0" />
<PackageReference Include="System.Formats.Asn1" Version="10.0.0" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="10.0.0" />
<PackageReference Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>

<ItemGroup>
<Content Include="Connectors\manifest.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

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

<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion readme_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Certificates issued for the Hashicorp Vault CA from within the Keyfactor Command
1. Create an entry for each of the PKI secrets engine roles you would like to use for issuing certificates from the Hashicorp Vault CA.
1. Navigate to the "Certificate Authorities" tab and click "Edit"
1. In the "Edit CA" window, navigate to the "Templates" tab.
1. Create an association between each of the certificate profiles we just created with the PKI secrets engine roles retreived from Vault.
1. Create an association between each of the certificate profiles we just created with the PKI secrets engine roles retrieved from Vault.

### Configure the CA in Keyfactor Command

Expand Down
Loading