11 changed files with 356 additions and 167 deletions
@ -1,7 +1,9 @@
@@ -1,7 +1,9 @@
|
||||
secrets.json |
||||
database.json |
||||
databaseTo.json |
||||
|
||||
# Development keys |
||||
*.key |
||||
*.crt |
||||
*.pfx |
||||
*.lock |
||||
|
||||
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Text; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Bit.KeyConnector.Repositories; |
||||
using Bit.KeyConnector.Services; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Hosting; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace Bit.KeyConnector.HostedServices |
||||
{ |
||||
public class TransferToHostedService : IHostedService, IDisposable |
||||
{ |
||||
private readonly IUserKeyRepository _userKeyRepository; |
||||
private readonly ICryptoService _cryptoService; |
||||
private readonly KeyConnectorSettings _keyConnectorSettings; |
||||
private readonly ILogger<TransferToHostedService> _logger; |
||||
|
||||
public TransferToHostedService( |
||||
IUserKeyRepository userKeyRepository, |
||||
ICryptoService cryptoService, |
||||
KeyConnectorSettings keyConnectorSettings, |
||||
ILogger<TransferToHostedService> logger) |
||||
{ |
||||
_userKeyRepository = userKeyRepository; |
||||
_cryptoService = cryptoService; |
||||
_keyConnectorSettings = keyConnectorSettings; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken) |
||||
{ |
||||
if (!ShouldRun()) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
_logger.LogInformation("Starting service to transfer to new database."); |
||||
WriteLockfile(); |
||||
|
||||
var transferToProvider = ConfigureTransferToServices(); |
||||
var transferToCryptoService = transferToProvider.GetRequiredService<ICryptoService>(); |
||||
var transferToUserKeyRepository = transferToProvider.GetRequiredService<IUserKeyRepository>(); |
||||
|
||||
var userKeys = await _userKeyRepository.ReadAllAsync(); |
||||
_logger.LogInformation("Found {0} user keys to transfer.", userKeys.Count); |
||||
foreach (var userKey in userKeys) |
||||
{ |
||||
try |
||||
{ |
||||
var key = await _cryptoService.AesDecryptToB64Async(userKey.Key); |
||||
userKey.Key = await transferToCryptoService.AesEncryptToB64Async(key); |
||||
await transferToUserKeyRepository.CreateAsync(userKey); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
_logger.LogWarning(e, "Failed to transfer user key {0}. Skip it.", userKey.Id); |
||||
} |
||||
} |
||||
_logger.LogInformation("Finished transferring user keys."); |
||||
} |
||||
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken) |
||||
{ |
||||
return Task.FromResult(0); |
||||
} |
||||
|
||||
public virtual void Dispose() |
||||
{ } |
||||
|
||||
private bool ShouldRun() |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(_keyConnectorSettings.TransferTo.LockfilePath)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
if (File.Exists(_keyConnectorSettings.TransferTo.LockfilePath)) |
||||
{ |
||||
_logger.LogInformation("Not running transfer service due to lock file existing at {0}.", |
||||
_keyConnectorSettings.TransferTo.LockfilePath); |
||||
return false; |
||||
} |
||||
|
||||
if (_keyConnectorSettings.TransferTo?.RsaKey == null || |
||||
_keyConnectorSettings.TransferTo.Database == null) |
||||
{ |
||||
_logger.LogInformation("Not running transfer service due to missing settings."); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private void WriteLockfile() |
||||
{ |
||||
using var fs = File.Create(_keyConnectorSettings.TransferTo.LockfilePath); |
||||
var nowString = Encoding.UTF8.GetBytes(DateTime.UtcNow.ToString()); |
||||
fs.Write(nowString, 0, nowString.Length); |
||||
} |
||||
|
||||
private IServiceProvider ConfigureTransferToServices() |
||||
{ |
||||
var settings = new KeyConnectorSettings |
||||
{ |
||||
RsaKey = _keyConnectorSettings.TransferTo.RsaKey, |
||||
Certificate = _keyConnectorSettings.TransferTo.Certificate, |
||||
Database = _keyConnectorSettings.TransferTo.Database |
||||
}; |
||||
var transferToServices = new ServiceCollection(); |
||||
transferToServices.AddSingleton(s => settings); |
||||
transferToServices.AddRsaKeyProvider(settings); |
||||
transferToServices.AddBaseServices(); |
||||
var efDatabaseProvider = transferToServices.AddDatabase(settings); |
||||
var transferToProvider = transferToServices.BuildServiceProvider(); |
||||
return transferToProvider; |
||||
} |
||||
} |
||||
} |
||||
@ -1,9 +1,12 @@
@@ -1,9 +1,12 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading.Tasks; |
||||
using Bit.KeyConnector.Models; |
||||
|
||||
namespace Bit.KeyConnector.Repositories |
||||
{ |
||||
public interface IUserKeyRepository : IRepository<UserKeyModel, Guid> |
||||
{ |
||||
Task<List<UserKeyModel>> ReadAllAsync(); |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
using System; |
||||
using System.Security.Claims; |
||||
using Bit.KeyConnector.Repositories; |
||||
using Bit.KeyConnector.Services; |
||||
using IdentityModel; |
||||
using IdentityServer4.AccessTokenValidation; |
||||
using JsonFlatFileDataStore; |
||||
using Microsoft.AspNetCore.Builder; |
||||
using Microsoft.AspNetCore.Hosting; |
||||
using Microsoft.AspNetCore.Identity; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Hosting; |
||||
|
||||
namespace Bit.KeyConnector |
||||
{ |
||||
public static class ServiceCollectionExtensions |
||||
{ |
||||
public static void AddRsaKeyProvider( |
||||
this IServiceCollection services, |
||||
KeyConnectorSettings settings) |
||||
{ |
||||
var rsaKeyProvider = settings.RsaKey.Provider?.ToLowerInvariant(); |
||||
if (rsaKeyProvider == "certificate" || rsaKeyProvider == "pkcs11") |
||||
{ |
||||
if (rsaKeyProvider == "certificate") |
||||
{ |
||||
services.AddSingleton<IRsaKeyService, LocalCertificateRsaKeyService>(); |
||||
} |
||||
else if (rsaKeyProvider == "pkcs11") |
||||
{ |
||||
services.AddSingleton<IRsaKeyService, Pkcs11RsaKeyService>(); |
||||
} |
||||
|
||||
var certificateProvider = settings.Certificate.Provider?.ToLowerInvariant(); |
||||
if (certificateProvider == "store") |
||||
{ |
||||
services.AddSingleton<ICertificateProviderService, StoreCertificateProviderService>(); |
||||
} |
||||
else if (certificateProvider == "filesystem") |
||||
{ |
||||
services.AddSingleton<ICertificateProviderService, FilesystemCertificateProviderService>(); |
||||
} |
||||
else if (certificateProvider == "azurestorage") |
||||
{ |
||||
services.AddSingleton<ICertificateProviderService, AzureStorageCertificateProviderService>(); |
||||
} |
||||
else if (certificateProvider == "azurekv") |
||||
{ |
||||
services.AddSingleton<ICertificateProviderService, AzureKeyVaultCertificateProviderService>(); |
||||
} |
||||
else if (certificateProvider == "vault") |
||||
{ |
||||
services.AddSingleton<ICertificateProviderService, HashicorpVaultCertificateProviderService>(); |
||||
} |
||||
else |
||||
{ |
||||
throw new Exception("Unknown certificate provider configured."); |
||||
} |
||||
} |
||||
else if (rsaKeyProvider == "azurekv") |
||||
{ |
||||
services.AddSingleton<IRsaKeyService, AzureKeyVaultRsaKeyService>(); |
||||
} |
||||
else if (rsaKeyProvider == "gcpkms") |
||||
{ |
||||
services.AddSingleton<IRsaKeyService, GoogleCloudKmsRsaKeyService>(); |
||||
} |
||||
else if (rsaKeyProvider == "awskms") |
||||
{ |
||||
services.AddSingleton<IRsaKeyService, AwsKmsRsaKeyService>(); |
||||
} |
||||
else |
||||
{ |
||||
throw new Exception("Unknown rsa key provider configured."); |
||||
} |
||||
} |
||||
|
||||
public static bool AddDatabase( |
||||
this IServiceCollection services, |
||||
KeyConnectorSettings settings) |
||||
{ |
||||
var databaseProvider = settings.Database.Provider?.ToLowerInvariant(); |
||||
var efDatabaseProvider = databaseProvider == "sqlserver" || databaseProvider == "postgresql" || |
||||
databaseProvider == "mysql" || databaseProvider == "sqlite"; |
||||
|
||||
if (databaseProvider == "json") |
||||
{ |
||||
// Assign foobar to keyProperty in order to not use incrementing Id functionality |
||||
services.AddSingleton<IDataStore>( |
||||
new DataStore(settings.Database.JsonFilePath, keyProperty: "--foobar--")); |
||||
services.AddSingleton<IApplicationDataRepository, Repositories.JsonFile.ApplicationDataRepository>(); |
||||
services.AddSingleton<IUserKeyRepository, Repositories.JsonFile.UserKeyRepository>(); |
||||
} |
||||
else if (databaseProvider == "mongo") |
||||
{ |
||||
services.AddSingleton<IApplicationDataRepository, Repositories.Mongo.ApplicationDataRepository>(); |
||||
services.AddSingleton<IUserKeyRepository, Repositories.Mongo.UserKeyRepository>(); |
||||
} |
||||
else if (efDatabaseProvider) |
||||
{ |
||||
if (databaseProvider == "sqlserver") |
||||
{ |
||||
services.AddDbContext<Repositories.EntityFramework.DatabaseContext, |
||||
Repositories.EntityFramework.SqlServerDatabaseContext>(); |
||||
} |
||||
else if (databaseProvider == "postgresql") |
||||
{ |
||||
services.AddDbContext<Repositories.EntityFramework.DatabaseContext, |
||||
Repositories.EntityFramework.PostgreSqlDatabaseContext>(); |
||||
} |
||||
else if (databaseProvider == "mysql") |
||||
{ |
||||
services.AddDbContext<Repositories.EntityFramework.DatabaseContext, |
||||
Repositories.EntityFramework.MySqlDatabaseContext>(); |
||||
} |
||||
else if (databaseProvider == "sqlite") |
||||
{ |
||||
services.AddDbContext<Repositories.EntityFramework.DatabaseContext, |
||||
Repositories.EntityFramework.SqliteDatabaseContext>(); |
||||
} |
||||
services.AddSingleton<IApplicationDataRepository, |
||||
Repositories.EntityFramework.ApplicationDataRepository>(); |
||||
services.AddSingleton<IUserKeyRepository, Repositories.EntityFramework.UserKeyRepository>(); |
||||
} |
||||
else |
||||
{ |
||||
throw new Exception("No database configured."); |
||||
} |
||||
|
||||
return efDatabaseProvider; |
||||
} |
||||
|
||||
public static void AddAuthentication( |
||||
this IServiceCollection services, |
||||
IWebHostEnvironment environment, |
||||
KeyConnectorSettings settings) |
||||
{ |
||||
services.Configure<IdentityOptions>(options => |
||||
{ |
||||
options.ClaimsIdentity = new ClaimsIdentityOptions |
||||
{ |
||||
UserNameClaimType = JwtClaimTypes.Email, |
||||
UserIdClaimType = JwtClaimTypes.Subject |
||||
}; |
||||
}); |
||||
services |
||||
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) |
||||
.AddIdentityServerAuthentication(options => |
||||
{ |
||||
options.Authority = settings.IdentityServerUri; |
||||
options.RequireHttpsMetadata = !environment.IsDevelopment() && |
||||
settings.IdentityServerUri.StartsWith("https"); |
||||
options.NameClaimType = ClaimTypes.Email; |
||||
options.SupportedTokens = SupportedTokens.Jwt; |
||||
}); |
||||
|
||||
services |
||||
.AddAuthorization(config => |
||||
{ |
||||
config.AddPolicy("Application", policy => |
||||
{ |
||||
policy.RequireAuthenticatedUser(); |
||||
policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external"); |
||||
policy.RequireClaim(JwtClaimTypes.Scope, "api"); |
||||
}); |
||||
}); |
||||
|
||||
if (environment.IsDevelopment()) |
||||
{ |
||||
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; |
||||
} |
||||
} |
||||
|
||||
public static void AddBaseServices( |
||||
this IServiceCollection services) |
||||
{ |
||||
services.AddSingleton<ICryptoFunctionService, CryptoFunctionService>(); |
||||
services.AddSingleton<ICryptoService, CryptoService>(); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue