Skip to content

Commit 89b7265

Browse files
authored
Merge pull request #67 from Keyfactor/release-3.1
Release 3.1.9
2 parents 3641e96 + 9774959 commit 89b7265

File tree

9 files changed

+299
-179
lines changed

9 files changed

+299
-179
lines changed

AzureKeyVault/AkvProperties.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal protected string VaultEndpoint
4545
{
4646
return PrivateEndpoint.TrimStart('.');
4747
}
48-
switch (AzureCloud)
48+
switch (AzureCloud?.Trim()?.ToLowerInvariant())
4949
{
5050
case "china":
5151
return "vault.azure.cn";
@@ -58,6 +58,6 @@ internal protected string VaultEndpoint
5858
}
5959
}
6060
}
61-
internal protected string VaultURL => $"https://{VaultName}.{VaultEndpoint}/";
61+
public string VaultURL => $"https://{VaultName}.{VaultEndpoint}/";
6262
}
6363
}

AzureKeyVault/AzureClient.cs

Lines changed: 14 additions & 22 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
{
@@ -34,16 +32,19 @@ private Uri AzureCloudEndpoint
3432
{
3533
get
3634
{
37-
switch (VaultProperties.AzureCloud?.ToLower())
35+
logger.LogTrace($"the AzureCloud is {VaultProperties.AzureCloud}, so we will use the following endpoint for authentication: ");
36+
switch (VaultProperties.AzureCloud?.Trim()?.ToLowerInvariant())
3837
{
39-
4038
case "china":
39+
logger.LogTrace(AzureAuthorityHosts.AzureChina.ToString());
4140
return AzureAuthorityHosts.AzureChina;
4241
//case "germany":
4342
// return AzureAuthorityHosts.AzureGermany; // germany is no longer a valid azure authority host as of 2021
4443
case "government":
44+
logger.LogTrace(AzureAuthorityHosts.AzureGovernment.ToString());
4545
return AzureAuthorityHosts.AzureGovernment;
4646
default:
47+
logger.LogTrace(AzureAuthorityHosts.AzurePublicCloud.ToString());
4748
return AzureAuthorityHosts.AzurePublicCloud;
4849
}
4950
}
@@ -94,6 +95,8 @@ internal protected virtual ArmClient getArmClient(string tenantId)
9495
{
9596
TokenCredential credential;
9697
var credentialOptions = new DefaultAzureCredentialOptions { AuthorityHost = AzureCloudEndpoint, AdditionallyAllowedTenants = { "*" } };
98+
logger.LogTrace($"creating an ARM client for management operations with authorityhost {AzureCloudEndpoint.ToString()}");
99+
97100
if (this.VaultProperties.UseAzureManagedIdentity)
98101
{
99102
logger.LogTrace("getting management client for a managed identity");
@@ -196,7 +199,7 @@ public virtual async Task<KeyVaultResource> CreateVault()
196199
}
197200
}
198201

199-
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, string tags = null)
202+
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, Dictionary<string,string> tags)
200203
{
201204
try
202205
{
@@ -217,25 +220,14 @@ public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(
217220

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

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

222-
if (!string.IsNullOrEmpty(tags))
225+
if (tags.Any())
223226
{
224-
if (!tags.IsValidJson())
227+
foreach (var tag in tags)
225228
{
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.");
229+
options.Tags.Add(tag);
228230
}
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]);
239231
}
240232

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

398390
logger.LogWarning(warning);
399-
warnings.Add(warning);
400-
//throw;
391+
warnings.Add(warning);
401392
}
393+
402394
return (vaultNames, warnings);
403395
}
404396
}

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: 6 additions & 5 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

