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

@@ -17,4 +17,6 @@ public static class ScanAnalysisKeys
public const string EntryTraceNdjson = "analysis.entrytrace.ndjson";
public const string SurfaceManifest = "analysis.surface.manifest";
public const string RegistryCredentials = "analysis.registry.credentials";
}

View File

@@ -0,0 +1,53 @@
using System.Text.Json;
namespace StellaOps.Scanner.Surface.Secrets;
public sealed record AttestationSecret(
string KeyPem,
string? CertificatePem,
string? CertificateChainPem,
string? RekorApiToken);
public static partial class SurfaceSecretParser
{
public static AttestationSecret ParseAttestationSecret(SurfaceSecretHandle handle)
{
ArgumentNullException.ThrowIfNull(handle);
var payload = handle.AsBytes();
if (payload.IsEmpty)
{
throw new InvalidOperationException("Surface secret payload is empty.");
}
using var document = JsonDocument.Parse(DecodeUtf8(payload));
var root = document.RootElement;
var keyPem = GetString(root, "keyPem")
?? GetString(root, "pem")
?? GetMetadataValue(handle.Metadata, "keyPem")
?? GetMetadataValue(handle.Metadata, "pem");
if (string.IsNullOrWhiteSpace(keyPem))
{
throw new InvalidOperationException("Attestation secret must include a 'keyPem' value.");
}
var certificatePem = GetString(root, "certificatePem")
?? GetMetadataValue(handle.Metadata, "certificatePem");
var certificateChainPem = GetString(root, "certificateChainPem")
?? GetMetadataValue(handle.Metadata, "certificateChainPem");
var rekorToken = GetString(root, "rekorToken")
?? GetString(root, "rekorApiToken")
?? GetMetadataValue(handle.Metadata, "rekorToken")
?? GetMetadataValue(handle.Metadata, "rekorApiToken");
return new AttestationSecret(
keyPem.Trim(),
certificatePem?.Trim(),
certificateChainPem?.Trim(),
rekorToken?.Trim());
}
}

View File

