up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -0,0 +1,173 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Provenance;
// Minimal stubs for document serialization without external dependencies.
public abstract class DocumentValue
{
public virtual object? Value => null;
public virtual DocumentObject AsDocumentObject =>
this as DocumentObject ?? throw new InvalidCastException("Value is not a DocumentObject.");
public virtual DocumentArray AsDocumentArray =>
this as DocumentArray ?? throw new InvalidCastException("Value is not a DocumentArray.");
public virtual string AsString => Value?.ToString() ?? string.Empty;
public virtual int AsInt32 => Convert.ToInt32(Value);
public virtual long AsInt64 => Convert.ToInt64(Value);
public virtual double AsDouble => Convert.ToDouble(Value);
public virtual bool AsBoolean => Convert.ToBoolean(Value);
internal static DocumentValue Wrap(object? value) =>
value switch
{
null => DocumentNull.Value,
DocumentValue doc => doc,
string s => new DocumentString(s),
bool b => new DocumentBoolean(b),
int i => new DocumentInt32(i),
long l => new DocumentInt64(l),
double d => new DocumentDouble(d),
IEnumerable<DocumentValue> docEnumerable => new DocumentArray(docEnumerable),
IEnumerable enumerable => new DocumentArray(enumerable.Cast<object?>()),
_ => new DocumentRaw(value)
};
}
public sealed class DocumentNull : DocumentValue
{
public static readonly DocumentNull ValueInstance = new();
public new static DocumentNull Value => ValueInstance;
}
public sealed class DocumentString : DocumentValue
{
public DocumentString(string value) => Value = value;
public override object? Value { get; }
public override string ToString() => Value?.ToString() ?? string.Empty;
}
public sealed class DocumentBoolean : DocumentValue
{
public DocumentBoolean(bool value) => Value = value;
public override object? Value { get; }
}
public sealed class DocumentInt32 : DocumentValue
{
public DocumentInt32(int value) => Value = value;
public override object? Value { get; }
}
public sealed class DocumentInt64 : DocumentValue
{
public DocumentInt64(long value) => Value = value;
public override object? Value { get; }
}
public sealed class DocumentDouble : DocumentValue
{
public DocumentDouble(double value) => Value = value;
public override object? Value { get; }
}
public sealed class DocumentRaw : DocumentValue
{
public DocumentRaw(object value) => Value = value;
public override object? Value { get; }
}
public record struct DocumentElement(string Name, DocumentValue Value);
public class DocumentObject : DocumentValue, IEnumerable<KeyValuePair<string, DocumentValue>>
{
private readonly Dictionary<string, object?> _values = new(StringComparer.Ordinal);
public DocumentObject()
{
}
public DocumentObject(string key, object? value)
{
_values[key] = value;
}
public DocumentValue this[string key]
{
get => DocumentValue.Wrap(_values[key]);
set => _values[key] = value;
}
public void Add(string key, object? value) => _values.Add(key, value);
public IEnumerable<DocumentElement> Elements => _values.Select(kvp => new DocumentElement(kvp.Key, DocumentValue.Wrap(kvp.Value)));
public bool Contains(string key) => ContainsKey(key);
public bool ContainsKey(string key) => _values.ContainsKey(key);
public override object? Value => this;
public override DocumentObject AsDocumentObject => this;
public IEnumerator<KeyValuePair<string, DocumentValue>> GetEnumerator() =>
_values.Select(kvp => new KeyValuePair<string, DocumentValue>(kvp.Key, DocumentValue.Wrap(kvp.Value))).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class DocumentArray : DocumentValue, IEnumerable<DocumentValue>
{
private readonly List<object?> _items = new();
public DocumentArray()
{
}
public DocumentArray(IEnumerable items)
{
foreach (var item in items)
{
Add(item);
}
}
public DocumentArray(IEnumerable<object?> items)
: this()
{
foreach (var item in items)
{
Add(item);
}
}
public DocumentValue this[int index] => DocumentValue.Wrap(_items[index]);
public void Add(DocumentObject doc) => _items.Add(doc);
public void Add(object? value) => _items.Add(value);
public int Count => _items.Count;
public override object? Value => this;
public override DocumentArray AsDocumentArray => this;
public IEnumerator<DocumentValue> GetEnumerator() => _items.Select(DocumentValue.Wrap).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public static class DocumentValueExtensions
{
public static DocumentObject AsDocumentObject(this object? value) => DocumentValue.Wrap(value).AsDocumentObject;
public static DocumentArray AsDocumentArray(this object? value) => DocumentValue.Wrap(value).AsDocumentArray;
public static string AsString(this object? value) => DocumentValue.Wrap(value).AsString;
public static int AsInt32(this object? value) => DocumentValue.Wrap(value).AsInt32;
public static long AsInt64(this object? value) => DocumentValue.Wrap(value).AsInt64;
public static double AsDouble(this object? value) => DocumentValue.Wrap(value).AsDouble;
public static bool AsBoolean(this object? value) => DocumentValue.Wrap(value).AsBoolean;
}

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
namespace StellaOps.Provenance;
public sealed class DsseKeyInfo
{
public string KeyId { get; set; } = default!; // e.g. "cosign:SHA256-PKIX:..."
public string? Issuer { get; set; } // e.g. Fulcio issuer, KMS URI, X.509 CN
public string? Algo { get; set; } // "ECDSA" | "RSA" | "Ed25519" | "Dilithium"
}
public sealed class DsseRekorInfo
{
public long LogIndex { get; set; } // Rekor log index
public string Uuid { get; set; } = default!; // Rekor entry UUID
public long? IntegratedTime { get; set; } // unix timestamp (seconds)
public long? MirrorSeq { get; set; } // optional mirror sequence in Proof-Market ledger
}
public sealed class DsseChainLink
{
public string Type { get; set; } = default!; // e.g. "build" | "sbom" | "scan"
public string Id { get; set; } = default!; // e.g. "att:build#..."
public string Digest { get; set; } = default!; // sha256 of DSSE envelope or payload
}
public sealed class DsseProvenance
{
public string EnvelopeDigest { get; set; } = default!; // sha256 of envelope (not payload)
public string PayloadType { get; set; } = default!; // "application/vnd.in-toto+json"
public DsseKeyInfo Key { get; set; } = new();
public DsseRekorInfo? Rekor { get; set; }
public IReadOnlyCollection<DsseChainLink>? Chain { get; set; }
}
public sealed class TrustInfo
{
public bool Verified { get; set; } // local cryptographic verification
public string? Verifier { get; set; } // e.g. "Authority@stella"
public int? Witnesses { get; set; } // number of verified transparency witnesses
public double? PolicyScore { get; set; } // lattice / policy score (0..1)
}

View File

@@ -0,0 +1,140 @@
namespace StellaOps.Provenance;
public static class ProvenanceExtensions
{
private const string ProvenanceFieldName = "provenance";
private const string DsseFieldName = "dsse";
private const string TrustFieldName = "trust";
private const string ChainFieldName = "chain";
private static DocumentValue StringOrNull(string? value) =>
value is null ? DocumentNull.Value : new DocumentString(value);
/// <summary>
/// Attach DSSE provenance + trust info to an event document in-place.
/// Designed for generic DocumentObject-based event envelopes.
/// </summary>
public static DocumentObject AttachDsseProvenance(
this DocumentObject eventDoc,
DsseProvenance dsse,
TrustInfo trust)
{
if (eventDoc is null) throw new ArgumentNullException(nameof(eventDoc));
if (dsse is null) throw new ArgumentNullException(nameof(dsse));
if (trust is null) throw new ArgumentNullException(nameof(trust));
var dsseDoc = new DocumentObject
{
{ "envelopeDigest", dsse.EnvelopeDigest },
{ "payloadType", dsse.PayloadType },
{ "key", new DocumentObject
{
{ "keyId", dsse.Key.KeyId },
{ "issuer", StringOrNull(dsse.Key.Issuer) },
{ "algo", StringOrNull(dsse.Key.Algo) }
}
}
};
if (dsse.Rekor is not null)
{
var rekorDoc = new DocumentObject
{
{ "logIndex", dsse.Rekor.LogIndex },
{ "uuid", dsse.Rekor.Uuid }
};
if (dsse.Rekor.IntegratedTime is not null)
rekorDoc.Add("integratedTime", dsse.Rekor.IntegratedTime);
if (dsse.Rekor.MirrorSeq is not null)
rekorDoc.Add("mirrorSeq", dsse.Rekor.MirrorSeq);
dsseDoc.Add("rekor", rekorDoc);
}
if (dsse.Chain is not null && dsse.Chain.Count > 0)
{
var chainArray = new DocumentArray();
foreach (var link in dsse.Chain)
{
chainArray.Add(new DocumentObject
{
{ "type", link.Type },
{ "id", link.Id },
{ "digest", link.Digest }
});
}
dsseDoc.Add(ChainFieldName, chainArray);
}
var trustDoc = new DocumentObject
{
{ "verified", trust.Verified },
{ "verifier", StringOrNull(trust.Verifier) }
};
if (trust.Witnesses is not null)
trustDoc.Add("witnesses", trust.Witnesses);
if (trust.PolicyScore is not null)
trustDoc.Add("policyScore", trust.PolicyScore);
var provenanceDoc = new DocumentObject
{
{ DsseFieldName, dsseDoc }
};
eventDoc[ProvenanceFieldName] = provenanceDoc;
eventDoc[TrustFieldName] = trustDoc;
return eventDoc;
}
/// <summary>
/// Helper to query for "cryptographically proven" events:
/// kind + subject.digest.sha256 + presence of Rekor logIndex + trust.verified = true.
/// </summary>
public static DocumentObject BuildProvenVexFilter(
string kind,
string subjectDigestSha256)
{
return new DocumentObject
{
{ "kind", kind },
{ "subject.digest.sha256", subjectDigestSha256 },
{ $"{ProvenanceFieldName}.{DsseFieldName}.rekor.logIndex", new DocumentObject("$exists", true) },
{ $"{TrustFieldName}.verified", true }
};
}
/// <summary>
/// Helper to query for events influencing policy without solid provenance.
/// </summary>
public static DocumentObject BuildUnprovenEvidenceFilter(
IEnumerable<string> kinds)
{
var kindsArray = new DocumentArray(kinds);
return new DocumentObject
{
{
"kind", new DocumentObject("$in", kindsArray)
},
{
"$or", new DocumentArray
{
new DocumentObject
{
{ $"{TrustFieldName}.verified", new DocumentObject("$ne", true) }
},
new DocumentObject
{
{ $"{ProvenanceFieldName}.{DsseFieldName}.rekor.logIndex",
new DocumentObject("$exists", false) }
}
}
}
};
}
}

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Provenance;
public static class ProvenanceJsonParser
{
public static (DsseProvenance Dsse, TrustInfo Trust) Parse(JsonElement root, TrustInfo? trustOverride = null)
{
var dsse = ParseDsse(root);
var trust = trustOverride ?? ParseTrust(root) ?? throw new InvalidOperationException("Provenance metadata missing trust block.");
return (dsse, trust);
}
public static (DsseProvenance Dsse, TrustInfo Trust) Parse(string json, TrustInfo? trustOverride = null)
{
using var document = JsonDocument.Parse(json);
return Parse(document.RootElement, trustOverride);
}
public static async Task<(DsseProvenance Dsse, TrustInfo Trust)> ParseAsync(
Stream utf8JsonStream,
TrustInfo? trustOverride = null,
CancellationToken cancellationToken = default)
{
var document = await JsonDocument.ParseAsync(utf8JsonStream, cancellationToken: cancellationToken).ConfigureAwait(false);
using (document)
{
return Parse(document.RootElement, trustOverride);
}
}
private static DsseProvenance ParseDsse(JsonElement root)
{
if (!root.TryGetProperty("dsse", out var dsseElement) || dsseElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("Provenance metadata missing dsse block.");
}
var keyElement = GetRequiredProperty(dsseElement, "key");
var dsse = new DsseProvenance
{
EnvelopeDigest = GetRequiredString(dsseElement, "envelopeDigest"),
PayloadType = GetRequiredString(dsseElement, "payloadType"),
Key = new DsseKeyInfo
{
KeyId = GetRequiredString(keyElement, "keyId"),
Issuer = GetOptionalString(keyElement, "issuer"),
Algo = GetOptionalString(keyElement, "algo"),
},
Chain = ParseChain(dsseElement)
};
if (dsseElement.TryGetProperty("rekor", out var rekorElement) && rekorElement.ValueKind == JsonValueKind.Object)
{
dsse.Rekor = new DsseRekorInfo
{
LogIndex = GetInt64(rekorElement, "logIndex"),
Uuid = GetRequiredString(rekorElement, "uuid"),
IntegratedTime = GetOptionalInt64(rekorElement, "integratedTime"),
MirrorSeq = GetOptionalInt64(rekorElement, "mirrorSeq")
};
}
return dsse;
}
private static IReadOnlyCollection<DsseChainLink>? ParseChain(JsonElement dsseElement)
{
if (!dsseElement.TryGetProperty("chain", out var chainElement) || chainElement.ValueKind != JsonValueKind.Array || chainElement.GetArrayLength() == 0)
{
return null;
}
var links = new List<DsseChainLink>(chainElement.GetArrayLength());
foreach (var entry in chainElement.EnumerateArray())
{
if (entry.ValueKind != JsonValueKind.Object)
{
continue;
}
var type = GetOptionalString(entry, "type");
var id = GetOptionalString(entry, "id");
var digest = GetOptionalString(entry, "digest");
if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(id) || string.IsNullOrEmpty(digest))
{
continue;
}
links.Add(new DsseChainLink
{
Type = type,
Id = id,
Digest = digest
});
}
return links.Count == 0 ? null : links;
}
private static TrustInfo? ParseTrust(JsonElement root)
{
if (!root.TryGetProperty("trust", out var trustElement) || trustElement.ValueKind != JsonValueKind.Object)
{
return null;
}
var trust = new TrustInfo
{
Verified = trustElement.TryGetProperty("verified", out var verified) && verified.ValueKind == JsonValueKind.True,
Verifier = GetOptionalString(trustElement, "verifier"),
Witnesses = trustElement.TryGetProperty("witnesses", out var witnessesElement) && witnessesElement.TryGetInt32(out var witnesses)
? witnesses
: null,
PolicyScore = trustElement.TryGetProperty("policyScore", out var scoreElement) && scoreElement.TryGetDouble(out var score)
? score
: null
};
return trust;
}
private static JsonElement GetRequiredProperty(JsonElement parent, string name)
{
if (!parent.TryGetProperty(name, out var property) || property.ValueKind == JsonValueKind.Null)
{
throw new InvalidOperationException($"Provenance metadata missing required property {name}.");
}
return property;
}
private static string GetRequiredString(JsonElement parent, string name)
{
var element = GetRequiredProperty(parent, name);
if (element.ValueKind is JsonValueKind.String)
{
var value = element.GetString();
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
}
throw new InvalidOperationException($"Provenance metadata property {name} must be a non-empty string.");
}
private static string? GetOptionalString(JsonElement parent, string name)
{
if (!parent.TryGetProperty(name, out var element))
{
return null;
}
return element.ValueKind == JsonValueKind.String ? element.GetString() : null;
}
private static long GetInt64(JsonElement parent, string name)
{
if (!parent.TryGetProperty(name, out var element))
{
throw new InvalidOperationException($"Provenance metadata missing {name}.");
}
if (element.TryGetInt64(out var value))
{
return value;
}
if (element.ValueKind == JsonValueKind.String && long.TryParse(element.GetString(), out value))
{
return value;
}
throw new InvalidOperationException($"Provenance metadata property {name} must be an integer.");
}
private static long? GetOptionalInt64(JsonElement parent, string name)
{
if (!parent.TryGetProperty(name, out var element))
{
return null;
}
if (element.TryGetInt64(out var value))
{
return value;
}
if (element.ValueKind == JsonValueKind.String && long.TryParse(element.GetString(), out value))
{
return value;
}
return null;
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>