feat: add tenant provisioning and key lifecycle (#5)
quality-gate / quality (push) Successful in 50s
quality-gate / quality (push) Successful in 50s
Closes #5
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Server.Provisioning;
|
||||
|
||||
internal interface ISecretProvider
|
||||
{
|
||||
bool TryGetSecret(string reference, out SecretMaterial? secret);
|
||||
}
|
||||
|
||||
internal sealed class SecretMaterial : IDisposable
|
||||
{
|
||||
private byte[]? _bytes;
|
||||
|
||||
public SecretMaterial(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Secret material cannot be empty.", nameof(bytes));
|
||||
}
|
||||
|
||||
_bytes = bytes.ToArray();
|
||||
}
|
||||
|
||||
public int Length => _bytes?.Length ?? 0;
|
||||
|
||||
public byte[] CopyBytes() => _bytes?.ToArray()
|
||||
?? throw new ObjectDisposedException(nameof(SecretMaterial));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_bytes is not null)
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(_bytes);
|
||||
_bytes = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => "[REDACTED SECRET]";
|
||||
}
|
||||
|
||||
internal sealed class EnvironmentSecretProvider : ISecretProvider
|
||||
{
|
||||
private const string Prefix = "env:";
|
||||
|
||||
public bool TryGetSecret(string reference, out SecretMaterial? secret)
|
||||
{
|
||||
secret = null;
|
||||
if (!reference.StartsWith(Prefix, StringComparison.Ordinal)
|
||||
|| reference.Length == Prefix.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string? encoded = Environment.GetEnvironmentVariable(reference[Prefix.Length..]);
|
||||
if (string.IsNullOrEmpty(encoded))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(encoded);
|
||||
secret = new SecretMaterial(bytes);
|
||||
CryptographicOperations.ZeroMemory(bytes);
|
||||
return true;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EphemeralDevelopmentSecretProvider : ISecretProvider, IDisposable
|
||||
{
|
||||
private const string Prefix = "development:ephemeral/";
|
||||
private readonly Dictionary<string, byte[]> _secrets = new(StringComparer.Ordinal);
|
||||
|
||||
public bool TryGetSecret(string reference, out SecretMaterial? secret)
|
||||
{
|
||||
secret = null;
|
||||
if (!reference.StartsWith(Prefix, StringComparison.Ordinal)
|
||||
|| reference.Length == Prefix.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_secrets.TryGetValue(reference, out byte[]? bytes))
|
||||
{
|
||||
bytes = RandomNumberGenerator.GetBytes(32);
|
||||
_secrets.Add(reference, bytes);
|
||||
}
|
||||
|
||||
secret = new SecretMaterial(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (byte[] bytes in _secrets.Values)
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(bytes);
|
||||
}
|
||||
|
||||
_secrets.Clear();
|
||||
}
|
||||
|
||||
public override string ToString() => "[EphemeralDevelopmentSecretProvider]";
|
||||
}
|
||||
|
||||
internal sealed class DictionarySecretProvider : ISecretProvider, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, byte[]> _secrets;
|
||||
|
||||
public DictionarySecretProvider(IReadOnlyDictionary<string, byte[]> secrets) =>
|
||||
_secrets = secrets.ToDictionary(
|
||||
static item => item.Key,
|
||||
static item => item.Value.ToArray(),
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public bool TryGetSecret(string reference, out SecretMaterial? secret)
|
||||
{
|
||||
if (_secrets.TryGetValue(reference, out byte[]? bytes))
|
||||
{
|
||||
secret = new SecretMaterial(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
secret = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (byte[] bytes in _secrets.Values)
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(bytes);
|
||||
}
|
||||
|
||||
_secrets.Clear();
|
||||
}
|
||||
|
||||
public override string ToString() => "[DictionarySecretProvider: REDACTED]";
|
||||
}
|
||||
Reference in New Issue
Block a user