@@ -96,12 +94,15 @@ public void InitializeStore(dynamic config)
9694
VaultProperties.SubscriptionId = properties.SubscriptionId ?? VaultProperties.SubscriptionId;
9795
VaultProperties.ResourceGroupName = !string.IsNullOrEmpty(properties.ResourceGroupName as string) ? properties.ResourceGroupName : VaultProperties.ResourceGroupName;
9896
VaultProperties.VaultName = properties.VaultName ?? VaultProperties.VaultName; // check the field in case of legacy paths.
97+
9998
VaultProperties.TenantId = !string.IsNullOrEmpty(VaultProperties.TenantId) ? VaultProperties.TenantId : config.CertificateStoreDetails?.ClientMachine; // Client Machine could be null in the case of managed identity. That's ok.
100-
VaultProperties.AzureCloud = !string.IsNullOrEmpty(properties.AzureCloud as string) ? properties.AzureCloud : VaultProperties.AzureCloud;
101-
VaultProperties.PrivateEndpoint = !string.IsNullOrEmpty(properties.PrivateEndpoint as string) ? properties.PrivateEndpoint : VaultProperties.PrivateEndpoint;
102-
99+
VaultProperties.AzureCloud = properties.AzureCloud;
100+
logger.LogTrace($"Azure Cloud: {VaultProperties.AzureCloud}");
101+
VaultProperties.PrivateEndpoint = properties.PrivateEndpoint;
102+
logger.LogTrace($"Private Endpoint: {VaultProperties.PrivateEndpoint}");
103103
string skuType = !string.IsNullOrEmpty(properties.SkuType as string) ? properties.SkuType : null;
104104
VaultProperties.PremiumSKU = skuType?.ToLower() == "premium";
105+
105106
VaultProperties.VaultRegion = !string.IsNullOrEmpty(properties.VaultRegion as string) ? properties.VaultRegion : VaultProperties.VaultRegion;
106107
VaultProperties.VaultRegion = VaultProperties.VaultRegion?.ToLower();
107108
}

AzureKeyVault/Jobs/Inventory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
4040

4141
inventoryItems = AzClient.GetCertificatesAsync().Result?.ToList();
4242

43-
logger.LogTrace($"Found {inventoryItems.Count()} Total Certificates in Azure Key Vault.");
43+
logger.LogTrace($"Found {inventoryItems.Count} Total Certificates in Azure Key Vault.");
4444
}
4545

4646
catch (Exception ex)

AzureKeyVault/Jobs/Management.cs

Lines changed: 60 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
{
@@ -38,8 +40,18 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
3840
Result = OrchestratorJobStatusJobResult.Failure,
3941
FailureMessage = "Invalid Management Operation"
4042
};
43+
object tagsObj;
44+
object preserveTagsObj;
45+
string tagsJSON;
46+
bool preserveTags;
4147

42-
var tagsJSON = config.JobProperties["CertificateTags"]?.ToString();
48+
config.JobProperties.TryGetValue("CertificateTags", out tagsObj);
49+
50+
config.JobProperties.TryGetValue(EntryParameters.PRESERVE_TAGS, out preserveTagsObj);
51+
52+
preserveTags = (bool)preserveTagsObj;
53+
54+
tagsJSON = tagsObj as string;
4355

4456
switch (config.OperationType)
4557
{
@@ -49,8 +61,8 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
4961
break;
5062
case CertStoreOperationType.Add:
5163
logger.LogDebug($"Begin Management > Add...");
52-
53-
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);
5466
break;
5567
case CertStoreOperationType.Remove:
5668
logger.LogDebug($"Begin Management > Remove...");
@@ -92,10 +104,25 @@ protected async Task<JobResult> PerformCreateVault(long jobHistoryId)
92104
#endregion
93105

94106
#region Add
95-
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)
96108
{
97109
var complete = new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = jobHistoryId };
98110

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+
99126
if (!string.IsNullOrWhiteSpace(pfxPassword)) // This is a PFX Entry
100127
{
101128
if (string.IsNullOrWhiteSpace(alias))
@@ -106,11 +133,27 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
106133

107134
try
108135
{
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+
109154
// if overwrite is unchecked, check for an existing cert first
110155
if (!overwrite)
111156
{
112-
logger.LogTrace($"checking for an existing cert with the alias {alias}");
113-
var existing = AzClient.GetCertificate(alias).Result;
114157
if (existing != null)
115158
{
116159
var message = $"A certificate named {alias} already exists and the overwrite checkbox was unchecked. No action was taken.";
@@ -120,8 +163,18 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
120163
return complete;
121164
}
122165
}
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+
}
123176

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

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

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
- 3.1.9
2+
- Added optional entry parameter to indicate that existing tags should be preserved if certificate is replaced
3+
- bug fix for government cloud host name resolution
4+
15
- 3.1.8
26
- Fixed bug where enrollment would fail if the CertificateTags field was not defined as an entry parameter
37
- Convert to .net6/8 dual build

0 commit comments

Comments
 (0)