From 9bc94fd805d89b18e392ea44838085a60166e929 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:05:23 +0200 Subject: [PATCH] chore(summary): Add key vault and fusion sql token support (#666) - [x] New feature - [ ] Bug fix - [ ] High impact **Description of work:** [AB#55619](https://statoil-proview.visualstudio.com/787035c2-8cf2-4d73-a83e-bb0e6d937eec/_workitems/edit/55619) This adds the keyvault support and azure sql token support. I've tested connecting to the Summary CI database which works. **Testing:** - [ ] Can be tested - [ ] Automatic tests created / updated - [x] Local tests are passing **Checklist:** - [ ] Considered automated tests - [ ] Considered updating specification / documentation - [x] Considered work items - [x] Considered security - [x] Performed developer testing - [x] Checklist finalized / ready for review --- .../ConfigurationExtensions.cs | 30 +++++++++++++++++++ .../Database/SqlTokenProvider.cs | 19 ++++++++++++ .../Fusion.Summary.Api.csproj | 1 + src/Fusion.Summary.Api/Program.cs | 26 +++++++++++----- 4 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/Fusion.Summary.Api/ConfigurationExtensions.cs create mode 100644 src/Fusion.Summary.Api/Database/SqlTokenProvider.cs diff --git a/src/Fusion.Summary.Api/ConfigurationExtensions.cs b/src/Fusion.Summary.Api/ConfigurationExtensions.cs new file mode 100644 index 000000000..519c315ab --- /dev/null +++ b/src/Fusion.Summary.Api/ConfigurationExtensions.cs @@ -0,0 +1,30 @@ +using Azure.Identity; + +namespace Fusion.Summary.Api; + +public static class ConfigurationExtensions +{ + public static void AddKeyVault(this WebApplicationBuilder builder) + { + var configuration = builder.Configuration; + var clientId = configuration["AzureAd:ClientId"]; + var tenantId = configuration["AzureAd:TenantId"]; + var clientSecret = configuration["AzureAd:ClientSecret"]; + var keyVaultUrl = configuration["KEYVAULT_URL"]; + + if (string.IsNullOrWhiteSpace(keyVaultUrl)) + { + Console.WriteLine("Skipping key vault as url is null, empty or whitespace."); + return; + } + + if (string.IsNullOrWhiteSpace(clientSecret)) + { + Console.WriteLine("Skipping key vault as clientSecret is null, empty or whitespace."); + return; + } + + var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + configuration.AddAzureKeyVault(new Uri(keyVaultUrl), credential); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Database/SqlTokenProvider.cs b/src/Fusion.Summary.Api/Database/SqlTokenProvider.cs new file mode 100644 index 000000000..cef00b878 --- /dev/null +++ b/src/Fusion.Summary.Api/Database/SqlTokenProvider.cs @@ -0,0 +1,19 @@ +using Fusion.Infrastructure.Database; +using Fusion.Integration.Configuration; + +namespace Fusion.Summary.Api.Database; + +public class SqlTokenProvider : ISqlTokenProvider +{ + private readonly IFusionTokenProvider fusionTokenProvider; + + public SqlTokenProvider(IFusionTokenProvider fusionTokenProvider) + { + this.fusionTokenProvider = fusionTokenProvider; + } + + public Task GetAccessTokenAsync() + { + return fusionTokenProvider.GetApplicationTokenAsync("https://database.windows.net/"); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Fusion.Summary.Api.csproj b/src/Fusion.Summary.Api/Fusion.Summary.Api.csproj index 838b39f97..0a9d61b22 100644 --- a/src/Fusion.Summary.Api/Fusion.Summary.Api.csproj +++ b/src/Fusion.Summary.Api/Fusion.Summary.Api.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Fusion.Summary.Api/Program.cs b/src/Fusion.Summary.Api/Program.cs index 516551114..5f3858339 100644 --- a/src/Fusion.Summary.Api/Program.cs +++ b/src/Fusion.Summary.Api/Program.cs @@ -3,6 +3,7 @@ using FluentValidation.AspNetCore; using Fusion.AspNetCore.Versioning; using Fusion.Resources.Api.Middleware; +using Fusion.Summary.Api; using Fusion.Summary.Api.Database; using Fusion.Summary.Api.Middleware; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -13,24 +14,30 @@ var builder = WebApplication.CreateBuilder(args); -builder.Configuration - .AddJsonFile("/app/secrets/appsettings.secrets.yaml", optional: true) - .AddJsonFile("/app/static/config/env.json", optional: true, reloadOnChange: true); +if (Environment.GetEnvironmentVariable("INTEGRATION_TEST_RUN") != "true") +{ + builder.Configuration + .AddJsonFile("/app/secrets/appsettings.secrets.yaml", optional: true) + .AddJsonFile("/app/config/appsettings.json", optional: true); // to be able to override settings by using a config map in kubernetes + + builder.AddKeyVault(); +} var azureAdClientId = builder.Configuration["AzureAd:ClientId"]; var azureAdClientSecret = builder.Configuration["AzureAd:ClientSecret"]; +var certThumbprint = builder.Configuration["Config:CertThumbprint"]; +var environment = builder.Configuration["Environment"]; var fusionEnvironment = builder.Configuration["FUSION_ENVIRONMENT"]; -var databaseConnectionString = builder.Configuration.GetConnectionString(nameof(SummaryDbContext)); +var databaseConnectionString = builder.Configuration.GetConnectionString(nameof(SummaryDbContext))!; builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddHealthChecks() .AddCheck("liveness", () => HealthCheckResult.Healthy()) .AddCheck("db", () => HealthCheckResult.Healthy(), tags: ["ready"]); -// TODO: Add a real health check, when database is added +// TODO: Add a real health check, when database is added in deployment pipelines and PR pipelines // .AddDbContextCheck("db", tags: new[] { "ready" }); - builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => @@ -54,17 +61,20 @@ builder.Services.AddFusionIntegration(f => { f.AddFusionAuthorization(); - f.UseServiceInformation("Fusion.Summary.Api", "Dev"); + f.UseServiceInformation("Fusion.Summary.Api", environment); f.UseDefaultEndpointResolver(fusionEnvironment ?? "ci"); f.UseDefaultTokenProvider(opts => { opts.ClientId = azureAdClientId ?? throw new InvalidOperationException("Missing AzureAd:ClientId"); opts.ClientSecret = azureAdClientSecret; + opts.CertificateThumbprint = certThumbprint; }); }); builder.Services.AddApplicationInsightsTelemetry(); -builder.Services.AddDbContext(options => options.UseSqlServer(databaseConnectionString)); +builder.Services.AddSqlDbContext(databaseConnectionString, enablePullRequestEnv: false) + .AddSqlTokenProvider() + .AddAccessTokenSupport(); builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); builder.Services.AddFluentValidationAutoValidation().AddValidatorsFromAssembly(typeof(Program).Assembly);