Skip to content

Commit fad860b

Browse files
Added entry parameter to indicate whether existing Tags should be re-added to new cert on overwrite.
1 parent a808ec9 commit fad860b

File tree

6 files changed

+85
-30
lines changed

6 files changed

+85
-30
lines changed

AzureKeyVault/AzureClient.cs

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
using Azure.ResourceManager.KeyVault;
1818
using Azure.ResourceManager.KeyVault.Models;
1919
using Azure.ResourceManager.Resources;
20-
using Azure.ResourceManager.Resources.Models;
2120
using Azure.Security.KeyVault.Certificates;
2221
using Keyfactor.Logging;
2322
using Keyfactor.Orchestrators.Common.Enums;
2423
using Keyfactor.Orchestrators.Extensions;
2524
using Microsoft.Extensions.Logging;
26-
using Newtonsoft.Json;
2725

2826
namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
2927
{
@@ -196,7 +194,7 @@ public virtual async Task<KeyVaultResource> CreateVault()
196194
}
197195
}
198196

199-
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, string tags = null)
197+
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, Dictionary<string,string> tags)
200198
{
201199
try
202200
{
@@ -217,25 +215,14 @@ public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(
217215

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

220-
var tagDict = new Dictionary<string, string>();
218+
var options = new ImportCertificateOptions(certName, p12bytes);
221219

222-
if (!string.IsNullOrEmpty(tags))
220+
if (tags.Any())
223221
{
224-
if (!tags.IsValidJson())
222+
foreach (var tag in tags)
225223
{
226-
logger.LogError($"the entry parameter provided for Certificate Tags: \" {tags} \", does not seem to be valid JSON.");
227-
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.");
224+
options.Tags.Add(tag);
228225
}
229-
logger.LogTrace($"converting the json value provided for tags into a Dictionary<string,string>");
230-
tagDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(tags);
231-
logger.LogTrace($"{tagDict.Count} tag(s) will be associated with the certificate in Azure KeyVault");
232-
}
233-
234-
var options = new ImportCertificateOptions(certName, p12bytes);
235-
236-
foreach (var tag in tagDict.Keys)
237-
{
238-
options.Tags.Add(tag, tagDict[tag]);
239226
}
240227

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

398385
logger.LogWarning(warning);
399-
warnings.Add(warning);
400-
//throw;
386+
warnings.Add(warning);
401387
}
388+
402389
return (vaultNames, warnings);
403390
}
404391
}

AzureKeyVault/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ static class AzureKeyVaultConstants
1313
public const string STORE_TYPE_NAME = "AKV";
1414
}
1515

16+
static class EntryParameters {
17+
public const string TAGS = "CertificateTags";
18+
public const string PRESERVE_TAGS = "PreserveExistingTags";
19+
}
20+
1621
static class JobTypes
1722
{
1823
public const string CREATE = "Create";

AzureKeyVault/Jobs/AzureKeyVaultJob.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
1818
public abstract class AzureKeyVaultJob<T> : IOrchestratorJobExtension
1919
{
2020
public string ExtensionName => AzureKeyVaultConstants.STORE_TYPE_NAME;
21-
2221
internal protected virtual AzureClient AzClient { get; set; }
2322
internal protected virtual AkvProperties VaultProperties { get; set; }
24-
2523
internal protected IPAMSecretResolver PamSecretResolver { get; set; }
2624
internal protected ILogger logger { get; set; }
2725

AzureKeyVault/Jobs/Management.cs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
using Keyfactor.Orchestrators.Extensions;
1616
using Microsoft.Extensions.Logging;
1717
using Keyfactor.Orchestrators.Extensions.Interfaces;
18+
using System.Collections.Generic;
19+
using Newtonsoft.Json;
1820

1921
namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
2022
{
@@ -39,10 +41,16 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
3941
FailureMessage = "Invalid Management Operation"
4042
};
4143
object tagsObj;
44+
object preserveTagsObj;
4245
string tagsJSON;
46+
bool preserveTags;
4347

4448
config.JobProperties.TryGetValue("CertificateTags", out tagsObj);
45-
49+
50+
config.JobProperties.TryGetValue(EntryParameters.PRESERVE_TAGS, out preserveTagsObj);
51+
52+
preserveTags = (bool)preserveTagsObj;
53+
4654
tagsJSON = tagsObj as string;
4755

4856
switch (config.OperationType)
@@ -53,8 +61,8 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
5361
break;
5462
case CertStoreOperationType.Add:
5563
logger.LogDebug($"Begin Management > Add...");
56-
57-
complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite);
64+
65+
complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite, preserveTags);
5866
break;
5967
case CertStoreOperationType.Remove:
6068
logger.LogDebug($"Begin Management > Remove...");
@@ -96,10 +104,25 @@ protected async Task<JobResult> PerformCreateVault(long jobHistoryId)
96104
#endregion
97105

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

