tests fixes and sprints work

This commit is contained in:
master
2026-01-22 19:08:46 +02:00
parent c32fff8f86
commit 726d70dc7f
881 changed files with 134434 additions and 6228 deletions

View File

@@ -9,6 +9,7 @@ using System.CommandLine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Extensions;
namespace StellaOps.Cli.Plugins.Timestamp;
@@ -27,19 +28,17 @@ public static class EvidenceCliCommands
Option<bool> verboseOption)
{
var artifactOption = new Option<FileInfo>("--artifact", "DSSE envelope or artifact file")
{
IsRequired = true
};
artifactOption.AddAlias("-a");
.AddAlias("-a")
.Required();
var tstOption = new Option<FileInfo?>("--tst", "Timestamp token file");
tstOption.AddAlias("-t");
var tstOption = new Option<FileInfo?>("--tst", "Timestamp token file")
.AddAlias("-t");
var rekorOption = new Option<FileInfo?>("--rekor-bundle", "Rekor bundle JSON file");
rekorOption.AddAlias("-r");
var rekorOption = new Option<FileInfo?>("--rekor-bundle", "Rekor bundle JSON file")
.AddAlias("-r");
var chainOption = new Option<FileInfo?>("--tsa-chain", "TSA certificate chain PEM file");
chainOption.AddAlias("-c");
var chainOption = new Option<FileInfo?>("--tsa-chain", "TSA certificate chain PEM file")
.AddAlias("-c");
var ocspOption = new Option<FileInfo?>("--ocsp", "Stapled OCSP response file");
@@ -165,18 +164,15 @@ public static class EvidenceCliCommands
Option<bool> verboseOption)
{
var artifactOption = new Option<string>("--artifact", "Artifact digest to export evidence for")
{
IsRequired = true
};
artifactOption.AddAlias("-a");
.AddAlias("-a")
.Required();
var outOption = new Option<DirectoryInfo>("--out", "Output directory for evidence bundle")
{
IsRequired = true
};
outOption.AddAlias("-o");
.AddAlias("-o")
.Required();
var formatOption = new Option<string>("--format", () => "bundle", "Export format: bundle, json, or individual");
var formatOption = new Option<string>("--format", "Export format: bundle, json, or individual")
.SetDefaultValue("bundle");
var cmd = new Command("export", "Export evidence for an artifact.")
{
@@ -190,7 +186,7 @@ public static class EvidenceCliCommands
{
var artifact = context.ParseResult.GetValueForOption(artifactOption)!;
var outDir = context.ParseResult.GetValueForOption(outOption)!;
var format = context.ParseResult.GetValueForOption(formatOption);
var format = context.ParseResult.GetValueForOption(formatOption) ?? "bundle";
var verbose = context.ParseResult.GetValueForOption(verboseOption);
var logger = services.GetRequiredService<ILogger<TimestampCliCommandModule>>();

View File

@@ -11,6 +11,7 @@ using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Extensions;
using StellaOps.Cli.Plugins;
namespace StellaOps.Cli.Plugins.Timestamp;
@@ -63,26 +64,24 @@ public sealed class TimestampCliCommandModule : ICliCommandModule
Option<bool> verboseOption)
{
var hashOption = new Option<string>("--hash", "SHA-256 hash to timestamp (hex string)")
{
IsRequired = true
};
hashOption.AddAlias("-h");
.AddAlias("-h")
.Required();
var fileOption = new Option<FileInfo?>("--file", "File to timestamp (computes hash automatically)");
fileOption.AddAlias("-f");
var fileOption = new Option<FileInfo?>("--file", "File to timestamp (computes hash automatically)")
.AddAlias("-f");
var tsaOption = new Option<string?>("--tsa", "TSA URL (uses default if not specified)");
tsaOption.AddAlias("-t");
var tsaOption = new Option<string?>("--tsa", "TSA URL (uses default if not specified)")
.AddAlias("-t");
var outOption = new Option<FileInfo>("--out", "Output file for timestamp token")
{
IsRequired = true
};
outOption.AddAlias("-o");
.AddAlias("-o")
.Required();
var certRequestOption = new Option<bool>("--cert-req", () => true, "Request TSA certificate in response");
var certRequestOption = new Option<bool>("--cert-req", "Request TSA certificate in response")
.SetDefaultValue(true);
var nonceOption = new Option<bool>("--nonce", () => true, "Include nonce in request");
var nonceOption = new Option<bool>("--nonce", "Include nonce in request")
.SetDefaultValue(true);
var policyOption = new Option<string?>("--policy", "TSA policy OID to request");
@@ -155,7 +154,7 @@ public sealed class TimestampCliCommandModule : ICliCommandModule
return;
}
var tsaUrl = tsa ?? options.Timestamping?.DefaultTsaUrl ?? "https://freetsa.org/tsr";
var tsaUrl = tsa ?? "https://freetsa.org/tsr";
if (verbose)
{
@@ -217,23 +216,23 @@ public sealed class TimestampCliCommandModule : ICliCommandModule
Option<bool> verboseOption)
{
var tstOption = new Option<FileInfo>("--tst", "Timestamp token file to verify")
{
IsRequired = true
};
tstOption.AddAlias("-t");
.AddAlias("-t")
.Required();
var artifactOption = new Option<FileInfo?>("--artifact", "Artifact file to verify against");
artifactOption.AddAlias("-a");
var artifactOption = new Option<FileInfo?>("--artifact", "Artifact file to verify against")
.AddAlias("-a");
var hashOption = new Option<string?>("--hash", "Hash to verify against (if artifact not provided)");
hashOption.AddAlias("-h");
var hashOption = new Option<string?>("--hash", "Hash to verify against (if artifact not provided)")
.AddAlias("-h");
var trustRootOption = new Option<FileInfo?>("--trust-root", "PEM file containing trusted TSA root certificates");
trustRootOption.AddAlias("-r");
var trustRootOption = new Option<FileInfo?>("--trust-root", "PEM file containing trusted TSA root certificates")
.AddAlias("-r");
var strictOption = new Option<bool>("--strict", () => false, "Fail on any warning");
var strictOption = new Option<bool>("--strict", "Fail on any warning")
.SetDefaultValue(false);
var offlineOption = new Option<bool>("--offline", () => false, "Verify using only bundled/stapled data");
var offlineOption = new Option<bool>("--offline", "Verify using only bundled/stapled data")
.SetDefaultValue(false);
var cmd = new Command("verify", "Verify an RFC-3161 timestamp token.")
{
@@ -387,12 +386,11 @@ public sealed class TimestampCliCommandModule : ICliCommandModule
Option<bool> verboseOption)
{
var tstOption = new Option<FileInfo>("--tst", "Timestamp token file to inspect")
{
IsRequired = true
};
tstOption.AddAlias("-t");
.AddAlias("-t")
.Required();
var jsonOption = new Option<bool>("--json", () => false, "Output as JSON");
var jsonOption = new Option<bool>("--json", "Output as JSON")
.SetDefaultValue(false);
var cmd = new Command("info", "Display information about a timestamp token.")
{

View File

@@ -12,6 +12,7 @@ using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Extensions;
using StellaOps.Cli.Plugins;
namespace StellaOps.Cli.Plugins.Vex;
@@ -108,9 +109,10 @@ public sealed class VexCliCommandModule : ICliCommandModule
var formatOption = new Option<OutputFormat>("--format")
{
Description = "Output format",
DefaultValueFactory = _ => OutputFormat.Table
Description = "Output format"
};
formatOption.AddAlias("-f");
formatOption.SetDefaultValue(OutputFormat.Table);
var cmd = new Command("auto-downgrade", "Auto-downgrade VEX based on runtime observations.")
{
@@ -256,10 +258,11 @@ public sealed class VexCliCommandModule : ICliCommandModule
DefaultValueFactory = _ => OutputFormat.Table
};
var schemaOption = new Option<string?>("--schema")
{
Description = "Schema version to validate against (e.g., openvex-0.2, csaf-2.0)"
};
var schemaOption = new Option<string?>("--schema")
{
Description = "Schema version to validate against (e.g., openvex-0.2, csaf-2.0)"
};
schemaOption.AddAlias("-s");
var strictOption = new Option<bool>("--strict")
{
@@ -310,16 +313,18 @@ public sealed class VexCliCommandModule : ICliCommandModule
Description = "Digest or component identifier (e.g., sha256:..., pkg:npm/...)"
};
var formatOption = new Option<string>("--format", new[] { "-f" })
var formatOption = new Option<string>("--format")
{
Description = "Output format: json (default), openvex"
};
formatOption.AddAlias("-f");
formatOption.SetDefaultValue("json");
var outputOption = new Option<string?>("--output", new[] { "-o" })
var outputOption = new Option<string?>("--output")
{
Description = "Write output to the specified file"
};
outputOption.AddAlias("-o");
var export = new Command("export", "Export VEX evidence for a digest or component")
{
@@ -446,135 +451,6 @@ public sealed class VexCliCommandModule : ICliCommandModule
return 0;
}
/// <summary>
/// Build the 'vex webhooks' command group.
/// Sprint: SPRINT_20260117_009_CLI_vex_processing (VPR-003)
/// </summary>
private static Command BuildWebhooksCommand(Option<bool> verboseOption)
{
var webhooks = new Command("webhooks", "Manage VEX webhook subscriptions.");
var formatOption = new Option<string>("--format", new[] { "-f" })
{
Description = "Output format: json (default)"
};
formatOption.SetDefaultValue("json");
var list = new Command("list", "List configured VEX webhooks")
{
formatOption,
verboseOption
};
list.SetAction((parseResult, ct) =>
{
var format = parseResult.GetValue(formatOption) ?? "json";
var payload = new[]
{
new { id = "wh-001", url = "https://hooks.stellaops.dev/vex", events = new[] { "vex.created", "vex.updated" }, status = "active" },
new { id = "wh-002", url = "https://hooks.example.com/vex", events = new[] { "vex.created" }, status = "paused" }
};
if (format.Equals("json", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(payload, new System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
}));
return Task.FromResult(0);
}
Console.WriteLine("Only json output is supported.");
return Task.FromResult(0);
});
var urlOption = new Option<string>("--url")
{
Description = "Webhook URL",
IsRequired = true
};
var eventsOption = new Option<string[]>("--events")
{
Description = "Event types (repeatable)",
Arity = ArgumentArity.ZeroOrMore
};
eventsOption.AllowMultipleArgumentsPerToken = true;
var add = new Command("add", "Register a VEX webhook")
{
urlOption,
eventsOption,
formatOption,
verboseOption
};
add.SetAction((parseResult, ct) =>
{
var url = parseResult.GetValue(urlOption) ?? string.Empty;
var events = parseResult.GetValue(eventsOption) ?? Array.Empty<string>();
var format = parseResult.GetValue(formatOption) ?? "json";
var payload = new
{
id = "wh-003",
url,
events = events.Length > 0 ? events : new[] { "vex.created" },
status = "active"
};
if (format.Equals("json", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(payload, new System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
}));
return Task.FromResult(0);
}
Console.WriteLine("Only json output is supported.");
return Task.FromResult(0);
});
var idArg = new Argument<string>("id")
{
Description = "Webhook identifier"
};
var remove = new Command("remove", "Unregister a VEX webhook")
{
idArg,
formatOption,
verboseOption
};
remove.SetAction((parseResult, ct) =>
{
var id = parseResult.GetValue(idArg) ?? string.Empty;
var format = parseResult.GetValue(formatOption) ?? "json";
var payload = new { id, status = "removed" };
if (format.Equals("json", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(payload, new System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
}));
return Task.FromResult(0);
}
Console.WriteLine("Only json output is supported.");
return Task.FromResult(0);
});
webhooks.Add(list);
webhooks.Add(add);
webhooks.Add(remove);
return webhooks;
}
/// <summary>
/// Execute VEX document verification.
/// Sprint: SPRINT_20260117_009_CLI_vex_processing (VPR-001)

View File

@@ -10,6 +10,7 @@ using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Extensions;
namespace StellaOps.Cli.Plugins.Vex;
@@ -60,10 +61,12 @@ public static class VexRekorCommandGroup
Description = "Include Rekor linkage details in output."
};
var formatOption = new Option<string>("--format", new[] { "-f" })
var formatOption = new Option<string>("--format")
{
Description = "Output format: text (default), json, yaml."
}.SetDefaultValue("text").FromAmong("text", "json", "yaml");
};
formatOption.AddAlias("-f");
formatOption.SetDefaultValue("text").FromAmong("text", "json", "yaml");
var command = new Command("show", "Display observation details including Rekor linkage.")
{
@@ -104,20 +107,22 @@ public static class VexRekorCommandGroup
Description = "Rekor server URL (default: https://rekor.sigstore.dev)."
};
var keyOption = new Option<string?>("--key", new[] { "-k" })
var keyOption = new Option<string?>("--key")
{
Description = "Signing key identifier."
};
keyOption.AddAlias("-k");
var dryRunOption = new Option<bool>("--dry-run")
{
Description = "Create DSSE envelope without submitting to Rekor."
};
var outputOption = new Option<string?>("--output", new[] { "-o" })
var outputOption = new Option<string?>("--output")
{
Description = "Output file for DSSE envelope."
};
outputOption.AddAlias("-o");
var command = new Command("attest", "Attest a VEX observation to Rekor transparency log.")
{
@@ -167,10 +172,12 @@ public static class VexRekorCommandGroup
Description = "Rekor server URL for online verification."
};
var formatOption = new Option<string>("--format", new[] { "-f" })
var formatOption = new Option<string>("--format")
{
Description = "Output format: text (default), json."
}.SetDefaultValue("text").FromAmong("text", "json");
};
formatOption.AddAlias("-f");
formatOption.SetDefaultValue("text").FromAmong("text", "json");
var command = new Command("verify-rekor", "Verify an observation's Rekor transparency log linkage.")
{
@@ -203,15 +210,19 @@ public static class VexRekorCommandGroup
StellaOpsCliOptions options,
Option<bool> verboseOption)
{
var limitOption = new Option<int>("--limit", new[] { "-n" })
var limitOption = new Option<int>("--limit")
{
Description = "Maximum number of results to return."
}.SetDefaultValue(50);
};
limitOption.AddAlias("-n");
limitOption.SetDefaultValue(50);
var formatOption = new Option<string>("--format", new[] { "-f" })
var formatOption = new Option<string>("--format")
{
Description = "Output format: text (default), json."
}.SetDefaultValue("text").FromAmong("text", "json");
};
formatOption.AddAlias("-f");
formatOption.SetDefaultValue("text").FromAmong("text", "json");
var command = new Command("list-pending", "List VEX observations pending Rekor attestation.")
{
@@ -248,7 +259,7 @@ public static class VexRekorCommandGroup
var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("StellaOpsApi");
var baseUrl = options.ApiBaseUrl?.TrimEnd('/') ?? "http://localhost:5000";
var baseUrl = options.BackendUrl?.TrimEnd('/') ?? "http://localhost:5000";
var url = $"{baseUrl}/api/v1/vex/observations/{observationId}";
if (showRekor)
@@ -351,7 +362,7 @@ public static class VexRekorCommandGroup
var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("StellaOpsApi");
var baseUrl = options.ApiBaseUrl?.TrimEnd('/') ?? "http://localhost:5000";
var baseUrl = options.BackendUrl?.TrimEnd('/') ?? "http://localhost:5000";
if (dryRun)
{
@@ -430,7 +441,7 @@ public static class VexRekorCommandGroup
var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("StellaOpsApi");
var baseUrl = options.ApiBaseUrl?.TrimEnd('/') ?? "http://localhost:5000";
var baseUrl = options.BackendUrl?.TrimEnd('/') ?? "http://localhost:5000";
var url = $"{baseUrl}/attestations/rekor/observations/{observationId}/verify";
if (offline)
@@ -515,7 +526,7 @@ public static class VexRekorCommandGroup
var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("StellaOpsApi");
var baseUrl = options.ApiBaseUrl?.TrimEnd('/') ?? "http://localhost:5000";
var baseUrl = options.BackendUrl?.TrimEnd('/') ?? "http://localhost:5000";
var url = $"{baseUrl}/attestations/rekor/pending?limit={limit}";
try