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,97 @@
|
||||
using System.Reflection;
|
||||
using FinalFactory.Rendezvous.Contracts;
|
||||
using FinalFactory.Rendezvous.Server.Provisioning;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Tests.Provisioning;
|
||||
|
||||
public sealed class ProvisioningSecurityTests
|
||||
{
|
||||
[Fact]
|
||||
public void StartupFailsClearlyWhenKeyMaterialIsAbsent()
|
||||
{
|
||||
ProvisioningOptions options = ProvisioningTestData.CreateOptions(
|
||||
ProvisioningTestData.CreateKey(secretReference: "missing-production-secret"));
|
||||
using DictionarySecretProvider empty = ProvisioningTestData.CreateSecrets();
|
||||
|
||||
ProvisioningConfigurationException exception = Assert.Throws<ProvisioningConfigurationException>(
|
||||
() => ProvisioningRuntime.Create(options, empty, ProvisioningTestData.Now));
|
||||
|
||||
Assert.Contains("key-1", exception.Message, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("missing-production-secret", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartupRequiresAnActivePublisherKeyForEveryEnabledPolicy()
|
||||
{
|
||||
ProvisioningOptions options = ProvisioningTestData.CreateOptions();
|
||||
options.Games.Add(ProvisioningTestData.CreatePolicy("unscouted", "production"));
|
||||
using DictionarySecretProvider secrets = ProvisioningTestData.CreateSecrets("secret-1");
|
||||
|
||||
ProvisioningConfigurationException exception = Assert.Throws<ProvisioningConfigurationException>(
|
||||
() => ProvisioningRuntime.Create(options, secrets, ProvisioningTestData.Now));
|
||||
|
||||
Assert.Contains("unscouted/production", exception.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("key", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecretMaterialCredentialsAndKeysAreRedactedFromDiagnostics()
|
||||
{
|
||||
byte[] knownSecret = Enumerable.Range(1, 32).Select(static value => (byte)value).ToArray();
|
||||
string encodedSecret = Convert.ToBase64String(knownSecret);
|
||||
using SecretMaterial material = new(knownSecret);
|
||||
using DictionarySecretProvider secrets = new(new Dictionary<string, byte[]>
|
||||
{
|
||||
["secret-1"] = knownSecret,
|
||||
});
|
||||
using SigningKeyRing keys = SigningKeyRing.Create(
|
||||
[ProvisioningTestData.CreateKey()],
|
||||
secrets);
|
||||
PrincipalCredentialService service = new(
|
||||
"final-factory-rendezvous",
|
||||
"rendezvous-service",
|
||||
TimeSpan.FromSeconds(30),
|
||||
keys);
|
||||
string token = service.Issue(
|
||||
ProvisioningTestData.CreateDedicatedPublisher(),
|
||||
ProvisioningTestData.Now);
|
||||
CredentialValidationResult result = service.Validate(token, ProvisioningTestData.Now);
|
||||
|
||||
string diagnostics = string.Join(
|
||||
'|',
|
||||
material,
|
||||
secrets,
|
||||
keys,
|
||||
service,
|
||||
result);
|
||||
Assert.DoesNotContain(encodedSecret, diagnostics, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(token, diagnostics, StringComparison.Ordinal);
|
||||
Assert.Contains("redacted", diagnostics, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PublicClientAndContractSurfacesContainNoProvisioningSecrets()
|
||||
{
|
||||
Type[] publicTypes = typeof(GameId).Assembly.GetExportedTypes()
|
||||
.Concat(Assembly.Load("FinalFactory.Rendezvous.Client").GetExportedTypes())
|
||||
.ToArray();
|
||||
string[] forbiddenTerms =
|
||||
[
|
||||
"GameSecret",
|
||||
"SigningKey",
|
||||
"KeyMaterial",
|
||||
"PublisherCredential",
|
||||
"SecretProvider",
|
||||
];
|
||||
|
||||
foreach (Type type in publicTypes)
|
||||
{
|
||||
IEnumerable<string> names = type
|
||||
.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Select(static member => member.Name)
|
||||
.Append(type.Name);
|
||||
Assert.DoesNotContain(names, name => forbiddenTerms.Any(term =>
|
||||
name.Contains(term, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user