license switch agpl -> busl1, sprints work, new product advisories
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) StellaOps. All rights reserved.
|
||||
// Licensed under the AGPL-3.0-or-later license.
|
||||
// Licensed under the BUSL-1.1 license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// AttestTimestampCommandTests.cs
|
||||
// Sprint: SPRINT_20260119_010 Attestor TST Integration
|
||||
// Description: Tests for attestation timestamp CLI handling.
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class AttestTimestampCommandTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleAttestSignAsync_WithTimestamp_WritesTimestampMetadata()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var predicatePath = Path.Combine(temp.Path, "predicate.json");
|
||||
var outputPath = Path.Combine(temp.Path, "attestation.json");
|
||||
|
||||
await File.WriteAllTextAsync(predicatePath, "{}", CancellationToken.None);
|
||||
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
var exitCode = await CommandHandlers.HandleAttestSignAsync(
|
||||
services,
|
||||
predicatePath,
|
||||
"https://example.test/predicate",
|
||||
"artifact",
|
||||
"sha256:abc123",
|
||||
keyId: null,
|
||||
keyless: false,
|
||||
useRekor: false,
|
||||
includeTimestamp: true,
|
||||
tsaUrl: "https://tsa.example",
|
||||
outputPath: outputPath,
|
||||
format: "dsse",
|
||||
verbose: false,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
|
||||
using var doc = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath, CancellationToken.None));
|
||||
Assert.True(doc.RootElement.TryGetProperty("timestamp", out var timestamp));
|
||||
Assert.True(timestamp.TryGetProperty("rfc3161", out _));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleAttestVerifyAsync_RequireTimestamp_FailsWhenMissing()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var envelopePath = Path.Combine(temp.Path, "envelope.json");
|
||||
var outputPath = Path.Combine(temp.Path, "verification.json");
|
||||
var rootPath = Path.Combine(temp.Path, "root.pem");
|
||||
|
||||
await File.WriteAllTextAsync(envelopePath, CreateEnvelopeJson(includeTimestamp: false), CancellationToken.None);
|
||||
await File.WriteAllTextAsync(rootPath, "root", CancellationToken.None);
|
||||
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
var exitCode = await CommandHandlers.HandleAttestVerifyAsync(
|
||||
services,
|
||||
envelopePath,
|
||||
policyPath: null,
|
||||
rootPath: rootPath,
|
||||
checkpointPath: null,
|
||||
outputPath: outputPath,
|
||||
format: "json",
|
||||
explain: false,
|
||||
requireTimestamp: true,
|
||||
maxSkew: null,
|
||||
verbose: false,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, exitCode);
|
||||
|
||||
using var doc = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath, CancellationToken.None));
|
||||
Assert.Equal("FAILED", doc.RootElement.GetProperty("status").GetString());
|
||||
Assert.True(doc.RootElement.GetProperty("timestamp").GetProperty("required").GetBoolean());
|
||||
Assert.False(doc.RootElement.GetProperty("timestamp").GetProperty("present").GetBoolean());
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleAttestVerifyAsync_RequireTimestamp_PassesWhenPresent()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var envelopePath = Path.Combine(temp.Path, "envelope.json");
|
||||
var outputPath = Path.Combine(temp.Path, "verification.json");
|
||||
var rootPath = Path.Combine(temp.Path, "root.pem");
|
||||
|
||||
await File.WriteAllTextAsync(envelopePath, CreateEnvelopeJson(includeTimestamp: true), CancellationToken.None);
|
||||
await File.WriteAllTextAsync(rootPath, "root", CancellationToken.None);
|
||||
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
var exitCode = await CommandHandlers.HandleAttestVerifyAsync(
|
||||
services,
|
||||
envelopePath,
|
||||
policyPath: null,
|
||||
rootPath: rootPath,
|
||||
checkpointPath: null,
|
||||
outputPath: outputPath,
|
||||
format: "json",
|
||||
explain: false,
|
||||
requireTimestamp: true,
|
||||
maxSkew: null,
|
||||
verbose: false,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
|
||||
using var doc = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath, CancellationToken.None));
|
||||
Assert.Equal("PASSED", doc.RootElement.GetProperty("status").GetString());
|
||||
Assert.True(doc.RootElement.GetProperty("timestamp").GetProperty("present").GetBoolean());
|
||||
}
|
||||
|
||||
private static string CreateEnvelopeJson(bool includeTimestamp)
|
||||
{
|
||||
var statement = new
|
||||
{
|
||||
_type = "https://in-toto.io/Statement/v1",
|
||||
subject = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
name = "artifact",
|
||||
digest = new Dictionary<string, string> { ["sha256"] = "abc123" }
|
||||
}
|
||||
},
|
||||
predicateType = "https://example.test/predicate",
|
||||
predicate = new { }
|
||||
};
|
||||
|
||||
var payloadJson = JsonSerializer.Serialize(statement);
|
||||
var payloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payloadJson));
|
||||
var envelope = new Dictionary<string, object?>
|
||||
{
|
||||
["payloadType"] = "application/vnd.in-toto+json",
|
||||
["payload"] = payloadBase64,
|
||||
["signatures"] = new[]
|
||||
{
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["keyid"] = "test-key",
|
||||
["sig"] = "abc123"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (includeTimestamp)
|
||||
{
|
||||
envelope["timestamp"] = new Dictionary<string, object?>
|
||||
{
|
||||
["rfc3161"] = new Dictionary<string, object?>
|
||||
{
|
||||
["tsaUrl"] = "https://tsa.example",
|
||||
["tokenDigest"] = "sha256:abc123",
|
||||
["generationTime"] = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero).ToString("o")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(envelope);
|
||||
}
|
||||
}
|
||||
@@ -84,4 +84,28 @@ public sealed class CommandFactoryTests
|
||||
|
||||
Assert.Contains(sbom.Subcommands, command => string.Equals(command.Name, "upload", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ExposesTimestampCommands()
|
||||
{
|
||||
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 ts = Assert.Single(root.Subcommands, command => string.Equals(command.Name, "ts", StringComparison.Ordinal));
|
||||
Assert.Contains(ts.Subcommands, command => string.Equals(command.Name, "rfc3161", StringComparison.Ordinal));
|
||||
Assert.Contains(ts.Subcommands, command => string.Equals(command.Name, "verify", StringComparison.Ordinal));
|
||||
Assert.Contains(ts.Subcommands, command => string.Equals(command.Name, "info", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ExposesEvidenceStoreCommand()
|
||||
{
|
||||
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 evidence = Assert.Single(root.Subcommands, command => string.Equals(command.Name, "evidence", StringComparison.Ordinal));
|
||||
Assert.Contains(evidence.Subcommands, command => string.Equals(command.Name, "store", StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ConfigCommandTests.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Sprint: SPRINT_20260112_014_CLI_config_viewer (CLI-CONFIG-014)
|
||||
// </copyright>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="DoctorCommandGroupTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// EvidenceStoreCommandTests.cs
|
||||
// Sprint: SPRINT_20260119_010 Attestor TST Integration
|
||||
// Description: Unit tests for evidence store CLI command.
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.CommandLine;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Configuration;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class EvidenceStoreCommandTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidenceStoreCommand_WritesManifest()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var artifactPath = Path.Combine(temp.Path, "artifact.dsse");
|
||||
var storeDir = Path.Combine(temp.Path, "store");
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes("evidence-store");
|
||||
await File.WriteAllBytesAsync(artifactPath, bytes, CancellationToken.None);
|
||||
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
var options = new StellaOpsCliOptions();
|
||||
var evidenceCommand = EvidenceCommandGroup.BuildEvidenceCommand(
|
||||
services,
|
||||
options,
|
||||
new Option<bool>("--verbose"),
|
||||
CancellationToken.None);
|
||||
var root = new RootCommand { evidenceCommand };
|
||||
|
||||
var exitCode = await root.Parse($"evidence store --artifact \"{artifactPath}\" --store-dir \"{storeDir}\"").InvokeAsync();
|
||||
Assert.Equal(0, exitCode);
|
||||
|
||||
var digest = "sha256:" + Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
||||
var evidenceDir = Path.Combine(storeDir, digest.Replace(':', '_'));
|
||||
var manifestPath = Path.Combine(evidenceDir, "manifest.json");
|
||||
|
||||
Assert.True(Directory.Exists(evidenceDir));
|
||||
Assert.True(File.Exists(manifestPath));
|
||||
|
||||
using var doc = JsonDocument.Parse(await File.ReadAllTextAsync(manifestPath, CancellationToken.None));
|
||||
Assert.Equal(digest, doc.RootElement.GetProperty("artifactDigest").GetString());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ProveCommandTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// TimestampCommandTests.cs
|
||||
// Sprint: SPRINT_20260119_010 Attestor TST Integration
|
||||
// Description: Unit tests for timestamp CLI commands.
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.CommandLine;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class TimestampCommandTests
|
||||
{
|
||||
private readonly Option<bool> _verboseOption = new("--verbose");
|
||||
private readonly CancellationToken _ct = CancellationToken.None;
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TimestampCommandGroup_ExposesSubcommands()
|
||||
{
|
||||
var command = TimestampCommandGroup.BuildTimestampCommand(_verboseOption, _ct);
|
||||
|
||||
Assert.Contains(command.Children, child => child.Name == "rfc3161");
|
||||
Assert.Contains(command.Children, child => child.Name == "verify");
|
||||
Assert.Contains(command.Children, child => child.Name == "info");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TimestampCommandGroup_Rfc3161ThenVerify_Succeeds()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var artifactPath = Path.Combine(temp.Path, "artifact.bin");
|
||||
var tokenPath = Path.Combine(temp.Path, "artifact.tst");
|
||||
var bytes = Encoding.UTF8.GetBytes("timestamp-test");
|
||||
await File.WriteAllBytesAsync(artifactPath, bytes, _ct);
|
||||
|
||||
var digest = "sha256:" + Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
||||
var root = new RootCommand { TimestampCommandGroup.BuildTimestampCommand(_verboseOption, _ct) };
|
||||
|
||||
var originalOut = Console.Out;
|
||||
var writer = new StringWriter();
|
||||
int exitCode;
|
||||
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse($"ts rfc3161 --hash {digest} --tsa https://tsa.example --out \"{tokenPath}\"").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
}
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.True(File.Exists(tokenPath));
|
||||
|
||||
var verifyWriter = new StringWriter();
|
||||
try
|
||||
{
|
||||
Console.SetOut(verifyWriter);
|
||||
exitCode = await root.Parse($"ts verify --tst \"{tokenPath}\" --artifact \"{artifactPath}\"").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
}
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.Contains("PASSED", verifyWriter.ToString());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="UnknownsGreyQueueCommandTests.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Sprint: SPRINT_20260112_010_CLI_unknowns_grey_queue_cli (CLI-UNK-005)
|
||||
// </copyright>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="VerifyBundleCommandTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Sprint: SPRINT_4100_0006_0001 - Crypto Plugin CLI Architecture
|
||||
// Task: T11 - Integration tests for crypto commands
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Sprint: SPRINT_20260112_011_CLI_evidence_card_remediate_cli (REMPR-CLI-003)
|
||||
// Task: REMPR-CLI-003 - CLI tests for open-pr command
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| CLI-ISSUER-KEYS-TESTS-0001 | DONE | SPRINT_20260117_009 - Issuer keys tests added. |
|
||||
| CLI-BINARY-ANALYSIS-TESTS-0001 | DONE | SPRINT_20260117_007 - Binary fingerprint/diff tests added. |
|
||||
| CLI-POLICY-TESTS-0001 | DONE | SPRINT_20260117_010 - Policy lattice/verdict/promote tests added. |
|
||||
| ATT-005 | DONE | SPRINT_20260119_010 - Timestamp CLI workflow tests added. |
|
||||
|
||||
Reference in New Issue
Block a user