111+
var tagDict = new Dictionary<string, string>();
112+
113+
if (!string.IsNullOrEmpty(tagsJSON))
114+
{
115+
if (!tagsJSON.IsValidJson())
116+
{
117+
logger.LogError($"the entry parameter provided for Certificate Tags: \" {tagsJSON} \", does not seem to be valid JSON.");
118+
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.");
119+
}
120+
else
121+
{
122+
tagDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(tagsJSON);
123+
}
124+
}
125+
103126
if (!string.IsNullOrWhiteSpace(pfxPassword)) // This is a PFX Entry
104127
{
105128
if (string.IsNullOrWhiteSpace(alias))
@@ -110,11 +133,27 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
110133

111134
try
112135
{
136+
var existingTags = new Dictionary<string, string>();
137+
logger.LogTrace($"checking for an existing cert with the alias {alias}");
138+
var existing = AzClient.GetCertificate(alias).Result;
139+
if (existing != null)
140+
{
141+
logger.LogTrace($"there is an existing cert..");
142+
}
143+
144+
existingTags = existing?.Properties.Tags as Dictionary<string, string> ?? new Dictionary<string, string>();
145+
146+
logger.LogTrace("existing cert tags: ");
147+
if (!existingTags.Any()) logger.LogTrace("(none)");
148+
149+
foreach (var tag in existingTags)
150+
{
151+
logger.LogTrace(tag.Key + " : " + tag.Value);
152+
}
153+
113154
// if overwrite is unchecked, check for an existing cert first
114155
if (!overwrite)
115156
{
116-
logger.LogTrace($"checking for an existing cert with the alias {alias}");
117-
var existing = AzClient.GetCertificate(alias).Result;
118157
if (existing != null)
119158
{
120159
var message = $"A certificate named {alias} already exists and the overwrite checkbox was unchecked. No action was taken.";
@@ -124,8 +163,18 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
124163
return complete;
125164
}
126165
}
166+
else if (preserveTags)
167+
{ // if preserveTags is true, we want to get the existing tags before replacing the cert
168+
foreach (var existingTag in existingTags)
169+
{
170+
if (!tagDict.ContainsKey(existingTag.Key)) // if it's not being overwritten by what was provided..
171+
{
172+
tagDict[existingTag.Key] = existingTag.Value; // then we include it
173+
}
174+
}
175+
}
127176

128-
var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagsJSON).Result;
177+
var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagDict).Result;
129178

130179
// Ensure the return object has a AKV version tag, and Thumbprint
131180
if (!string.IsNullOrEmpty(cert.Properties.Version) &&

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
- 3.1.9
2+
- Added optional entry parameter to indicate that existing tags should be preserved if certificate is replaced
3+
14
- 3.1.8
25
- Fixed bug where enrollment would fail if the CertificateTags field was not defined as an entry parameter
36
- Convert to .net6/8 dual build

integration-manifest.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,20 @@
3333
"OnRemove": false,
3434
"OnReenrollment": false
3535
}
36-
}
36+
},
37+
{
38+
"Name": "PreserveExistingTags",
39+
"DisplayName": "Preserve Existing Tags",
40+
"Description": "If true, this will perform a union of any tags provided with enrollment with the tags on the existing cert with the same alias and apply the result to the new certificate.",
41+
"Type": "Bool",
42+
"DefaultValue": "False",
43+
"RequiredWhen": {
44+
"HasPrivateKey": false,
45+
"OnAdd": false,
46+
"OnRemove": false,
47+
"OnReenrollment": false
48+
}
49+
}
3750
],
3851
"JobProperties": [],
3952
"LocalStore": false,

0 commit comments

Comments
 (0)