573 lines
18 KiB
C#
573 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization.Attributes;
|
|
using StellaOps.Vexer.Core;
|
|
|
|
namespace StellaOps.Vexer.Storage.Mongo;
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexRawDocumentRecord
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = default!;
|
|
|
|
public string ProviderId { get; set; } = default!;
|
|
|
|
public string Format { get; set; } = default!;
|
|
|
|
public string SourceUri { get; set; } = default!;
|
|
|
|
public DateTime RetrievedAt { get; set; }
|
|
= DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
|
|
|
public string Digest { get; set; } = default!;
|
|
|
|
public byte[] Content { get; set; } = Array.Empty<byte>();
|
|
|
|
[BsonRepresentation(BsonType.ObjectId)]
|
|
public string? GridFsObjectId { get; set; }
|
|
= null;
|
|
|
|
public Dictionary<string, string> Metadata { get; set; } = new(StringComparer.Ordinal);
|
|
|
|
public static VexRawDocumentRecord FromDomain(VexRawDocument document, bool includeContent = true)
|
|
=> new()
|
|
{
|
|
Id = document.Digest,
|
|
ProviderId = document.ProviderId,
|
|
Format = document.Format.ToString().ToLowerInvariant(),
|
|
SourceUri = document.SourceUri.ToString(),
|
|
RetrievedAt = document.RetrievedAt.UtcDateTime,
|
|
Digest = document.Digest,
|
|
Content = includeContent ? document.Content.ToArray() : Array.Empty<byte>(),
|
|
Metadata = document.Metadata.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal),
|
|
};
|
|
|
|
public VexRawDocument ToDomain()
|
|
=> ToDomain(new ReadOnlyMemory<byte>(Content ?? Array.Empty<byte>()));
|
|
|
|
public VexRawDocument ToDomain(ReadOnlyMemory<byte> content)
|
|
=> new(
|
|
ProviderId,
|
|
Enum.Parse<VexDocumentFormat>(Format, ignoreCase: true),
|
|
new Uri(SourceUri),
|
|
RetrievedAt,
|
|
Digest,
|
|
content,
|
|
(Metadata ?? new Dictionary<string, string>(StringComparer.Ordinal))
|
|
.ToImmutableDictionary(StringComparer.Ordinal));
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexExportManifestRecord
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = default!;
|
|
|
|
public string QuerySignature { get; set; } = default!;
|
|
|
|
public string Format { get; set; } = default!;
|
|
|
|
public DateTime CreatedAt { get; set; }
|
|
= DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
|
|
|
public string ArtifactAlgorithm { get; set; } = default!;
|
|
|
|
public string ArtifactDigest { get; set; } = default!;
|
|
|
|
public int ClaimCount { get; set; }
|
|
= 0;
|
|
|
|
public bool FromCache { get; set; }
|
|
= false;
|
|
|
|
public List<string> SourceProviders { get; set; } = new();
|
|
|
|
public string? ConsensusRevision { get; set; }
|
|
= null;
|
|
|
|
public string? PredicateType { get; set; }
|
|
= null;
|
|
|
|
public string? RekorApiVersion { get; set; }
|
|
= null;
|
|
|
|
public string? RekorLocation { get; set; }
|
|
= null;
|
|
|
|
public string? RekorLogIndex { get; set; }
|
|
= null;
|
|
|
|
public string? RekorInclusionProofUri { get; set; }
|
|
= null;
|
|
|
|
public string? EnvelopeDigest { get; set; }
|
|
= null;
|
|
|
|
public DateTime? SignedAt { get; set; }
|
|
= null;
|
|
|
|
public long SizeBytes { get; set; }
|
|
= 0;
|
|
|
|
public static VexExportManifestRecord FromDomain(VexExportManifest manifest)
|
|
=> new()
|
|
{
|
|
Id = CreateId(manifest.QuerySignature, manifest.Format),
|
|
QuerySignature = manifest.QuerySignature.Value,
|
|
Format = manifest.Format.ToString().ToLowerInvariant(),
|
|
CreatedAt = manifest.CreatedAt.UtcDateTime,
|
|
ArtifactAlgorithm = manifest.Artifact.Algorithm,
|
|
ArtifactDigest = manifest.Artifact.Digest,
|
|
ClaimCount = manifest.ClaimCount,
|
|
FromCache = manifest.FromCache,
|
|
SourceProviders = manifest.SourceProviders.ToList(),
|
|
ConsensusRevision = manifest.ConsensusRevision,
|
|
PredicateType = manifest.Attestation?.PredicateType,
|
|
RekorApiVersion = manifest.Attestation?.Rekor?.ApiVersion,
|
|
RekorLocation = manifest.Attestation?.Rekor?.Location,
|
|
RekorLogIndex = manifest.Attestation?.Rekor?.LogIndex,
|
|
RekorInclusionProofUri = manifest.Attestation?.Rekor?.InclusionProofUri?.ToString(),
|
|
EnvelopeDigest = manifest.Attestation?.EnvelopeDigest,
|
|
SignedAt = manifest.Attestation?.SignedAt?.UtcDateTime,
|
|
SizeBytes = manifest.SizeBytes,
|
|
};
|
|
|
|
public VexExportManifest ToDomain()
|
|
{
|
|
var signedAt = SignedAt.HasValue
|
|
? new DateTimeOffset(DateTime.SpecifyKind(SignedAt.Value, DateTimeKind.Utc))
|
|
: (DateTimeOffset?)null;
|
|
|
|
var attestation = PredicateType is null
|
|
? null
|
|
: new VexAttestationMetadata(
|
|
PredicateType,
|
|
RekorApiVersion is null || RekorLocation is null
|
|
? null
|
|
: new VexRekorReference(
|
|
RekorApiVersion,
|
|
RekorLocation,
|
|
RekorLogIndex,
|
|
RekorInclusionProofUri is null ? null : new Uri(RekorInclusionProofUri)),
|
|
EnvelopeDigest,
|
|
signedAt);
|
|
|
|
return new VexExportManifest(
|
|
Id,
|
|
new VexQuerySignature(QuerySignature),
|
|
Enum.Parse<VexExportFormat>(Format, ignoreCase: true),
|
|
CreatedAt,
|
|
new VexContentAddress(ArtifactAlgorithm, ArtifactDigest),
|
|
ClaimCount,
|
|
SourceProviders,
|
|
FromCache,
|
|
ConsensusRevision,
|
|
attestation,
|
|
SizeBytes);
|
|
}
|
|
|
|
public static string CreateId(VexQuerySignature signature, VexExportFormat format)
|
|
=> string.Format(CultureInfo.InvariantCulture, "{0}|{1}", signature.Value, format.ToString().ToLowerInvariant());
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexProviderRecord
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = default!;
|
|
|
|
public string DisplayName { get; set; } = default!;
|
|
|
|
public string Kind { get; set; } = default!;
|
|
|
|
public List<string> BaseUris { get; set; } = new();
|
|
|
|
public VexProviderDiscoveryDocument? Discovery { get; set; }
|
|
= null;
|
|
|
|
public VexProviderTrustDocument? Trust { get; set; }
|
|
= null;
|
|
|
|
public bool Enabled { get; set; }
|
|
= true;
|
|
|
|
public static VexProviderRecord FromDomain(VexProvider provider)
|
|
=> new()
|
|
{
|
|
Id = provider.Id,
|
|
DisplayName = provider.DisplayName,
|
|
Kind = provider.Kind.ToString().ToLowerInvariant(),
|
|
BaseUris = provider.BaseUris.Select(uri => uri.ToString()).ToList(),
|
|
Discovery = VexProviderDiscoveryDocument.FromDomain(provider.Discovery),
|
|
Trust = VexProviderTrustDocument.FromDomain(provider.Trust),
|
|
Enabled = provider.Enabled,
|
|
};
|
|
|
|
public VexProvider ToDomain()
|
|
{
|
|
var uris = BaseUris?.Select(uri => new Uri(uri)) ?? Enumerable.Empty<Uri>();
|
|
return new VexProvider(
|
|
Id,
|
|
DisplayName,
|
|
Enum.Parse<VexProviderKind>(Kind, ignoreCase: true),
|
|
uris,
|
|
Discovery?.ToDomain(),
|
|
Trust?.ToDomain(),
|
|
Enabled);
|
|
}
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexProviderDiscoveryDocument
|
|
{
|
|
public string? WellKnownMetadata { get; set; }
|
|
= null;
|
|
|
|
public string? RolIeService { get; set; }
|
|
= null;
|
|
|
|
public static VexProviderDiscoveryDocument? FromDomain(VexProviderDiscovery? discovery)
|
|
=> discovery is null
|
|
? null
|
|
: new VexProviderDiscoveryDocument
|
|
{
|
|
WellKnownMetadata = discovery.WellKnownMetadata?.ToString(),
|
|
RolIeService = discovery.RolIeService?.ToString(),
|
|
};
|
|
|
|
public VexProviderDiscovery ToDomain()
|
|
=> new(
|
|
WellKnownMetadata is null ? null : new Uri(WellKnownMetadata),
|
|
RolIeService is null ? null : new Uri(RolIeService));
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexProviderTrustDocument
|
|
{
|
|
public double Weight { get; set; }
|
|
= 1.0;
|
|
|
|
public VexCosignTrustDocument? Cosign { get; set; }
|
|
= null;
|
|
|
|
public List<string> PgpFingerprints { get; set; } = new();
|
|
|
|
public static VexProviderTrustDocument? FromDomain(VexProviderTrust? trust)
|
|
=> trust is null
|
|
? null
|
|
: new VexProviderTrustDocument
|
|
{
|
|
Weight = trust.Weight,
|
|
Cosign = trust.Cosign is null ? null : VexCosignTrustDocument.FromDomain(trust.Cosign),
|
|
PgpFingerprints = trust.PgpFingerprints.ToList(),
|
|
};
|
|
|
|
public VexProviderTrust ToDomain()
|
|
=> new(
|
|
Weight,
|
|
Cosign?.ToDomain(),
|
|
PgpFingerprints);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexCosignTrustDocument
|
|
{
|
|
public string Issuer { get; set; } = default!;
|
|
|
|
public string IdentityPattern { get; set; } = default!;
|
|
|
|
public static VexCosignTrustDocument FromDomain(VexCosignTrust trust)
|
|
=> new()
|
|
{
|
|
Issuer = trust.Issuer,
|
|
IdentityPattern = trust.IdentityPattern,
|
|
};
|
|
|
|
public VexCosignTrust ToDomain()
|
|
=> new(Issuer, IdentityPattern);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexConsensusRecord
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = default!;
|
|
|
|
public string VulnerabilityId { get; set; } = default!;
|
|
|
|
public VexProductDocument Product { get; set; } = default!;
|
|
|
|
public string Status { get; set; } = default!;
|
|
|
|
public DateTime CalculatedAt { get; set; }
|
|
= DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
|
|
|
public List<VexConsensusSourceDocument> Sources { get; set; } = new();
|
|
|
|
public List<VexConsensusConflictDocument> Conflicts { get; set; } = new();
|
|
|
|
public string? PolicyVersion { get; set; }
|
|
= null;
|
|
|
|
public string? PolicyRevisionId { get; set; }
|
|
= null;
|
|
|
|
public string? PolicyDigest { get; set; }
|
|
= null;
|
|
|
|
public string? Summary { get; set; }
|
|
= null;
|
|
|
|
public static string CreateId(string vulnerabilityId, string productKey)
|
|
=> string.Format(CultureInfo.InvariantCulture, "{0}|{1}", vulnerabilityId.Trim(), productKey.Trim());
|
|
|
|
public static VexConsensusRecord FromDomain(VexConsensus consensus)
|
|
=> new()
|
|
{
|
|
Id = CreateId(consensus.VulnerabilityId, consensus.Product.Key),
|
|
VulnerabilityId = consensus.VulnerabilityId,
|
|
Product = VexProductDocument.FromDomain(consensus.Product),
|
|
Status = consensus.Status.ToString().ToLowerInvariant(),
|
|
CalculatedAt = consensus.CalculatedAt.UtcDateTime,
|
|
Sources = consensus.Sources.Select(VexConsensusSourceDocument.FromDomain).ToList(),
|
|
Conflicts = consensus.Conflicts.Select(VexConsensusConflictDocument.FromDomain).ToList(),
|
|
PolicyVersion = consensus.PolicyVersion,
|
|
PolicyRevisionId = consensus.PolicyRevisionId,
|
|
PolicyDigest = consensus.PolicyDigest,
|
|
Summary = consensus.Summary,
|
|
};
|
|
|
|
public VexConsensus ToDomain()
|
|
=> new(
|
|
VulnerabilityId,
|
|
Product.ToDomain(),
|
|
Enum.Parse<VexConsensusStatus>(Status, ignoreCase: true),
|
|
new DateTimeOffset(CalculatedAt, TimeSpan.Zero),
|
|
Sources.Select(static source => source.ToDomain()),
|
|
Conflicts.Select(static conflict => conflict.ToDomain()),
|
|
PolicyVersion,
|
|
Summary,
|
|
PolicyRevisionId,
|
|
PolicyDigest);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexProductDocument
|
|
{
|
|
public string Key { get; set; } = default!;
|
|
|
|
public string? Name { get; set; }
|
|
= null;
|
|
|
|
public string? Version { get; set; }
|
|
= null;
|
|
|
|
public string? Purl { get; set; }
|
|
= null;
|
|
|
|
public string? Cpe { get; set; }
|
|
= null;
|
|
|
|
public List<string> ComponentIdentifiers { get; set; } = new();
|
|
|
|
public static VexProductDocument FromDomain(VexProduct product)
|
|
=> new()
|
|
{
|
|
Key = product.Key,
|
|
Name = product.Name,
|
|
Version = product.Version,
|
|
Purl = product.Purl,
|
|
Cpe = product.Cpe,
|
|
ComponentIdentifiers = product.ComponentIdentifiers.ToList(),
|
|
};
|
|
|
|
public VexProduct ToDomain()
|
|
=> new(
|
|
Key,
|
|
Name,
|
|
Version,
|
|
Purl,
|
|
Cpe,
|
|
ComponentIdentifiers);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexConsensusSourceDocument
|
|
{
|
|
public string ProviderId { get; set; } = default!;
|
|
|
|
public string Status { get; set; } = default!;
|
|
|
|
public string DocumentDigest { get; set; } = default!;
|
|
|
|
public double Weight { get; set; }
|
|
= 0;
|
|
|
|
public string? Justification { get; set; }
|
|
= null;
|
|
|
|
public string? Detail { get; set; }
|
|
= null;
|
|
|
|
public VexConfidenceDocument? Confidence { get; set; }
|
|
= null;
|
|
|
|
public static VexConsensusSourceDocument FromDomain(VexConsensusSource source)
|
|
=> new()
|
|
{
|
|
ProviderId = source.ProviderId,
|
|
Status = source.Status.ToString().ToLowerInvariant(),
|
|
DocumentDigest = source.DocumentDigest,
|
|
Weight = source.Weight,
|
|
Justification = source.Justification?.ToString().ToLowerInvariant(),
|
|
Detail = source.Detail,
|
|
Confidence = source.Confidence is null ? null : VexConfidenceDocument.FromDomain(source.Confidence),
|
|
};
|
|
|
|
public VexConsensusSource ToDomain()
|
|
=> new(
|
|
ProviderId,
|
|
Enum.Parse<VexClaimStatus>(Status, ignoreCase: true),
|
|
DocumentDigest,
|
|
Weight,
|
|
string.IsNullOrWhiteSpace(Justification) ? null : Enum.Parse<VexJustification>(Justification, ignoreCase: true),
|
|
Detail,
|
|
Confidence?.ToDomain());
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexConsensusConflictDocument
|
|
{
|
|
public string ProviderId { get; set; } = default!;
|
|
|
|
public string Status { get; set; } = default!;
|
|
|
|
public string DocumentDigest { get; set; } = default!;
|
|
|
|
public string? Justification { get; set; }
|
|
= null;
|
|
|
|
public string? Detail { get; set; }
|
|
= null;
|
|
|
|
public string? Reason { get; set; }
|
|
= null;
|
|
|
|
public static VexConsensusConflictDocument FromDomain(VexConsensusConflict conflict)
|
|
=> new()
|
|
{
|
|
ProviderId = conflict.ProviderId,
|
|
Status = conflict.Status.ToString().ToLowerInvariant(),
|
|
DocumentDigest = conflict.DocumentDigest,
|
|
Justification = conflict.Justification?.ToString().ToLowerInvariant(),
|
|
Detail = conflict.Detail,
|
|
Reason = conflict.Reason,
|
|
};
|
|
|
|
public VexConsensusConflict ToDomain()
|
|
=> new(
|
|
ProviderId,
|
|
Enum.Parse<VexClaimStatus>(Status, ignoreCase: true),
|
|
DocumentDigest,
|
|
string.IsNullOrWhiteSpace(Justification) ? null : Enum.Parse<VexJustification>(Justification, ignoreCase: true),
|
|
Detail,
|
|
Reason);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexConfidenceDocument
|
|
{
|
|
public string Level { get; set; } = default!;
|
|
|
|
public double? Score { get; set; }
|
|
= null;
|
|
|
|
public string? Method { get; set; }
|
|
= null;
|
|
|
|
public static VexConfidenceDocument FromDomain(VexConfidence confidence)
|
|
=> new()
|
|
{
|
|
Level = confidence.Level,
|
|
Score = confidence.Score,
|
|
Method = confidence.Method,
|
|
};
|
|
|
|
public VexConfidence ToDomain()
|
|
=> new(Level, Score, Method);
|
|
}
|
|
|
|
[BsonIgnoreExtraElements]
|
|
internal sealed class VexCacheEntryRecord
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = default!;
|
|
|
|
public string QuerySignature { get; set; } = default!;
|
|
|
|
public string Format { get; set; } = default!;
|
|
|
|
public string ArtifactAlgorithm { get; set; } = default!;
|
|
|
|
public string ArtifactDigest { get; set; } = default!;
|
|
|
|
public DateTime CreatedAt { get; set; }
|
|
= DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
|
|
|
public long SizeBytes { get; set; }
|
|
= 0;
|
|
|
|
public string? ManifestId { get; set; }
|
|
= null;
|
|
|
|
[BsonRepresentation(BsonType.ObjectId)]
|
|
public string? GridFsObjectId { get; set; }
|
|
= null;
|
|
|
|
public DateTime? ExpiresAt { get; set; }
|
|
= null;
|
|
|
|
public static string CreateId(VexQuerySignature signature, VexExportFormat format)
|
|
=> string.Format(CultureInfo.InvariantCulture, "{0}|{1}", signature.Value, format.ToString().ToLowerInvariant());
|
|
|
|
public static VexCacheEntryRecord FromDomain(VexCacheEntry entry)
|
|
=> new()
|
|
{
|
|
Id = CreateId(entry.QuerySignature, entry.Format),
|
|
QuerySignature = entry.QuerySignature.Value,
|
|
Format = entry.Format.ToString().ToLowerInvariant(),
|
|
ArtifactAlgorithm = entry.Artifact.Algorithm,
|
|
ArtifactDigest = entry.Artifact.Digest,
|
|
CreatedAt = entry.CreatedAt.UtcDateTime,
|
|
SizeBytes = entry.SizeBytes,
|
|
ManifestId = entry.ManifestId,
|
|
GridFsObjectId = entry.GridFsObjectId,
|
|
ExpiresAt = entry.ExpiresAt?.UtcDateTime,
|
|
};
|
|
|
|
public VexCacheEntry ToDomain()
|
|
{
|
|
var signature = new VexQuerySignature(QuerySignature);
|
|
var artifact = new VexContentAddress(ArtifactAlgorithm, ArtifactDigest);
|
|
var createdAt = new DateTimeOffset(DateTime.SpecifyKind(CreatedAt, DateTimeKind.Utc));
|
|
var expires = ExpiresAt.HasValue
|
|
? new DateTimeOffset(DateTime.SpecifyKind(ExpiresAt.Value, DateTimeKind.Utc))
|
|
: (DateTimeOffset?)null;
|
|
|
|
return new VexCacheEntry(
|
|
signature,
|
|
Enum.Parse<VexExportFormat>(Format, ignoreCase: true),
|
|
artifact,
|
|
createdAt,
|
|
SizeBytes,
|
|
ManifestId,
|
|
GridFsObjectId,
|
|
expires);
|
|
}
|
|
}
|