Files
git.stella-ops.org/src/StellaOps.Vexer.Storage.Mongo/VexMongoModels.cs

605 lines
19 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);
}
}
[BsonIgnoreExtraElements]
internal sealed class VexConnectorStateDocument
{
[BsonId]
public string ConnectorId { get; set; } = default!;
public DateTime? LastUpdated { get; set; }
= null;
public List<string> DocumentDigests { get; set; } = new();
public static VexConnectorStateDocument FromRecord(VexConnectorState state)
=> new()
{
ConnectorId = state.ConnectorId,
LastUpdated = state.LastUpdated?.UtcDateTime,
DocumentDigests = state.DocumentDigests.ToList(),
};
public VexConnectorState ToRecord()
{
var lastUpdated = LastUpdated.HasValue
? new DateTimeOffset(DateTime.SpecifyKind(LastUpdated.Value, DateTimeKind.Utc))
: (DateTimeOffset?)null;
return new VexConnectorState(
ConnectorId,
lastUpdated,
DocumentDigests.ToImmutableArray());
}
}