diff --git a/AzureEnterpriseApplicationOrchestrator.Tests/AzureEnterpriseApplicationOrchestrator.Tests.csproj b/AzureEnterpriseApplicationOrchestrator.Tests/AzureEnterpriseApplicationOrchestrator.Tests.csproj index 8a9f5e9..865dc95 100644 --- a/AzureEnterpriseApplicationOrchestrator.Tests/AzureEnterpriseApplicationOrchestrator.Tests.csproj +++ b/AzureEnterpriseApplicationOrchestrator.Tests/AzureEnterpriseApplicationOrchestrator.Tests.csproj @@ -11,8 +11,11 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AzureEnterpriseApplicationOrchestrator/AzureEnterpriseApplicationOrchestrator.csproj b/AzureEnterpriseApplicationOrchestrator/AzureEnterpriseApplicationOrchestrator.csproj index ffd4b51..0d8fe15 100644 --- a/AzureEnterpriseApplicationOrchestrator/AzureEnterpriseApplicationOrchestrator.csproj +++ b/AzureEnterpriseApplicationOrchestrator/AzureEnterpriseApplicationOrchestrator.csproj @@ -16,13 +16,15 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + + diff --git a/AzureEnterpriseApplicationOrchestrator/AzureSP2Jobs/Inventory.cs b/AzureEnterpriseApplicationOrchestrator/AzureSP2Jobs/Inventory.cs index 8b966e7..c35f273 100644 --- a/AzureEnterpriseApplicationOrchestrator/AzureSP2Jobs/Inventory.cs +++ b/AzureEnterpriseApplicationOrchestrator/AzureSP2Jobs/Inventory.cs @@ -105,5 +105,4 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd return result; } -} - +} \ No newline at end of file diff --git a/AzureEnterpriseApplicationOrchestrator/Client/GraphClient.cs b/AzureEnterpriseApplicationOrchestrator/Client/GraphClient.cs index e90ddc9..c2ff301 100644 --- a/AzureEnterpriseApplicationOrchestrator/Client/GraphClient.cs +++ b/AzureEnterpriseApplicationOrchestrator/Client/GraphClient.cs @@ -800,9 +800,25 @@ private OperationResult> InventoryFromKeyCrede foreach (KeyCredential keyCredential in keyCredentials) { - string customKeyIdentifier = Convert.ToHexString(keyCredential.CustomKeyIdentifier); + // CustomKeyIdentifier can be null for key credentials returned by Graph. Fall back to the + // KeyId so that the credential can still be tracked and reported instead of throwing. + string customKeyIdentifier; + if (keyCredential.CustomKeyIdentifier != null) + { + customKeyIdentifier = Convert.ToHexString(keyCredential.CustomKeyIdentifier); + } + else if (keyCredential.KeyId != null) + { + customKeyIdentifier = keyCredential.KeyId.ToString(); + _logger.LogWarning($"Key credential with DisplayName \"{keyCredential.DisplayName}\" has no CustomKeyIdentifier - using KeyId \"{customKeyIdentifier}\" instead"); + } + else + { + _logger.LogWarning($"Skipping key credential with DisplayName \"{keyCredential.DisplayName}\" because it has neither a CustomKeyIdentifier nor a KeyId"); + continue; + } - if (!string.IsNullOrWhiteSpace(keyCredential.Usage) && keyCredential.Usage.Equals("Sign", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(keyCredential.Usage)) { _logger.LogDebug($"Certificate with CustomKeyIdentifier \"{customKeyIdentifier}\" has a private key entry"); privateKeyMap[customKeyIdentifier] = true; @@ -810,7 +826,32 @@ private OperationResult> InventoryFromKeyCrede // We only track the case where the private key exists because there will be several keyCredentials // for Service Principals where one is the certificate and the other is the private key. - X509Certificate2 certificate = GetCertificateFromKeyCredential(keyCredential); + // If the thumbprint is already in the map, skip it + if (keyIdMap.ContainsKey(customKeyIdentifier)) + { + _logger.LogTrace($"Skipping certificate with CustomKeyIdentifier \"{customKeyIdentifier}\" because it's already in the inventory"); + continue; + } + + X509Certificate2 certificate; + try + { + certificate = GetCertificateFromKeyCredential(keyCredential); + } + catch (Exception ex) + { + string message = $"Exception retrieving certificate for key credential with DisplayName \"{keyCredential.DisplayName}\" ({customKeyIdentifier}): {ex.Message}"; + _logger.LogWarning(message); + failedCertificateMap[customKeyIdentifier] = message; + continue; + } + + if (!string.Equals(keyCredential.Type, "AsymmetricX509Cert", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug($"Skipping key credential with DisplayName \"{keyCredential.DisplayName}\" because its type is \"{keyCredential.Type}\", not a certificate"); + continue; + } + if (certificate == null) { string message = $"Unable to retrieve certificate for key credential with DisplayName \"{keyCredential.DisplayName}\" ({customKeyIdentifier})"; @@ -824,12 +865,6 @@ private OperationResult> InventoryFromKeyCrede failedCertificateMap.Remove(customKeyIdentifier); } - // If the thumbprint is already in the map, skip it - if (keyIdMap.ContainsKey(customKeyIdentifier)) - { - _logger.LogTrace($"Skipping certificate with CustomKeyIdentifier \"{customKeyIdentifier}\" because it's already in the inventory"); - continue; - } keyIdMap[customKeyIdentifier] = true; // Assemble the certificates for the inventory @@ -967,7 +1002,9 @@ protected IEnumerable DeepCopyPasswordList(List