diff --git a/Microservice.Customer.Api/Extensions/IServiceCollectionExtensions.cs b/Microservice.Customer.Api/Extensions/IServiceCollectionExtensions.cs index 9656544..1fd3c2c 100644 --- a/Microservice.Customer.Api/Extensions/IServiceCollectionExtensions.cs +++ b/Microservice.Customer.Api/Extensions/IServiceCollectionExtensions.cs @@ -6,10 +6,12 @@ using Microservice.Customer.Api.Data.Repository.Interfaces; using Microservice.Customer.Api.Helpers; using Microservice.Customer.Api.Helpers.Interfaces; +using Microservice.Customer.Api.Helpers.Providers; using Microservice.Customer.Api.Helpers.Swagger; using Microservice.Customer.Api.Mediatr.UpdateCustomer; using Microservice.Customer.Api.MediatR.GetCustomer; using Microservice.Customer.Api.Middleware; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -44,11 +46,37 @@ public static void ConfigureAutoMapper(this IServiceCollection services) services.AddAutoMapper(Assembly.GetAssembly(typeof(GetCustomerMapper))); } - public static void ConfigureDatabaseContext(this IServiceCollection services, ConfigurationManager configuration) + //public static void ConfigureDatabaseContext(this IServiceCollection services, ConfigurationManager configuration) + //{ + // services.AddDbContextFactory(options => + // options.UseSqlServer(configuration.GetConnectionString(Helpers.Constants.DatabaseConnectionString), + // options => options.EnableRetryOnFailure())); + //} + + public static void ConfigureSqlServer(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment) { - services.AddDbContextFactory(options => - options.UseSqlServer(configuration.GetConnectionString(Helpers.Constants.DatabaseConnectionString), - options => options.EnableRetryOnFailure())); + if (environment.IsProduction()) + { + services.AddDbContextFactory(options => + { + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, + new ProductionAzureSQLProvider()); + var sqlConnection = new SqlConnection(configuration.GetConnectionString(Constants.AzureDatabaseConnectionString)); + options.UseSqlServer(sqlConnection); + }); + } + else if (environment.IsDevelopment()) + { + services.AddDbContextFactory(options => + { + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, + new DevelopmentAzureSQLProvider()); + var sqlConnection = new SqlConnection(configuration.GetConnectionString(Constants.LocalDatabaseConnectionString)); + options.UseSqlServer(sqlConnection); + }); + } } public static void ConfigureMediatr(this IServiceCollection services) diff --git a/Microservice.Customer.Api/Helpers/Constants.cs b/Microservice.Customer.Api/Helpers/Constants.cs index e55bd1d..77b7949 100644 --- a/Microservice.Customer.Api/Helpers/Constants.cs +++ b/Microservice.Customer.Api/Helpers/Constants.cs @@ -6,5 +6,13 @@ public class Constants public const string JwtAudience = "JWT_AUDIENCE"; public const string JwtSymmetricSecurityKey = "JWT_SYMMETRIC_SECURITY_KEY"; - public const string DatabaseConnectionString = "SQLAZURECONNSTR_CUSTOMER"; + //public const string DatabaseConnectionString = "SQLAZURECONNSTR_CUSTOMER"; + + public const string AzureUserAssignedManagedIdentityClientId = "AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID"; + public const string AzureDatabaseConnectionString = "AZURE_MANAGED_IDENTITY_SQL_CONNECTION"; + + public const string AzureLocalDevelopmentClientId = "AZURE_LOCAL_DEVELOPMENT_CLIENT_ID"; + public const string AzureLocalDevelopmentClientSecret = "AZURE_LOCAL_DEVELOPMENT_CLIENT_SECRET"; + public const string AzureLocalDevelopmentTenantId = "AZURE_LOCAL_DEVELOPMENT_TENANT_ID"; + public const string LocalDatabaseConnectionString = "LOCAL_CONNECTION"; } \ No newline at end of file diff --git a/Microservice.Customer.Api/Helpers/EnvironmentVariables.cs b/Microservice.Customer.Api/Helpers/EnvironmentVariables.cs index f545f5f..1403d87 100644 --- a/Microservice.Customer.Api/Helpers/EnvironmentVariables.cs +++ b/Microservice.Customer.Api/Helpers/EnvironmentVariables.cs @@ -9,6 +9,13 @@ public class EnvironmentVariables public static string JwtIssuer => GetEnvironmentVariable(Constants.JwtIssuer); public static string JwtAudience => GetEnvironmentVariable(Constants.JwtAudience); public static string JwtSymmetricSecurityKey => GetEnvironmentVariable(Constants.JwtSymmetricSecurityKey); + // public static string AzureDatabaseConnectionString => GetEnvironmentVariable(Constants.AzureDatabaseConnectionString); + public static string AzureUserAssignedManagedIdentityClientId => GetEnvironmentVariable(Constants.AzureUserAssignedManagedIdentityClientId); + //public static string LocalDatabaseConnectionString => GetEnvironmentVariable(Constants.LocalDatabaseConnectionString); + public static string LocalDevelopmentClientId => GetEnvironmentVariable(Constants.AzureLocalDevelopmentClientId); + public static string LocalDevelopmentClientSecret => GetEnvironmentVariable(Constants.AzureLocalDevelopmentClientSecret); + public static string LocalDevelopmentTenantId => GetEnvironmentVariable(Constants.AzureLocalDevelopmentTenantId); + public static string GetEnvironmentVariable(string name) { diff --git a/Microservice.Customer.Api/Helpers/Providers/DevelopmentAzureSQLProvider.cs b/Microservice.Customer.Api/Helpers/Providers/DevelopmentAzureSQLProvider.cs new file mode 100644 index 0000000..ca46a92 --- /dev/null +++ b/Microservice.Customer.Api/Helpers/Providers/DevelopmentAzureSQLProvider.cs @@ -0,0 +1,33 @@ +using Azure.Core; +using Azure.Identity; +using Microsoft.Data.SqlClient; + +namespace Microservice.Customer.Api.Helpers.Providers; + +public class DevelopmentAzureSQLProvider : SqlAuthenticationProvider +{ + private readonly TokenCredential _credential; + + public DevelopmentAzureSQLProvider() + { + var clientId = EnvironmentVariables.LocalDevelopmentClientId; + var clientSecret = EnvironmentVariables.LocalDevelopmentClientSecret; + var tenantId = EnvironmentVariables.LocalDevelopmentTenantId; + + _credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + } + + private static readonly string[] _azureSqlScopes = + [ + "https://database.windows.net//.default" + ]; + + public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + var tokenRequestContext = new TokenRequestContext(_azureSqlScopes); + var tokenResult = await _credential.GetTokenAsync(tokenRequestContext, default); + return new SqlAuthenticationToken(tokenResult.Token, tokenResult.ExpiresOn); + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) => authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal); +} \ No newline at end of file diff --git a/Microservice.Customer.Api/Helpers/Providers/ProductionAzureSQLProvider.cs b/Microservice.Customer.Api/Helpers/Providers/ProductionAzureSQLProvider.cs new file mode 100644 index 0000000..e26c755 --- /dev/null +++ b/Microservice.Customer.Api/Helpers/Providers/ProductionAzureSQLProvider.cs @@ -0,0 +1,32 @@ +using Azure.Core; +using Azure.Identity; +using Microsoft.Data.SqlClient; + +namespace Microservice.Customer.Api.Helpers.Providers; + +public class ProductionAzureSQLProvider : SqlAuthenticationProvider +{ + private readonly TokenCredential _credential; + + public ProductionAzureSQLProvider() + { + _credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions + { + ManagedIdentityClientId = EnvironmentVariables.AzureUserAssignedManagedIdentityClientId, + }); + } + + private static readonly string[] _azureSqlScopes = + [ + "https://database.windows.net//.default" + ]; + + public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + var tokenRequestContext = new TokenRequestContext(_azureSqlScopes); + var tokenResult = await _credential.GetTokenAsync(tokenRequestContext, default); + return new SqlAuthenticationToken(tokenResult.Token, tokenResult.ExpiresOn); + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) => authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity); +} diff --git a/Microservice.Customer.Api/Program.cs b/Microservice.Customer.Api/Program.cs index b690156..7250a65 100644 --- a/Microservice.Customer.Api/Program.cs +++ b/Microservice.Customer.Api/Program.cs @@ -2,6 +2,12 @@ using Microservice.Customer.Api.Extensions; var builder = WebApplication.CreateBuilder(args); +var environment = builder.Environment; + +builder.Configuration + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); @@ -9,7 +15,7 @@ builder.Services.ConfigureApiVersioning(); builder.Services.ConfigureAutoMapper(); builder.Services.ConfigureDI(); -builder.Services.ConfigureDatabaseContext(builder.Configuration); +builder.Services.ConfigureSqlServer(builder.Configuration, environment); builder.Services.ConfigureExceptionHandling(); builder.Services.ConfigureJwt(); builder.Services.ConfigureMediatr(); diff --git a/docker-compose.yml b/docker-compose.yml index 3b85c35..326e259 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,10 @@ services: JWT_ISSUER: ${JWT_ISSUER} JWT_AUDIENCE: ${JWT_AUDIENCE} JWT_SYMMETRIC_SECURITY_KEY: ${JWT_SYMMETRIC_SECURITY_KEY} + AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID: ${AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID} + AZURE_LOCAL_DEVELOPMENT_CLIENT_ID: ${AZURE_LOCAL_DEVELOPMENT_CLIENT_ID} + AZURE_LOCAL_DEVELOPMENT_CLIENT_SECRET: ${AZURE_LOCAL_DEVELOPMENT_CLIENT_SECRET} + AZURE_LOCAL_DEVELOPMENT_TENANT_ID: ${AZURE_LOCAL_DEVELOPMENT_TENANT_ID} networks: - ms-order-system