274 lines
9.0 KiB
C#
274 lines
9.0 KiB
C#
// -----------------------------------------------------------------------------
|
|
// WitnessCommandGroup.cs
|
|
// Sprint: SPRINT_3700_0005_0001_witness_ui_cli
|
|
// Tasks: CLI-001, CLI-002, CLI-003, CLI-004
|
|
// Description: CLI command group for reachability witness operations.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System.CommandLine;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using StellaOps.Cli.Extensions;
|
|
using Spectre.Console;
|
|
|
|
namespace StellaOps.Cli.Commands;
|
|
|
|
/// <summary>
|
|
/// CLI command group for reachability witness operations.
|
|
/// </summary>
|
|
internal static class WitnessCommandGroup
|
|
{
|
|
internal static Command BuildWitnessCommand(
|
|
IServiceProvider services,
|
|
Option<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var witness = new Command("witness", "Reachability witness operations.");
|
|
|
|
witness.Add(BuildWitnessShowCommand(services, verboseOption, cancellationToken));
|
|
witness.Add(BuildWitnessVerifyCommand(services, verboseOption, cancellationToken));
|
|
witness.Add(BuildWitnessListCommand(services, verboseOption, cancellationToken));
|
|
witness.Add(BuildWitnessExportCommand(services, verboseOption, cancellationToken));
|
|
|
|
return witness;
|
|
}
|
|
|
|
private static Command BuildWitnessShowCommand(
|
|
IServiceProvider services,
|
|
Option<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var witnessIdArg = new Argument<string>("witness-id")
|
|
{
|
|
Description = "The witness ID to display (e.g., wit:sha256:abc123)."
|
|
};
|
|
|
|
var formatOption = new Option<string>("--format", new[] { "-f" })
|
|
{
|
|
Description = "Output format: text (default), json, yaml."
|
|
}.SetDefaultValue("text").FromAmong("text", "json", "yaml");
|
|
|
|
var noColorOption = new Option<bool>("--no-color")
|
|
{
|
|
Description = "Disable colored output."
|
|
};
|
|
|
|
var pathOnlyOption = new Option<bool>("--path-only")
|
|
{
|
|
Description = "Show only the call path, not full witness details."
|
|
};
|
|
|
|
var command = new Command("show", "Display a witness with call path visualization.")
|
|
{
|
|
witnessIdArg,
|
|
formatOption,
|
|
noColorOption,
|
|
pathOnlyOption,
|
|
verboseOption
|
|
};
|
|
|
|
command.SetAction(parseResult =>
|
|
{
|
|
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
|
var format = parseResult.GetValue(formatOption)!;
|
|
var noColor = parseResult.GetValue(noColorOption);
|
|
var pathOnly = parseResult.GetValue(pathOnlyOption);
|
|
var verbose = parseResult.GetValue(verboseOption);
|
|
|
|
return CommandHandlers.HandleWitnessShowAsync(
|
|
services,
|
|
witnessId,
|
|
format,
|
|
noColor,
|
|
pathOnly,
|
|
verbose,
|
|
cancellationToken);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildWitnessVerifyCommand(
|
|
IServiceProvider services,
|
|
Option<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var witnessIdArg = new Argument<string>("witness-id")
|
|
{
|
|
Description = "The witness ID to verify."
|
|
};
|
|
|
|
var publicKeyOption = new Option<string?>("--public-key", new[] { "-k" })
|
|
{
|
|
Description = "Path to public key file (default: fetch from authority)."
|
|
};
|
|
|
|
var offlineOption = new Option<bool>("--offline")
|
|
{
|
|
Description = "Verify using local key only, don't fetch from server."
|
|
};
|
|
|
|
var command = new Command("verify", "Verify a witness signature.")
|
|
{
|
|
witnessIdArg,
|
|
publicKeyOption,
|
|
offlineOption,
|
|
verboseOption
|
|
};
|
|
|
|
command.SetAction(parseResult =>
|
|
{
|
|
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
|
var publicKeyPath = parseResult.GetValue(publicKeyOption);
|
|
var offline = parseResult.GetValue(offlineOption);
|
|
var verbose = parseResult.GetValue(verboseOption);
|
|
|
|
return CommandHandlers.HandleWitnessVerifyAsync(
|
|
services,
|
|
witnessId,
|
|
publicKeyPath,
|
|
offline,
|
|
verbose,
|
|
cancellationToken);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildWitnessListCommand(
|
|
IServiceProvider services,
|
|
Option<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var scanOption = new Option<string>("--scan", new[] { "-s" })
|
|
{
|
|
Description = "Scan ID to list witnesses for.",
|
|
Required = true
|
|
};
|
|
|
|
var vulnOption = new Option<string?>("--vuln", new[] { "-v" })
|
|
{
|
|
Description = "Filter witnesses by CVE/vulnerability ID."
|
|
};
|
|
|
|
var tierOption = new Option<string?>("--tier")
|
|
{
|
|
Description = "Filter by confidence tier: confirmed, likely, present, unreachable."
|
|
}.FromAmong("confirmed", "likely", "present", "unreachable");
|
|
|
|
var reachableOnlyOption = new Option<bool>("--reachable-only")
|
|
{
|
|
Description = "Show only reachable witnesses."
|
|
};
|
|
|
|
// EBPF-003: Add --probe-type filter option
|
|
// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
|
|
var probeTypeOption = new Option<string?>("--probe-type", new[] { "-p" })
|
|
{
|
|
Description = "Filter by eBPF probe type: kprobe, kretprobe, uprobe, uretprobe, tracepoint, usdt, fentry, fexit."
|
|
}.FromAmong("kprobe", "kretprobe", "uprobe", "uretprobe", "tracepoint", "usdt", "fentry", "fexit");
|
|
|
|
var formatOption = new Option<string>("--format", new[] { "-f" })
|
|
{
|
|
Description = "Output format: table (default), json."
|
|
}.SetDefaultValue("table").FromAmong("table", "json");
|
|
|
|
var limitOption = new Option<int>("--limit", new[] { "-l" })
|
|
{
|
|
Description = "Maximum number of witnesses to return."
|
|
}.SetDefaultValue(50);
|
|
|
|
var command = new Command("list", "List witnesses for a scan.")
|
|
{
|
|
scanOption,
|
|
vulnOption,
|
|
tierOption,
|
|
reachableOnlyOption,
|
|
probeTypeOption,
|
|
formatOption,
|
|
limitOption,
|
|
verboseOption
|
|
};
|
|
|
|
command.SetAction(parseResult =>
|
|
{
|
|
var scanId = parseResult.GetValue(scanOption)!;
|
|
var vuln = parseResult.GetValue(vulnOption);
|
|
var tier = parseResult.GetValue(tierOption);
|
|
var reachableOnly = parseResult.GetValue(reachableOnlyOption);
|
|
var probeType = parseResult.GetValue(probeTypeOption);
|
|
var format = parseResult.GetValue(formatOption)!;
|
|
var limit = parseResult.GetValue(limitOption);
|
|
var verbose = parseResult.GetValue(verboseOption);
|
|
|
|
return CommandHandlers.HandleWitnessListAsync(
|
|
services,
|
|
scanId,
|
|
vuln,
|
|
tier,
|
|
reachableOnly,
|
|
probeType,
|
|
format,
|
|
limit,
|
|
verbose,
|
|
cancellationToken);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildWitnessExportCommand(
|
|
IServiceProvider services,
|
|
Option<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var witnessIdArg = new Argument<string>("witness-id")
|
|
{
|
|
Description = "The witness ID to export."
|
|
};
|
|
|
|
var formatOption = new Option<string>("--format", new[] { "-f" })
|
|
{
|
|
Description = "Export format: json (default), sarif."
|
|
}.SetDefaultValue("json").FromAmong("json", "sarif");
|
|
|
|
var outputOption = new Option<string?>("--output", new[] { "-o" })
|
|
{
|
|
Description = "Output file path (default: stdout)."
|
|
};
|
|
|
|
var includeDsseOption = new Option<bool>("--include-dsse")
|
|
{
|
|
Description = "Include DSSE envelope in export."
|
|
};
|
|
|
|
var command = new Command("export", "Export a witness to file.")
|
|
{
|
|
witnessIdArg,
|
|
formatOption,
|
|
outputOption,
|
|
includeDsseOption,
|
|
verboseOption
|
|
};
|
|
|
|
command.SetAction(parseResult =>
|
|
{
|
|
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
|
var format = parseResult.GetValue(formatOption)!;
|
|
var outputPath = parseResult.GetValue(outputOption);
|
|
var includeDsse = parseResult.GetValue(includeDsseOption);
|
|
var verbose = parseResult.GetValue(verboseOption);
|
|
|
|
return CommandHandlers.HandleWitnessExportAsync(
|
|
services,
|
|
witnessId,
|
|
format,
|
|
outputPath,
|
|
includeDsse,
|
|
verbose,
|
|
cancellationToken);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
}
|