tests fixes and sprints work
This commit is contained in:
@@ -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>>();
|
||||
|
||||
@@ -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.")
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user