diff --git a/.github/workflows/Dylan.Convey.Secrets.Vault.yml b/.github/workflows/Dylan.Convey.Secrets.Vault.yml new file mode 100644 index 00000000..f62d272f --- /dev/null +++ b/.github/workflows/Dylan.Convey.Secrets.Vault.yml @@ -0,0 +1,39 @@ +name: .NET + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y.%m.%d%H')" + - name: Say current date + run: echo "${{ steps.date.outputs.date }}" + - name: Restore dependencies + run: dotnet restore + working-directory: src/Convey.Secrets.Vault/src/Convey.Secrets.Vault + - name: Build + run: dotnet build --no-restore + working-directory: src/Convey.Secrets.Vault/src/Convey.Secrets.Vault + - name: Test + run: dotnet test --no-build --verbosity normal + working-directory: src/Convey.Secrets.Vault/src/Convey.Secrets.Vault + - name: Pack + run: dotnet pack -p:PackageVersion=${{ steps.date.outputs.date }} + working-directory: src/Convey.Secrets.Vault/src/Convey.Secrets.Vault + - name: push + run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json + working-directory: src/Convey.Secrets.Vault/src/Convey.Secrets.Vault diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 9f9ab67c..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 6.0.100 - - name: Build with dotnet - run: dotnet build --configuration Release diff --git a/samples/Conveyor.Services.Orders/Program.cs b/samples/Conveyor.Services.Orders/Program.cs index fa05140b..74f1f046 100644 --- a/samples/Conveyor.Services.Orders/Program.cs +++ b/samples/Conveyor.Services.Orders/Program.cs @@ -31,6 +31,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; namespace Conveyor.Services.Orders; @@ -43,6 +44,12 @@ public static Task Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddCommandLine(args) + .AddJsonFile("appsettings.json") + .Build(); + webBuilder.ConfigureServices(services => services .AddConvey() .AddErrorHandler() @@ -87,6 +94,6 @@ public static IHostBuilder CreateHostBuilder(string[] args) .UseRabbitMq() .SubscribeEvent()) .UseLogging() - .UseVault(); + .UseVault(configuration); }); } \ No newline at end of file diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Convey.Secrets.Vault.csproj b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Convey.Secrets.Vault.csproj index fdc7a172..3f06477f 100644 --- a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Convey.Secrets.Vault.csproj +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Convey.Secrets.Vault.csproj @@ -2,7 +2,7 @@ Convey.Secrets.Vault - DevMentors.io + DevMentors.io net6.0 Latest Convey.Secrets.Vault @@ -10,9 +10,10 @@ Convey.Secrets.Vault - - - + + + + diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Extensions.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Extensions.cs index bac54b53..7669cc4f 100644 --- a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Extensions.cs +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Extensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Convey; using Convey.Secrets.Vault.Internals; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -23,9 +24,8 @@ public static class Extensions private static readonly ILeaseService LeaseService = new LeaseService(); private static readonly ICertificatesService CertificatesService = new CertificatesService(); - public static IHostBuilder UseVault(this IHostBuilder builder, string keyValuePath = null, - string sectionName = SectionName) - => builder.ConfigureServices(services => services.AddVault(sectionName)) + public static IHostBuilder UseVault(this IHostBuilder builder,IConfiguration configuration, string sectionName = SectionName) + => builder.ConfigureServices(services => services.AddVault(configuration, sectionName)) .ConfigureAppConfiguration((ctx, cfg) => { var options = cfg.Build().GetOptions(sectionName); @@ -34,12 +34,11 @@ public static IHostBuilder UseVault(this IHostBuilder builder, string keyValuePa return; } - cfg.AddVaultAsync(options, keyValuePath).GetAwaiter().GetResult(); + cfg.AddVaultAsync(options).GetAwaiter().GetResult(); }); - public static IWebHostBuilder UseVault(this IWebHostBuilder builder, string keyValuePath = null, - string sectionName = SectionName) - => builder.ConfigureServices(services => services.AddVault(sectionName)) + public static IWebHostBuilder UseVault(this IWebHostBuilder builder,IConfiguration configuration, string sectionName = SectionName) + => builder.ConfigureServices(services => services.AddVault(configuration, sectionName)) .ConfigureAppConfiguration((ctx, cfg) => { var options = cfg.Build().GetOptions(sectionName); @@ -48,21 +47,17 @@ public static IWebHostBuilder UseVault(this IWebHostBuilder builder, string keyV return; } - cfg.AddVaultAsync(options, keyValuePath).GetAwaiter().GetResult(); + cfg.AddVaultAsync(options).GetAwaiter().GetResult(); }); - private static IServiceCollection AddVault(this IServiceCollection services, string sectionName) + private static IServiceCollection AddVault(this IServiceCollection services,IConfiguration configuration, string sectionName) { if (string.IsNullOrWhiteSpace(sectionName)) { sectionName = SectionName; } - IConfiguration configuration; - using (var serviceProvider = services.BuildServiceProvider()) - { - configuration = serviceProvider.GetRequiredService(); - } + var options = configuration.GetOptions(sectionName); VerifyOptions(options); @@ -95,7 +90,7 @@ private static void VerifyOptions(VaultOptions options) options.Kv = new VaultOptions.KeyValueOptions { Enabled = options.Enabled, - Path = options.Key + Paths = new List { options.Key } }; } @@ -113,23 +108,11 @@ private static void VerifyOptions(VaultOptions options) } } - private static async Task AddVaultAsync(this IConfigurationBuilder builder, VaultOptions options, - string keyValuePath) + private static async Task AddVaultAsync(this IConfigurationBuilder builder, VaultOptions options) { VerifyOptions(options); - var kvPath = string.IsNullOrWhiteSpace(keyValuePath) ? options.Kv?.Path : keyValuePath; var (client, _) = GetClientAndSettings(options); - if (!string.IsNullOrWhiteSpace(kvPath) && options.Kv.Enabled) - { - Console.WriteLine($"Loading settings from Vault: '{options.Url}', KV path: '{kvPath}'."); - var keyValueSecrets = new KeyValueSecrets(client, options); - var secret = await keyValueSecrets.GetAsync(kvPath); - var parser = new JsonParser(); - var json = JsonConvert.SerializeObject(secret); - var data = parser.Parse(json); - var source = new MemoryConfigurationSource {InitialData = data}; - builder.Add(source); - } + builder.AddVaultKeyValueConfiguration(options, client); if (options.Pki is not null && options.Pki.Enabled) { diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/IVaultPeriodicalWatcher.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/IVaultPeriodicalWatcher.cs new file mode 100644 index 00000000..e9e47f75 --- /dev/null +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/IVaultPeriodicalWatcher.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Primitives; + +namespace Convey.Secrets.Vault.Internals +{ + internal interface IVaultPeriodicalWatcher + { + void Dispose(); + IChangeToken Watch(); + } +} \ No newline at end of file diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultHostedService.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultHostedService.cs index 64e890bf..43945c6a 100644 --- a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultHostedService.cs +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultHostedService.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using VaultSharp; @@ -17,9 +18,10 @@ internal sealed class VaultHostedService : BackgroundService private readonly VaultOptions _options; private readonly ILogger _logger; private readonly int _interval; + private readonly IConfiguration _configuration; public VaultHostedService(IVaultClient client, ILeaseService leaseService, ICertificatesIssuer certificatesIssuer, - ICertificatesService certificatesService, VaultOptions options, ILogger logger) + ICertificatesService certificatesService, VaultOptions options, ILogger logger, IConfiguration configuration) { _client = client; _leaseService = leaseService; @@ -28,6 +30,7 @@ public VaultHostedService(IVaultClient client, ILeaseService leaseService, ICert _options = options; _logger = logger; _interval = _options.RenewalsInterval <= 0 ? 10 : _options.RenewalsInterval; + _configuration = configuration; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -37,7 +40,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) return; } - if ((_options.Pki is null || !_options.Pki.Enabled) && + if ((_options.Kv is null || !_options.Kv.Enabled || !_options.Kv.AutoRenewal) && + (_options.Pki is null || !_options.Pki.Enabled) && (_options.Lease is null || _options.Lease.All(l => !l.Value.Enabled) || !_options.Lease.Any(l => l.Value.AutoRenewal))) { @@ -50,6 +54,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var now = DateTime.UtcNow; var nextIterationAt = now.AddSeconds(2 * _interval); + //if (_options.Kv is not null && _options.Kv.Enabled && _options.Kv.AutoRenewal) + //{ + // _configuration.Pro + // //var manager = new VaultKeyValueConfigurationProvider(_client, _options); + // //await manager.UpdateConfiguration(); + //} if (_options.Pki is not null && _options.Pki.Enabled) { diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationBuilderExtensions.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationBuilderExtensions.cs new file mode 100644 index 00000000..29505d78 --- /dev/null +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationBuilderExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VaultSharp; + +namespace Convey.Secrets.Vault.Internals +{ + internal static class VaultKeyValueConfigurationBuilderExtensions + { + public static IConfigurationBuilder AddVaultKeyValueConfiguration(this IConfigurationBuilder builder, + VaultOptions options, + IVaultClient client) + { + IVaultPeriodicalWatcher watcher = null; + if (options.Kv.AutoRenewal) + { + watcher = new VaultPeriodicalWatcher(TimeSpan.FromSeconds(options.RenewalsInterval)); + } + + return builder.Add(new VaultKeyValueConfigurationSource() + { + Options = options, + Client = client, + PeriodicalWatcher = watcher + }); + } + } +} diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationProvider.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationProvider.cs new file mode 100644 index 00000000..9990e015 --- /dev/null +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationProvider.cs @@ -0,0 +1,63 @@ + +using Convey.Secrets.Vault; +using Convey.Secrets.Vault.Internals; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VaultSharp; + +namespace Convey.Secrets.Vault.Internals +{ + internal class VaultKeyValueConfigurationProvider: ConfigurationProvider + { + private readonly VaultKeyValueConfigurationSource _source; + private readonly IVaultClient _client; + private readonly VaultOptions _options; + private readonly IDisposable _changeTokenRegistration; + + public VaultKeyValueConfigurationProvider(VaultKeyValueConfigurationSource source) + { + _source = source; + _client = source.Client; + _options = source.Options; + + if (_source.PeriodicalWatcher != null) + { + _changeTokenRegistration = ChangeToken.OnChange( + () => _source.PeriodicalWatcher.Watch(), + Load + ); + } + } + + + public override void Load() + { + var kvPaths = _options.Kv?.Paths; + if(kvPaths is null || kvPaths.Count() == 0) + { + kvPaths.Add(_options.Kv.Path); + } + JObject kvConfiguration = new JObject(); + foreach (var kvPath in kvPaths) + { + if (!string.IsNullOrWhiteSpace(kvPath) && _options.Kv.Enabled) + { + Console.WriteLine($"Loading settings from Vault: '{_options.Url}', KV path: '{kvPath}'."); + var keyValueSecrets = new KeyValueSecrets(_client, _options); + var secret = keyValueSecrets.GetAsync(kvPath).GetAwaiter().GetResult(); + kvConfiguration.Merge(JObject.FromObject(secret)); + + } + } + Data = new JsonParser().Parse(kvConfiguration.ToString()); + } + } +} diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationSource.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationSource.cs new file mode 100644 index 00000000..6201ac53 --- /dev/null +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultKeyValueConfigurationSource.cs @@ -0,0 +1,24 @@ +using Convey.Secrets.Vault; +using Convey.Secrets.Vault.Internals; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VaultSharp; + +namespace Convey.Secrets.Vault.Internals +{ + internal class VaultKeyValueConfigurationSource : IConfigurationSource + { + public VaultOptions Options { get; set; } + public IVaultClient Client { get; set; } + public IVaultPeriodicalWatcher PeriodicalWatcher { get; set; } + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + + return new VaultKeyValueConfigurationProvider(this); + } + } +} diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultPeriodicalWatcher.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultPeriodicalWatcher.cs new file mode 100644 index 00000000..985b2c86 --- /dev/null +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/Internals/VaultPeriodicalWatcher.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Convey.Secrets.Vault.Internals +{ + internal class VaultPeriodicalWatcher : IDisposable, IVaultPeriodicalWatcher + { + + private readonly TimeSpan _refreshInterval; + private IChangeToken _changeToken; + private readonly Timer _timer; + private CancellationTokenSource _cancellationTokenSource; + + public VaultPeriodicalWatcher(TimeSpan refreshInterval) + { + _refreshInterval = refreshInterval; + _timer = new Timer(Change, null, TimeSpan.Zero, _refreshInterval); + } + + private void Change(object state) + { + _cancellationTokenSource?.Cancel(); + } + + public IChangeToken Watch() + { + _cancellationTokenSource = new CancellationTokenSource(); + _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); + + return _changeToken; + } + + public void Dispose() + { + _timer?.Dispose(); + _cancellationTokenSource?.Dispose(); + } + } +} diff --git a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/VaultOptions.cs b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/VaultOptions.cs index f73cf1b3..082ebade 100644 --- a/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/VaultOptions.cs +++ b/src/Convey.Secrets.Vault/src/Convey.Secrets.Vault/VaultOptions.cs @@ -23,7 +23,9 @@ public class KeyValueOptions public int EngineVersion { get; set; } = 2; public string MountPoint { get; set; } = "kv"; public string Path { get; set; } + public List Paths { get; set; } public int? Version { get; set; } + public bool AutoRenewal { get; set; } } public class LeaseOptions