Files
Rendezvous/tests/FinalFactory.Rendezvous.Tests/Provisioning/ProvisioningSecurityTests.cs
KyuubiYoru 47382ddadc
quality-gate / quality (push) Successful in 50s
feat: add tenant provisioning and key lifecycle (#5)
Closes #5
2026-07-16 05:13:35 +02:00

98 lines
3.9 KiB
C#

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)));
}
}
}