180 lines
5.5 KiB
C#
180 lines
5.5 KiB
C#
// Licensed to StellaOps under the AGPL-3.0-or-later license.
|
|
|
|
using System.CommandLine;
|
|
|
|
namespace StellaOps.Cli.Commands.ReachGraph;
|
|
|
|
/// <summary>
|
|
/// CLI command group for reachability graph operations.
|
|
/// stella reachgraph [slice|replay|verify]
|
|
/// </summary>
|
|
public static class ReachGraphCommandGroup
|
|
{
|
|
public static Command Build()
|
|
{
|
|
var command = new Command("reachgraph", "Reachability graph operations");
|
|
|
|
command.Add(BuildSliceCommand());
|
|
command.Add(BuildReplayCommand());
|
|
command.Add(BuildVerifyCommand());
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildSliceCommand()
|
|
{
|
|
var digestOption = new Option<string>("--digest", "-d")
|
|
{
|
|
Description = "BLAKE3 digest of the graph",
|
|
Required = true
|
|
};
|
|
|
|
var cveOption = new Option<string?>("--cve")
|
|
{
|
|
Description = "CVE identifier to slice by"
|
|
};
|
|
|
|
var purlOption = new Option<string?>("--purl", "-p")
|
|
{
|
|
Description = "Package PURL pattern to slice by"
|
|
};
|
|
|
|
var entrypointOption = new Option<string?>("--entrypoint", "-e")
|
|
{
|
|
Description = "Entrypoint path or symbol pattern"
|
|
};
|
|
|
|
var fileOption = new Option<string?>("--file", "-f")
|
|
{
|
|
Description = "File path pattern (glob) to slice by"
|
|
};
|
|
|
|
var depthOption = new Option<int>("--depth")
|
|
{
|
|
Description = "Max traversal depth"
|
|
}.SetDefaultValue(3);
|
|
|
|
var outputOption = new Option<string>("--output", "-o")
|
|
{
|
|
Description = "Output format: json, table, or dot (GraphViz)"
|
|
}.SetDefaultValue("table");
|
|
|
|
var apiUrlOption = new Option<string>("--api-url")
|
|
{
|
|
Description = "ReachGraph Store API URL"
|
|
}.SetDefaultValue("http://localhost:5000");
|
|
|
|
var command = new Command("slice", "Query a slice of a reachability graph")
|
|
{
|
|
digestOption,
|
|
cveOption,
|
|
purlOption,
|
|
entrypointOption,
|
|
fileOption,
|
|
depthOption,
|
|
outputOption,
|
|
apiUrlOption
|
|
};
|
|
|
|
command.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var digest = parseResult.GetValue(digestOption) ?? string.Empty;
|
|
var cve = parseResult.GetValue(cveOption);
|
|
var purl = parseResult.GetValue(purlOption);
|
|
var entrypoint = parseResult.GetValue(entrypointOption);
|
|
var file = parseResult.GetValue(fileOption);
|
|
var depth = parseResult.GetValue(depthOption);
|
|
var output = parseResult.GetValue(outputOption) ?? "table";
|
|
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
|
|
|
|
await ReachGraphCommandHandlers.HandleSliceAsync(
|
|
digest, cve, purl, entrypoint, file, depth, output, apiUrl);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildReplayCommand()
|
|
{
|
|
var inputsOption = new Option<string>("--inputs", "-i")
|
|
{
|
|
Description = "Comma-separated input files (sbom.json,vex.json,callgraph.json)",
|
|
Required = true
|
|
};
|
|
|
|
var expectedOption = new Option<string>("--expected", "-e")
|
|
{
|
|
Description = "Expected BLAKE3 digest",
|
|
Required = true
|
|
};
|
|
|
|
var outputFileOption = new Option<string?>("--output-file", "-o")
|
|
{
|
|
Description = "Write computed graph to file"
|
|
};
|
|
|
|
var verboseOption = new Option<bool>("--verbose", "-v")
|
|
{
|
|
Description = "Show verbose output"
|
|
};
|
|
|
|
var apiUrlOption = new Option<string>("--api-url")
|
|
{
|
|
Description = "ReachGraph Store API URL"
|
|
}.SetDefaultValue("http://localhost:5000");
|
|
|
|
var command = new Command("replay", "Verify deterministic replay of a graph")
|
|
{
|
|
inputsOption,
|
|
expectedOption,
|
|
outputFileOption,
|
|
verboseOption,
|
|
apiUrlOption
|
|
};
|
|
|
|
command.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var inputs = parseResult.GetValue(inputsOption) ?? string.Empty;
|
|
var expected = parseResult.GetValue(expectedOption) ?? string.Empty;
|
|
var outputFile = parseResult.GetValue(outputFileOption);
|
|
var verbose = parseResult.GetValue(verboseOption);
|
|
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
|
|
|
|
await ReachGraphCommandHandlers.HandleReplayAsync(
|
|
inputs, expected, outputFile, verbose, apiUrl);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
|
|
private static Command BuildVerifyCommand()
|
|
{
|
|
var digestOption = new Option<string>("--digest", "-d")
|
|
{
|
|
Description = "BLAKE3 digest of the graph to verify",
|
|
Required = true
|
|
};
|
|
|
|
var apiUrlOption = new Option<string>("--api-url")
|
|
{
|
|
Description = "ReachGraph Store API URL"
|
|
}.SetDefaultValue("http://localhost:5000");
|
|
|
|
var command = new Command("verify", "Verify signatures on a reachability graph")
|
|
{
|
|
digestOption,
|
|
apiUrlOption
|
|
};
|
|
|
|
command.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var digest = parseResult.GetValue(digestOption) ?? string.Empty;
|
|
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
|
|
|
|
await ReachGraphCommandHandlers.HandleVerifyAsync(digest, apiUrl);
|
|
});
|
|
|
|
return command;
|
|
}
|
|
}
|