Skip to content

Commit

Permalink
Merge 5d823d8 into 8cf092c
Browse files Browse the repository at this point in the history
  • Loading branch information
joevanwanzeeleKF authored Feb 13, 2025
2 parents 8cf092c + 5d823d8 commit 8fd9ac1
Show file tree
Hide file tree
Showing 14 changed files with 237 additions and 85 deletions.
13 changes: 7 additions & 6 deletions AzureKeyVault/AkvProperties.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System.Collections.Generic;

Expand Down
69 changes: 45 additions & 24 deletions AzureKeyVault/AzureClient.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Azure;
using Azure.Core;
Expand All @@ -17,11 +17,13 @@
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 @@ -194,7 +196,7 @@ public virtual async Task<KeyVaultResource> CreateVault()
}
}

public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword)
public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(string certName, string contents, string pfxPassword, string tags = null)
{
try
{
Expand All @@ -206,22 +208,37 @@ public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(
RecoverDeletedCertificateOperation recovery = await CertClient.StartRecoverDeletedCertificateAsync(certName);
recovery.WaitForCompletion();
}
logger.LogTrace("begin creating x509 certificate from contents.");
var bytes = Convert.FromBase64String(contents);

var x509Collection = new X509Certificate2Collection();//(bytes, pfxPassword, X509KeyStorageFlags.Exportable);
logger.LogTrace($"converting to pkcs12 without password for importing to keyvault");

var p12bytes = Helpers.ConvertPfxToPasswordlessPkcs12(contents, pfxPassword);

logger.LogTrace($"got a byte array with length {p12bytes.Length}");

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

x509Collection.Import(bytes, pfxPassword, X509KeyStorageFlags.Exportable);
var tagDict = new Dictionary<string, string>();

var certWithKey = x509Collection.Export(X509ContentType.Pkcs12);
if (!string.IsNullOrEmpty(tags))
{
if (!tags.IsValidJson())
{
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.");
}
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);

logger.LogTrace($"importing created x509 certificate named {1}", certName);
logger.LogTrace($"There are {x509Collection.Count} certificates in the chain.");
var cert = await CertClient.ImportCertificateAsync(new ImportCertificateOptions(certName, certWithKey));
foreach (var tag in tagDict.Keys)
{
options.Tags.Add(tag, tagDict[tag]);
}

// var fullCert = _secretClient.GetSecret(certName);
// The certificate must be retrieved as a secret from AKV in order to have the full chain included.
var cert = await CertClient.ImportCertificateAsync(options);

return cert;
}
Expand Down Expand Up @@ -278,8 +295,9 @@ public virtual async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesAsyn
var fullInventoryList = new List<CertificateProperties>();
var failedCount = 0;
Exception innerException = null;

await foreach (var cert in inventory) {

await foreach (var cert in inventory)
{
logger.LogTrace($"adding cert with ID: {cert.Id} to the list.");
fullInventoryList.Add(cert); // convert to list from pages
}
Expand All @@ -300,23 +318,26 @@ public virtual async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesAsyn
PrivateKeyEntry = true,
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
UseChainLevel = true,
Certificates = new List<string>() { Convert.ToBase64String(cert.Value.Cer) }
Certificates = new List<string>() { Convert.ToBase64String(cert.Value.Cer) },
Parameters = cert.Value.Properties.Tags as Dictionary<string, object>
});
}
catch (Exception ex)
{
failedCount++;
innerException = ex;
logger.LogError($"Failed to retreive details for certificate {certificate.Name}. Exception: {ex.Message}");
logger.LogError($"Failed to retreive details for certificate {certificate.Name}. Exception: {ex.Message}");
// continuing with inventory instead of throwing, in case there's an issue with a single certificate
}
}

if (failedCount == fullInventoryList.Count()) {
if (failedCount == fullInventoryList.Count() && failedCount > 0)
{
throw new Exception("Unable to retreive details for certificates.", innerException);
}

if (failedCount > 0) {
if (failedCount > 0)
{
logger.LogWarning($"{failedCount} of {fullInventoryList.Count()} certificates were not able to be retreieved. Please review the errors.");
}

Expand Down
15 changes: 8 additions & 7 deletions AzureKeyVault/AzureKeyVault.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Core" Version="1.44.1" />
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Azure.Core" Version="1.45.0" />
<PackageReference Include="Azure.Identity" Version="1.13.2" />
<PackageReference Include="Azure.ResourceManager" Version="1.13.0" />
<PackageReference Include="Azure.ResourceManager.KeyVault" Version="1.3.0" />
<PackageReference Include="Azure.ResourceManager.Resources" Version="1.9.0" />
<PackageReference Include="Azure.Security.KeyVault.Administration" Version="4.5.0" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" />
<PackageReference Include="Keyfactor.Logging" Version="1.1.1" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
<PackageReference Include="Keyfactor.Logging" Version="1.1.2" />
<PackageReference Include="Keyfactor.Orchestrators.Common" Version="3.2.0" />
<PackageReference Include="Keyfactor.Orchestrators.IOrchestratorJobExtensions" Version="0.7.0" />
<PackageReference Include="Keyfactor.Platform.IPAMProvider" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.1" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.66.1" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.68.0" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.68.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
Expand Down
13 changes: 7 additions & 6 deletions AzureKeyVault/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
{
Expand Down
89 changes: 89 additions & 0 deletions AzureKeyVault/Helpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text.Json.Nodes;

namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
{
public static class Helpers
{
public static bool IsValidJson(this string jsonString)
{
try
{
var unescapedJSON = System.Text.RegularExpressions.Regex.Unescape(jsonString);
var tmpObj = JsonValue.Parse(unescapedJSON);
}
catch (FormatException fex)
{
//Invalid json format
return false;
}
catch (Exception ex) //some other exception
{
return false;
}
return true;
}
public static byte[] ConvertPfxToPasswordlessPkcs12(string base64Pfx, string pfxPassword)
{
// Decode the Base64-encoded PFX data
byte[] pfxBytes = Convert.FromBase64String(base64Pfx);
using (var inputStream = new MemoryStream(pfxBytes))
{
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
store.Load(inputStream, pfxPassword.ToCharArray());

string alias = null;
foreach (string a in store.Aliases)
{
if (store.IsKeyEntry(a))
{
alias = a;
break;
}
}

using (var outputStream = new MemoryStream())
{
var newStore = builder.Build();

if (alias != null)
{
// Extract private key and certificate chain if available
var keyEntry = store.GetKey(alias);
var chain = store.GetCertificateChain(alias);
newStore.SetKeyEntry("converted-key", keyEntry, chain);
}
else
{
// If no private key, include just the certificate chain
foreach (string certAlias in store.Aliases)
{
if (store.IsCertificateEntry(certAlias))
{
var cert = store.GetCertificate(certAlias);
newStore.SetCertificateEntry(certAlias, cert);
}
}
}

// Save the new PKCS#12 store without a password
newStore.Save(outputStream, null, new SecureRandom());
return outputStream.ToArray();
}
}
}
}
}
13 changes: 7 additions & 6 deletions AzureKeyVault/JobAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System;

Expand Down
13 changes: 7 additions & 6 deletions AzureKeyVault/Jobs/AzureKeyVaultJob.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System;
using System.Collections.Generic;
Expand Down
13 changes: 7 additions & 6 deletions AzureKeyVault/Jobs/Discovery.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System;
using System.Collections.Generic;
Expand Down
Loading

0 comments on commit 8fd9ac1

Please # to comment.