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
27 changes: 7 additions & 20 deletions AzureKeyVault/AzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@
using Azure.ResourceManager.KeyVault;
using Azure.ResourceManager.KeyVault.Models;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
using Azure.Security.KeyVault.Certificates;
using Keyfactor.Logging;
using Keyfactor.Orchestrators.Common.Enums;
using Keyfactor.Orchestrators.Extensions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
{
Expand Down Expand Up @@ -196,7 +194,7 @@ public virtual async Task<KeyVaultResource> CreateVault()
}
}

public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, string tags = null)
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, Dictionary<string,string> tags)
{
try
{
Expand All @@ -217,25 +215,14 @@ public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(

logger.LogTrace($"calling ImportCertificateAsync on the KeyVault certificate client to import certificate {certName}");

var tagDict = new Dictionary<string, string>();
var options = new ImportCertificateOptions(certName, p12bytes);

if (!string.IsNullOrEmpty(tags))
if (tags.Any())
{
if (!tags.IsValidJson())
foreach (var tag in tags)
{
logger.LogError($"the entry parameter provided for Certificate Tags: \" {tags} \", does not seem to be valid JSON.");
throw new Exception($"the string \" {tags} \" is not a valid json string. Please enter a valid json string for CertificateTags in the entry parameter or leave empty for no tags to be applied.");
options.Tags.Add(tag);
}
logger.LogTrace($"converting the json value provided for tags into a Dictionary<string,string>");
tagDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(tags);
logger.LogTrace($"{tagDict.Count} tag(s) will be associated with the certificate in Azure KeyVault");
}

var options = new ImportCertificateOptions(certName, p12bytes);

foreach (var tag in tagDict.Keys)
{
options.Tags.Add(tag, tagDict[tag]);
}

var cert = await CertClient.ImportCertificateAsync(options);
Expand Down Expand Up @@ -396,9 +383,9 @@ public virtual (List<string>, List<string>) GetVaults()
var warning = $"Exception thrown performing discovery on tenantId {searchTenantId} and subscription ID {searchSubscription}. Exception message: {ex.Message}";

logger.LogWarning(warning);
warnings.Add(warning);
//throw;
warnings.Add(warning);
}

return (vaultNames, warnings);
}
}
Expand Down
5 changes: 5 additions & 0 deletions AzureKeyVault/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ static class AzureKeyVaultConstants
public const string STORE_TYPE_NAME = "AKV";
}

static class EntryParameters {
public const string TAGS = "CertificateTags";
public const string PRESERVE_TAGS = "PreserveExistingTags";
}

static class JobTypes
{
public const string CREATE = "Create";
Expand Down
2 changes: 0 additions & 2 deletions AzureKeyVault/Jobs/AzureKeyVaultJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
public abstract class AzureKeyVaultJob<T> : IOrchestratorJobExtension
{
public string ExtensionName => AzureKeyVaultConstants.STORE_TYPE_NAME;

internal protected virtual AzureClient AzClient { get; set; }
internal protected virtual AkvProperties VaultProperties { get; set; }

internal protected IPAMSecretResolver PamSecretResolver { get; set; }
internal protected ILogger logger { get; set; }

Expand Down
67 changes: 60 additions & 7 deletions AzureKeyVault/Jobs/Management.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Keyfactor.Orchestrators.Extensions;
using Microsoft.Extensions.Logging;
using Keyfactor.Orchestrators.Extensions.Interfaces;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
{
Expand All @@ -38,8 +40,18 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
Result = OrchestratorJobStatusJobResult.Failure,
FailureMessage = "Invalid Management Operation"
};
object tagsObj;
object preserveTagsObj;
string tagsJSON;
bool preserveTags;

var tagsJSON = config.JobProperties["CertificateTags"]?.ToString();
config.JobProperties.TryGetValue("CertificateTags", out tagsObj);

config.JobProperties.TryGetValue(EntryParameters.PRESERVE_TAGS, out preserveTagsObj);

preserveTags = (bool)preserveTagsObj;

tagsJSON = tagsObj as string;

switch (config.OperationType)
{
Expand All @@ -49,8 +61,8 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
break;
case CertStoreOperationType.Add:
logger.LogDebug($"Begin Management > Add...");
complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite);

complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite, preserveTags);
break;
case CertStoreOperationType.Remove:
logger.LogDebug($"Begin Management > Remove...");
Expand Down Expand Up @@ -92,10 +104,25 @@ protected async Task<JobResult> PerformCreateVault(long jobHistoryId)
#endregion

#region Add
protected virtual JobResult PerformAddition(string alias, string pfxPassword, string entryContents, string tagsJSON, long jobHistoryId, bool overwrite)
protected virtual JobResult PerformAddition(string alias, string pfxPassword, string entryContents, string tagsJSON, long jobHistoryId, bool overwrite, bool preserveTags)
{
var complete = new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = jobHistoryId };

var tagDict = new Dictionary<string, string>();

if (!string.IsNullOrEmpty(tagsJSON))
{
if (!tagsJSON.IsValidJson())
{
logger.LogError($"the entry parameter provided for Certificate Tags: \" {tagsJSON} \", does not seem to be valid JSON.");
throw new Exception($"the string \" {tagsJSON} \" is not a valid json string. Please enter a valid json string for CertificateTags in the entry parameter or leave empty for no tags to be applied.");
}
else
{
tagDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(tagsJSON);
}
}

if (!string.IsNullOrWhiteSpace(pfxPassword)) // This is a PFX Entry
{
if (string.IsNullOrWhiteSpace(alias))
Expand All @@ -106,11 +133,27 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st

try
{
var existingTags = new Dictionary<string, string>();
logger.LogTrace($"checking for an existing cert with the alias {alias}");
var existing = AzClient.GetCertificate(alias).Result;
if (existing != null)
{
logger.LogTrace($"there is an existing cert..");
}

existingTags = existing?.Properties.Tags as Dictionary<string, string> ?? new Dictionary<string, string>();

logger.LogTrace("existing cert tags: ");
if (!existingTags.Any()) logger.LogTrace("(none)");

foreach (var tag in existingTags)
{
logger.LogTrace(tag.Key + " : " + tag.Value);
}

// if overwrite is unchecked, check for an existing cert first
if (!overwrite)
{
logger.LogTrace($"checking for an existing cert with the alias {alias}");
var existing = AzClient.GetCertificate(alias).Result;
if (existing != null)
{
var message = $"A certificate named {alias} already exists and the overwrite checkbox was unchecked. No action was taken.";
Expand All @@ -120,8 +163,18 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
return complete;
}
}
else if (preserveTags)
{ // if preserveTags is true, we want to get the existing tags before replacing the cert
foreach (var existingTag in existingTags)
{
if (!tagDict.ContainsKey(existingTag.Key)) // if it's not being overwritten by what was provided..
{
tagDict[existingTag.Key] = existingTag.Value; // then we include it
}
}
}

var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagsJSON).Result;
var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagDict).Result;

// Ensure the return object has a AKV version tag, and Thumbprint
if (!string.IsNullOrEmpty(cert.Properties.Version) &&
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
- 3.1.9
- Added optional entry parameter to indicate that existing tags should be preserved if certificate is replaced

- 3.1.8
- Fixed bug where enrollment would fail if the CertificateTags field was not defined as an entry parameter
- Convert to .net6/8 dual build
Expand Down
Loading