191 lines
5.4 KiB
C#
191 lines
5.4 KiB
C#
namespace StellaOps.Offline.E2E.Tests;
|
|
|
|
using StellaOps.Testing.AirGap;
|
|
|
|
[Trait("Category", "AirGap")]
|
|
[Trait("Category", "E2E")]
|
|
public class OfflineE2ETests : NetworkIsolatedTestBase
|
|
{
|
|
[Fact]
|
|
public async Task Scan_WithOfflineBundle_ProducesVerdict()
|
|
{
|
|
// Arrange
|
|
var bundlePath = GetOfflineBundlePath();
|
|
var imageTarball = Path.Combine(bundlePath, "images", "test-image.tar");
|
|
|
|
// Skip if bundle doesn't exist (local dev)
|
|
if (!Directory.Exists(bundlePath))
|
|
{
|
|
// Skip - requires offline bundle
|
|
return;
|
|
}
|
|
|
|
// Act
|
|
// TODO: Implement scanner offline execution
|
|
var result = await SimulateScanAsync(imageTarball, bundlePath);
|
|
|
|
// Assert
|
|
result.Success.Should().BeTrue();
|
|
result.Verdict.Should().NotBeNull();
|
|
AssertNoNetworkCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Scan_ProducesSbom_WithOfflineBundle()
|
|
{
|
|
var bundlePath = GetOfflineBundlePath();
|
|
var imageTarball = Path.Combine(bundlePath, "images", "test-image.tar");
|
|
|
|
if (!Directory.Exists(bundlePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var result = await SimulateScanAsync(imageTarball, bundlePath);
|
|
|
|
result.Sbom.Should().NotBeNull();
|
|
result.Sbom?.Components.Should().NotBeEmpty();
|
|
AssertNoNetworkCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Attestation_SignAndVerify_WithOfflineBundle()
|
|
{
|
|
var bundlePath = GetOfflineBundlePath();
|
|
var imageTarball = Path.Combine(bundlePath, "images", "test-image.tar");
|
|
|
|
if (!Directory.Exists(bundlePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Scan and generate attestation
|
|
var scanResult = await SimulateScanAsync(imageTarball, bundlePath);
|
|
|
|
// Sign attestation (offline with local keys)
|
|
var keyPath = Path.Combine(bundlePath, "keys", "signing-key.pem");
|
|
var signResult = await SimulateSignAttestationAsync(
|
|
scanResult.Sbom!,
|
|
keyPath);
|
|
|
|
signResult.Success.Should().BeTrue();
|
|
|
|
// Verify signature (offline with local trust roots)
|
|
var trustRootPath = Path.Combine(bundlePath, "certs", "trust-root.pem");
|
|
var verifyResult = await SimulateVerifyAttestationAsync(
|
|
signResult.Attestation,
|
|
trustRootPath);
|
|
|
|
verifyResult.Valid.Should().BeTrue();
|
|
AssertNoNetworkCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PolicyEvaluation_WithOfflineBundle_Works()
|
|
{
|
|
var bundlePath = GetOfflineBundlePath();
|
|
var imageTarball = Path.Combine(bundlePath, "images", "vuln-image.tar");
|
|
|
|
if (!Directory.Exists(bundlePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var scanResult = await SimulateScanAsync(imageTarball, bundlePath);
|
|
|
|
// Policy evaluation should work offline
|
|
var policyPath = Path.Combine(bundlePath, "policies", "default.rego");
|
|
var policyResult = await SimulatePolicyEvaluationAsync(
|
|
scanResult.Verdict,
|
|
policyPath);
|
|
|
|
policyResult.Should().NotBeNull();
|
|
policyResult?.Decision.Should().BeOneOf("allow", "deny", "warn");
|
|
AssertNoNetworkCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task VexApplication_WithOfflineBundle_Works()
|
|
{
|
|
var bundlePath = GetOfflineBundlePath();
|
|
var imageTarball = Path.Combine(bundlePath, "images", "vuln-with-vex.tar");
|
|
|
|
if (!Directory.Exists(bundlePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var scanResult = await SimulateScanAsync(imageTarball, bundlePath);
|
|
|
|
// VEX should be applied from offline bundle
|
|
var vexApplied = scanResult.Verdict?.VexStatements?.Any() ?? false;
|
|
vexApplied.Should().BeTrue("VEX from offline bundle should be applied");
|
|
|
|
AssertNoNetworkCalls();
|
|
}
|
|
|
|
// Simulation methods for testing infrastructure
|
|
private static async Task<ScanResult> SimulateScanAsync(string imagePath, string bundlePath)
|
|
{
|
|
await Task.CompletedTask;
|
|
return new ScanResult
|
|
{
|
|
Success = true,
|
|
Verdict = new Verdict { VexStatements = [] },
|
|
Sbom = new Sbom { Components = ["test-component"] }
|
|
};
|
|
}
|
|
|
|
private static async Task<SignResult> SimulateSignAttestationAsync(Sbom sbom, string keyPath)
|
|
{
|
|
await Task.CompletedTask;
|
|
return new SignResult { Success = true, Attestation = "mock-attestation" };
|
|
}
|
|
|
|
private static async Task<VerifyResult> SimulateVerifyAttestationAsync(string attestation, string trustRoot)
|
|
{
|
|
await Task.CompletedTask;
|
|
return new VerifyResult { Valid = true };
|
|
}
|
|
|
|
private static async Task<PolicyResult> SimulatePolicyEvaluationAsync(Verdict? verdict, string policyPath)
|
|
{
|
|
await Task.CompletedTask;
|
|
return new PolicyResult { Decision = "allow" };
|
|
}
|
|
}
|
|
|
|
// Mock types for testing
|
|
public record ScanResult
|
|
{
|
|
public bool Success { get; init; }
|
|
public Verdict? Verdict { get; init; }
|
|
public Sbom? Sbom { get; init; }
|
|
}
|
|
|
|
public record Verdict
|
|
{
|
|
public IReadOnlyList<string>? VexStatements { get; init; }
|
|
}
|
|
|
|
public record Sbom
|
|
{
|
|
public IReadOnlyList<string> Components { get; init; } = [];
|
|
}
|
|
|
|
public record SignResult
|
|
{
|
|
public bool Success { get; init; }
|
|
public string? Attestation { get; init; }
|
|
}
|
|
|
|
public record VerifyResult
|
|
{
|
|
public bool Valid { get; init; }
|
|
}
|
|
|
|
public record PolicyResult
|
|
{
|
|
public string? Decision { get; init; }
|
|
}
|