Implement MongoDB-based storage for Pack Run approval, artifact, log, and state management
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added MongoPackRunApprovalStore for managing approval states with MongoDB.
- Introduced MongoPackRunArtifactUploader for uploading and storing artifacts.
- Created MongoPackRunLogStore to handle logging of pack run events.
- Developed MongoPackRunStateStore for persisting and retrieving pack run states.
- Implemented unit tests for MongoDB stores to ensure correct functionality.
- Added MongoTaskRunnerTestContext for setting up MongoDB test environment.
- Enhanced PackRunStateFactory to correctly initialize state with gate reasons.
This commit is contained in:
master
2025-11-07 10:01:35 +02:00
parent e5ffcd6535
commit a1ce3f74fa
122 changed files with 8730 additions and 914 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.Surface.Env;
@@ -28,6 +29,13 @@ internal sealed class ScannerSurfaceSecretConfigurator : IConfigureOptions<Scann
ArgumentNullException.ThrowIfNull(options);
var tenant = _surfaceEnvironment.Settings.Secrets.Tenant;
ApplyCasAccessSecret(options, tenant);
ApplyRegistrySecret(options, tenant);
ApplyAttestationSecret(options, tenant);
}
private void ApplyCasAccessSecret(ScannerWebServiceOptions options, string tenant)
{
var request = new SurfaceSecretRequest(
Tenant: tenant,
Component: ComponentName,
@@ -56,6 +64,120 @@ internal sealed class ScannerSurfaceSecretConfigurator : IConfigureOptions<Scann
ApplySecret(options.ArtifactStore ??= new ScannerWebServiceOptions.ArtifactStoreOptions(), secret);
}
private void ApplyRegistrySecret(ScannerWebServiceOptions options, string tenant)
{
var request = new SurfaceSecretRequest(
Tenant: tenant,
Component: ComponentName,
SecretType: "registry");
RegistryAccessSecret? secret = null;
try
{
using var handle = _secretProvider.GetAsync(request).AsTask().GetAwaiter().GetResult();
secret = SurfaceSecretParser.ParseRegistryAccessSecret(handle);
}
catch (SurfaceSecretNotFoundException)
{
_logger.LogDebug("Surface secret 'registry' not found for {Component}; leaving registry credentials unchanged.", ComponentName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to resolve surface secret 'registry' for {Component}.", ComponentName);
}
if (secret is null)
{
return;
}
options.Registry ??= new ScannerWebServiceOptions.RegistryOptions();
options.Registry.DefaultRegistry = secret.DefaultRegistry;
options.Registry.Credentials = new List<ScannerWebServiceOptions.RegistryCredentialOptions>();
foreach (var entry in secret.Entries)
{
var credential = new ScannerWebServiceOptions.RegistryCredentialOptions
{
Registry = entry.Registry,
Username = entry.Username,
Password = entry.Password,
IdentityToken = entry.IdentityToken,
RegistryToken = entry.RegistryToken,
RefreshToken = entry.RefreshToken,
ExpiresAt = entry.ExpiresAt,
AllowInsecureTls = entry.AllowInsecureTls,
Email = entry.Email
};
if (entry.Scopes.Count > 0)
{
credential.Scopes = new List<string>(entry.Scopes);
}
if (entry.Headers.Count > 0)
{
foreach (var (key, value) in entry.Headers)
{
credential.Headers[key] = value;
}
}
options.Registry.Credentials.Add(credential);
}
_logger.LogInformation(
"Surface secret 'registry' applied for {Component} (default: {DefaultRegistry}, entries: {Count}).",
ComponentName,
options.Registry.DefaultRegistry ?? "(none)",
options.Registry.Credentials.Count);
}
private void ApplyAttestationSecret(ScannerWebServiceOptions options, string tenant)
{
var request = new SurfaceSecretRequest(
Tenant: tenant,
Component: ComponentName,
SecretType: "attestation");
AttestationSecret? secret = null;
try
{
using var handle = _secretProvider.GetAsync(request).AsTask().GetAwaiter().GetResult();
secret = SurfaceSecretParser.ParseAttestationSecret(handle);
}
catch (SurfaceSecretNotFoundException)
{
_logger.LogDebug("Surface secret 'attestation' not found for {Component}; retaining signing configuration.", ComponentName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to resolve surface secret 'attestation' for {Component}.", ComponentName);
}
if (secret is null)
{
return;
}
options.Signing ??= new ScannerWebServiceOptions.SigningOptions();
if (!string.IsNullOrWhiteSpace(secret.KeyPem))
{
options.Signing.KeyPem = secret.KeyPem;
}
if (!string.IsNullOrWhiteSpace(secret.CertificatePem))
{
options.Signing.CertificatePem = secret.CertificatePem;
}
if (!string.IsNullOrWhiteSpace(secret.CertificateChainPem))
{
options.Signing.CertificateChainPem = secret.CertificateChainPem;
}
}
private void ApplySecret(ScannerWebServiceOptions.ArtifactStoreOptions artifactStore, CasAccessSecret secret)
{
if (!string.IsNullOrWhiteSpace(secret.Driver))

View File

@@ -26,10 +26,15 @@ public sealed class ScannerWebServiceOptions
/// </summary>
public QueueOptions Queue { get; set; } = new();
/// <summary>
/// Object store configuration for SBOM artefacts.
/// </summary>
public ArtifactStoreOptions ArtifactStore { get; set; } = new();
/// <summary>
/// Object store configuration for SBOM artefacts.
/// </summary>
public ArtifactStoreOptions ArtifactStore { get; set; } = new();
/// <summary>
/// Registry credential configuration for report/export operations.
/// </summary>
public RegistryOptions Registry { get; set; } = new();
/// <summary>
/// Feature flags toggling optional behaviours.
@@ -144,11 +149,11 @@ public sealed class ScannerWebServiceOptions
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class FeatureFlagOptions
{
public bool AllowAnonymousScanSubmission { get; set; }
public bool EnableSignedReports { get; set; } = true;
public sealed class FeatureFlagOptions
{
public bool AllowAnonymousScanSubmission { get; set; }
public bool EnableSignedReports { get; set; } = true;
public bool EnablePolicyPreview { get; set; } = true;
@@ -233,11 +238,11 @@ public sealed class ScannerWebServiceOptions
}
}
public sealed class SigningOptions
{
public bool Enabled { get; set; } = false;
public string KeyId { get; set; } = string.Empty;
public sealed class SigningOptions
{
public bool Enabled { get; set; } = false;
public string KeyId { get; set; } = string.Empty;
public string Algorithm { get; set; } = "ed25519";
@@ -251,12 +256,44 @@ public sealed class ScannerWebServiceOptions
public string? CertificatePemFile { get; set; }
public string? CertificateChainPem { get; set; }
public string? CertificateChainPemFile { get; set; }
public int EnvelopeTtlSeconds { get; set; } = 600;
}
public string? CertificateChainPem { get; set; }
public string? CertificateChainPemFile { get; set; }
public int EnvelopeTtlSeconds { get; set; } = 600;
}
public sealed class RegistryOptions
{
public string? DefaultRegistry { get; set; }
public IList<RegistryCredentialOptions> Credentials { get; set; } = new List<RegistryCredentialOptions>();
}
public sealed class RegistryCredentialOptions
{
public string Registry { get; set; } = string.Empty;
public string? Username { get; set; }
public string? Password { get; set; }
public string? IdentityToken { get; set; }
public string? RegistryToken { get; set; }
public string? RefreshToken { get; set; }
public DateTimeOffset? ExpiresAt { get; set; }
public IList<string> Scopes { get; set; } = new List<string>();
public bool? AllowInsecureTls { get; set; }
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string? Email { get; set; }
}
public sealed class ApiOptions
{

View File

@@ -179,6 +179,10 @@ internal sealed class SurfacePointerService : ISurfacePointerService
ArtifactDocumentFormat.SpdxJson => "spdx-json",
ArtifactDocumentFormat.BomIndex => "bom-index",
ArtifactDocumentFormat.DsseJson => "dsse-json",
ArtifactDocumentFormat.SurfaceManifestJson => "surface.manifest",
ArtifactDocumentFormat.EntryTraceGraphJson => "entrytrace.graph",
ArtifactDocumentFormat.EntryTraceNdjson => "entrytrace.ndjson",
ArtifactDocumentFormat.ComponentFragmentJson => "layer.fragments",
_ => format.ToString().ToLowerInvariant()
};
@@ -199,6 +203,12 @@ internal sealed class SurfacePointerService : ISurfacePointerService
ArtifactDocumentType.Diff => ("diff", null),
ArtifactDocumentType.Attestation => ("attestation", null),
ArtifactDocumentType.Index => ("bom-index", null),
ArtifactDocumentType.SurfaceManifest => ("surface.manifest", null),
ArtifactDocumentType.SurfaceEntryTrace when document.Format == ArtifactDocumentFormat.EntryTraceGraphJson
=> ("entrytrace.graph", null),
ArtifactDocumentType.SurfaceEntryTrace when document.Format == ArtifactDocumentFormat.EntryTraceNdjson
=> ("entrytrace.ndjson", null),
ArtifactDocumentType.SurfaceLayerFragment => ("layer.fragments", "inventory"),
_ => (document.Type.ToString().ToLowerInvariant(), null)
};
}

View File

@@ -6,7 +6,7 @@
| SCANNER-SURFACE-02 | DONE (2025-11-05) | Scanner WebService Guild | SURFACE-FS-02 | Publish Surface.FS pointers (CAS URIs, manifests) via scan/report APIs and update attestation metadata.<br>2025-11-05: Surface pointers projected through scan/report endpoints, orchestrator samples + DSSE fixtures refreshed with manifest block, readiness tests updated to use validator stub. | OpenAPI updated; clients regenerated; integration tests validate pointer presence and tenancy. |
| SCANNER-ENV-02 | TODO (2025-11-06) | Scanner WebService Guild, Ops Guild | SURFACE-ENV-02 | Wire Surface.Env helpers into WebService hosting (cache roots, feature flags) and document configuration.<br>2025-11-02: Cache root resolution switched to helper; feature flag bindings updated; Helm/Compose updates pending review.<br>2025-11-05 14:55Z: Aligning readiness checks, docs, and Helm/Compose templates with Surface.Env outputs and planning test coverage for configuration fallbacks.<br>2025-11-06 17:05Z: Surface.Env documentation/README refreshed; warning catalogue captured for ops handoff.<br>2025-11-06 07:45Z: Helm values (dev/stage/prod/airgap/mirror) and Compose examples updated with `SCANNER_SURFACE_*` defaults plus rollout warning note in `deploy/README.md`.<br>2025-11-06 07:55Z: Paused; follow-up automation captured under `DEVOPS-OPENSSL-11-001/002` and pending Surface.Env readiness tests. | Service uses helper; env table documented; helm/compose templates updated. |
> 2025-11-05 19:18Z: Added configurator to project wiring and unit test ensuring Surface.Env cache root is honoured.
| SCANNER-SECRETS-02 | DOING (2025-11-06) | Scanner WebService Guild, Security Guild | SURFACE-SECRETS-02 | Replace ad-hoc secret wiring with Surface.Secrets for report/export operations (registry and CAS tokens).<br>2025-11-02: Export/report flows now depend on Surface.Secrets stub; integration tests in progress.<br>2025-11-06: Restarting work to eliminate file-based secrets, plumb provider handles through report/export services, and extend failure/rotation tests.<br>2025-11-06 21:40Z: Added configurator + storage post-config to hydrate artifact/CAS credentials from `cas-access` secrets with unit coverage. | Secrets fetched through shared provider; unit/integration tests cover rotation + failure cases. |
| SCANNER-SECRETS-02 | DONE (2025-11-06) | Scanner WebService Guild, Security Guild | SURFACE-SECRETS-02 | Replace ad-hoc secret wiring with Surface.Secrets for report/export operations (registry and CAS tokens).<br>2025-11-02: Export/report flows now depend on Surface.Secrets stub; integration tests in progress.<br>2025-11-06: Restarting work to eliminate file-based secrets, plumb provider handles through report/export services, and extend failure/rotation tests.<br>2025-11-06 21:40Z: Added configurator + storage post-config to hydrate artifact/CAS credentials from `cas-access` secrets with unit coverage.<br>2025-11-06 23:58Z: Registry & attestation secrets now resolved via Surface.Secrets (options + tests updated); dotnet test suites executed with .NET 10 RC2 runtime where available. | Secrets fetched through shared provider; unit/integration tests cover rotation + failure cases. |
| SCANNER-EVENTS-16-301 | BLOCKED (2025-10-26) | Scanner WebService Guild | ORCH-SVC-38-101, NOTIFY-SVC-38-001 | Emit orchestrator-compatible envelopes (`scanner.event.*`) and update integration tests to verify Notifier ingestion (no Redis queue coupling). | Tests assert envelope schema + orchestrator publish; Notifier consumer harness passes; docs updated with new event contract. Blocked by .NET 10 preview OpenAPI/Auth dependency drift preventing `dotnet test` completion. |
| SCANNER-EVENTS-16-302 | DONE (2025-11-06) | Scanner WebService Guild | SCANNER-EVENTS-16-301 | Extend orchestrator event links (report/policy/attestation) once endpoints are finalised across gateway + console.<br>2025-11-06 22:55Z: Dispatcher now honours configurable API/console base segments, JSON samples/docs refreshed, and `ReportEventDispatcherTests` extended. Tests: `StellaOps.Scanner.WebService.Tests` build until pre-existing `SurfaceCacheOptionsConfiguratorTests` ctor signature drift (tracked separately). | Links section covers UI/API targets; downstream consumers validated; docs/samples updated. |