notify doctors work, audit work, new product advisory sprints

This commit is contained in:
master
2026-01-13 08:36:29 +02:00
parent b8868a5f13
commit 9ca7cb183e
343 changed files with 24492 additions and 3544 deletions

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Cryptography;
using StellaOps.Determinism;
using StellaOps.Policy.Engine.AirGap;
using StellaOps.Policy.RiskProfile.Models;
using Xunit;
@@ -10,6 +11,7 @@ public sealed class RiskProfileAirGapExportServiceTests
{
private readonly FakeCryptoHash _cryptoHash = new();
private readonly FakeTimeProvider _timeProvider = new();
private readonly IGuidProvider _guidProvider = new SequentialGuidProvider();
private readonly NullLogger<RiskProfileAirGapExportService> _logger = new();
private RiskProfileAirGapExportService CreateService(ISealedModeService? sealedMode = null)
@@ -17,6 +19,7 @@ public sealed class RiskProfileAirGapExportServiceTests
return new RiskProfileAirGapExportService(
_cryptoHash,
_timeProvider,
_guidProvider,
_logger,
sealedMode);
}

View File

@@ -0,0 +1,156 @@
using System.Net;
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Engine.Attestation;
using StellaOps.TestKit.Fixtures;
using Xunit;
namespace StellaOps.Policy.Engine.Tests.Integration;
public sealed class PolicyEngineApiHostTests : IClassFixture<PolicyEngineWebServiceFixture>
{
private readonly PolicyEngineWebServiceFixture _factory;
public PolicyEngineApiHostTests(PolicyEngineWebServiceFixture factory)
{
_factory = factory;
}
[Fact]
public async Task Healthz_ReturnsOk()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync("/healthz");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task PolicyLintRules_RequireAuth()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v1/policy/lint/rules");
Assert.True(response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden);
}
[Fact]
public async Task PolicyLintRules_WithAuth_ReturnsOk()
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add(TestAuthHandler.HeaderName, TestAuthHandler.HeaderValue);
var response = await client.GetAsync("/api/v1/policy/lint/rules");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task PolicySnapshotsApi_RequiresAuth()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync("/api/policy/snapshots");
Assert.True(response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden);
}
[Fact]
public void VerdictAttestationOptions_BindFromConfiguration()
{
var options = _factory.Services.GetRequiredService<VerdictAttestationOptions>();
Assert.True(options.Enabled);
Assert.True(options.FailOnError);
Assert.True(options.RekorEnabled);
Assert.Equal("http://attestor.test", options.AttestorUrl);
Assert.Equal(TimeSpan.FromSeconds(15), options.Timeout);
}
}
public sealed class PolicyEngineWebServiceFixture : WebServiceFixture<StellaOps.Policy.Engine.Program>
{
public PolicyEngineWebServiceFixture()
: base(ConfigureServices, ConfigureWebHost)
{
}
private static void ConfigureServices(IServiceCollection services)
{
services.RemoveAll<IHostedService>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = TestAuthHandler.SchemeName;
options.DefaultChallengeScheme = TestAuthHandler.SchemeName;
})
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
TestAuthHandler.SchemeName,
_ => { });
}
private static void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((_, config) =>
{
var settings = new Dictionary<string, string?>
{
["VerdictAttestation:Enabled"] = "true",
["VerdictAttestation:FailOnError"] = "true",
["VerdictAttestation:RekorEnabled"] = "true",
["VerdictAttestation:AttestorUrl"] = "http://attestor.test",
["VerdictAttestation:Timeout"] = "00:00:15"
};
config.AddInMemoryCollection(settings);
});
}
}
internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string SchemeName = "Test";
public const string HeaderName = "X-Test-Auth";
public const string HeaderValue = "true";
public TestAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(HeaderName, out var value) ||
!string.Equals(value, HeaderValue, StringComparison.Ordinal))
{
return Task.FromResult(AuthenticateResult.Fail("Missing test auth header."));
}
var claims = new[]
{
new Claim("scope", "policy:read"),
new Claim("tenant_id", "test-tenant"),
new Claim(ClaimTypes.NameIdentifier, "test-user")
};
var identity = new ClaimsIdentity(claims, SchemeName);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, SchemeName);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}

View File

@@ -137,7 +137,7 @@ public sealed class PolicyBundleServiceTests
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());
var metadataExtractor = new PolicyMetadataExtractor();
var compilationService = new PolicyCompilationService(compiler, complexity, metadataExtractor, new StaticOptionsMonitor(options.Value), TimeProvider.System);
var repo = new InMemoryPolicyPackRepository();
var repo = new InMemoryPolicyPackRepository(TimeProvider.System);
return new ServiceHarness(
new PolicyBundleService(compilationService, repo, TimeProvider.System),
repo);

