audit remarks work
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,7 @@ internal static class CommandFactory
|
||||
root.Add(BuildAuthCommand(services, options, verboseOption, cancellationToken));
|
||||
root.Add(BuildTenantsCommand(services, options, verboseOption, cancellationToken));
|
||||
root.Add(BuildPolicyCommand(services, options, verboseOption, cancellationToken));
|
||||
root.Add(ToolsCommandGroup.BuildToolsCommand(loggerFactory, cancellationToken));
|
||||
root.Add(BuildTaskRunnerCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildFindingsCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildAdviseCommand(services, options, verboseOption, cancellationToken));
|
||||
|
||||
@@ -10,13 +10,12 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Cli.Replay;
|
||||
using StellaOps.Canonicalization.Json;
|
||||
using StellaOps.Canonicalization.Verification;
|
||||
using StellaOps.Policy.Replay;
|
||||
using StellaOps.Replay.Core;
|
||||
using StellaOps.Replay.Core.Export;
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
using StellaOps.Testing.Manifests.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
|
||||
25
src/Cli/StellaOps.Cli/Commands/ToolsCommandGroup.cs
Normal file
25
src/Cli/StellaOps.Cli/Commands/ToolsCommandGroup.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Tools;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
internal static class ToolsCommandGroup
|
||||
{
|
||||
internal static Command BuildToolsCommand(ILoggerFactory loggerFactory, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(loggerFactory);
|
||||
|
||||
var tools = new Command("tools", "Local policy tooling and maintenance commands.");
|
||||
var validationRunner = new PolicyValidationRunner(new PolicyValidationCli());
|
||||
|
||||
tools.Add(PolicyDslValidatorCommand.BuildCommand(validationRunner, cancellationToken));
|
||||
tools.Add(PolicySchemaExporterCommand.BuildCommand(new PolicySchemaExporterRunner(), cancellationToken));
|
||||
tools.Add(PolicySimulationSmokeCommand.BuildCommand(new PolicySimulationSmokeRunner(loggerFactory), cancellationToken));
|
||||
|
||||
return tools;
|
||||
}
|
||||
}
|
||||
60
src/Cli/StellaOps.Cli/Replay/RunManifest.cs
Normal file
60
src/Cli/StellaOps.Cli/Replay/RunManifest.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Cli.Replay;
|
||||
|
||||
public sealed record RunManifest
|
||||
{
|
||||
public required string RunId { get; init; }
|
||||
public string SchemaVersion { get; init; } = "1.0.0";
|
||||
public required ImmutableArray<ArtifactDigest> ArtifactDigests { get; init; }
|
||||
public ImmutableArray<SbomReference> SbomDigests { get; init; } = [];
|
||||
public required FeedSnapshot FeedSnapshot { get; init; }
|
||||
public required PolicySnapshot PolicySnapshot { get; init; }
|
||||
public required ToolVersions ToolVersions { get; init; }
|
||||
public required CryptoProfile CryptoProfile { get; init; }
|
||||
public required EnvironmentProfile EnvironmentProfile { get; init; }
|
||||
public long? PrngSeed { get; init; }
|
||||
public required string CanonicalizationVersion { get; init; }
|
||||
public required DateTimeOffset InitiatedAt { get; init; }
|
||||
public string? ManifestDigest { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ArtifactDigest(
|
||||
string Algorithm,
|
||||
string Digest,
|
||||
string? MediaType,
|
||||
string? Reference);
|
||||
|
||||
public sealed record SbomReference(
|
||||
string Format,
|
||||
string Digest,
|
||||
string? Uri);
|
||||
|
||||
public sealed record FeedSnapshot(
|
||||
string FeedId,
|
||||
string Version,
|
||||
string Digest,
|
||||
DateTimeOffset SnapshotAt);
|
||||
|
||||
public sealed record PolicySnapshot(
|
||||
string PolicyVersion,
|
||||
string LatticeRulesDigest,
|
||||
ImmutableArray<string> EnabledRules);
|
||||
|
||||
public sealed record ToolVersions(
|
||||
string ScannerVersion,
|
||||
string SbomGeneratorVersion,
|
||||
string ReachabilityEngineVersion,
|
||||
string AttestorVersion,
|
||||
ImmutableDictionary<string, string> AdditionalTools);
|
||||
|
||||
public sealed record CryptoProfile(
|
||||
string ProfileName,
|
||||
ImmutableArray<string> TrustRootIds,
|
||||
ImmutableArray<string> AllowedAlgorithms);
|
||||
|
||||
public sealed record EnvironmentProfile(
|
||||
string Name,
|
||||
bool ValkeyEnabled,
|
||||
string? PostgresVersion,
|
||||
string? ValkeyVersion);
|
||||
43
src/Cli/StellaOps.Cli/Replay/RunManifestSerializer.cs
Normal file
43
src/Cli/StellaOps.Cli/Replay/RunManifestSerializer.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Canonical.Json;
|
||||
|
||||
namespace StellaOps.Cli.Replay;
|
||||
|
||||
internal static class RunManifestSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
public static string Serialize(RunManifest manifest)
|
||||
{
|
||||
var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(manifest, JsonOptions);
|
||||
var canonicalBytes = CanonJson.CanonicalizeParsedJson(jsonBytes);
|
||||
return Encoding.UTF8.GetString(canonicalBytes);
|
||||
}
|
||||
|
||||
public static RunManifest Deserialize(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<RunManifest>(json, JsonOptions)
|
||||
?? throw new InvalidOperationException("Failed to deserialize manifest");
|
||||
}
|
||||
|
||||
public static string ComputeDigest(RunManifest manifest)
|
||||
{
|
||||
var withoutDigest = manifest with { ManifestDigest = null };
|
||||
var json = Serialize(withoutDigest);
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(json));
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
public static RunManifest WithDigest(RunManifest manifest)
|
||||
=> manifest with { ManifestDigest = ComputeDigest(manifest) };
|
||||
}
|
||||
@@ -49,8 +49,8 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.DependencyInjection/StellaOps.Cryptography.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.BouncyCastle/StellaOps.Cryptography.Plugin.BouncyCastle.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Canonicalization/StellaOps.Canonicalization.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.DeltaVerdict/StellaOps.DeltaVerdict.csproj" />
|
||||
<ProjectReference Include="../../__Tests/__Libraries/StellaOps.Testing.Manifests/StellaOps.Testing.Manifests.csproj" />
|
||||
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
|
||||
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Importer/StellaOps.AirGap.Importer.csproj" />
|
||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||
@@ -69,6 +69,7 @@
|
||||
<ProjectReference Include="../../Policy/StellaOps.PolicyDsl/StellaOps.PolicyDsl.csproj" />
|
||||
<ProjectReference Include="../../Policy/__Libraries/StellaOps.Policy/StellaOps.Policy.csproj" />
|
||||
<ProjectReference Include="../../Policy/StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Policy.Tools/StellaOps.Policy.Tools.csproj" />
|
||||
<ProjectReference Include="../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
|
||||
<ProjectReference Include="../../Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Infrastructure.Postgres/StellaOps.Infrastructure.Postgres.csproj" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -54,8 +55,20 @@ public sealed class ScannerDownloadVerifyTests
|
||||
internal static class CommandHandlersTestShim
|
||||
{
|
||||
public static Task VerifyBundlePublicAsync(string path, ILogger logger, CancellationToken token)
|
||||
=> typeof(CommandHandlers)
|
||||
.GetMethod("VerifyBundleAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
|
||||
.Invoke(null, new object[] { path, logger, token }) as Task
|
||||
?? Task.CompletedTask;
|
||||
{
|
||||
var method = typeof(CommandHandlers).GetMethod(
|
||||
"VerifyBundleAsync",
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(string), typeof(ILogger), typeof(CancellationToken) },
|
||||
modifiers: null);
|
||||
|
||||
if (method is null)
|
||||
{
|
||||
throw new MissingMethodException(nameof(CommandHandlers), "VerifyBundleAsync");
|
||||
}
|
||||
|
||||
return method.Invoke(null, new object[] { path, logger, token }) as Task
|
||||
?? Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Configuration;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class ToolsCommandGroupTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_ExposesToolsCommands()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.None));
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
var root = CommandFactory.Create(services, new StellaOpsCliOptions(), CancellationToken.None, loggerFactory);
|
||||
|
||||
var tools = Assert.Single(root.Subcommands, command => string.Equals(command.Name, "tools", StringComparison.Ordinal));
|
||||
|
||||
Assert.Contains(tools.Subcommands, command => string.Equals(command.Name, "policy-dsl-validate", StringComparison.Ordinal));
|
||||
Assert.Contains(tools.Subcommands, command => string.Equals(command.Name, "policy-schema-export", StringComparison.Ordinal));
|
||||
Assert.Contains(tools.Subcommands, command => string.Equals(command.Name, "policy-simulation-smoke", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolsCommand_PolicyDslValidator_HasExpectedOptions()
|
||||
{
|
||||
var command = BuildToolsCommand().Subcommands.First(c => c.Name == "policy-dsl-validate");
|
||||
|
||||
Assert.NotNull(FindOption(command, "--strict", "-s"));
|
||||
Assert.NotNull(FindOption(command, "--json", "-j"));
|
||||
Assert.Contains(command.Arguments, argument => string.Equals(argument.Name, "inputs", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolsCommand_PolicySchemaExporter_HasExpectedOptions()
|
||||
{
|
||||
var command = BuildToolsCommand().Subcommands.First(c => c.Name == "policy-schema-export");
|
||||
|
||||
Assert.NotNull(FindOption(command, "--output", "-o"));
|
||||
Assert.NotNull(FindOption(command, "--repo-root", "-r"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolsCommand_PolicySimulationSmoke_HasExpectedOptions()
|
||||
{
|
||||
var command = BuildToolsCommand().Subcommands.First(c => c.Name == "policy-simulation-smoke");
|
||||
|
||||
Assert.NotNull(FindOption(command, "--scenario-root", "-r"));
|
||||
Assert.NotNull(FindOption(command, "--output", "-o"));
|
||||
Assert.NotNull(FindOption(command, "--repo-root"));
|
||||
Assert.NotNull(FindOption(command, "--fixed-time"));
|
||||
}
|
||||
|
||||
private static Command BuildToolsCommand()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.None));
|
||||
return ToolsCommandGroup.BuildToolsCommand(loggerFactory, CancellationToken.None);
|
||||
}
|
||||
|
||||
private static Option? FindOption(Command command, params string[] aliases)
|
||||
{
|
||||
return command.Options.FirstOrDefault(option =>
|
||||
aliases.Any(alias => string.Equals(option.Name, alias, StringComparison.Ordinal) || option.Aliases.Contains(alias)));
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,8 @@ public sealed class CliIntegrationTests : IDisposable
|
||||
|
||||
// Act & Assert
|
||||
var act = async () => await client.ScanAsync("slow/image:v1");
|
||||
await act.Should().ThrowAsync<TimeoutException>();
|
||||
await act.Should().ThrowAsync<Exception>()
|
||||
.Where(ex => ex is TimeoutException || ex is TaskCanceledException);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Cli.Replay;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Replay;
|
||||
|
||||
public sealed class RunManifestSerializerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Serialize_UsesCanonicalOrdering()
|
||||
{
|
||||
var manifest = CreateManifest();
|
||||
|
||||
var json1 = RunManifestSerializer.Serialize(manifest);
|
||||
var json2 = RunManifestSerializer.Serialize(manifest);
|
||||
|
||||
Assert.Equal(json1, json2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_IsStable()
|
||||
{
|
||||
var manifest = CreateManifest();
|
||||
|
||||
var digest1 = RunManifestSerializer.ComputeDigest(manifest);
|
||||
var digest2 = RunManifestSerializer.ComputeDigest(manifest);
|
||||
|
||||
Assert.Equal(digest1, digest2);
|
||||
Assert.Equal(64, digest1.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundTrip_PreservesFields()
|
||||
{
|
||||
var manifest = CreateManifest();
|
||||
|
||||
var json = RunManifestSerializer.Serialize(manifest);
|
||||
var deserialized = RunManifestSerializer.Deserialize(json);
|
||||
|
||||
var normalized = RunManifestSerializer.Serialize(deserialized);
|
||||
|
||||
Assert.Equal(json, normalized);
|
||||
}
|
||||
|
||||
private static RunManifest CreateManifest()
|
||||
{
|
||||
return new RunManifest
|
||||
{
|
||||
RunId = "run-1",
|
||||
SchemaVersion = "1.0.0",
|
||||
ArtifactDigests = ImmutableArray.Create(
|
||||
new ArtifactDigest("sha256", new string('a', 64), "application/vnd.oci.image.layer.v1.tar", "example")),
|
||||
SbomDigests = ImmutableArray.Create(
|
||||
new SbomReference("cyclonedx-1.6", new string('b', 64), "sbom.json")),
|
||||
FeedSnapshot = new FeedSnapshot("nvd", "2025.12.01", new string('c', 64), new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero)),
|
||||
PolicySnapshot = new PolicySnapshot("policy-1", new string('d', 64), ImmutableArray.Create("rule-1")),
|
||||
ToolVersions = new ToolVersions("1.0.0", "1.0.0", "1.0.0", "1.0.0", ImmutableDictionary<string, string>.Empty),
|
||||
CryptoProfile = new CryptoProfile("default", ImmutableArray.Create("root-1"), ImmutableArray.Create("sha256")),
|
||||
EnvironmentProfile = new EnvironmentProfile("postgres-only", false, "16", null),
|
||||
PrngSeed = 42,
|
||||
CanonicalizationVersion = "1.0.0",
|
||||
InitiatedAt = new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user