Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,292 @@
using System.Text.Json;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Attestor.Core.Validation;
using Xunit;
namespace StellaOps.Attestor.Core.Tests.Validation;
public sealed class PredicateSchemaValidatorTests
{
private readonly PredicateSchemaValidator _validator;
public PredicateSchemaValidatorTests()
{
_validator = new PredicateSchemaValidator(NullLogger<PredicateSchemaValidator>.Instance);
}
[Fact]
public void Validate_ValidSbomPredicate_ReturnsValid()
{
var json = """
{
"format": "spdx-3.0.1",
"digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"componentCount": 42,
"uri": "https://example.com/sbom.json",
"tooling": "syft",
"createdAt": "2025-12-22T00:00:00Z"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/sbom@v1", predicate);
Assert.True(result.IsValid);
Assert.Null(result.ErrorMessage);
}
[Fact]
public void Validate_ValidVexPredicate_ReturnsValid()
{
var json = """
{
"format": "openvex",
"statements": [
{
"vulnerability": "CVE-2024-12345",
"status": "not_affected",
"justification": "Component not used",
"products": ["pkg:npm/lodash@4.17.21"]
}
],
"digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"author": "security@example.com",
"timestamp": "2025-12-22T00:00:00Z"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/vex@v1", predicate);
Assert.True(result.IsValid);
}
[Fact]
public void Validate_ValidReachabilityPredicate_ReturnsValid()
{
var json = """
{
"result": "unreachable",
"confidence": 0.95,
"graphDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"paths": [],
"entrypoints": [
{
"type": "http",
"route": "/api/users",
"auth": "required"
}
],
"computedAt": "2025-12-22T00:00:00Z",
"expiresAt": "2025-12-29T00:00:00Z"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/reachability@v1", predicate);
Assert.True(result.IsValid);
}
[Fact]
public void Validate_ValidPolicyDecisionPredicate_ReturnsValid()
{
var json = """
{
"finding_id": "CVE-2024-12345@pkg:npm/lodash@4.17.20",
"cve": "CVE-2024-12345",
"component_purl": "pkg:npm/lodash@4.17.20",
"decision": "Block",
"reasoning": {
"rules_evaluated": 5,
"rules_matched": ["high-severity", "reachable"],
"final_score": 85.5,
"risk_multiplier": 1.2,
"reachability_state": "reachable",
"vex_status": "affected",
"summary": "High severity vulnerability is reachable"
},
"evidence_refs": [
"sha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"
],
"evaluated_at": "2025-12-22T00:00:00Z",
"expires_at": "2025-12-23T00:00:00Z",
"policy_version": "1.0.0",
"policy_hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/policy-decision@v1", predicate);
Assert.True(result.IsValid);
}
[Fact]
public void Validate_ValidHumanApprovalPredicate_ReturnsValid()
{
var json = """
{
"schema": "human-approval-v1",
"approval_id": "approval-123",
"finding_id": "CVE-2024-12345",
"decision": "AcceptRisk",
"approver": {
"user_id": "alice@example.com",
"display_name": "Alice Smith",
"role": "Security Engineer"
},
"justification": "Risk accepted for legacy system scheduled for decommission in 30 days",
"approved_at": "2025-12-22T00:00:00Z",
"expires_at": "2026-01-22T00:00:00Z"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/human-approval@v1", predicate);
Assert.True(result.IsValid);
}
[Fact]
public void Validate_InvalidVexStatus_ReturnsFail()
{
var json = """
{
"format": "openvex",
"statements": [
{
"vulnerability": "CVE-2024-12345",
"status": "invalid_status",
"products": []
}
],
"digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/vex@v1", predicate);
Assert.False(result.IsValid);
Assert.NotNull(result.ErrorMessage);
}
[Fact]
public void Validate_MissingRequiredField_ReturnsFail()
{
var json = """
{
"format": "spdx-3.0.1",
"componentCount": 42
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/sbom@v1", predicate);
Assert.False(result.IsValid);
Assert.Contains("digest", result.ErrorMessage ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Validate_UnknownPredicateType_ReturnsSkip()
{
var json = """
{
"someField": "someValue"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/unknown@v1", predicate);
Assert.True(result.IsValid);
Assert.Contains("skip", result.ErrorMessage ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Validate_InvalidDigestFormat_ReturnsFail()
{
var json = """
{
"format": "spdx-3.0.1",
"digest": "invalid-digest-format",
"componentCount": 42
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/sbom@v1", predicate);
Assert.False(result.IsValid);
Assert.NotEmpty(result.Errors);
}
[Fact]
public void Validate_NormalizePredicateType_HandlesWithAndWithoutPrefix()
{
var json = """
{
"format": "spdx-3.0.1",
"digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"componentCount": 42
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result1 = _validator.Validate("stella.ops/sbom@v1", predicate);
var result2 = _validator.Validate("sbom@v1", predicate);
Assert.True(result1.IsValid);
Assert.True(result2.IsValid);
}
[Fact]
public void Validate_ValidBoundaryPredicate_ReturnsValid()
{
var json = """
{
"surface": "http",
"exposure": "public",
"observedAt": "2025-12-22T00:00:00Z",
"endpoints": [
{
"route": "/api/users/:id",
"method": "GET",
"auth": "required"
}
],
"auth": {
"mechanism": "jwt",
"required_scopes": ["read:users"]
},
"controls": ["rate-limit", "WAF"],
"expiresAt": "2025-12-25T00:00:00Z"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/boundary@v1", predicate);
Assert.True(result.IsValid);
}
[Fact]
public void Validate_InvalidReachabilityConfidence_ReturnsFail()
{
var json = """
{
"result": "reachable",
"confidence": 1.5,
"graphDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
""";
var predicate = JsonDocument.Parse(json).RootElement;
var result = _validator.Validate("stella.ops/reachability@v1", predicate);
Assert.False(result.IsValid);
}
}

View File

@@ -0,0 +1,176 @@
using System.Text.Json;
using Json.Schema;
using Microsoft.Extensions.Logging;
namespace StellaOps.Attestor.Core.Validation;
/// <summary>
/// Validation result for predicate schema validation.
/// </summary>
public sealed record ValidationResult
{
public required bool IsValid { get; init; }
public required string? ErrorMessage { get; init; }
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
public static ValidationResult Valid() => new()
{
IsValid = true,
ErrorMessage = null
};
public static ValidationResult Invalid(string message, IReadOnlyList<string>? errors = null) => new()
{
IsValid = false,
ErrorMessage = message,
Errors = errors ?? Array.Empty<string>()
};
public static ValidationResult Skip(string reason) => new()
{
IsValid = true,
ErrorMessage = $"Skipped: {reason}"
};
}
/// <summary>
/// Interface for validating attestation predicates against JSON schemas.
/// </summary>
public interface IPredicateSchemaValidator
{
/// <summary>
/// Validates a predicate against its JSON schema.
/// </summary>
/// <param name="predicateType">The predicate type URI (e.g., "stella.ops/sbom@v1").</param>
/// <param name="predicate">The predicate JSON element to validate.</param>
/// <returns>Validation result.</returns>
ValidationResult Validate(string predicateType, JsonElement predicate);
}
/// <summary>
/// Validates attestation predicates against their JSON schemas.
/// </summary>
public sealed class PredicateSchemaValidator : IPredicateSchemaValidator
{
private readonly IReadOnlyDictionary<string, JsonSchema> _schemas;
private readonly ILogger<PredicateSchemaValidator> _logger;
public PredicateSchemaValidator(ILogger<PredicateSchemaValidator> logger)
{
_logger = logger;
_schemas = LoadSchemas();
}
public ValidationResult Validate(string predicateType, JsonElement predicate)
{
// Normalize predicate type (handle both with and without stella.ops/ prefix)
var normalizedType = NormalizePredicateType(predicateType);
if (!_schemas.TryGetValue(normalizedType, out var schema))
{
_logger.LogDebug("No schema found for predicate type {PredicateType}, skipping validation", predicateType);
return ValidationResult.Skip($"No schema for {predicateType}");
}
try
{
var results = schema.Evaluate(predicate, new EvaluationOptions
{
OutputFormat = OutputFormat.List
});
if (results.IsValid)
{
_logger.LogDebug("Predicate {PredicateType} validated successfully", predicateType);
return ValidationResult.Valid();
}
var errors = CollectErrors(results);
_logger.LogWarning("Predicate {PredicateType} validation failed: {ErrorCount} errors",
predicateType, errors.Count);
return ValidationResult.Invalid(
$"Schema validation failed for {predicateType}",
errors);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating predicate {PredicateType}", predicateType);
return ValidationResult.Invalid($"Validation error: {ex.Message}");
}
}
private static string NormalizePredicateType(string predicateType)
{
// Handle both "stella.ops/sbom@v1" and "sbom@v1" formats
if (predicateType.StartsWith("stella.ops/", StringComparison.OrdinalIgnoreCase))
{
return predicateType["stella.ops/".Length..];
}
return predicateType;
}
private static IReadOnlyList<string> CollectErrors(EvaluationResults results)
{
var errors = new List<string>();
if (results.HasErrors)
{
foreach (var detail in results.Details)
{
if (detail.HasErrors)
{
var errorMsg = detail.Errors?.FirstOrDefault()?.Value ?? "Unknown error";
var location = detail.InstanceLocation.ToString();
errors.Add($"{location}: {errorMsg}");
}
}
}
return errors;
}
private static IReadOnlyDictionary<string, JsonSchema> LoadSchemas()
{
var schemas = new Dictionary<string, JsonSchema>(StringComparer.OrdinalIgnoreCase);
// Load embedded schema resources
var assembly = typeof(PredicateSchemaValidator).Assembly;
var resourcePrefix = "StellaOps.Attestor.Core.Schemas.";
var schemaFiles = new[]
{
("sbom@v1", "sbom.v1.schema.json"),
("vex@v1", "vex.v1.schema.json"),
("reachability@v1", "reachability.v1.schema.json"),
("boundary@v1", "boundary.v1.schema.json"),
("policy-decision@v1", "policy-decision.v1.schema.json"),
("human-approval@v1", "human-approval.v1.schema.json")
};
foreach (var (key, fileName) in schemaFiles)
{
var resourceName = resourcePrefix + fileName;
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null)
{
// Schema not embedded, skip gracefully
continue;
}
try
{
var schema = JsonSchema.FromStream(stream);
schemas[key] = schema;
}
catch (Exception ex)
{
// Log and continue - don't fail on single schema load error
Console.WriteLine($"Failed to load schema {fileName}: {ex.Message}");
}
}
return schemas;
}
}