From e87406c124e5cd72c5d070b1369ed86adddc0e2f Mon Sep 17 00:00:00 2001 From: Mohammad Moattar Date: Fri, 2 Jul 2021 17:29:26 +0100 Subject: [PATCH 1/5] Added keyvault support --- ...Easify.Azure.AspNetCore.AppInsights.csproj | 2 +- .../Easify.Azure.AspNetCore.csproj | 19 ++++++++ .../KeyVaults/KeyVaultExtensions.cs | 45 +++++++++++++++++++ .../KeyVaults/KeyVaultOptions.cs | 10 +++++ src/Easify.Azure.sln | 6 +++ src/Easify.Azure/Easify.Azure.csproj | 7 +++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj create mode 100644 src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs create mode 100644 src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs create mode 100644 src/Easify.Azure/Easify.Azure.csproj diff --git a/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj b/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj index 12dc0f3..37c94c2 100644 --- a/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj +++ b/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj b/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj new file mode 100644 index 0000000..34b9df7 --- /dev/null +++ b/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + true + Easify.AspNetCore.Observability.AppInsights + Mohammad Moattar + https://github.com/icgam/Easify.Azure + https://github.com/icgam/Easify.Azure + default + + + + + + + + + diff --git a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs new file mode 100644 index 0000000..6480101 --- /dev/null +++ b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Azure.Extensions.AspNetCore.Configuration.Secrets; +using Azure.Identity; +using Microsoft.Extensions.Configuration; + +namespace Easify.Azure.AspNetCore.KeyVaults +{ + public static class KeyVaultExtensions + { + public static IConfigurationBuilder AddAzureKeyVault(this IConfigurationBuilder builder) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var config = builder.Build(); + var options = new KeyVaultOptions(); + config.GetSection(nameof(KeyVaultOptions)).Bind(options); + + if (!options.KeyVaultNames.Any()) + return builder; + + using var store = new X509Store(StoreLocation.LocalMachine); + store.Open(OpenFlags.ReadOnly); + var certs = store.Certificates.Find( + X509FindType.FindByThumbprint, + config[options.AzureAdApplicationCertThumbprint], false); + + foreach (var keyVaultName in options.KeyVaultNames) + { + builder.AddAzureKeyVault(new Uri($"https://{keyVaultName}.vault.azure.net/"), + new ClientCertificateCredential(options.AzureAdTenantId, options.AzureAdApplicationId, certs.OfType().Single()), + new KeyVaultSecretManager()); + } + store.Close(); + + return builder; + } + + public static IConfigurationBuilder AddAzureKeyVault(this ConfigurationBuilder builder, Func condition) + { + return condition() ? builder.AddAzureKeyVault() : builder; + } + } +} \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs new file mode 100644 index 0000000..1e17a2c --- /dev/null +++ b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs @@ -0,0 +1,10 @@ +namespace Easify.Azure.AspNetCore.KeyVaults +{ + public class KeyVaultOptions + { + public string AzureAdTenantId { get; set; } + public string AzureAdApplicationId { get; set; } + public string AzureAdApplicationCertThumbprint { get; set; } + public string[] KeyVaultNames { get; set; } = { }; + } +} \ No newline at end of file diff --git a/src/Easify.Azure.sln b/src/Easify.Azure.sln index ee8770b..fee01e6 100644 --- a/src/Easify.Azure.sln +++ b/src/Easify.Azure.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easify.Azure.AspNetCore.App EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easify.Azure.AspNetCore.AppInsights.UnitTests", "Easify.Azure.AspNetCore.AppInsights.UnitTests\Easify.Azure.AspNetCore.AppInsights.UnitTests.csproj", "{A80E9116-95B6-43A5-A227-4C96D6C8ADB5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easify.Azure.AspNetCore", "Easify.Azure.AspNetCore\Easify.Azure.AspNetCore.csproj", "{D413300A-5272-4D1C-9F14-5E20C1D3B24E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {A80E9116-95B6-43A5-A227-4C96D6C8ADB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {A80E9116-95B6-43A5-A227-4C96D6C8ADB5}.Release|Any CPU.ActiveCfg = Release|Any CPU {A80E9116-95B6-43A5-A227-4C96D6C8ADB5}.Release|Any CPU.Build.0 = Release|Any CPU + {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Easify.Azure/Easify.Azure.csproj b/src/Easify.Azure/Easify.Azure.csproj new file mode 100644 index 0000000..2756020 --- /dev/null +++ b/src/Easify.Azure/Easify.Azure.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + From dcd606e96d6fd48500f4a7af904030146186e1cd Mon Sep 17 00:00:00 2001 From: Mohammad Moattar Date: Mon, 5 Jul 2021 09:32:10 +0100 Subject: [PATCH 2/5] Fix the title and project specifications --- .../Easify.Azure.AspNetCore.AppInsights.csproj | 2 +- src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj b/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj index 37c94c2..e223119 100644 --- a/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj +++ b/src/Easify.Azure.AspNetCore.AppInsights/Easify.Azure.AspNetCore.AppInsights.csproj @@ -3,7 +3,7 @@ netstandard2.0 true - Easify.AspNetCore.Observability.AppInsights + Easify.Azure.AspNetCore.AppInsights Mohammad Moattar https://github.com/icgam/Easify.Azure https://github.com/icgam/Easify.Azure diff --git a/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj b/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj index 34b9df7..502cb9f 100644 --- a/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj +++ b/src/Easify.Azure.AspNetCore/Easify.Azure.AspNetCore.csproj @@ -3,7 +3,7 @@ netstandard2.0 true - Easify.AspNetCore.Observability.AppInsights + Easify.Azure.AspNetCore Mohammad Moattar https://github.com/icgam/Easify.Azure https://github.com/icgam/Easify.Azure From 399d6ca607d6511494659e3fd19401683333650f Mon Sep 17 00:00:00 2001 From: Mohammad Moattar Date: Mon, 5 Jul 2021 09:45:18 +0100 Subject: [PATCH 3/5] Adding test project for the AspNetCore Project --- .../ServiceCollectionExtensionsTests.cs | 1 + .../Easify.Azure.AspNetCore.UnitTests.csproj | 32 +++++++++++++++++++ src/Easify.Azure.sln | 6 ++++ 3 files changed, 39 insertions(+) create mode 100644 src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj diff --git a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs index de70f11..344963b 100644 --- a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs +++ b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs @@ -38,5 +38,6 @@ public void Should_ApplicationInsight_IsResolvableFromContainer_WhenTheExtension sp.GetRequiredService().Should().NotBeNull().And .BeAssignableTo(typeof(ApplicationInfoTelemetryInitializer)); } + } } \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj b/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj new file mode 100644 index 0000000..df45b91 --- /dev/null +++ b/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/src/Easify.Azure.sln b/src/Easify.Azure.sln index fee01e6..a0bd9fa 100644 --- a/src/Easify.Azure.sln +++ b/src/Easify.Azure.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easify.Azure.AspNetCore.App EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easify.Azure.AspNetCore", "Easify.Azure.AspNetCore\Easify.Azure.AspNetCore.csproj", "{D413300A-5272-4D1C-9F14-5E20C1D3B24E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easify.Azure.AspNetCore.UnitTests", "Easify.Azure.AspNetCore.UnitTests\Easify.Azure.AspNetCore.UnitTests.csproj", "{F647B941-F688-4C7F-A45B-5F0AD5772FE1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Debug|Any CPU.Build.0 = Debug|Any CPU {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D413300A-5272-4D1C-9F14-5E20C1D3B24E}.Release|Any CPU.Build.0 = Release|Any CPU + {F647B941-F688-4C7F-A45B-5F0AD5772FE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F647B941-F688-4C7F-A45B-5F0AD5772FE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F647B941-F688-4C7F-A45B-5F0AD5772FE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F647B941-F688-4C7F-A45B-5F0AD5772FE1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 1eda736bed34299ac219c123aca1387beb827bbf Mon Sep 17 00:00:00 2001 From: davidcassell Date: Wed, 14 Jul 2021 09:24:41 +0100 Subject: [PATCH 4/5] [ADP-9462] JL --- .../ServiceCollectionExtensionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs index 344963b..de70f11 100644 --- a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs +++ b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs @@ -38,6 +38,5 @@ public void Should_ApplicationInsight_IsResolvableFromContainer_WhenTheExtension sp.GetRequiredService().Should().NotBeNull().And .BeAssignableTo(typeof(ApplicationInfoTelemetryInitializer)); } - } } \ No newline at end of file From ed95688af499983f4379f9c24f6fdc0a469d04c3 Mon Sep 17 00:00:00 2001 From: Mohammad Moattar Date: Fri, 23 Jul 2021 10:03:56 +0100 Subject: [PATCH 5/5] Add few test for the key vault configuration --- .../ServiceCollectionExtensionsTests.cs | 1 - .../Easify.Azure.AspNetCore.UnitTests.csproj | 4 -- .../KeyVault/KeyVaultExtensionsTests.cs | 49 +++++++++++++++++++ .../appsettings.json | 15 ++++++ .../KeyVaults/KeyVaultExtensions.cs | 13 +++-- .../KeyVaults/KeyVaultOptions.cs | 5 +- src/Easify.Azure/Easify.Azure.csproj | 7 --- 7 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 src/Easify.Azure.AspNetCore.UnitTests/KeyVault/KeyVaultExtensionsTests.cs create mode 100644 src/Easify.Azure.AspNetCore.UnitTests/appsettings.json delete mode 100644 src/Easify.Azure/Easify.Azure.csproj diff --git a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs index 344963b..de70f11 100644 --- a/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs +++ b/src/Easify.Azure.AspNetCore.AppInsights.UnitTests/ServiceCollectionExtensionsTests.cs @@ -38,6 +38,5 @@ public void Should_ApplicationInsight_IsResolvableFromContainer_WhenTheExtension sp.GetRequiredService().Should().NotBeNull().And .BeAssignableTo(typeof(ApplicationInfoTelemetryInitializer)); } - } } \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj b/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj index df45b91..154b234 100644 --- a/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj +++ b/src/Easify.Azure.AspNetCore.UnitTests/Easify.Azure.AspNetCore.UnitTests.csproj @@ -25,8 +25,4 @@ - - - - diff --git a/src/Easify.Azure.AspNetCore.UnitTests/KeyVault/KeyVaultExtensionsTests.cs b/src/Easify.Azure.AspNetCore.UnitTests/KeyVault/KeyVaultExtensionsTests.cs new file mode 100644 index 0000000..c3c9d1a --- /dev/null +++ b/src/Easify.Azure.AspNetCore.UnitTests/KeyVault/KeyVaultExtensionsTests.cs @@ -0,0 +1,49 @@ +using Easify.Azure.AspNetCore.KeyVaults; +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Easify.Azure.AspNetCore.UnitTests.KeyVault +{ + public class KeyVaultExtensionsTests + { + [Fact] + public void Should_AddAzureKeyVault_SkipAddingKeyVaultConfiguration_WhenNoNameHasBennProvidedForKeyVaults() + { + // Arrange + var builder = new ConfigurationBuilder(); + + //Act + builder.AddAzureKeyVault(m => + { + m.KeyVaultNames = new string[]{}; + m.AzureAdApplicationId = "AppID"; + m.AzureAdApplicationCertThumbprint = "CertificateThumbprint"; + m.AzureAdTenantId = "TenantID"; + }); + + //Assert + builder.Sources.Count.Should().Be(0); + } + + [Fact] + public void Should_AddAzureKeyVault_SkipAddingKeyVaultConfiguration_WhenTheConditionalRegistrationIsFalse() + { + // Arrange + var builder = new ConfigurationBuilder(); + + + //Act + builder.AddAzureKeyVault(() => false, m => + { + m.KeyVaultNames = new string[]{}; + m.AzureAdApplicationId = "AppID"; + m.AzureAdApplicationCertThumbprint = "CertificateThumbprint"; + m.AzureAdTenantId = "TenantID"; + }); + + //Assert + builder.Sources.Count.Should().Be(0); + } + } +} \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore.UnitTests/appsettings.json b/src/Easify.Azure.AspNetCore.UnitTests/appsettings.json new file mode 100644 index 0000000..aad4403 --- /dev/null +++ b/src/Easify.Azure.AspNetCore.UnitTests/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "KeyVaultOptions": { + "AzureAdTenantId": "ConfigurationTenantId", + "AzureAdApplicationId": "ConfigurationApplicationId", + "AzureAdApplicationCertThumbprint": "ConfigurationCertificateThumbprint", + "KeyVaultNames": [] + } +} \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs index 6480101..2ce1d13 100644 --- a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs +++ b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Cryptography.X509Certificates; using Azure.Extensions.AspNetCore.Configuration.Secrets; @@ -9,18 +10,21 @@ namespace Easify.Azure.AspNetCore.KeyVaults { public static class KeyVaultExtensions { - public static IConfigurationBuilder AddAzureKeyVault(this IConfigurationBuilder builder) + [ExcludeFromCodeCoverage] + public static IConfigurationBuilder AddAzureKeyVault(this IConfigurationBuilder builder, Action configure = null) { if (builder == null) throw new ArgumentNullException(nameof(builder)); var config = builder.Build(); var options = new KeyVaultOptions(); config.GetSection(nameof(KeyVaultOptions)).Bind(options); + + configure?.Invoke(options); if (!options.KeyVaultNames.Any()) return builder; - using var store = new X509Store(StoreLocation.LocalMachine); + using var store = new X509Store(options.LocalCertificateStore); store.Open(OpenFlags.ReadOnly); var certs = store.Certificates.Find( X509FindType.FindByThumbprint, @@ -37,9 +41,10 @@ public static IConfigurationBuilder AddAzureKeyVault(this IConfigurationBuilder return builder; } - public static IConfigurationBuilder AddAzureKeyVault(this ConfigurationBuilder builder, Func condition) + public static IConfigurationBuilder AddAzureKeyVault(this ConfigurationBuilder builder, Func condition, + Action configure = null) { - return condition() ? builder.AddAzureKeyVault() : builder; + return condition() ? builder.AddAzureKeyVault(configure) : builder; } } } \ No newline at end of file diff --git a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs index 1e17a2c..52eedde 100644 --- a/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs +++ b/src/Easify.Azure.AspNetCore/KeyVaults/KeyVaultOptions.cs @@ -1,10 +1,13 @@ -namespace Easify.Azure.AspNetCore.KeyVaults +using System.Security.Cryptography.X509Certificates; + +namespace Easify.Azure.AspNetCore.KeyVaults { public class KeyVaultOptions { public string AzureAdTenantId { get; set; } public string AzureAdApplicationId { get; set; } public string AzureAdApplicationCertThumbprint { get; set; } + public StoreLocation LocalCertificateStore { get; set; } = StoreLocation.CurrentUser; public string[] KeyVaultNames { get; set; } = { }; } } \ No newline at end of file diff --git a/src/Easify.Azure/Easify.Azure.csproj b/src/Easify.Azure/Easify.Azure.csproj deleted file mode 100644 index 2756020..0000000 --- a/src/Easify.Azure/Easify.Azure.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard2.0 - - -