Skip to content

Commit

Permalink
Multiple options for configuring RavenDB client certificate (#4767)
Browse files Browse the repository at this point in the history
* Centralize certificate finding

* Add settings to Primary/Audit and plumb through to certificate finder

* Setting for certificate path, in cases where mounted from a secrets mount

* Add certificate password

* Changes from review
  • Loading branch information
DavidBoike authored Feb 3, 2025
1 parent 793468c commit 4633f7b
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
namespace ServiceControl.Audit.Persistence.RavenDB
{
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
Expand Down Expand Up @@ -38,21 +35,11 @@ public async Task Initialize(CancellationToken cancellationToken = default)
{
await initializeSemaphore.WaitAsync(cancellationToken);

// Look for raven-client-certificate.pfx in same directory as application code
var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");
X509Certificate2? certificate = null;

if (File.Exists(certificatePath))
{
certificate = new X509Certificate2(certificatePath);
}

var store = new DocumentStore
{
Database = configuration.Name,
Urls = [configuration.ServerConfiguration.ConnectionString],
Certificate = certificate,
Certificate = RavenClientCertificate.FindClientCertificate(configuration.ServerConfiguration),
Conventions = new DocumentConventions
{
SaveEnumsAsIntegers = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration
public const string DatabaseNameKey = "RavenDB/DatabaseName";
public const string DatabasePathKey = "DbPath";
public const string ConnectionStringKey = "RavenDB/ConnectionString";
public const string ClientCertificatePathKey = "RavenDB/ClientCertificatePath";
public const string ClientCertificateBase64Key = "RavenDB/ClientCertificateBase64";
public const string ClientCertificatePasswordKey = "RavenDB/ClientCertificatePassword";
public const string DatabaseMaintenancePortKey = "DatabaseMaintenancePort";
public const string ExpirationProcessTimerInSecondsKey = "ExpirationProcessTimerInSeconds";
public const string LogPathKey = "LogPath";
Expand All @@ -24,6 +27,9 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration
DatabaseNameKey,
DatabasePathKey,
ConnectionStringKey,
ClientCertificatePathKey,
ClientCertificateBase64Key,
ClientCertificatePasswordKey,
DatabaseMaintenancePortKey,
ExpirationProcessTimerInSecondsKey,
LogPathKey,
Expand Down Expand Up @@ -59,6 +65,19 @@ internal static DatabaseConfiguration GetDatabaseConfiguration(PersistenceSettin
}

serverConfiguration = new ServerConfiguration(connectionString);

if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificatePathKey, out var clientCertificatePath))
{
serverConfiguration.ClientCertificatePath = clientCertificatePath;
}
if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificateBase64Key, out var clientCertificateBase64))
{
serverConfiguration.ClientCertificateBase64 = clientCertificateBase64;
}
if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificatePasswordKey, out var clientCertificatePassword))
{
serverConfiguration.ClientCertificatePassword = clientCertificatePassword;
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace ServiceControl.Audit.Persistence.RavenDB
{
public class ServerConfiguration
using ServiceControl.RavenDB;

public class ServerConfiguration : IRavenClientCertificateInfo
{
public ServerConfiguration(string connectionString)
{
Expand All @@ -18,6 +20,9 @@ public ServerConfiguration(string dbPath, string serverUrl, string logPath, stri
}

public string ConnectionString { get; }
public string ClientCertificatePath { get; set; }
public string ClientCertificateBase64 { get; set; }
public string ClientCertificatePassword { get; set; }
public bool UseEmbeddedServer { get; }
public string DbPath { get; internal set; } //Setter for ATT only
public string ServerUrl { get; }
Expand Down
3 changes: 3 additions & 0 deletions src/ServiceControl.Persistence.RavenDB/RavenBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ static class RavenBootstrapper
public const string DatabaseMaintenancePortKey = "DatabaseMaintenancePort";
public const string ExpirationProcessTimerInSecondsKey = "ExpirationProcessTimerInSeconds";
public const string ConnectionStringKey = "RavenDB/ConnectionString";
public const string ClientCertificatePathKey = "RavenDB/ClientCertificatePath";
public const string ClientCertificateBase64Key = "RavenDB/ClientCertificateBase64";
public const string ClientCertificatePasswordKey = "RavenDB/ClientCertificatePassword";
public const string MinimumStorageLeftRequiredForIngestionKey = "MinimumStorageLeftRequiredForIngestion";
public const string DatabaseNameKey = "RavenDB/DatabaseName";
public const string LogsPathKey = "LogPath";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
namespace ServiceControl.Persistence.RavenDB
{
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
Expand Down Expand Up @@ -38,21 +35,11 @@ public async Task Initialize(CancellationToken cancellationToken)
{
await initializeSemaphore.WaitAsync(cancellationToken);

// Look for raven-client-certificate.pfx in same directory as application code
var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");
X509Certificate2? certificate = null;

if (File.Exists(certificatePath))
{
certificate = new X509Certificate2(certificatePath);
}

var store = new DocumentStore
{
Database = settings.DatabaseName,
Urls = [settings.ConnectionString],
Certificate = certificate,
Certificate = RavenClientCertificate.FindClientCertificate(settings),
Conventions = new DocumentConventions
{
SaveEnumsAsIntegers = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ static T GetRequiredSetting<T>(SettingsRootNamespace settingsRootNamespace, stri
var settings = new RavenPersisterSettings
{
ConnectionString = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ConnectionStringKey),
ClientCertificatePath = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificatePathKey),
ClientCertificateBase64 = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificateBase64Key),
ClientCertificatePassword = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificatePasswordKey),
DatabaseName = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabaseNameKey, RavenPersisterSettings.DatabaseNameDefault),
DatabasePath = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabasePathKey, DefaultDatabaseLocation()),
DatabaseMaintenancePort = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabaseMaintenancePortKey, RavenPersisterSettings.DatabaseMaintenancePortDefault),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
using Particular.LicensingComponent.Contracts;
using ServiceControl.Persistence;
using ServiceControl.Persistence.RavenDB.CustomChecks;
using ServiceControl.RavenDB;

class RavenPersisterSettings : PersistenceSettings
class RavenPersisterSettings : PersistenceSettings, IRavenClientCertificateInfo
{
public int DatabaseMaintenancePort { get; set; } = DatabaseMaintenancePortDefault;
public int ExpirationProcessTimerInSeconds { get; set; } = ExpirationProcessTimerInSecondsDefault;
Expand All @@ -23,6 +24,9 @@ class RavenPersisterSettings : PersistenceSettings
/// User provided external RavenDB instance connection string
/// </summary>
public string ConnectionString { get; set; }
public string ClientCertificatePath { get; set; }
public string ClientCertificateBase64 { get; set; }
public string ClientCertificatePassword { get; set; }
public bool UseEmbeddedServer => string.IsNullOrWhiteSpace(ConnectionString);
public string LogPath { get; set; }
public string LogsMode { get; set; } = LogsModeDefault;
Expand Down
51 changes: 51 additions & 0 deletions src/ServiceControl.RavenDB/RavenClientCertificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#nullable enable

namespace ServiceControl.RavenDB;

using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

public static class RavenClientCertificate
{
public static X509Certificate2? FindClientCertificate(IRavenClientCertificateInfo certInfo)
{
if (certInfo.ClientCertificateBase64 is not null)
{
try
{
var bytes = Convert.FromBase64String(certInfo.ClientCertificateBase64);
return new X509Certificate2(bytes, certInfo.ClientCertificatePassword);
}
catch (Exception x) when (x is FormatException or CryptographicException)
{
throw new Exception("Could not read the RavenDB client certificate from the configured Base64 value.", x);
}
}

if (certInfo.ClientCertificatePath is not null)
{
if (!File.Exists(certInfo.ClientCertificatePath))
{
throw new Exception("Could not read the RavenDB client certificate from the supplied path because no file was found.");
}
return new X509Certificate2(certInfo.ClientCertificatePath, certInfo.ClientCertificatePassword);
}

var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");

if (File.Exists(certificatePath))
{
return new X509Certificate2(certificatePath, certInfo.ClientCertificatePassword);
}
return null;
}
}

public interface IRavenClientCertificateInfo
{
string? ClientCertificatePath { get; }
string? ClientCertificateBase64 { get; }
string? ClientCertificatePassword { get; }
}

0 comments on commit 4633f7b

Please sign in to comment.