Files
git.stella-ops.org/src/StellaOps.Excititor.Core/VexClaim.cs
master 5ce40d2eeb feat: Initialize Zastava Webhook service with TLS and Authority authentication
- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
2025-10-19 18:36:22 +03:00

331 lines
9.1 KiB
C#

using System.Collections.Immutable;
using System.Runtime.Serialization;
namespace StellaOps.Excititor.Core;
public sealed record VexClaim
{
public VexClaim(
string vulnerabilityId,
string providerId,
VexProduct product,
VexClaimStatus status,
VexClaimDocument document,
DateTimeOffset firstSeen,
DateTimeOffset lastSeen,
VexJustification? justification = null,
string? detail = null,
VexConfidence? confidence = null,
VexSignalSnapshot? signals = null,
ImmutableDictionary<string, string>? additionalMetadata = null)
{
if (string.IsNullOrWhiteSpace(vulnerabilityId))
{
throw new ArgumentException("Vulnerability id must be provided.", nameof(vulnerabilityId));
}
if (string.IsNullOrWhiteSpace(providerId))
{
throw new ArgumentException("Provider id must be provided.", nameof(providerId));
}
if (lastSeen < firstSeen)
{
throw new ArgumentOutOfRangeException(nameof(lastSeen), "Last seen timestamp cannot be earlier than first seen.");
}
VulnerabilityId = vulnerabilityId.Trim();
ProviderId = providerId.Trim();
Product = product ?? throw new ArgumentNullException(nameof(product));
Status = status;
Document = document ?? throw new ArgumentNullException(nameof(document));
FirstSeen = firstSeen;
LastSeen = lastSeen;
Justification = justification;
Detail = string.IsNullOrWhiteSpace(detail) ? null : detail.Trim();
Confidence = confidence;
Signals = signals;
AdditionalMetadata = NormalizeMetadata(additionalMetadata);
}
public string VulnerabilityId { get; }
public string ProviderId { get; }
public VexProduct Product { get; }
public VexClaimStatus Status { get; }
public VexJustification? Justification { get; }
public string? Detail { get; }
public VexClaimDocument Document { get; }
public DateTimeOffset FirstSeen { get; }
public DateTimeOffset LastSeen { get; }
public VexConfidence? Confidence { get; }
public VexSignalSnapshot? Signals { get; }
public ImmutableSortedDictionary<string, string> AdditionalMetadata { get; }
private static ImmutableSortedDictionary<string, string> NormalizeMetadata(
ImmutableDictionary<string, string>? additionalMetadata)
{
if (additionalMetadata is null || additionalMetadata.Count == 0)
{
return ImmutableSortedDictionary<string, string>.Empty;
}
var builder = ImmutableSortedDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
foreach (var (key, value) in additionalMetadata)
{
if (string.IsNullOrWhiteSpace(key))
{
continue;
}
builder[key.Trim()] = value?.Trim() ?? string.Empty;
}
return builder.ToImmutable();
}
}
public sealed record VexProduct
{
public VexProduct(
string key,
string? name,
string? version = null,
string? purl = null,
string? cpe = null,
IEnumerable<string>? componentIdentifiers = null)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException("Product key must be provided.", nameof(key));
}
Key = key.Trim();
Name = string.IsNullOrWhiteSpace(name) ? null : name.Trim();
Version = string.IsNullOrWhiteSpace(version) ? null : version.Trim();
Purl = string.IsNullOrWhiteSpace(purl) ? null : purl.Trim();
Cpe = string.IsNullOrWhiteSpace(cpe) ? null : cpe.Trim();
ComponentIdentifiers = NormalizeComponentIdentifiers(componentIdentifiers);
}
public string Key { get; }
public string? Name { get; }
public string? Version { get; }
public string? Purl { get; }
public string? Cpe { get; }
public ImmutableArray<string> ComponentIdentifiers { get; }
private static ImmutableArray<string> NormalizeComponentIdentifiers(IEnumerable<string>? identifiers)
{
if (identifiers is null)
{
return ImmutableArray<string>.Empty;
}
var set = new SortedSet<string>(StringComparer.Ordinal);
foreach (var identifier in identifiers)
{
if (string.IsNullOrWhiteSpace(identifier))
{
continue;
}
set.Add(identifier.Trim());
}
return set.Count == 0 ? ImmutableArray<string>.Empty : set.ToImmutableArray();
}
}
public sealed record VexClaimDocument
{
public VexClaimDocument(
VexDocumentFormat format,
string digest,
Uri sourceUri,
string? revision = null,
VexSignatureMetadata? signature = null)
{
if (string.IsNullOrWhiteSpace(digest))
{
throw new ArgumentException("Document digest must be provided.", nameof(digest));
}
Format = format;
Digest = digest.Trim();
SourceUri = sourceUri ?? throw new ArgumentNullException(nameof(sourceUri));
Revision = string.IsNullOrWhiteSpace(revision) ? null : revision.Trim();
Signature = signature;
}
public VexDocumentFormat Format { get; }
public string Digest { get; }
public Uri SourceUri { get; }
public string? Revision { get; }
public VexSignatureMetadata? Signature { get; }
}
public sealed record VexSignatureMetadata
{
public VexSignatureMetadata(
string type,
string? subject = null,
string? issuer = null,
string? keyId = null,
DateTimeOffset? verifiedAt = null,
string? transparencyLogReference = null)
{
if (string.IsNullOrWhiteSpace(type))
{
throw new ArgumentException("Signature type must be provided.", nameof(type));
}
Type = type.Trim();
Subject = string.IsNullOrWhiteSpace(subject) ? null : subject.Trim();
Issuer = string.IsNullOrWhiteSpace(issuer) ? null : issuer.Trim();
KeyId = string.IsNullOrWhiteSpace(keyId) ? null : keyId.Trim();
VerifiedAt = verifiedAt;
TransparencyLogReference = string.IsNullOrWhiteSpace(transparencyLogReference)
? null
: transparencyLogReference.Trim();
}
public string Type { get; }
public string? Subject { get; }
public string? Issuer { get; }
public string? KeyId { get; }
public DateTimeOffset? VerifiedAt { get; }
public string? TransparencyLogReference { get; }
}
public sealed record VexConfidence
{
public VexConfidence(string level, double? score = null, string? method = null)
{
if (string.IsNullOrWhiteSpace(level))
{
throw new ArgumentException("Confidence level must be provided.", nameof(level));
}
if (score is not null && (double.IsNaN(score.Value) || double.IsInfinity(score.Value)))
{
throw new ArgumentOutOfRangeException(nameof(score), "Confidence score must be a finite number.");
}
Level = level.Trim();
Score = score;
Method = string.IsNullOrWhiteSpace(method) ? null : method.Trim();
}
public string Level { get; }
public double? Score { get; }
public string? Method { get; }
}
[DataContract]
public enum VexDocumentFormat
{
[EnumMember(Value = "csaf")]
Csaf,
[EnumMember(Value = "cyclonedx")]
CycloneDx,
[EnumMember(Value = "openvex")]
OpenVex,
[EnumMember(Value = "oci_attestation")]
OciAttestation,
}
[DataContract]
public enum VexClaimStatus
{
[EnumMember(Value = "affected")]
Affected,
[EnumMember(Value = "not_affected")]
NotAffected,
[EnumMember(Value = "fixed")]
Fixed,
[EnumMember(Value = "under_investigation")]
UnderInvestigation,
}
[DataContract]
public enum VexJustification
{
[EnumMember(Value = "component_not_present")]
ComponentNotPresent,
[EnumMember(Value = "component_not_configured")]
ComponentNotConfigured,
[EnumMember(Value = "vulnerable_code_not_present")]
VulnerableCodeNotPresent,
[EnumMember(Value = "vulnerable_code_not_in_execute_path")]
VulnerableCodeNotInExecutePath,
[EnumMember(Value = "vulnerable_code_cannot_be_controlled_by_adversary")]
VulnerableCodeCannotBeControlledByAdversary,
[EnumMember(Value = "inline_mitigations_already_exist")]
InlineMitigationsAlreadyExist,
[EnumMember(Value = "protected_by_mitigating_control")]
ProtectedByMitigatingControl,
[EnumMember(Value = "code_not_present")]
CodeNotPresent,
[EnumMember(Value = "code_not_reachable")]
CodeNotReachable,
[EnumMember(Value = "requires_configuration")]
RequiresConfiguration,
[EnumMember(Value = "requires_dependency")]
RequiresDependency,
[EnumMember(Value = "requires_environment")]
RequiresEnvironment,
[EnumMember(Value = "protected_by_compensating_control")]
ProtectedByCompensatingControl,
[EnumMember(Value = "protected_at_perimeter")]
ProtectedAtPerimeter,
[EnumMember(Value = "protected_at_runtime")]
ProtectedAtRuntime,
}