From cf9597de850de1ef721a35ad79ac67b9fdb9e1d4 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sat, 4 Jun 2022 21:05:47 +0200 Subject: refactor: Use Vault to get configuration --- server/src/Data/Static/AppConfiguration.cs | 27 ++++++++ server/src/Data/Static/AppEnvironmentVariables.cs | 25 +------ server/src/Program.cs | 63 +++++++++-------- server/src/Services/ForgotPasswordService.cs | 14 ++-- server/src/Services/MailService.cs | 12 ++-- server/src/Services/VaultService.cs | 80 ++++++++++++++++++++++ server/src/Utilities/ConfigurationExtensions.cs | 35 ++++++---- .../src/Utilities/GithubAuthenticationHelpers.cs | 7 +- 8 files changed, 184 insertions(+), 79 deletions(-) create mode 100644 server/src/Data/Static/AppConfiguration.cs create mode 100644 server/src/Services/VaultService.cs (limited to 'server') diff --git a/server/src/Data/Static/AppConfiguration.cs b/server/src/Data/Static/AppConfiguration.cs new file mode 100644 index 0000000..a6494ea --- /dev/null +++ b/server/src/Data/Static/AppConfiguration.cs @@ -0,0 +1,27 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public class AppConfiguration +{ + public string DB_HOST { get; set; } + public string DB_PORT { get; set; } + public string DB_USER { get; set; } + public string DB_PASSWORD { get; set; } + public string DB_NAME { get; set; } + public string QUARTZ_DB_HOST { get; set; } + public string QUARTZ_DB_PORT { get; set; } + public string QUARTZ_DB_USER { get; set; } + public string QUARTZ_DB_PASSWORD { get; set; } + public string QUARTZ_DB_NAME { get; set; } + public string SEQ_API_KEY { get; set; } + public string SEQ_API_URL { get; set; } + public string SMTP_HOST { get; set; } + public string SMTP_PORT { get; set; } + public string SMTP_USER { get; set; } + public string SMTP_PASSWORD { get; set; } + public string EMAIL_FROM_ADDRESS { get; set; } + public string EMAIL_FROM_DISPLAY_NAME { get; set; } + public string PORTAL_URL { get; set; } + public string GITHUB_CLIENT_ID { get; set; } + public string GITHUB_CLIENT_SECRET { get; set; } + public string APP_AES_KEY { get; set; } +} diff --git a/server/src/Data/Static/AppEnvironmentVariables.cs b/server/src/Data/Static/AppEnvironmentVariables.cs index a734146..181eced 100644 --- a/server/src/Data/Static/AppEnvironmentVariables.cs +++ b/server/src/Data/Static/AppEnvironmentVariables.cs @@ -2,26 +2,7 @@ namespace IOL.GreatOffice.Api.Data.Static; public static class AppEnvironmentVariables { - public const string DB_HOST = "DB_HOST"; - public const string DB_PORT = "DB_PORT"; - public const string DB_USER = "DB_USER"; - public const string DB_PASSWORD = "DB_PASSWORD"; - public const string DB_NAME = "DB_NAME"; - public const string QUARTZ_DB_HOST = "QUARTZ_DB_HOST"; - public const string QUARTZ_DB_PORT = "QUARTZ_DB_PORT"; - public const string QUARTZ_DB_USER = "QUARTZ_DB_USER"; - public const string QUARTZ_DB_PASSWORD = "QUARTZ_DB_PASSWORD"; - public const string QUARTZ_DB_NAME = "QUARTZ_DB_NAME"; - public const string SEQ_API_KEY = "SEQ_API_KEY"; - public const string SEQ_API_URL = "SEQ_API_URL"; - public const string SMTP_HOST = "SMTP_HOST"; - public const string SMTP_PORT = "SMTP_PORT"; - public const string SMTP_USER = "SMTP_USER"; - public const string SMTP_PASSWORD = "SMTP_PASSWORD"; - public const string EMAIL_FROM_ADDRESS = "EMAIL_FROM_ADDRESS"; - public const string EMAIL_FROM_DISPLAY_NAME = "EMAIL_FROM_DISPLAY_NAME"; - public const string ACCOUNTS_URL = "ACCOUNTS_URL"; - public const string GITHUB_CLIENT_ID = "GH_CLIENT_ID"; - public const string GITHUB_CLIENT_SECRET = "GH_CLIENT_SECRET"; - public const string APP_AES_KEY = "APP_AES_KEY"; + public const string VAULT_TOKEN = "VAULT_TOKEN"; + public const string VAULT_URL = "VAULT_URL"; + public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET"; } diff --git a/server/src/Program.cs b/server/src/Program.cs index b449117..d6e8929 100644 --- a/server/src/Program.cs +++ b/server/src/Program.cs @@ -37,12 +37,12 @@ global using IOL.GreatOffice.Api.Data; global using IOL.GreatOffice.Api.Data.Static; global using IOL.GreatOffice.Api.Services; global using IOL.GreatOffice.Api.Utilities; -using System.Diagnostics; using System.Reflection; using IOL.GreatOffice.Api.Endpoints.V1; using IOL.GreatOffice.Api.Jobs; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Options; using Quartz; namespace IOL.GreatOffice.Api; @@ -51,39 +51,50 @@ public static class Program { public static WebApplicationBuilder CreateAppBuilder(string[] args) { var builder = WebApplication.CreateBuilder(args); + builder.Services.AddLogging(); + builder.Services.AddHttpClient(); - var seqUrl = builder.Configuration.GetValue(AppEnvironmentVariables.SEQ_API_URL); - var seqKey = builder.Configuration.GetValue(AppEnvironmentVariables.SEQ_API_KEY); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + var vaultService = builder.Services.BuildServiceProvider().GetRequiredService(); + var configurationResponse = vaultService.GetSecretAsync(builder.Configuration.GetValue(AppEnvironmentVariables.MAIN_CONFIG_SHEET)).Result; + builder.Services.AddOptions() + .Configure(o => { + foreach (var property in typeof(AppConfiguration).GetProperties().Where(p => p.CanWrite)) { + property.SetValue(o, property.GetValue(configurationResponse.Data.Data, null), null); + } + }); + var configuration = builder.Services.BuildServiceProvider().GetRequiredService>().Value; var logger = new LoggerConfiguration() .Enrich.FromLogContext() .ReadFrom.Configuration(builder.Configuration) .WriteTo.Console(); - if (!builder.Environment.IsDevelopment() && seqUrl.HasValue() && seqKey.HasValue()) { - logger.WriteTo.Seq(seqUrl, apiKey: seqKey); + if (!builder.Environment.IsDevelopment() && configuration.SEQ_API_KEY.HasValue() && configuration.SEQ_API_URL.HasValue()) { + logger.WriteTo.Seq(configuration.SEQ_API_URL, apiKey: configuration.SEQ_API_KEY); } Log.Logger = logger.CreateLogger(); - Log.Information("Starting web host" + Log.Information("Starting web host, " + JsonSerializer.Serialize(new { DateTime.UtcNow, PID = Environment.ProcessId, - DB_HOST = builder.Configuration.GetValue(AppEnvironmentVariables.DB_HOST), - DB_PORT = builder.Configuration.GetValue(AppEnvironmentVariables.DB_PORT), - DB_USER = builder.Configuration.GetValue(AppEnvironmentVariables.DB_USER), - DB_NAME = builder.Configuration.GetValue(AppEnvironmentVariables.DB_NAME), - DB_PASS = builder.Configuration.GetValue(AppEnvironmentVariables.DB_PASSWORD).Obfuscate() ?? "!!!Empty!!!", - QUARTZ_DB_HOST = builder.Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_HOST), - QUARTZ_DB_PORT = builder.Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_PORT), - QUARTZ_DB_USER = builder.Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_USER), - QUARTZ_DB_NAME = builder.Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_NAME), - QUARTZ_DB_PASS = builder.Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_PASSWORD).Obfuscate() - ?? "!!!Empty!!!", + configuration.DB_HOST, + configuration.DB_PORT, + configuration.DB_USER, + configuration.DB_NAME, + DB_PASS = configuration.DB_PASSWORD.Obfuscate() ?? "!!!Empty!!!", + configuration.QUARTZ_DB_HOST, + configuration.QUARTZ_DB_PORT, + configuration.QUARTZ_DB_USER, + configuration.QUARTZ_DB_NAME, + QUARTZ_DB_PASS = configuration.QUARTZ_DB_PASSWORD.Obfuscate() ?? "!!!Empty!!!", }, new JsonSerializerOptions() { WriteIndented = true })); - builder.Host.UseSerilog(Log.Logger); builder.WebHost.ConfigureKestrel(kestrel => { kestrel.AddServerHeader = false; @@ -104,7 +115,7 @@ public static class Program builder.Services.Configure(JsonSettings.Default); builder.Services.AddQuartz(options => { options.UsePersistentStore(o => { - o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString()); + o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString(configuration)); o.UseSerializer(); }); options.UseMicrosoftDependencyInjectionJobFactory(); @@ -132,20 +143,20 @@ public static class Program }; }) .AddGitHub(options => { - options.ClientSecret = builder.Configuration.GetValue(AppEnvironmentVariables.GITHUB_CLIENT_SECRET); - options.ClientId = builder.Configuration.GetValue(AppEnvironmentVariables.GITHUB_CLIENT_ID); + options.ClientSecret = builder.Configuration.GetValue(configuration.GITHUB_CLIENT_SECRET); + options.ClientId = builder.Configuration.GetValue(configuration.GITHUB_CLIENT_ID); options.SaveTokens = true; options.CorrelationCookie.Name = "gh_correlation"; options.CorrelationCookie.SameSite = SameSiteMode.Lax; options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always; options.CorrelationCookie.HttpOnly = true; - options.Events.OnCreatingTicket = context => GithubAuthenticationHelpers.HandleGithubTicketCreation(context, builder.Configuration); + options.Events.OnCreatingTicket = context => GithubAuthenticationHelpers.HandleGithubTicketCreation(context, builder.Configuration, configuration); }) .AddScheme(AppConstants.BASIC_AUTH_SCHEME, default); builder.Services.AddDbContext(options => { - options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(), + options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(configuration), npgsqlDbContextOptionsBuilder => { npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); npgsqlDbContextOptionsBuilder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default); @@ -193,16 +204,10 @@ public static class Program }); }); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddLogging(); - builder.Services.AddHttpClient(); builder.Services .AddControllers() .AddJsonOptions(JsonSettings.Default); - return builder; } diff --git a/server/src/Services/ForgotPasswordService.cs b/server/src/Services/ForgotPasswordService.cs index de38b29..e6b6acf 100644 --- a/server/src/Services/ForgotPasswordService.cs +++ b/server/src/Services/ForgotPasswordService.cs @@ -1,16 +1,18 @@ +using Microsoft.Extensions.Options; + namespace IOL.GreatOffice.Api.Services; public class ForgotPasswordService { private readonly AppDbContext _context; private readonly MailService _mailService; - private readonly IConfiguration _configuration; + private readonly IOptions _configuration; private readonly ILogger _logger; public ForgotPasswordService( AppDbContext context, - IConfiguration configuration, + IOptions configuration, ILogger logger, MailService mailService ) { @@ -57,9 +59,9 @@ public class ForgotPasswordService var request = new ForgotPasswordRequest(user); _context.ForgotPasswordRequests.Add(request); await _context.SaveChangesAsync(cancellationToken); - var accountsUrl = _configuration.GetValue(AppEnvironmentVariables.ACCOUNTS_URL); - var emailFromAddress = _configuration.GetValue(AppEnvironmentVariables.EMAIL_FROM_ADDRESS); - var emailFromDisplayName = _configuration.GetValue(AppEnvironmentVariables.EMAIL_FROM_DISPLAY_NAME); + var portalUrl = _configuration.Value.PORTAL_URL; + var emailFromAddress = _configuration.Value.EMAIL_FROM_ADDRESS; + var emailFromDisplayName = _configuration.Value.EMAIL_FROM_DISPLAY_NAME; var zonedExpirationDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(request.ExpirationDate, requestTz.Id); var message = new MailMessage { From = new MailAddress(emailFromAddress, emailFromDisplayName), @@ -72,7 +74,7 @@ Hi {user.Username} Go to the following link to set a new password. -{accountsUrl}/#/reset-password?id={request.Id} +{portalUrl}/#/reset-password?id={request.Id} The link expires at {zonedExpirationDate:yyyy-MM-dd hh:mm}. If you did not request a password reset, no action is required. diff --git a/server/src/Services/MailService.cs b/server/src/Services/MailService.cs index b271de4..d773303 100644 --- a/server/src/Services/MailService.cs +++ b/server/src/Services/MailService.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Options; + namespace IOL.GreatOffice.Api.Services; public class MailService @@ -13,12 +15,12 @@ public class MailService /// /// /// - public MailService(IConfiguration configuration, ILogger logger) { + public MailService(IOptions configuration, ILogger logger) { _logger = logger; - _emailHost = configuration.GetValue(AppEnvironmentVariables.SMTP_HOST); - _emailPort = configuration.GetValue(AppEnvironmentVariables.SMTP_PORT); - _emailUser = configuration.GetValue(AppEnvironmentVariables.SMTP_USER); - _emailPassword = configuration.GetValue(AppEnvironmentVariables.SMTP_PASSWORD); + _emailHost = configuration.Value.SMTP_HOST; + _emailPort = Convert.ToInt32(configuration.Value.SMTP_PORT); + _emailUser = configuration.Value.SMTP_USER; + _emailPassword = configuration.Value.SMTP_PASSWORD; } /// diff --git a/server/src/Services/VaultService.cs b/server/src/Services/VaultService.cs new file mode 100644 index 0000000..388f8d4 --- /dev/null +++ b/server/src/Services/VaultService.cs @@ -0,0 +1,80 @@ +namespace IOL.GreatOffice.Api.Services; + +public class VaultService +{ + private readonly HttpClient _client; + + public VaultService(HttpClient client, IConfiguration configuration) { + var token = configuration.GetValue("VAULT_TOKEN"); + var vaultUrl = configuration.GetValue("VAULT_URL"); + if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty"); + if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty"); + client.DefaultRequestHeaders.Add("X-Vault-Token", token); + client.BaseAddress = new Uri(vaultUrl); + _client = client; + } + + public async Task> GetSecretAsync(string path) { + return await _client.GetFromJsonAsync>("/v1/kv/data/" + path); + } + + public async Task RenewTokenAsync(string token) { + var response = await _client.PostAsJsonAsync("v1/auth/token/renew", + new { + Token = token + }); + if (response.IsSuccessStatusCode) { + return await response.Content.ReadFromJsonAsync(); + } + + return default; + } + + public class RenewTokenResponse + { + public Guid RequestId { get; set; } + public string LeaseId { get; set; } + public bool Renewable { get; set; } + public long LeaseDuration { get; set; } + public object Data { get; set; } + public object WrapInfo { get; set; } + public List Warnings { get; set; } + public Auth Auth { get; set; } + } + + public class Auth + { + public string ClientToken { get; set; } + public string Accessor { get; set; } + public List Policies { get; set; } + public List TokenPolicies { get; set; } + public object Metadata { get; set; } + public long LeaseDuration { get; set; } + public bool Renewable { get; set; } + public string EntityId { get; set; } + public string TokenType { get; set; } + public bool Orphan { get; set; } + public object MfaRequirement { get; set; } + public long NumUses { get; set; } + } + + public class GetSecretResponse + { + public VaultSecret Data { get; set; } + } + + public class VaultSecret + { + public T Data { get; set; } + public VaultSecretMetadata Metadata { get; set; } + } + + public class VaultSecretMetadata + { + public DateTimeOffset CreatedTime { get; set; } + public object CustomMetadata { get; set; } + public string DeletionTime { get; set; } + public bool Destroyed { get; set; } + public long Version { get; set; } + } +} diff --git a/server/src/Utilities/ConfigurationExtensions.cs b/server/src/Utilities/ConfigurationExtensions.cs index 772059a..41b6ab3 100644 --- a/server/src/Utilities/ConfigurationExtensions.cs +++ b/server/src/Utilities/ConfigurationExtensions.cs @@ -1,27 +1,34 @@ +using Microsoft.Extensions.Options; + namespace IOL.GreatOffice.Api.Utilities; public static class ConfigurationExtensions { - public static string GetAppDatabaseConnectionString(this IConfiguration configuration) { - var host = configuration.GetValue(AppEnvironmentVariables.DB_HOST); - var port = configuration.GetValue(AppEnvironmentVariables.DB_PORT); - var database = configuration.GetValue(AppEnvironmentVariables.DB_NAME); - var user = configuration.GetValue(AppEnvironmentVariables.DB_USER); - var password = configuration.GetValue(AppEnvironmentVariables.DB_PASSWORD); - - if (configuration.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + public static string GetAppDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) { + var host = configuration.DB_HOST; + var port = configuration.DB_PORT; + var database = configuration.DB_NAME; + var user = configuration.DB_USER; + var password = configuration.DB_PASSWORD; + + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { return $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; } return $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; } - public static string GetQuartzDatabaseConnectionString(this IConfiguration Configuration) { - var host = Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_HOST); - var port = Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_PORT); - var database = Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_NAME); - var user = Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_USER); - var password = Configuration.GetValue(AppEnvironmentVariables.QUARTZ_DB_PASSWORD); + public static string GetQuartzDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) { + var host = configuration.QUARTZ_DB_HOST; + var port = configuration.QUARTZ_DB_PORT; + var database = configuration.QUARTZ_DB_NAME; + var user = configuration.QUARTZ_DB_USER; + var password = configuration.QUARTZ_DB_PASSWORD; + Log.Information(host); + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + return $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; + } + return $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; } diff --git a/server/src/Utilities/GithubAuthenticationHelpers.cs b/server/src/Utilities/GithubAuthenticationHelpers.cs index cf0cabb..f924ecc 100644 --- a/server/src/Utilities/GithubAuthenticationHelpers.cs +++ b/server/src/Utilities/GithubAuthenticationHelpers.cs @@ -1,11 +1,12 @@ using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.Extensions.Options; using Npgsql; namespace IOL.GreatOffice.Api.Utilities; public static class GithubAuthenticationHelpers { - public static async Task HandleGithubTicketCreation(OAuthCreatingTicketContext context, IConfiguration configuration) { + public static async Task HandleGithubTicketCreation(OAuthCreatingTicketContext context, IConfiguration configuration, AppConfiguration options) { var githubId = context.Identity?.FindFirst(p => p.Type == ClaimTypes.NameIdentifier)?.Value; var githubUsername = context.Identity?.FindFirst(p => p.Type == ClaimTypes.Name)?.Value; var githubEmail = context.Identity?.FindFirst(p => p.Type == ClaimTypes.Email)?.Value; @@ -19,7 +20,7 @@ public static class GithubAuthenticationHelpers context.Identity.RemoveClaim(claim); } - var connstring = configuration.GetAppDatabaseConnectionString(); + var connstring = configuration.GetAppDatabaseConnectionString(options); var connection = new NpgsqlConnection(connstring); Log.Information($"Getting user mappings for github user: {githubId}"); @@ -57,7 +58,7 @@ public static class GithubAuthenticationHelpers await insertUserCommand.ExecuteNonQueryAsync(); await connection.CloseAsync(); - var refreshTokenEncryptionKey = configuration.GetValue(AppEnvironmentVariables.APP_AES_KEY); + var refreshTokenEncryptionKey = options.APP_AES_KEY; string insertMappingQuery; if (context.RefreshToken.HasValue() && refreshTokenEncryptionKey.HasValue()) { -- cgit v1.3