View File

@@ -436,7 +436,7 @@ public sealed class PolicyRuntimeEvaluationServiceTests
private static TestHarness CreateHarness()
{
var repository = new InMemoryPolicyPackRepository();
var repository = new InMemoryPolicyPackRepository(TimeProvider.System);
var cacheLogger = NullLogger<InMemoryPolicyEvaluationCache>.Instance;
var serviceLogger = NullLogger<PolicyRuntimeEvaluationService>.Instance;
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());

View File

@@ -12,7 +12,7 @@ public sealed class PolicyRuntimeEvaluatorTests
[Fact]
public async Task EvaluateAsync_ReturnsDeterministicDecisionAndCaches()
{
var repo = new InMemoryPolicyPackRepository();
var repo = new InMemoryPolicyPackRepository(TimeProvider.System);
await repo.StoreBundleAsync(
"pack-1",
1,
@@ -41,7 +41,7 @@ public sealed class PolicyRuntimeEvaluatorTests
[Fact]
public async Task EvaluateAsync_ThrowsWhenBundleMissing()
{
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository());
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository(TimeProvider.System));
var request = new PolicyEvaluationRequest("pack-x", 1, "subject-a");
await Assert.ThrowsAsync<InvalidOperationException>(() => evaluator.EvaluateAsync(request, TestContext.Current.CancellationToken));

View File

@@ -2,6 +2,7 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Policy.Engine.Tenancy;
using Xunit;
@@ -139,11 +140,13 @@ public sealed class TenantContextMiddlewareTests
private readonly NullLogger<TenantContextMiddleware> _logger;
private readonly TenantContextAccessor _tenantAccessor;
private readonly TenantContextOptions _options;
private readonly TimeProvider _timeProvider;
public TenantContextMiddlewareTests()
{
_logger = NullLogger<TenantContextMiddleware>.Instance;
_tenantAccessor = new TenantContextAccessor();
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
_options = new TenantContextOptions
{
Enabled = true,
@@ -166,7 +169,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
@@ -192,7 +196,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123", "project-456");
@@ -214,7 +219,8 @@ public sealed class TenantContextMiddlewareTests
var middleware = new TenantContextMiddleware(
_ => { nextCalled = true; return Task.CompletedTask; },
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
@@ -245,7 +251,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(optionsNotRequired),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
@@ -266,7 +273,8 @@ public sealed class TenantContextMiddlewareTests
var middleware = new TenantContextMiddleware(
_ => { nextCalled = true; return Task.CompletedTask; },
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/healthz", tenantId: null);
@@ -287,7 +295,8 @@ public sealed class TenantContextMiddlewareTests
var middleware = new TenantContextMiddleware(
_ => { nextCalled = true; return Task.CompletedTask; },
MsOptions.Options.Create(disabledOptions),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
@@ -313,7 +322,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", tenantId);
@@ -338,7 +348,8 @@ public sealed class TenantContextMiddlewareTests
var middleware = new TenantContextMiddleware(
_ => { nextCalled = true; return Task.CompletedTask; },
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", tenantId);
@@ -358,7 +369,8 @@ public sealed class TenantContextMiddlewareTests
var middleware = new TenantContextMiddleware(
_ => Task.CompletedTask,
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", longTenantId);
@@ -384,7 +396,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123", projectId);
@@ -409,7 +422,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
var claims = new[]
@@ -440,7 +454,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
var claims = new[]
@@ -471,7 +486,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
var claims = new[] { new Claim("sub", "user-id-123") };
@@ -498,7 +514,8 @@ public sealed class TenantContextMiddlewareTests
return Task.CompletedTask;
},
MsOptions.Options.Create(_options),
_logger);
_logger,
_timeProvider);
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
context.Request.Headers["X-StellaOps-Actor"] = "service-account-123";

View File

@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Determinism;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Engine.ReachabilityFacts;
using StellaOps.Policy.Engine.Vex;
@@ -398,6 +399,7 @@ public class VexDecisionEmitterTests
gateEvaluator,
options,
TimeProvider.System,
SystemGuidProvider.Instance,
NullLogger<VexDecisionEmitter>.Instance);
}

View File

@@ -8,6 +8,7 @@ using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using MsOptions = Microsoft.Extensions.Options;
using Moq;
using StellaOps.Determinism;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Engine.ReachabilityFacts;
using StellaOps.Policy.Engine.Vex;
@@ -499,6 +500,7 @@ public sealed class VexDecisionReachabilityIntegrationTests
gateEvaluator,
new OptionsMonitorWrapper<VexDecisionEmitterOptions>(options.Value),
timeProvider ?? TimeProvider.System,
SystemGuidProvider.Instance,
NullLogger<VexDecisionEmitter>.Instance);
}