- Implemented PolicyDslValidator with command-line options for strict mode and JSON output. - Created PolicySchemaExporter to generate JSON schemas for policy-related models. - Developed PolicySimulationSmoke tool to validate policy simulations against expected outcomes. - Added project files and necessary dependencies for each tool. - Ensured proper error handling and usage instructions across tools.
186 lines
8.1 KiB
C#
186 lines
8.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MongoDB.Driver;
|
|
using StellaOps.Authority.Plugins.Abstractions;
|
|
using StellaOps.Authority.Plugin.Standard.Storage;
|
|
using StellaOps.Authority.Storage.Mongo.Documents;
|
|
using StellaOps.Authority.Storage.Mongo.Stores;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Authority.Plugin.Standard.Tests;
|
|
|
|
public class StandardClientProvisioningStoreTests
|
|
{
|
|
[Fact]
|
|
public async Task CreateOrUpdateAsync_HashesSecretAndPersistsDocument()
|
|
{
|
|
var store = new TrackingClientStore();
|
|
var revocations = new TrackingRevocationStore();
|
|
var provisioning = new StandardClientProvisioningStore("standard", store, revocations, TimeProvider.System);
|
|
|
|
var registration = new AuthorityClientRegistration(
|
|
clientId: "bootstrap-client",
|
|
confidential: true,
|
|
displayName: "Bootstrap",
|
|
clientSecret: "SuperSecret1!",
|
|
allowedGrantTypes: new[] { "client_credentials" },
|
|
allowedScopes: new[] { "scopeA" });
|
|
|
|
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
|
|
|
|
Assert.True(result.Succeeded);
|
|
Assert.True(store.Documents.TryGetValue("bootstrap-client", out var document));
|
|
Assert.NotNull(document);
|
|
Assert.Equal(AuthoritySecretHasher.ComputeHash("SuperSecret1!"), document!.SecretHash);
|
|
Assert.Equal("standard", document.Plugin);
|
|
|
|
var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", CancellationToken.None);
|
|
Assert.NotNull(descriptor);
|
|
Assert.Equal("bootstrap-client", descriptor!.ClientId);
|
|
Assert.True(descriptor.Confidential);
|
|
Assert.Contains("client_credentials", descriptor.AllowedGrantTypes);
|
|
Assert.Contains("scopea", descriptor.AllowedScopes);
|
|
}
|
|
|
|
[Fact]
|
|
[Fact]
|
|
public async Task CreateOrUpdateAsync_NormalisesTenant()
|
|
{
|
|
var store = new TrackingClientStore();
|
|
var revocations = new TrackingRevocationStore();
|
|
var provisioning = new StandardClientProvisioningStore("standard", store, revocations, TimeProvider.System);
|
|
|
|
var registration = new AuthorityClientRegistration(
|
|
clientId: "tenant-client",
|
|
confidential: false,
|
|
displayName: "Tenant Client",
|
|
clientSecret: null,
|
|
allowedGrantTypes: new[] { "client_credentials" },
|
|
allowedScopes: new[] { "scopeA" },
|
|
tenant: " Tenant-Alpha " );
|
|
|
|
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
|
|
|
|
Assert.True(store.Documents.TryGetValue("tenant-client", out var document));
|
|
Assert.NotNull(document);
|
|
Assert.Equal("tenant-alpha", document!.Properties[AuthorityClientMetadataKeys.Tenant]);
|
|
|
|
var descriptor = await provisioning.FindByClientIdAsync("tenant-client", CancellationToken.None);
|
|
Assert.NotNull(descriptor);
|
|
Assert.Equal("tenant-alpha", descriptor!.Tenant);
|
|
}
|
|
|
|
|
|
public async Task CreateOrUpdateAsync_StoresAudiences()
|
|
{
|
|
var store = new TrackingClientStore();
|
|
var revocations = new TrackingRevocationStore();
|
|
var provisioning = new StandardClientProvisioningStore("standard", store, revocations, TimeProvider.System);
|
|
|
|
var registration = new AuthorityClientRegistration(
|
|
clientId: "signer",
|
|
confidential: false,
|
|
displayName: "Signer",
|
|
clientSecret: null,
|
|
allowedGrantTypes: new[] { "client_credentials" },
|
|
allowedScopes: new[] { "signer.sign" },
|
|
allowedAudiences: new[] { "attestor", "signer" });
|
|
|
|
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
|
|
|
|
Assert.True(result.Succeeded);
|
|
Assert.True(store.Documents.TryGetValue("signer", out var document));
|
|
Assert.NotNull(document);
|
|
Assert.Equal("attestor signer", document!.Properties[AuthorityClientMetadataKeys.Audiences]);
|
|
|
|
var descriptor = await provisioning.FindByClientIdAsync("signer", CancellationToken.None);
|
|
Assert.NotNull(descriptor);
|
|
Assert.Equal(new[] { "attestor", "signer" }, descriptor!.AllowedAudiences.OrderBy(value => value, StringComparer.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateOrUpdateAsync_MapsCertificateBindings()
|
|
{
|
|
var store = new TrackingClientStore();
|
|
var revocations = new TrackingRevocationStore();
|
|
var provisioning = new StandardClientProvisioningStore("standard", store, revocations, TimeProvider.System);
|
|
|
|
var bindingRegistration = new AuthorityClientCertificateBindingRegistration(
|
|
thumbprint: "aa:bb:cc:dd",
|
|
serialNumber: "01ff",
|
|
subject: "CN=mtls-client",
|
|
issuer: "CN=test-ca",
|
|
subjectAlternativeNames: new[] { "client.mtls.test", "spiffe://client" },
|
|
notBefore: DateTimeOffset.UtcNow.AddMinutes(-5),
|
|
notAfter: DateTimeOffset.UtcNow.AddHours(1),
|
|
label: "primary");
|
|
|
|
var registration = new AuthorityClientRegistration(
|
|
clientId: "mtls-client",
|
|
confidential: true,
|
|
displayName: "MTLS Client",
|
|
clientSecret: "secret",
|
|
allowedGrantTypes: new[] { "client_credentials" },
|
|
allowedScopes: new[] { "signer.sign" },
|
|
allowedAudiences: new[] { "signer" },
|
|
certificateBindings: new[] { bindingRegistration });
|
|
|
|
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
|
|
|
|
Assert.True(store.Documents.TryGetValue("mtls-client", out var document));
|
|
Assert.NotNull(document);
|
|
var binding = Assert.Single(document!.CertificateBindings);
|
|
Assert.Equal("AABBCCDD", binding.Thumbprint);
|
|
Assert.Equal("01ff", binding.SerialNumber);
|
|
Assert.Equal("CN=mtls-client", binding.Subject);
|
|
Assert.Equal("CN=test-ca", binding.Issuer);
|
|
Assert.Equal(new[] { "client.mtls.test", "spiffe://client" }, binding.SubjectAlternativeNames);
|
|
Assert.Equal(bindingRegistration.NotBefore, binding.NotBefore);
|
|
Assert.Equal(bindingRegistration.NotAfter, binding.NotAfter);
|
|
Assert.Equal("primary", binding.Label);
|
|
}
|
|
|
|
private sealed class TrackingClientStore : IAuthorityClientStore
|
|
{
|
|
public Dictionary<string, AuthorityClientDocument> Documents { get; } = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public ValueTask<AuthorityClientDocument?> FindByClientIdAsync(string clientId, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
Documents.TryGetValue(clientId, out var document);
|
|
return ValueTask.FromResult(document);
|
|
}
|
|
|
|
public ValueTask UpsertAsync(AuthorityClientDocument document, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
Documents[document.ClientId] = document;
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
public ValueTask<bool> DeleteByClientIdAsync(string clientId, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
var removed = Documents.Remove(clientId);
|
|
return ValueTask.FromResult(removed);
|
|
}
|
|
}
|
|
|
|
private sealed class TrackingRevocationStore : IAuthorityRevocationStore
|
|
{
|
|
public List<AuthorityRevocationDocument> Upserts { get; } = new();
|
|
|
|
public ValueTask UpsertAsync(AuthorityRevocationDocument document, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
Upserts.Add(document);
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
public ValueTask<bool> RemoveAsync(string category, string revocationId, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
=> ValueTask.FromResult(true);
|
|
|
|
public ValueTask<IReadOnlyList<AuthorityRevocationDocument>> GetActiveAsync(DateTimeOffset asOf, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
=> ValueTask.FromResult<IReadOnlyList<AuthorityRevocationDocument>>(Array.Empty<AuthorityRevocationDocument>());
|
|
}
|
|
}
|