up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -1,183 +1,183 @@
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);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.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]
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);
}
{
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);
}
[Fact]
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>());
}
}
{
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>());
}
}

View File

@@ -8,13 +8,13 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard;
using StellaOps.Authority.Plugin.Standard.Bootstrap;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
@@ -24,7 +24,7 @@ public class StandardPluginRegistrarTests
[Fact]
public async Task Register_ConfiguresIdentityProviderAndSeedsBootstrapUser()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-tests");
var configuration = new ConfigurationBuilder()
@@ -86,7 +86,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_LogsWarning_WhenPasswordPolicyWeaker()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-password-policy");
var configuration = new ConfigurationBuilder()
@@ -131,7 +131,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_ForcesPasswordCapability_WhenManifestMissing()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-capabilities");
var configuration = new ConfigurationBuilder().Build();
@@ -163,7 +163,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_Throws_WhenBootstrapConfigurationIncomplete()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-bootstrap-validation");
var configuration = new ConfigurationBuilder()
@@ -197,7 +197,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_NormalizesTokenSigningKeyDirectory()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-token-signing");
var configuration = new ConfigurationBuilder()
@@ -389,7 +389,7 @@ internal sealed class TestAuthEventSink : IAuthEventSink
internal static class StandardPluginRegistrarTestHelpers
{
public static ServiceCollection CreateServiceCollection(
IMongoDatabase database,
IDatabase database,
IAuthEventSink? authEventSink = null,
IAuthorityCredentialAuditContextAccessor? auditContextAccessor = null)
{

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;
@@ -16,14 +16,14 @@ namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardUserCredentialStoreTests : IAsyncLifetime
{
private readonly IMongoDatabase database;
private readonly IDatabase database;
private readonly StandardPluginOptions options;
private readonly StandardUserCredentialStore store;
private readonly TestAuditLogger auditLogger;
public StandardUserCredentialStoreTests()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
database = client.GetDatabase("authority-tests");
options = new StandardPluginOptions
{
@@ -171,9 +171,9 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.True(auditEntry.Success);
Assert.Equal("legacy", auditEntry.Username);
var updated = await database.GetCollection<StandardUserDocument>("authority_users_standard")
.Find(u => u.NormalizedUsername == "legacy")
.FirstOrDefaultAsync();
var results = await database.GetCollection<StandardUserDocument>("authority_users_standard")
.FindAsync(u => u.NormalizedUsername == "legacy");
var updated = results.FirstOrDefault();
Assert.NotNull(updated);
Assert.StartsWith("$argon2id$", updated!.PasswordHash, StringComparison.Ordinal);