Add unit tests for Router configuration and transport layers
- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly. - Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified. - Created tests for ConfigValidationResult to check success and error scenarios. - Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig. - Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport. - Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
@@ -8,6 +8,5 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.IssuerDirectory.Storage.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL data source for the IssuerDirectory module.
|
||||
/// Manages connection pooling, tenant context, and session configuration.
|
||||
/// </summary>
|
||||
public sealed class IssuerDirectoryDataSource : DataSourceBase
|
||||
{
|
||||
private readonly ILogger<IssuerDirectoryDataSource> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new IssuerDirectory data source.
|
||||
/// </summary>
|
||||
/// <param name="options">PostgreSQL connection options.</param>
|
||||
/// <param name="logger">Logger for diagnostics.</param>
|
||||
public IssuerDirectoryDataSource(PostgresOptions options, ILogger<IssuerDirectoryDataSource> logger)
|
||||
: base(options, logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ModuleName => "IssuerDirectory";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConnectionOpened(string role)
|
||||
{
|
||||
_logger.LogDebug("IssuerDirectory connection opened with role {Role}.", role);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConnectionClosed(string role)
|
||||
{
|
||||
_logger.LogDebug("IssuerDirectory connection closed for role {Role}.", role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
-- Migration: 001_initial_schema
|
||||
-- Category: startup
|
||||
-- Description: Initial schema for IssuerDirectory PostgreSQL storage
|
||||
-- Source: docs/db/schemas/issuer.sql
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS issuer;
|
||||
|
||||
-- Issuers (tenant or global)
|
||||
CREATE TABLE IF NOT EXISTS issuer.issuers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL, -- use @global GUID for seed publishers
|
||||
name TEXT NOT NULL, -- logical issuer name (slug)
|
||||
display_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
endpoints JSONB DEFAULT '{}'::jsonb, -- CSAF feeds, OIDC issuer URLs, contact links
|
||||
contact JSONB DEFAULT '{}'::jsonb, -- Contact information
|
||||
metadata JSONB DEFAULT '{}'::jsonb, -- Domain metadata (CVE org ID, CSAF publisher ID, etc.)
|
||||
tags TEXT[] DEFAULT '{}',
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','deprecated')),
|
||||
is_system_seed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_by TEXT,
|
||||
UNIQUE (tenant_id, name)
|
||||
);
|
||||
|
||||
-- Keys
|
||||
CREATE TABLE IF NOT EXISTS issuer.issuer_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
issuer_id UUID NOT NULL REFERENCES issuer.issuers(id) ON DELETE CASCADE,
|
||||
tenant_id UUID NOT NULL,
|
||||
key_id TEXT NOT NULL, -- stable key identifier
|
||||
key_type TEXT NOT NULL CHECK (key_type IN ('ed25519','x509','dsse','kms','hsm','fido2')),
|
||||
public_key TEXT NOT NULL, -- PEM / base64
|
||||
fingerprint TEXT NOT NULL, -- canonical fingerprint for dedupe
|
||||
not_before TIMESTAMPTZ,
|
||||
not_after TIMESTAMPTZ,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','retired','revoked')),
|
||||
replaces_key_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_by TEXT,
|
||||
retired_at TIMESTAMPTZ,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
revoke_reason TEXT,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
UNIQUE (issuer_id, key_id),
|
||||
UNIQUE (fingerprint)
|
||||
);
|
||||
|
||||
-- Trust overrides (tenant-scoped weights)
|
||||
CREATE TABLE IF NOT EXISTS issuer.trust_overrides (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
issuer_id UUID NOT NULL REFERENCES issuer.issuers(id) ON DELETE CASCADE,
|
||||
tenant_id UUID NOT NULL, -- consumer tenant applying the override
|
||||
weight NUMERIC(5,2) NOT NULL CHECK (weight >= -10 AND weight <= 10),
|
||||
rationale TEXT,
|
||||
expires_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_by TEXT,
|
||||
UNIQUE (issuer_id, tenant_id)
|
||||
);
|
||||
|
||||
-- Audit log (issuer-domain specific)
|
||||
CREATE TABLE IF NOT EXISTS issuer.audit (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
actor TEXT,
|
||||
action TEXT NOT NULL, -- create_issuer, update_issuer, delete_issuer, add_key, rotate_key, revoke_key, set_trust, delete_trust, seed_csaf
|
||||
issuer_id UUID,
|
||||
key_id TEXT,
|
||||
trust_override_id UUID,
|
||||
reason TEXT,
|
||||
details JSONB DEFAULT '{}'::jsonb,
|
||||
correlation_id TEXT,
|
||||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Schema migrations tracking
|
||||
CREATE TABLE IF NOT EXISTS issuer.schema_migrations (
|
||||
migration_name TEXT PRIMARY KEY,
|
||||
category TEXT NOT NULL DEFAULT 'startup',
|
||||
checksum TEXT NOT NULL,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
applied_by TEXT,
|
||||
duration_ms INT,
|
||||
|
||||
CONSTRAINT valid_category CHECK (category IN ('startup', 'release', 'seed', 'data'))
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_issuers_tenant ON issuer.issuers(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_issuers_status ON issuer.issuers(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_issuers_slug ON issuer.issuers(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_keys_issuer ON issuer.issuer_keys(issuer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_keys_status ON issuer.issuer_keys(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_keys_tenant ON issuer.issuer_keys(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_trust_tenant ON issuer.trust_overrides(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_tenant_time ON issuer.audit(tenant_id, occurred_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_issuer ON issuer.audit(issuer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schema_migrations_applied_at ON issuer.schema_migrations(applied_at DESC);
|
||||
|
||||
-- Updated-at trigger for issuers/trust overrides
|
||||
CREATE OR REPLACE FUNCTION issuer.update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_issuers_updated_at ON issuer.issuers;
|
||||
CREATE TRIGGER trg_issuers_updated_at
|
||||
BEFORE UPDATE ON issuer.issuers
|
||||
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_keys_updated_at ON issuer.issuer_keys;
|
||||
CREATE TRIGGER trg_keys_updated_at
|
||||
BEFORE UPDATE ON issuer.issuer_keys
|
||||
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_trust_updated_at ON issuer.trust_overrides;
|
||||
CREATE TRIGGER trg_trust_updated_at
|
||||
BEFORE UPDATE ON issuer.trust_overrides
|
||||
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();
|
||||
@@ -0,0 +1,66 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.IssuerDirectory.Storage.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering IssuerDirectory PostgreSQL storage services.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the IssuerDirectory PostgreSQL data source.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configureOptions">Options configuration delegate.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddIssuerDirectoryPostgresStorage(
|
||||
this IServiceCollection services,
|
||||
Action<PostgresOptions> configureOptions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configureOptions);
|
||||
|
||||
var options = new PostgresOptions
|
||||
{
|
||||
ConnectionString = string.Empty,
|
||||
SchemaName = "issuer"
|
||||
};
|
||||
configureOptions(options);
|
||||
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var logger = sp.GetRequiredService<ILogger<IssuerDirectoryDataSource>>();
|
||||
return new IssuerDirectoryDataSource(options, logger);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the IssuerDirectory PostgreSQL data source with provided options.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="options">PostgreSQL options.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddIssuerDirectoryPostgresStorage(
|
||||
this IServiceCollection services,
|
||||
PostgresOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
// Ensure schema is set for issuer module
|
||||
if (string.IsNullOrWhiteSpace(options.SchemaName))
|
||||
{
|
||||
options.SchemaName = "issuer";
|
||||
}
|
||||
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var logger = sp.GetRequiredService<ILogger<IssuerDirectoryDataSource>>();
|
||||
return new IssuerDirectoryDataSource(options, logger);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<RootNamespace>StellaOps.IssuerDirectory.Storage.Postgres</RootNamespace>
|
||||
<AssemblyName>StellaOps.IssuerDirectory.Storage.Postgres</AssemblyName>
|
||||
<Description>PostgreSQL storage implementation for IssuerDirectory module</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.IssuerDirectory.Core\StellaOps.IssuerDirectory.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Migrations\*.sql" LogicalName="%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.W
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.Core.Tests", "StellaOps.IssuerDirectory.Core.Tests\StellaOps.IssuerDirectory.Core.Tests.csproj", "{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.Storage.Postgres", "StellaOps.IssuerDirectory.Storage.Postgres\StellaOps.IssuerDirectory.Storage.Postgres.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -33,6 +35,10 @@ Global
|
||||
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user