Skip to content

Commit

Permalink
Allow to change encryption algorithm (#4591)
Browse files Browse the repository at this point in the history
  • Loading branch information
4lexKislitsyn committed Apr 5, 2024
1 parent 7d0db6a commit dbe75a8
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 78 deletions.
66 changes: 66 additions & 0 deletions src/modules/secrets/Elsa.Secrets/Encryption/AesSecretEncryptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace Elsa.Secrets.Encryption
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Secrets.Models;
using Elsa.Secrets.Options;
using Microsoft.Extensions.Options;

public class AesSecretEncryptor : ISecretEncryptor
{
private readonly SecretsConfigOptions _secretsConfig;

public AesSecretEncryptor(IOptions<SecretsConfigOptions> options)
{
_secretsConfig = options.Value;
}

public Task EncryptProperties(Secret secret, CancellationToken cancellationToken = default)
{
if (_secretsConfig.EncryptionEnabled == true)
{
foreach (var property in secret.Properties)
{
if (property.IsEncrypted || (_secretsConfig.EncryptedProperties?.Contains(property.Name, StringComparer.OrdinalIgnoreCase) ?? false))
{
continue;
}

foreach (var (key, value) in property.Expressions)
{
property.Expressions[key] = AesEncryption.Encrypt(_secretsConfig.EncryptionKey, value);
}

property.IsEncrypted = true;
}
}

return Task.CompletedTask;
}

public Task DecryptPropertiesAsync(Secret secret, CancellationToken cancellationToken = default)
{
if (_secretsConfig.EncryptionEnabled == true)
{
foreach (var property in secret.Properties)
{
if (!property.IsEncrypted)
{
continue;
}

foreach (var (key, value) in property.Expressions)
{
property.Expressions[key] = AesEncryption.Decrypt(_secretsConfig.EncryptionKey, value);
}

property.IsEncrypted = false;
}
}

return Task.CompletedTask;
}
}
}
12 changes: 12 additions & 0 deletions src/modules/secrets/Elsa.Secrets/Encryption/ISecretEncryptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Elsa.Secrets.Encryption
{
using System.Threading;
using System.Threading.Tasks;
using Elsa.Secrets.Models;

public interface ISecretEncryptor
{
Task EncryptProperties(Secret secret, CancellationToken cancellationToken = default);
Task DecryptPropertiesAsync(Secret secret, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

namespace Elsa.Secrets.Extensions
{
using Elsa.Secrets.Encryption;

public static class SecretsOptionsBuilderExtensions
{
public static ElsaOptionsBuilder AddSecrets(this ElsaOptionsBuilder elsaOptions, IConfiguration configuration)
Expand All @@ -23,7 +25,8 @@ public static ElsaOptionsBuilder AddSecrets(this ElsaOptionsBuilder elsaOptions,
.AddScoped<ISecretsManager, SecretsManager>()
.AddScoped<ISecretsProvider, SecretsProvider>()
.Decorate<ISecretsStore, EventPublishingSecretsStore>()
.AddNotificationHandlersFrom<DescribingActivityTypeHandler>();
.AddNotificationHandlersFrom<DescribingActivityTypeHandler>()
.AddSingleton<ISecretEncryptor, AesSecretEncryptor>();

elsaOptions.Services
.TryAddProvider<IExpressionHandler, SecretsExpressionHandler>(ServiceLifetime.Scoped);
Expand Down
98 changes: 22 additions & 76 deletions src/modules/secrets/Elsa.Secrets/Manager/SecretsManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using Elsa.Persistence.Specifications;
using Elsa.Secrets.Models;
using Elsa.Secrets.Persistence;
Expand All @@ -9,57 +8,51 @@
using Elsa.Secrets.Persistence.Specifications;
using System.Linq;
using Elsa.Secrets.Encryption;
using Elsa.Secrets.Options;
using Microsoft.Extensions.Options;

namespace Elsa.Secrets.Manager
{
public class SecretsManager : ISecretsManager
{
private static readonly string HidedValue = new string('*', 8);
private readonly ISecretsStore _secretsStore;
private readonly bool _encryptionEnabled;
private readonly string _encryptionKey;
private readonly string[] _encryptedProperties;
private readonly ISecretEncryptor _encryptor;

public SecretsManager(ISecretsStore secretsStore, IOptions<SecretsConfigOptions> options)
public SecretsManager(ISecretsStore secretsStore, ISecretEncryptor encryptor)
{
_secretsStore = secretsStore;

_encryptionEnabled = options.Value.Enabled ?? false;
_encryptionKey = options.Value.EncryptionKey;
_encryptedProperties = options.Value.EncryptedProperties;
_encryptor = encryptor;
}

public async Task<Secret?> GetSecretById(string id, CancellationToken cancellationToken = default) {
public virtual async Task<Secret?> GetSecretById(string id, CancellationToken cancellationToken = default) {
var specification = new SecretsIdSpecification(id);
var secret = await _secretsStore.FindAsync(specification, cancellationToken: cancellationToken);
DecryptProperties(secret);
await _encryptor.DecryptPropertiesAsync(secret, cancellationToken);

return secret;
}

public async Task<Secret?> GetSecretByName(string name, CancellationToken cancellationToken = default) {
public virtual async Task<Secret?> GetSecretByName(string name, CancellationToken cancellationToken = default) {
var specification = new SecretsNameSpecification(name);
var secrets = await _secretsStore.FindManyAsync(specification, OrderBySpecification.OrderBy<Secret>(s => s.Type), cancellationToken: cancellationToken);
var secret = secrets.FirstOrDefault();
DecryptProperties(secret);
await _encryptor.DecryptPropertiesAsync(secret, cancellationToken);

return secret;
}

public async Task<IEnumerable<Secret>> GetSecrets(CancellationToken cancellationToken = default)
public virtual async Task<IEnumerable<Secret>> GetSecrets(CancellationToken cancellationToken = default)
{
var specification = Specification<Secret>.Identity;
var secrets = await _secretsStore.FindManyAsync(specification, cancellationToken: cancellationToken);
foreach (var secret in secrets)
{
DecryptProperties(secret);
await _encryptor.DecryptPropertiesAsync(secret, cancellationToken);
}

return secrets;
}

public async Task<IEnumerable<Secret>> GetSecretViewModels(CancellationToken cancellationToken = default)
public virtual async Task<IEnumerable<Secret>> GetSecretViewModels(CancellationToken cancellationToken = default)
{
var specification = Specification<Secret>.Identity;
var secrets = await _secretsStore.FindManyAsync(specification, cancellationToken: cancellationToken);
Expand All @@ -71,7 +64,7 @@ public async Task<IEnumerable<Secret>> GetSecretViewModels(CancellationToken can
return secrets;
}

public async Task<IEnumerable<Secret>> GetSecrets(string type, bool decrypt = true, CancellationToken cancellationToken = default)
public virtual async Task<IEnumerable<Secret>> GetSecrets(string type, bool decrypt = true, CancellationToken cancellationToken = default)
{
var specification = new SecretTypeSpecification(type);
var secrets = await _secretsStore.FindManyAsync(specification, cancellationToken: cancellationToken);
Expand All @@ -80,32 +73,32 @@ public async Task<IEnumerable<Secret>> GetSecrets(string type, bool decrypt = tr
{
foreach (var secret in secrets)
{
DecryptProperties(secret);
await _encryptor.DecryptPropertiesAsync(secret, cancellationToken);
}
}


return secrets;
}

public async Task<Secret> AddOrUpdateSecret(Secret secret, bool restoreHiddenProperties, CancellationToken cancellationToken = default)
public virtual async Task<Secret> AddOrUpdateSecret(Secret secret, bool restoreHiddenProperties, CancellationToken cancellationToken = default)
{
var clone = secret.Clone() as Secret;

if (restoreHiddenProperties)
{
await RestoreHiddenProperties(clone, cancellationToken);
}
EncryptProperties(clone);

await _encryptor.EncryptProperties(clone, cancellationToken);

if (clone.Id == null)
await _secretsStore.AddAsync(clone);
await _secretsStore.AddAsync(clone, cancellationToken);
else
await _secretsStore.UpdateAsync(clone);
await _secretsStore.UpdateAsync(clone, cancellationToken);
return clone;
}

private async Task RestoreHiddenProperties(Secret secret, CancellationToken cancellationToken)
protected virtual async Task RestoreHiddenProperties(Secret secret, CancellationToken cancellationToken)
{
var specification = new SecretsIdSpecification(secret.Id);
var existingSecret = await _secretsStore.FindAsync(specification, cancellationToken: cancellationToken);
Expand All @@ -120,63 +113,16 @@ private async Task RestoreHiddenProperties(Secret secret, CancellationToken canc
}
}
}
private void HideEncryptedProperties(Secret secret)

protected virtual void HideEncryptedProperties(Secret secret)
{
foreach (var secretProperty in secret.Properties)
{
if (!secretProperty.IsEncrypted) continue;
foreach (var key in secretProperty.Expressions.Keys)
{
secretProperty.Expressions[key] = new string('*', 8);
}
}
}

private void EncryptProperties(Secret secret)
{
if (!_encryptionEnabled)
{
return;
}
foreach (var property in secret.Properties)
{
var encrypt = _encryptedProperties.Contains(property.Name, StringComparer.OrdinalIgnoreCase);
if (!encrypt || property.IsEncrypted)
{
continue;
}

foreach (var key in property.Expressions.Keys)
{
var value = property.Expressions[key];
property.Expressions[key] = AesEncryption.Encrypt(_encryptionKey, value);
secretProperty.Expressions[key] = HidedValue;
}

property.IsEncrypted = true;
}
}

private void DecryptProperties(Secret secret)
{
if (!_encryptionEnabled)
{
return;
}
foreach (var property in secret.Properties)
{
if (!property.IsEncrypted)
{
continue;
}

foreach (var key in property.Expressions.Keys)
{
var value = property.Expressions[key];
property.Expressions[key] = AesEncryption.Decrypt(_encryptionKey, value);
}

property.IsEncrypted = false;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ public async Task<ICollection<string>> GetSecretsAsync(string type)
public async Task<bool> IsSecretValueSensitiveData(string type, string name)
{
var secrets = await _secretsManager.GetSecrets(type, false);
var formatter = _valueFormatters.FirstOrDefault(x => x.Type == type);
var secret = secrets.Where(x => x.Name?.Equals(name, StringComparison.InvariantCultureIgnoreCase) == true && x.Type?.Equals(type, StringComparison.InvariantCultureIgnoreCase) == true)
?.FirstOrDefault();
if (secret == null)
{
return false;
}

var formatter = _valueFormatters.FirstOrDefault(x => x.Type == type);
return formatter?.IsSecretValueSensitiveData(secret) ?? false;
}

Expand Down

0 comments on commit dbe75a8

Please sign in to comment.