-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added simple configuration for platform configuration as well as tena…
…ncy configuration. #4
- Loading branch information
1 parent
39162b5
commit 0dadaac
Showing
28 changed files
with
1,088 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Configuration | ||
|
||
## Design Principles | ||
|
||
1. We want all components in each host to have access to static configuration (read-only), that would be set at deployment time. | ||
2. We want that configuration to be written into packaged assets just before deployment, in the CD pipeline. | ||
3. We want that configuration to be specific to a specific environment (e.g. Local development, Staging, Holding or Production) | ||
4. We do not want developers writing anything but local development environment settings (secrets or otherwise) into configuration files. With one exception: the configuration used to configure components for integration testing against real 3rd party systems (i.e. in tests of the category: `Integration.External`). These 3rd party accounts/environments, should never be related to production environments, and are designed only for testing-only. Configuration (and especially any secrets) used for these accounts/environments can NEVER lead those with access to them to compromise the system or its integrity. | ||
5. We will need some configuration for the SaaS "platform" (all shared components), and some configuration for each "tenant" running on the platform. These two sets of configuration must be kept separate for each other, but may not be stored in the same repositories. (e.g. platform configuration is defined in appsettings.json, whilst tenancy configuration is stored in a database) | ||
6. Configuration needs to be hierarchical (e.g. namespaced), and hierarchical in terms of layering. | ||
7. Settings are expected to be of only 3 types: `string`, `number` and `boolean` | ||
8. Components are responsible for reading their own configuration, and shall not re-use other components configuration. | ||
9. Secrets may be stored separately from non-confidential configuration in other repositories (e.g. files, databases, 3rd party services). | ||
10. We want to be able to change storage location of configuration at any time, without breaking code (e.g. files, databases, 3rd party services). | ||
11. We want to use dependency injection to give components their configuration. | ||
|
||
## Implementation | ||
|
||
The `IConfigurationSettings` abstraction is used to give access to configuration for both "Platform" settings and "Tenancy" settings. | ||
|
||
### Platform Settings | ||
|
||
Platform settings are setting that are shared across all components running in the platform. | ||
|
||
For example: | ||
|
||
* Connection strings to centralized repositories (for hosting data pertaining to all tenants on the platform) | ||
* Account details for accessing shared 3rd party system accounts via adapters (e.g. an email provider) | ||
* Keys and defaults for various application and domain services | ||
|
||
Most of these settings will be stored in standard places that are supported by the .NET runtime, such as `appsettings.json` files for the specific environment. | ||
|
||
### Tenancy Settings | ||
|
||
Tenancy settings are setting that are specific to a tenant running on the platform. | ||
|
||
For example: | ||
|
||
* Connection strings to a tenant's physically partitioned repository (e.g. in a nearby datacenter of their choice) | ||
* Account details for accessing a specific 3rd party system account via adapters (e.g. an accounting integration) | ||
|
||
At runtime, in a multi-tenanted host, when the inbound HTTP request is destined for an API that is tenanted, the `ITenantContext` will define the tenancy and settings for the current HTTP request. | ||
|
||
These settings are generally read from a dynamic repository (e.g. a database, or 3rd party service), and they are unique to the specific tenant. | ||
|
||
> Never to be accidentally accessed by or exposed to other tenants running on the platform |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Application.Interfaces.Resources; | ||
|
||
public class TenantSetting | ||
{ | ||
public bool IsEncrypted { get; set; } | ||
|
||
public string? Value { get; set; } | ||
} |
11 changes: 11 additions & 0 deletions
11
src/Application.Interfaces/Services/ITenantSettingsService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using Application.Interfaces.Resources; | ||
|
||
namespace Application.Interfaces.Services; | ||
|
||
/// <summary> | ||
/// Defines an application service for working with tenant-specific settings | ||
/// </summary> | ||
public interface ITenantSettingsService | ||
{ | ||
IReadOnlyDictionary<string, TenantSetting> CreateForNewTenant(ICallerContext context, string tenantId); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace Common.Configuration; | ||
|
||
/// <summary> | ||
/// Configuration settings for the platform, and the current tenancy | ||
/// </summary> | ||
public interface IConfigurationSettings | ||
{ | ||
ISettings Platform { get; } | ||
|
||
ISettings Tenancy { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
namespace Common.Configuration; | ||
|
||
/// <summary> | ||
/// Defines a provider of simple settings | ||
/// </summary> | ||
public interface ISettings | ||
{ | ||
public bool IsConfigured { get; } | ||
|
||
public bool GetBool(string key); | ||
|
||
public double GetNumber(string key); | ||
|
||
public string GetString(string key); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Domain.Interfaces.Services; | ||
|
||
/// <summary> | ||
/// Defines a domain service for reading tenant settings | ||
/// </summary> | ||
public interface ITenantSettingService | ||
{ | ||
string Decrypt(string encryptedValue); | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Infrastructure.Common.UnitTests/DomainServices/AesEncryptionServiceSpec.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#if TESTINGONLY | ||
using FluentAssertions; | ||
using Infrastructure.Common.DomainServices; | ||
using Xunit; | ||
|
||
namespace Infrastructure.Common.UnitTests.DomainServices; | ||
|
||
[Trait("Category", "Unit")] | ||
public class AesEncryptionServiceSpec | ||
{ | ||
private readonly AesEncryptionService _service; | ||
|
||
public AesEncryptionServiceSpec() | ||
{ | ||
var secret = AesEncryptionService.CreateAesSecret(); | ||
|
||
_service = new AesEncryptionService(secret); | ||
} | ||
|
||
[Fact] | ||
public void WhenDecryptAndEncrypted_ThenReturnsPlainText() | ||
{ | ||
var cipherText = _service.Encrypt("avalue"); | ||
var plainText = _service.Decrypt(cipherText); | ||
|
||
cipherText.Should().NotBe("avalue"); | ||
plainText.Should().NotBe(cipherText); | ||
plainText.Should().Be("avalue"); | ||
} | ||
} | ||
#endif |
18 changes: 18 additions & 0 deletions
18
src/Infrastructure.Common.UnitTests/Infrastructure.Common.UnitTests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Application.Interfaces\Application.Interfaces.csproj" /> | ||
<ProjectReference Include="..\Infrastructure.Common\Infrastructure.Common.csproj" /> | ||
<ProjectReference Include="..\UnitTesting.Common\UnitTesting.Common.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
90 changes: 90 additions & 0 deletions
90
src/Infrastructure.Common/DomainServices/AesEncryptionService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
using System.Security.Cryptography; | ||
|
||
namespace Infrastructure.Common.DomainServices; | ||
|
||
/// <summary> | ||
/// Provides a domain service for encrypting values, using AES encryption | ||
/// </summary> | ||
public class AesEncryptionService | ||
{ | ||
private const string SecretKeyDelimiter = "::"; | ||
private readonly string _aesSecret; | ||
|
||
public AesEncryptionService(string aesSecret) | ||
{ | ||
_aesSecret = aesSecret; | ||
} | ||
|
||
public string Decrypt(string cipherText) | ||
{ | ||
using var aes = CreateAes(); | ||
Check failure on line 20 in src/Infrastructure.Common/DomainServices/AesEncryptionService.cs GitHub Actions / build-test
|
||
var (cryptKey, iv) = GetAesKeysFromSecret(_aesSecret); | ||
using var decryptor = aes.CreateDecryptor(cryptKey, iv); | ||
|
||
var cipher = Convert.FromBase64String(cipherText); | ||
using var ms = new MemoryStream(cipher); | ||
using var cryptoStream = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); | ||
using var reader = new StreamReader(cryptoStream); | ||
return reader.ReadToEnd(); | ||
} | ||
|
||
public string Encrypt(string plainText) | ||
{ | ||
using var aes = CreateAes(); | ||
Check failure on line 33 in src/Infrastructure.Common/DomainServices/AesEncryptionService.cs GitHub Actions / build-test
|
||
var (cryptKey, iv) = GetAesKeysFromSecret(_aesSecret); | ||
using var encryptor = aes.CreateEncryptor(cryptKey, iv); | ||
|
||
byte[] cipher; | ||
using (var ms = new MemoryStream()) | ||
{ | ||
using (var cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) | ||
{ | ||
using (var writer = new StreamWriter(cryptoStream)) | ||
{ | ||
writer.Write(plainText); | ||
} | ||
|
||
cipher = ms.ToArray(); | ||
} | ||
} | ||
|
||
return Convert.ToBase64String(cipher); | ||
} | ||
|
||
private static (byte[] key, byte[] iv) GetAesKeysFromSecret(string aesSecret) | ||
{ | ||
var rightSide = aesSecret.Substring(0, aesSecret.IndexOf(SecretKeyDelimiter, StringComparison.Ordinal)); | ||
var leftSide = aesSecret.Substring(aesSecret.IndexOf(SecretKeyDelimiter, StringComparison.Ordinal) | ||
+ SecretKeyDelimiter.Length); | ||
var cryptKey = Convert.FromBase64String(rightSide); | ||
var iv = Convert.FromBase64String(leftSide); | ||
|
||
return (cryptKey, iv); | ||
} | ||
|
||
#if TESTINGONLY | ||
|
||
public static string CreateAesSecret() | ||
{ | ||
CreateKeyAndIv(out var cryptKey, out var iv); | ||
return $"{Convert.ToBase64String(cryptKey)}{SecretKeyDelimiter}{Convert.ToBase64String(iv)}"; | ||
} | ||
|
||
private static void CreateKeyAndIv(out byte[] cryptKey, out byte[] iv) | ||
{ | ||
using var aes = CreateAes(); | ||
cryptKey = aes.Key; | ||
iv = aes.IV; | ||
} | ||
|
||
private static SymmetricAlgorithm CreateAes() | ||
{ | ||
var aes = Aes.Create(); | ||
aes.KeySize = 256; | ||
aes.BlockSize = 128; | ||
aes.Mode = CipherMode.CBC; | ||
aes.Padding = PaddingMode.PKCS7; | ||
return aes; | ||
} | ||
#endif | ||
} |
23 changes: 23 additions & 0 deletions
23
src/Infrastructure.Common/DomainServices/TenantSettingService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Domain.Interfaces.Services; | ||
|
||
namespace Infrastructure.Common.DomainServices; | ||
|
||
/// <summary> | ||
/// Provides a domain service for handling settings for a tenant | ||
/// </summary> | ||
public class TenantSettingService : ITenantSettingService | ||
{ | ||
public const string EncryptionServiceSecretSettingName = "DomainServices:TenantSettingService:AesSecret"; | ||
|
||
private readonly AesEncryptionService _encryptionService; | ||
|
||
public TenantSettingService(AesEncryptionService encryptionService) | ||
{ | ||
_encryptionService = encryptionService; | ||
} | ||
|
||
public string Decrypt(string encryptedValue) | ||
{ | ||
return _encryptionService.Decrypt(encryptedValue); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Infrastructure.Interfaces; | ||
|
||
namespace Infrastructure.Common; | ||
|
||
/// <summary> | ||
/// Defines a simple tenancy context that can be set | ||
/// </summary> | ||
public class SimpleTenancyContext : ITenancyContext | ||
{ | ||
public string? Current { get; private set; } | ||
|
||
public IReadOnlyDictionary<string, object> Settings { get; private set; } = new Dictionary<string, object>(); | ||
|
||
public void Set(string id, Dictionary<string, object> settings) | ||
{ | ||
Current = id; | ||
Settings = settings; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Infrastructure.Interfaces; | ||
|
||
/// <summary> | ||
/// Defines the context of a tenancy operating on the platform | ||
/// </summary> | ||
public interface ITenancyContext | ||
{ | ||
string? Current { get; } | ||
|
||
public IReadOnlyDictionary<string, object> Settings { get; } | ||
|
||
void Set(string id, Dictionary<string, object> settings); | ||
} |
Oops, something went wrong.