Skip to content

Commit 4633f7b

Browse files
authored
Multiple options for configuring RavenDB client certificate (#4767)
* 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
1 parent 793468c commit 4633f7b

File tree

8 files changed

+89
-30
lines changed

8 files changed

+89
-30
lines changed

src/ServiceControl.Audit.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
namespace ServiceControl.Audit.Persistence.RavenDB
44
{
55
using System;
6-
using System.IO;
7-
using System.Reflection;
8-
using System.Security.Cryptography.X509Certificates;
96
using System.Threading;
107
using System.Threading.Tasks;
118
using Raven.Client.Documents;
@@ -38,21 +35,11 @@ public async Task Initialize(CancellationToken cancellationToken = default)
3835
{
3936
await initializeSemaphore.WaitAsync(cancellationToken);
4037

41-
// Look for raven-client-certificate.pfx in same directory as application code
42-
var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
43-
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");
44-
X509Certificate2? certificate = null;
45-
46-
if (File.Exists(certificatePath))
47-
{
48-
certificate = new X509Certificate2(certificatePath);
49-
}
50-
5138
var store = new DocumentStore
5239
{
5340
Database = configuration.Name,
5441
Urls = [configuration.ServerConfiguration.ConnectionString],
55-
Certificate = certificate,
42+
Certificate = RavenClientCertificate.FindClientCertificate(configuration.ServerConfiguration),
5643
Conventions = new DocumentConventions
5744
{
5845
SaveEnumsAsIntegers = true

src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration
1212
public const string DatabaseNameKey = "RavenDB/DatabaseName";
1313
public const string DatabasePathKey = "DbPath";
1414
public const string ConnectionStringKey = "RavenDB/ConnectionString";
15+
public const string ClientCertificatePathKey = "RavenDB/ClientCertificatePath";
16+
public const string ClientCertificateBase64Key = "RavenDB/ClientCertificateBase64";
17+
public const string ClientCertificatePasswordKey = "RavenDB/ClientCertificatePassword";
1518
public const string DatabaseMaintenancePortKey = "DatabaseMaintenancePort";
1619
public const string ExpirationProcessTimerInSecondsKey = "ExpirationProcessTimerInSeconds";
1720
public const string LogPathKey = "LogPath";
@@ -24,6 +27,9 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration
2427
DatabaseNameKey,
2528
DatabasePathKey,
2629
ConnectionStringKey,
30+
ClientCertificatePathKey,
31+
ClientCertificateBase64Key,
32+
ClientCertificatePasswordKey,
2733
DatabaseMaintenancePortKey,
2834
ExpirationProcessTimerInSecondsKey,
2935
LogPathKey,
@@ -59,6 +65,19 @@ internal static DatabaseConfiguration GetDatabaseConfiguration(PersistenceSettin
5965
}
6066

6167
serverConfiguration = new ServerConfiguration(connectionString);
68+
69+
if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificatePathKey, out var clientCertificatePath))
70+
{
71+
serverConfiguration.ClientCertificatePath = clientCertificatePath;
72+
}
73+
if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificateBase64Key, out var clientCertificateBase64))
74+
{
75+
serverConfiguration.ClientCertificateBase64 = clientCertificateBase64;
76+
}
77+
if (settings.PersisterSpecificSettings.TryGetValue(ClientCertificatePasswordKey, out var clientCertificatePassword))
78+
{
79+
serverConfiguration.ClientCertificatePassword = clientCertificatePassword;
80+
}
6281
}
6382
else
6483
{

src/ServiceControl.Audit.Persistence.RavenDB/ServerConfiguration.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace ServiceControl.Audit.Persistence.RavenDB
22
{
3-
public class ServerConfiguration
3+
using ServiceControl.RavenDB;
4+
5+
public class ServerConfiguration : IRavenClientCertificateInfo
46
{
57
public ServerConfiguration(string connectionString)
68
{
@@ -18,6 +20,9 @@ public ServerConfiguration(string dbPath, string serverUrl, string logPath, stri
1820
}
1921

2022
public string ConnectionString { get; }
23+
public string ClientCertificatePath { get; set; }
24+
public string ClientCertificateBase64 { get; set; }
25+
public string ClientCertificatePassword { get; set; }
2126
public bool UseEmbeddedServer { get; }
2227
public string DbPath { get; internal set; } //Setter for ATT only
2328
public string ServerUrl { get; }

src/ServiceControl.Persistence.RavenDB/RavenBootstrapper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ static class RavenBootstrapper
77
public const string DatabaseMaintenancePortKey = "DatabaseMaintenancePort";
88
public const string ExpirationProcessTimerInSecondsKey = "ExpirationProcessTimerInSeconds";
99
public const string ConnectionStringKey = "RavenDB/ConnectionString";
10+
public const string ClientCertificatePathKey = "RavenDB/ClientCertificatePath";
11+
public const string ClientCertificateBase64Key = "RavenDB/ClientCertificateBase64";
12+
public const string ClientCertificatePasswordKey = "RavenDB/ClientCertificatePassword";
1013
public const string MinimumStorageLeftRequiredForIngestionKey = "MinimumStorageLeftRequiredForIngestion";
1114
public const string DatabaseNameKey = "RavenDB/DatabaseName";
1215
public const string LogsPathKey = "LogPath";

src/ServiceControl.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
namespace ServiceControl.Persistence.RavenDB
44
{
55
using System;
6-
using System.IO;
7-
using System.Reflection;
8-
using System.Security.Cryptography.X509Certificates;
96
using System.Threading;
107
using System.Threading.Tasks;
118
using Raven.Client.Documents;
@@ -38,21 +35,11 @@ public async Task Initialize(CancellationToken cancellationToken)
3835
{
3936
await initializeSemaphore.WaitAsync(cancellationToken);
4037

41-
// Look for raven-client-certificate.pfx in same directory as application code
42-
var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
43-
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");
44-
X509Certificate2? certificate = null;
45-
46-
if (File.Exists(certificatePath))
47-
{
48-
certificate = new X509Certificate2(certificatePath);
49-
}
50-
5138
var store = new DocumentStore
5239
{
5340
Database = settings.DatabaseName,
5441
Urls = [settings.ConnectionString],
55-
Certificate = certificate,
42+
Certificate = RavenClientCertificate.FindClientCertificate(settings),
5643
Conventions = new DocumentConventions
5744
{
5845
SaveEnumsAsIntegers = true

src/ServiceControl.Persistence.RavenDB/RavenPersistenceConfiguration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ static T GetRequiredSetting<T>(SettingsRootNamespace settingsRootNamespace, stri
3434
var settings = new RavenPersisterSettings
3535
{
3636
ConnectionString = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ConnectionStringKey),
37+
ClientCertificatePath = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificatePathKey),
38+
ClientCertificateBase64 = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificateBase64Key),
39+
ClientCertificatePassword = SettingsReader.Read<string>(settingsRootNamespace, RavenBootstrapper.ClientCertificatePasswordKey),
3740
DatabaseName = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabaseNameKey, RavenPersisterSettings.DatabaseNameDefault),
3841
DatabasePath = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabasePathKey, DefaultDatabaseLocation()),
3942
DatabaseMaintenancePort = SettingsReader.Read(settingsRootNamespace, RavenBootstrapper.DatabaseMaintenancePortKey, RavenPersisterSettings.DatabaseMaintenancePortDefault),

src/ServiceControl.Persistence.RavenDB/RavenPersisterSettings.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
using Particular.LicensingComponent.Contracts;
33
using ServiceControl.Persistence;
44
using ServiceControl.Persistence.RavenDB.CustomChecks;
5+
using ServiceControl.RavenDB;
56

6-
class RavenPersisterSettings : PersistenceSettings
7+
class RavenPersisterSettings : PersistenceSettings, IRavenClientCertificateInfo
78
{
89
public int DatabaseMaintenancePort { get; set; } = DatabaseMaintenancePortDefault;
910
public int ExpirationProcessTimerInSeconds { get; set; } = ExpirationProcessTimerInSecondsDefault;
@@ -23,6 +24,9 @@ class RavenPersisterSettings : PersistenceSettings
2324
/// User provided external RavenDB instance connection string
2425
/// </summary>
2526
public string ConnectionString { get; set; }
27+
public string ClientCertificatePath { get; set; }
28+
public string ClientCertificateBase64 { get; set; }
29+
public string ClientCertificatePassword { get; set; }
2630
public bool UseEmbeddedServer => string.IsNullOrWhiteSpace(ConnectionString);
2731
public string LogPath { get; set; }
2832
public string LogsMode { get; set; } = LogsModeDefault;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#nullable enable
2+
3+
namespace ServiceControl.RavenDB;
4+
5+
using System.Reflection;
6+
using System.Security.Cryptography;
7+
using System.Security.Cryptography.X509Certificates;
8+
9+
public static class RavenClientCertificate
10+
{
11+
public static X509Certificate2? FindClientCertificate(IRavenClientCertificateInfo certInfo)
12+
{
13+
if (certInfo.ClientCertificateBase64 is not null)
14+
{
15+
try
16+
{
17+
var bytes = Convert.FromBase64String(certInfo.ClientCertificateBase64);
18+
return new X509Certificate2(bytes, certInfo.ClientCertificatePassword);
19+
}
20+
catch (Exception x) when (x is FormatException or CryptographicException)
21+
{
22+
throw new Exception("Could not read the RavenDB client certificate from the configured Base64 value.", x);
23+
}
24+
}
25+
26+
if (certInfo.ClientCertificatePath is not null)
27+
{
28+
if (!File.Exists(certInfo.ClientCertificatePath))
29+
{
30+
throw new Exception("Could not read the RavenDB client certificate from the supplied path because no file was found.");
31+
}
32+
return new X509Certificate2(certInfo.ClientCertificatePath, certInfo.ClientCertificatePassword);
33+
}
34+
35+
var applicationDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
36+
var certificatePath = Path.Combine(applicationDirectory, "raven-client-certificate.pfx");
37+
38+
if (File.Exists(certificatePath))
39+
{
40+
return new X509Certificate2(certificatePath, certInfo.ClientCertificatePassword);
41+
}
42+
return null;
43+
}
44+
}
45+
46+
public interface IRavenClientCertificateInfo
47+
{
48+
string? ClientCertificatePath { get; }
49+
string? ClientCertificateBase64 { get; }
50+
string? ClientCertificatePassword { get; }
51+
}

0 commit comments

Comments
 (0)