@@ -19,7 +19,7 @@ public sealed record CasAccessSecret(
string? SessionToken,
bool? AllowInsecureTls);
public static class SurfaceSecretParser
public static partial class SurfaceSecretParser
{
public static CasAccessSecret ParseCasAccessSecret(SurfaceSecretHandle handle)
{

View File

@@ -0,0 +1,347 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Text;
using System.Text.Json;
namespace StellaOps.Scanner.Surface.Secrets;
public sealed record RegistryAccessSecret(
IReadOnlyList<RegistryCredential> Entries,
string? DefaultRegistry);
public sealed record RegistryCredential(
string Registry,
string? Username,
string? Password,
string? IdentityToken,
string? RegistryToken,
string? RefreshToken,
DateTimeOffset? ExpiresAt,
IReadOnlyCollection<string> Scopes,
bool? AllowInsecureTls,
IReadOnlyDictionary<string, string> Headers,
string? Email);
public static partial class SurfaceSecretParser
{
public static RegistryAccessSecret ParseRegistryAccessSecret(SurfaceSecretHandle handle)
{
ArgumentNullException.ThrowIfNull(handle);
var entries = new List<RegistryCredential>();
string? defaultRegistry = null;
var payload = handle.AsBytes();
if (!payload.IsEmpty)
{
var jsonText = DecodeUtf8(payload);
using var document = JsonDocument.Parse(jsonText);
var root = document.RootElement;
defaultRegistry = GetString(root, "defaultRegistry") ?? GetMetadataValue(handle.Metadata, "defaultRegistry");
if (TryParseRegistryEntries(root, handle.Metadata, entries) ||
TryParseAuthsObject(root, handle.Metadata, entries))
{
// entries already populated
}
else if (root.ValueKind == JsonValueKind.Object && root.GetRawText().Length > 2) // not empty object
{
entries.Add(ParseRegistryEntry(root, handle.Metadata, fallbackRegistry: null));
}
}
if (entries.Count == 0 && TryCreateRegistryEntryFromMetadata(handle.Metadata, out var metadataEntry))
{
entries.Add(metadataEntry);
}
if (entries.Count == 0)
{
throw new InvalidOperationException("Registry secret payload does not contain credentials.");
}
defaultRegistry ??= GetMetadataValue(handle.Metadata, "defaultRegistry")
?? entries[0].Registry;
return new RegistryAccessSecret(
new ReadOnlyCollection<RegistryCredential>(entries),
string.IsNullOrWhiteSpace(defaultRegistry) ? entries[0].Registry : defaultRegistry.Trim());
}
private static bool TryParseRegistryEntries(
JsonElement root,
IReadOnlyDictionary<string, string> metadata,
ICollection<RegistryCredential> entries)
{
if (!TryGetPropertyIgnoreCase(root, "entries", out var entriesElement) ||
entriesElement.ValueKind != JsonValueKind.Array)
{
return false;
}
foreach (var entryElement in entriesElement.EnumerateArray())
{
if (entryElement.ValueKind != JsonValueKind.Object)
{
continue;
}
entries.Add(ParseRegistryEntry(entryElement, metadata, fallbackRegistry: null));
}
return entries.Count > 0;
}
private static bool TryParseAuthsObject(
JsonElement root,
IReadOnlyDictionary<string, string> metadata,
ICollection<RegistryCredential> entries)
{
if (!TryGetPropertyIgnoreCase(root, "auths", out var authsElement) ||
authsElement.ValueKind != JsonValueKind.Object)
{
return false;
}
foreach (var property in authsElement.EnumerateObject())
{
if (property.Value.ValueKind != JsonValueKind.Object)
{
continue;
}
entries.Add(ParseRegistryEntry(property.Value, metadata, property.Name));
}
return entries.Count > 0;
}
private static RegistryCredential ParseRegistryEntry(
JsonElement element,
IReadOnlyDictionary<string, string> metadata,
string? fallbackRegistry)
{
var registry = GetString(element, "registry")
?? GetString(element, "server")
?? fallbackRegistry
?? GetMetadataValue(metadata, "registry")
?? throw new InvalidOperationException("Registry credential is missing a registry identifier.");
registry = registry.Trim();
var username = GetString(element, "username") ?? GetString(element, "user");
var password = GetString(element, "password") ?? GetString(element, "pass");
var token = GetString(element, "token") ?? GetString(element, "registryToken");
var identityToken = GetString(element, "identityToken") ?? GetString(element, "identitytoken");
var refreshToken = GetString(element, "refreshToken");
var email = GetString(element, "email");
var allowInsecure = GetBoolean(element, "allowInsecureTls");
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
PopulateHeaders(element, headers);
PopulateMetadataHeaders(metadata, headers);
var scopes = new List<string>();
PopulateScopes(element, scopes);
PopulateMetadataScopes(metadata, scopes);
var expiresAt = ParseDateTime(element, "expiresAt");
var auth = GetString(element, "auth");
if (!string.IsNullOrWhiteSpace(auth))
{
TryApplyBasicAuth(auth, ref username, ref password);
}
username ??= GetMetadataValue(metadata, "username");
password ??= GetMetadataValue(metadata, "password");
token ??= GetMetadataValue(metadata, "token") ?? GetMetadataValue(metadata, "registryToken");
identityToken ??= GetMetadataValue(metadata, "identityToken");
refreshToken ??= GetMetadataValue(metadata, "refreshToken");
email ??= GetMetadataValue(metadata, "email");
return new RegistryCredential(
registry,
username?.Trim(),
password,
identityToken,
token,
refreshToken,
expiresAt,
scopes.Count == 0 ? Array.Empty<string>() : new ReadOnlyCollection<string>(scopes),
allowInsecure,
new ReadOnlyDictionary<string, string>(headers),
email);
}
private static bool TryCreateRegistryEntryFromMetadata(
IReadOnlyDictionary<string, string> metadata,
out RegistryCredential entry)
{
var registry = GetMetadataValue(metadata, "registry");
var username = GetMetadataValue(metadata, "username");
var password = GetMetadataValue(metadata, "password");
var identityToken = GetMetadataValue(metadata, "identityToken");
var token = GetMetadataValue(metadata, "token") ?? GetMetadataValue(metadata, "registryToken");
if (string.IsNullOrWhiteSpace(registry) &&
string.IsNullOrWhiteSpace(username) &&
string.IsNullOrWhiteSpace(password) &&
string.IsNullOrWhiteSpace(identityToken) &&
string.IsNullOrWhiteSpace(token))
{
entry = null!;
return false;
}
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
PopulateMetadataHeaders(metadata, headers);
var scopes = new List<string>();
PopulateMetadataScopes(metadata, scopes);
entry = new RegistryCredential(
registry?.Trim() ?? "registry.local",
username?.Trim(),
password,
identityToken,
token,
GetMetadataValue(metadata, "refreshToken"),
ParseDateTime(metadata, "expiresAt"),
scopes.Count == 0 ? Array.Empty<string>() : new ReadOnlyCollection<string>(scopes),
ParseBoolean(metadata, "allowInsecureTls"),
new ReadOnlyDictionary<string, string>(headers),
GetMetadataValue(metadata, "email"));
return true;
}
private static void PopulateScopes(JsonElement element, ICollection<string> scopes)
{
if (!TryGetPropertyIgnoreCase(element, "scopes", out var scopesElement))
{
return;
}
switch (scopesElement.ValueKind)
{
case JsonValueKind.Array:
foreach (var scope in scopesElement.EnumerateArray())
{
if (scope.ValueKind == JsonValueKind.String)
{
var value = scope.GetString();
if (!string.IsNullOrWhiteSpace(value))
{
scopes.Add(value.Trim());
}
}
}
break;
case JsonValueKind.String:
var text = scopesElement.GetString();
if (!string.IsNullOrWhiteSpace(text))
{
foreach (var part in text.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
scopes.Add(part.Trim());
}
}
break;
}
}
private static void PopulateMetadataScopes(IReadOnlyDictionary<string, string> metadata, ICollection<string> scopes)
{
foreach (var (key, value) in metadata)
{
if (!key.StartsWith("scope", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
scopes.Add(value.Trim());
}
}
private static void TryApplyBasicAuth(string auth, ref string? username, ref string? password)
{
try
{
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(auth));
var separator = decoded.IndexOf(':');
if (separator >= 0)
{
username ??= decoded[..separator];
password ??= decoded[(separator + 1)..];
}
}
catch (FormatException)
{
// ignore malformed auth; caller may still have explicit username/password fields
}
}
private static DateTimeOffset? ParseDateTime(JsonElement element, string propertyName)
{
if (!TryGetPropertyIgnoreCase(element, propertyName, out var value) ||
value.ValueKind != JsonValueKind.String)
{
return null;
}
var text = value.GetString();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
if (DateTimeOffset.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed))
{
return parsed;
}
return null;
}
private static DateTimeOffset? ParseDateTime(IReadOnlyDictionary<string, string> metadata, string key)
{
var value = GetMetadataValue(metadata, key);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed))
{
return parsed;
}
return null;
}
private static bool? ParseBoolean(IReadOnlyDictionary<string, string> metadata, string key)
{
var value = GetMetadataValue(metadata, key);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (bool.TryParse(value, out var parsed))
{
return parsed;
}
return null;
}
}