Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
256 lines
8.3 KiB
C#
256 lines
8.3 KiB
C#
using System.CommandLine;
|
|
using System.CommandLine.Invocation;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace StellaOps.Cli.Commands.Proof;
|
|
|
|
/// <summary>
|
|
/// Command group for proof chain operations.
|
|
/// Implements advisory §15 CLI commands.
|
|
/// </summary>
|
|
public class ProofCommandGroup
|
|
{
|
|
private readonly ILogger<ProofCommandGroup> _logger;
|
|
|
|
public ProofCommandGroup(ILogger<ProofCommandGroup> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build the proof command tree.
|
|
/// </summary>
|
|
public Command BuildCommand()
|
|
{
|
|
var proofCommand = new Command("proof", "Proof chain operations");
|
|
|
|
proofCommand.AddCommand(BuildVerifyCommand());
|
|
proofCommand.AddCommand(BuildSpineCommand());
|
|
|
|
return proofCommand;
|
|
}
|
|
|
|
private Command BuildVerifyCommand()
|
|
{
|
|
var artifactArg = new Argument<string>(
|
|
name: "artifact",
|
|
description: "Artifact digest (sha256:...) or PURL");
|
|
|
|
var sbomOption = new Option<FileInfo?>(
|
|
aliases: ["-s", "--sbom"],
|
|
description: "Path to SBOM file");
|
|
|
|
var vexOption = new Option<FileInfo?>(
|
|
aliases: ["--vex"],
|
|
description: "Path to VEX file");
|
|
|
|
var anchorOption = new Option<Guid?>(
|
|
aliases: ["-a", "--anchor"],
|
|
description: "Trust anchor ID");
|
|
|
|
var offlineOption = new Option<bool>(
|
|
name: "--offline",
|
|
description: "Offline mode (skip Rekor verification)");
|
|
|
|
var outputOption = new Option<string>(
|
|
name: "--output",
|
|
getDefaultValue: () => "text",
|
|
description: "Output format: text, json");
|
|
|
|
var verboseOption = new Option<int>(
|
|
aliases: ["-v", "--verbose"],
|
|
getDefaultValue: () => 0,
|
|
description: "Verbose output level (use -vv for very verbose)");
|
|
|
|
var verifyCommand = new Command("verify", "Verify an artifact's proof chain")
|
|
{
|
|
artifactArg,
|
|
sbomOption,
|
|
vexOption,
|
|
anchorOption,
|
|
offlineOption,
|
|
outputOption,
|
|
verboseOption
|
|
};
|
|
|
|
verifyCommand.SetHandler(async (context) =>
|
|
{
|
|
var artifact = context.ParseResult.GetValueForArgument(artifactArg);
|
|
var sbomFile = context.ParseResult.GetValueForOption(sbomOption);
|
|
var vexFile = context.ParseResult.GetValueForOption(vexOption);
|
|
var anchorId = context.ParseResult.GetValueForOption(anchorOption);
|
|
var offline = context.ParseResult.GetValueForOption(offlineOption);
|
|
var output = context.ParseResult.GetValueForOption(outputOption) ?? "text";
|
|
var verbose = context.ParseResult.GetValueForOption(verboseOption);
|
|
|
|
context.ExitCode = await VerifyAsync(
|
|
artifact,
|
|
sbomFile,
|
|
vexFile,
|
|
anchorId,
|
|
offline,
|
|
output,
|
|
verbose,
|
|
context.GetCancellationToken());
|
|
});
|
|
|
|
return verifyCommand;
|
|
}
|
|
|
|
private Command BuildSpineCommand()
|
|
{
|
|
var spineCommand = new Command("spine", "Proof spine operations");
|
|
|
|
// stellaops proof spine create
|
|
var createCommand = new Command("create", "Create a proof spine for an artifact");
|
|
var artifactArg = new Argument<string>("artifact", "Artifact digest or PURL");
|
|
createCommand.AddArgument(artifactArg);
|
|
createCommand.SetHandler(async (context) =>
|
|
{
|
|
var artifact = context.ParseResult.GetValueForArgument(artifactArg);
|
|
context.ExitCode = await CreateSpineAsync(artifact, context.GetCancellationToken());
|
|
});
|
|
|
|
// stellaops proof spine show
|
|
var showCommand = new Command("show", "Show proof spine details");
|
|
var bundleArg = new Argument<string>("bundleId", "Proof bundle ID");
|
|
showCommand.AddArgument(bundleArg);
|
|
showCommand.SetHandler(async (context) =>
|
|
{
|
|
var bundleId = context.ParseResult.GetValueForArgument(bundleArg);
|
|
context.ExitCode = await ShowSpineAsync(bundleId, context.GetCancellationToken());
|
|
});
|
|
|
|
spineCommand.AddCommand(createCommand);
|
|
spineCommand.AddCommand(showCommand);
|
|
|
|
return spineCommand;
|
|
}
|
|
|
|
private async Task<int> VerifyAsync(
|
|
string artifact,
|
|
FileInfo? sbomFile,
|
|
FileInfo? vexFile,
|
|
Guid? anchorId,
|
|
bool offline,
|
|
string output,
|
|
int verbose,
|
|
CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
if (verbose > 0)
|
|
{
|
|
_logger.LogDebug("Starting proof verification for {Artifact}", artifact);
|
|
}
|
|
|
|
// Validate artifact format
|
|
if (!IsValidArtifactId(artifact))
|
|
{
|
|
_logger.LogError("Invalid artifact format: {Artifact}", artifact);
|
|
return ProofExitCodes.SystemError;
|
|
}
|
|
|
|
if (verbose > 0)
|
|
{
|
|
_logger.LogDebug("Artifact format valid: {Artifact}", artifact);
|
|
}
|
|
|
|
// TODO: Implement actual verification using IVerificationPipeline
|
|
// 1. Load SBOM if provided
|
|
// 2. Load VEX if provided
|
|
// 3. Find or use specified trust anchor
|
|
// 4. Run verification pipeline
|
|
// 5. Check Rekor inclusion (unless offline)
|
|
// 6. Generate receipt
|
|
|
|
if (verbose > 0)
|
|
{
|
|
_logger.LogDebug("Verification pipeline not yet implemented");
|
|
}
|
|
|
|
if (output == "json")
|
|
{
|
|
Console.WriteLine("{");
|
|
Console.WriteLine($" \"artifact\": \"{artifact}\",");
|
|
Console.WriteLine(" \"status\": \"pass\",");
|
|
Console.WriteLine(" \"message\": \"Verification successful (stub)\"");
|
|
Console.WriteLine("}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("StellaOps Scan Summary");
|
|
Console.WriteLine("══════════════════════");
|
|
Console.WriteLine($"Artifact: {artifact}");
|
|
Console.WriteLine("Status: PASS (stub - verification not yet implemented)");
|
|
}
|
|
|
|
return ProofExitCodes.Success;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Verification failed for {Artifact}", artifact);
|
|
return ProofExitCodes.SystemError;
|
|
}
|
|
}
|
|
|
|
private async Task<int> CreateSpineAsync(string artifact, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Creating proof spine for {Artifact}", artifact);
|
|
|
|
// TODO: Implement spine creation using IProofSpineAssembler
|
|
Console.WriteLine($"Creating proof spine for: {artifact}");
|
|
Console.WriteLine("Spine creation not yet implemented");
|
|
|
|
return ProofExitCodes.Success;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to create spine for {Artifact}", artifact);
|
|
return ProofExitCodes.SystemError;
|
|
}
|
|
}
|
|
|
|
private async Task<int> ShowSpineAsync(string bundleId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Showing proof spine {BundleId}", bundleId);
|
|
|
|
// TODO: Implement spine retrieval
|
|
Console.WriteLine($"Proof spine: {bundleId}");
|
|
Console.WriteLine("Spine display not yet implemented");
|
|
|
|
return ProofExitCodes.Success;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to show spine {BundleId}", bundleId);
|
|
return ProofExitCodes.SystemError;
|
|
}
|
|
}
|
|
|
|
private static bool IsValidArtifactId(string artifact)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(artifact))
|
|
return false;
|
|
|
|
// sha256:<64-hex>
|
|
if (artifact.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var hash = artifact[7..];
|
|
return hash.Length == 64 && hash.All(c => "0123456789abcdef".Contains(char.ToLowerInvariant(c)));
|
|
}
|
|
|
|
// pkg:type/...
|
|
if (artifact.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return artifact.Length > 5; // Minimal PURL validation
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|