up
Some checks failed
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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-12 09:35:37 +02:00
parent ce5ec9c158
commit efaf3cb789
238 changed files with 146274 additions and 5767 deletions

View File

@@ -6,10 +6,9 @@ using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using StellaOps.Policy;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Storage.Mongo;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Zastava.Core.Contracts;
@@ -42,8 +41,8 @@ public sealed class RuntimeEndpointsTests
Assert.Equal(0, payload.Duplicates);
using var scope = factory.Services.CreateScope();
var collections = scope.ServiceProvider.GetRequiredService<MongoCollectionProvider>();
var stored = await collections.RuntimeEvents.Find(FilterDefinition<RuntimeEventDocument>.Empty).ToListAsync();
var repository = scope.ServiceProvider.GetRequiredService<RuntimeEventRepository>();
var stored = await repository.ListAsync(CancellationToken.None);
Assert.Equal(2, stored.Count);
Assert.Contains(stored, doc => doc.EventId == "evt-001");
Assert.All(stored, doc =>
@@ -98,8 +97,8 @@ public sealed class RuntimeEndpointsTests
Assert.NotNull(response.Headers.RetryAfter);
using var scope = factory.Services.CreateScope();
var collections = scope.ServiceProvider.GetRequiredService<MongoCollectionProvider>();
var count = await collections.RuntimeEvents.CountDocumentsAsync(FilterDefinition<RuntimeEventDocument>.Empty);
var repository = scope.ServiceProvider.GetRequiredService<RuntimeEventRepository>();
var count = await repository.CountAsync(CancellationToken.None);
Assert.Equal(0, count);
}
@@ -117,8 +116,11 @@ public sealed class RuntimeEndpointsTests
using (var scope = factory.Services.CreateScope())
{
var collections = scope.ServiceProvider.GetRequiredService<MongoCollectionProvider>();
var artifacts = scope.ServiceProvider.GetRequiredService<ArtifactRepository>();
var links = scope.ServiceProvider.GetRequiredService<LinkRepository>();
var policyStore = scope.ServiceProvider.GetRequiredService<PolicySnapshotStore>();
var runtimeRepository = scope.ServiceProvider.GetRequiredService<RuntimeEventRepository>();
await runtimeRepository.TruncateAsync(CancellationToken.None);
const string policyYaml = """
version: "1.0"
@@ -138,52 +140,44 @@ rules:
var sbomArtifactId = CatalogIdFactory.CreateArtifactId(ArtifactDocumentType.ImageBom, "sha256:sbomdigest");
var attestationArtifactId = CatalogIdFactory.CreateArtifactId(ArtifactDocumentType.Attestation, "sha256:attdigest");
await collections.Artifacts.InsertManyAsync(new[]
await artifacts.UpsertAsync(new ArtifactDocument
{
new ArtifactDocument
{
Id = sbomArtifactId,
Type = ArtifactDocumentType.ImageBom,
Format = ArtifactDocumentFormat.CycloneDxJson,
MediaType = "application/json",
BytesSha256 = "sha256:sbomdigest",
RefCount = 1,
CreatedAtUtc = DateTime.UtcNow,
UpdatedAtUtc = DateTime.UtcNow
},
new ArtifactDocument
{
Id = attestationArtifactId,
Type = ArtifactDocumentType.Attestation,
Format = ArtifactDocumentFormat.DsseJson,
MediaType = "application/vnd.dsse.envelope+json",
BytesSha256 = "sha256:attdigest",
RefCount = 1,
CreatedAtUtc = DateTime.UtcNow,
UpdatedAtUtc = DateTime.UtcNow,
Rekor = new RekorReference { Uuid = "rekor-uuid", Url = "https://rekor.example/uuid/rekor-uuid", Index = 7 }
}
});
Id = sbomArtifactId,
Type = ArtifactDocumentType.ImageBom,
Format = ArtifactDocumentFormat.CycloneDxJson,
MediaType = "application/json",
BytesSha256 = "sha256:sbomdigest",
RefCount = 1
}, CancellationToken.None);
await collections.Links.InsertManyAsync(new[]
await artifacts.UpsertAsync(new ArtifactDocument
{
new LinkDocument
{
Id = Guid.NewGuid().ToString("N"),
FromType = LinkSourceType.Image,
FromDigest = imageDigest,
ArtifactId = sbomArtifactId,
CreatedAtUtc = DateTime.UtcNow
},
new LinkDocument
{
Id = Guid.NewGuid().ToString("N"),
FromType = LinkSourceType.Image,
FromDigest = imageDigest,
ArtifactId = attestationArtifactId,
CreatedAtUtc = DateTime.UtcNow
}
});
Id = attestationArtifactId,
Type = ArtifactDocumentType.Attestation,
Format = ArtifactDocumentFormat.DsseJson,
MediaType = "application/vnd.dsse.envelope+json",
BytesSha256 = "sha256:attdigest",
RefCount = 1,
Rekor = new RekorReference { Uuid = "rekor-uuid", Url = "https://rekor.example/uuid/rekor-uuid", Index = 7 }
}, CancellationToken.None);
await links.UpsertAsync(new LinkDocument
{
Id = Guid.NewGuid().ToString("N"),
FromType = LinkSourceType.Image,
FromDigest = imageDigest,
ArtifactId = sbomArtifactId,
CreatedAtUtc = DateTime.UtcNow
}, CancellationToken.None);
await links.UpsertAsync(new LinkDocument
{
Id = Guid.NewGuid().ToString("N"),
FromType = LinkSourceType.Image,
FromDigest = imageDigest,
ArtifactId = attestationArtifactId,
CreatedAtUtc = DateTime.UtcNow
}, CancellationToken.None);
}
var ingestRequest = new RuntimeEventsIngestRequestDto
@@ -247,7 +241,7 @@ rules:
using (var scope = factory.Services.CreateScope())
{
var collections = scope.ServiceProvider.GetRequiredService<MongoCollectionProvider>();
var runtimeRepository = scope.ServiceProvider.GetRequiredService<RuntimeEventRepository>();
var policyStore = scope.ServiceProvider.GetRequiredService<PolicySnapshotStore>();
const string policyYaml = """
@@ -259,7 +253,7 @@ rules: []
CancellationToken.None);
// Intentionally skip artifacts/links to simulate missing metadata.
await collections.RuntimeEvents.DeleteManyAsync(Builders<RuntimeEventDocument>.Filter.Empty);
await runtimeRepository.TruncateAsync(CancellationToken.None);
}
var response = await client.PostAsJsonAsync("/api/v1/policy/runtime", new RuntimePolicyRequestDto

View File

@@ -6,20 +6,22 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Mongo2Go;
using StellaOps.Infrastructure.Postgres.Testing;
using StellaOps.Scanner.Storage;
using StellaOps.Scanner.Surface.Validation;
namespace StellaOps.Scanner.WebService.Tests;
internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
{
private readonly MongoDbRunner mongoRunner;
private readonly Dictionary<string, string?> configuration = new()
{
["scanner:storage:driver"] = "mongo",
["scanner:storage:dsn"] = string.Empty,
["scanner:queue:driver"] = "redis",
["scanner:queue:dsn"] = "redis://localhost:6379",
namespace StellaOps.Scanner.WebService.Tests;
internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
{
private readonly ScannerWebServicePostgresFixture postgresFixture;
private readonly Dictionary<string, string?> configuration = new()
{
["scanner:storage:driver"] = "postgres",
["scanner:storage:dsn"] = string.Empty,
["scanner:storage:database"] = string.Empty,
["scanner:queue:driver"] = "redis",
["scanner:queue:dsn"] = "redis://localhost:6379",
["scanner:artifactStore:driver"] = "rustfs",
["scanner:artifactStore:endpoint"] = "https://rustfs.local/api/v1/",
["scanner:artifactStore:accessKey"] = "test-access",
@@ -28,37 +30,40 @@ internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
["scanner:artifactStore:timeoutSeconds"] = "30",
["scanner:telemetry:minimumLogLevel"] = "Information",
["scanner:telemetry:enableRequestLogging"] = "false",
["scanner:events:enabled"] = "false",
["scanner:features:enableSignedReports"] = "false"
};
private readonly Action<IDictionary<string, string?>>? configureConfiguration;
private readonly Action<IServiceCollection>? configureServices;
public ScannerApplicationFactory(
Action<IDictionary<string, string?>>? configureConfiguration = null,
Action<IServiceCollection>? configureServices = null)
{
EnsureMongo2GoEnvironment();
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
configuration["scanner:storage:dsn"] = mongoRunner.ConnectionString;
this.configureConfiguration = configureConfiguration;
this.configureServices = configureServices;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
configureConfiguration?.Invoke(configuration);
builder.UseEnvironment("Testing");
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", null);
Environment.SetEnvironmentVariable("SCANNER__STORAGE__DSN", configuration["scanner:storage:dsn"]);
["scanner:events:enabled"] = "false",
["scanner:features:enableSignedReports"] = "false"
};
private readonly Action<IDictionary<string, string?>>? configureConfiguration;
private readonly Action<IServiceCollection>? configureServices;
public ScannerApplicationFactory(
Action<IDictionary<string, string?>>? configureConfiguration = null,
Action<IServiceCollection>? configureServices = null)
{
postgresFixture = new ScannerWebServicePostgresFixture();
postgresFixture.InitializeAsync().GetAwaiter().GetResult();
configuration["scanner:storage:dsn"] = postgresFixture.ConnectionString;
configuration["scanner:storage:database"] = postgresFixture.SchemaName;
this.configureConfiguration = configureConfiguration;
this.configureServices = configureServices;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
configureConfiguration?.Invoke(configuration);
builder.UseEnvironment("Testing");
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", null);
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", null);
Environment.SetEnvironmentVariable("SCANNER__STORAGE__DSN", configuration["scanner:storage:dsn"]);
Environment.SetEnvironmentVariable("SCANNER__STORAGE__DATABASE", configuration["scanner:storage:database"]);
Environment.SetEnvironmentVariable("SCANNER__QUEUE__DSN", configuration["scanner:queue:dsn"]);
Environment.SetEnvironmentVariable("SCANNER__ARTIFACTSTORE__ENDPOINT", configuration["scanner:artifactStore:endpoint"]);
Environment.SetEnvironmentVariable("SCANNER__ARTIFACTSTORE__ACCESSKEY", configuration["scanner:artifactStore:accessKey"]);
@@ -70,42 +75,42 @@ internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
{
Environment.SetEnvironmentVariable("SCANNER__EVENTS__ENABLED", eventsEnabled);
}
if (configuration.TryGetValue("scanner:authority:enabled", out var authorityEnabled))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", authorityEnabled);
}
if (configuration.TryGetValue("scanner:authority:allowAnonymousFallback", out var allowAnonymous))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", allowAnonymous);
}
if (configuration.TryGetValue("scanner:authority:issuer", out var authorityIssuer))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", authorityIssuer);
}
if (configuration.TryGetValue("scanner:authority:audiences:0", out var primaryAudience))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", primaryAudience);
}
if (configuration.TryGetValue("scanner:authority:clientId", out var clientId))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", clientId);
}
if (configuration.TryGetValue("scanner:authority:clientSecret", out var clientSecret))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", clientSecret);
}
builder.ConfigureAppConfiguration((_, configBuilder) =>
{
configBuilder.AddInMemoryCollection(configuration);
});
if (configuration.TryGetValue("scanner:authority:enabled", out var authorityEnabled))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", authorityEnabled);
}
if (configuration.TryGetValue("scanner:authority:allowAnonymousFallback", out var allowAnonymous))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", allowAnonymous);
}
if (configuration.TryGetValue("scanner:authority:issuer", out var authorityIssuer))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", authorityIssuer);
}
if (configuration.TryGetValue("scanner:authority:audiences:0", out var primaryAudience))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", primaryAudience);
}
if (configuration.TryGetValue("scanner:authority:clientId", out var clientId))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", clientId);
}
if (configuration.TryGetValue("scanner:authority:clientSecret", out var clientSecret))
{
Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", clientSecret);
}
builder.ConfigureAppConfiguration((_, configBuilder) =>
{
configBuilder.AddInMemoryCollection(configuration);
});
builder.ConfigureTestServices(services =>
{
configureServices?.Invoke(services);
@@ -113,65 +118,15 @@ internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
services.AddSingleton<ISurfaceValidatorRunner, TestSurfaceValidatorRunner>();
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
mongoRunner.Dispose();
}
}
private static void EnsureMongo2GoEnvironment()
{
if (!OperatingSystem.IsLinux())
{
return;
}
var libraryPath = ResolveOpenSslLibraryPath();
if (libraryPath is null)
{
return;
}
var existing = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH");
if (string.IsNullOrEmpty(existing))
{
Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", libraryPath);
return;
}
var segments = existing.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (Array.IndexOf(segments, libraryPath) < 0)
{
Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", string.Join(':', new[] { libraryPath }.Concat(segments)));
}
}
private static string? ResolveOpenSslLibraryPath()
{
var current = AppContext.BaseDirectory;
while (!string.IsNullOrEmpty(current))
{
var candidate = Path.Combine(current, "tools", "openssl", "linux-x64");
if (Directory.Exists(candidate))
{
return candidate;
}
var parent = Directory.GetParent(current);
if (parent is null)
{
break;
}
current = parent.FullName;
}
return null;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
postgresFixture.DisposeAsync().AsTask().GetAwaiter().GetResult();
}
}
private sealed class TestSurfaceValidatorRunner : ISurfaceValidatorRunner
@@ -186,4 +141,11 @@ internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
CancellationToken cancellationToken = default)
=> ValueTask.CompletedTask;
}
private sealed class ScannerWebServicePostgresFixture : PostgresIntegrationFixture
{
protected override System.Reflection.Assembly? GetMigrationAssembly() => typeof(ScannerStorageOptions).Assembly;
protected override string GetModuleName() => "Scanner.Storage.WebService.Tests";
}
}

View File

@@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj" />
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Infrastructure.Postgres.Testing\\StellaOps.Infrastructure.Postgres.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\docs\events\samples\scanner.event.report.ready@1.sample.json">
@@ -18,4 +19,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>