update exportcenter_ii and fix Sm2AttestorTests

This commit is contained in:
StellaOps Bot
2025-12-07 22:51:01 +02:00
parent 7c24ed96ee
commit 4b124fb056
3 changed files with 174 additions and 9 deletions

View File

@@ -40,7 +40,7 @@
| 6 | EXPORT-OBS-54-001 | DONE | Depends on EXPORT-OBS-53-001. | Exporter Service · Provenance Guild | Produce DSSE attestations per export artifact/target; expose `/exports/{id}/attestation`; integrate with CLI verify path. |
| 7 | EXPORT-OBS-54-002 | DONE | Depends on EXPORT-OBS-54-001 and PROV-OBS-53-003. | Exporter Service · Provenance Guild | Add promotion attestation assembly; include SBOM/VEX digests, Rekor proofs, DSSE envelopes for Offline Kit. |
| 8 | EXPORT-OBS-55-001 | DONE | Depends on EXPORT-OBS-54-001. | Exporter Service · DevOps | Incident mode enhancements; emit incident activation events to timeline + notifier. |
| 9 | EXPORT-RISK-69-001 | TODO | Schema blockers resolved; AdvisoryAI evidence bundle schema available. | Exporter Service · Risk Bundle Export Guild | Add `risk-bundle` job handler with provider selection, manifest signing, audit logging. |
| 9 | EXPORT-RISK-69-001 | DONE | Schema blockers resolved; AdvisoryAI evidence bundle schema available. | Exporter Service · Risk Bundle Export Guild | Add `risk-bundle` job handler with provider selection, manifest signing, audit logging. |
| 10 | EXPORT-RISK-69-002 | TODO | Depends on EXPORT-RISK-69-001. | Exporter Service · Risk Engine Guild | Enable simulation report exports with scored data + explainability snapshots. |
| 11 | EXPORT-RISK-70-001 | TODO | Depends on EXPORT-RISK-69-002. | Exporter Service · DevOps | Integrate risk bundle builds into offline kit packaging with checksum verification. |
| 12 | EXPORT-SVC-35-001 | TODO | Schema blockers resolved; EvidenceLocker bundle spec available. | Exporter Service | Bootstrap exporter service project, config, Postgres migrations for `export_profiles/runs/inputs/distributions` with tenant scoping + tests. |
@@ -93,6 +93,7 @@
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-07 | **EXPORT-RISK-69-001 DONE:** Implemented risk-bundle job handler with provider selection, manifest signing, and audit logging. Created `RiskBundle/` namespace with: `RiskBundleJobModels.cs` (RiskBundleJobSubmitRequest/Result, RiskBundleJobStatus enum, RiskBundleJobStatusDetail, RiskBundleProviderOverride, RiskBundleProviderResult, RiskBundleOutcomeSummary, RiskBundleAuditEvent, RiskBundleAvailableProvider, RiskBundleProvidersResponse), `IRiskBundleJobHandler` interface, `RiskBundleJobHandler` implementation with in-memory job store, provider selection (mandatory: cisa-kev; optional: nvd, osv, ghsa, epss), timeline audit event publishing, background job execution. Created `RiskBundleEndpoints.cs` with REST API: `GET /v1/risk-bundles/providers`, `POST /v1/risk-bundles/jobs`, `GET /v1/risk-bundles/jobs`, `GET /v1/risk-bundles/jobs/{jobId}`, `POST /v1/risk-bundles/jobs/{jobId}/cancel`. Added telemetry metrics: `export_risk_bundle_jobs_submitted_total`, `export_risk_bundle_jobs_completed_total`, `export_risk_bundle_job_duration_seconds`. Build succeeded with 0 errors. | Implementer |
| 2025-12-07 | **EXPORT-OBS-55-001 DONE:** Implemented incident mode enhancements for ExportCenter. Created `Incident/` namespace with: `ExportIncidentModels.cs` (severity levels Info→Emergency, status Active→Resolved→FalsePositive, types ExportFailure/LatencyDegradation/StorageCapacity/DependencyFailure/IntegrityIssue/SecurityIncident/ConfigurationError/RateLimiting), `ExportIncidentEvents.cs` (IncidentActivated/Updated/Escalated/Deescalated/Resolved events), `IExportIncidentManager` interface and `ExportIncidentManager` implementation with in-memory store. `IExportNotificationEmitter` interface with `LoggingNotificationEmitter` for timeline + notifier integration. Added `PublishIncidentEventAsync` to `IExportTimelinePublisher`. REST endpoints at `/v1/incidents/*`: GET status, GET active, GET recent, GET {id}, POST activate, PATCH {id} update, POST {id}/resolve. Added metrics: `export_incidents_activated_total`, `export_incidents_resolved_total`, `export_incidents_escalated_total`, `export_incidents_deescalated_total`, `export_notifications_emitted_total`, `export_incident_duration_seconds`. | Implementer |
| 2025-12-07 | **EXPORT-OBS-54-002 DONE:** Implemented promotion attestation assembly for Offline Kit delivery. Created `PromotionAttestationModels.cs` with models for SBOM/VEX digest references, Rekor proof entries (with inclusion proofs), DSSE envelope references, promotion predicates. Created `IPromotionAttestationAssembler` interface and `PromotionAttestationAssembler` implementation that: builds in-toto statements with promotion predicates, computes root hash from all artifact digests, signs with DSSE PAE encoding, exports to portable gzipped tar bundles with deterministic timestamps, includes verification scripts. Created `PromotionAttestationEndpoints.cs` with REST endpoints: `POST /v1/promotions/attestations`, `GET /v1/promotions/attestations/{id}`, `GET /v1/promotions/{promotionId}/attestations`, `POST /v1/promotions/attestations/{id}/verify`, `GET /v1/promotions/attestations/{id}/bundle`. Bundle export includes promotion-assembly.json, promotion.dsse.json, rekor-proofs.ndjson, envelopes/, checksums.txt, verify-promotion.sh. | Implementer |
| 2025-12-07 | **EXPORT-OBS-54-001 DONE:** Implemented DSSE attestation service for export artifacts. Created `Attestation/` namespace with `ExportAttestationModels.cs` (DSSE envelope, in-toto statement, predicates, subjects, verification info), `IExportAttestationService` interface, `ExportAttestationService` implementation. Created `IExportAttestationSigner` interface and `ExportAttestationSigner` implementing DSSE PAE (Pre-Authentication Encoding) per spec with ECDSA-P256-SHA256 signing. REST endpoints at `/v1/exports/{id}/attestation` (GET), `/v1/exports/attestations/{attestationId}` (GET), `/v1/exports/{id}/attestation/verify` (POST). Includes base64url encoding, key ID computation, public key PEM export for verification. | Implementer |

View File

@@ -107,9 +107,9 @@ internal static class Sm2TestKeyFactory
var curve = Org.BouncyCastle.Asn1.GM.GMNamedCurves.GetByName("SM2P256V1");
var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed());
var generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator("EC");
generator.Init(new Org.BouncyCastle.Crypto.Generators.ECKeyGenerationParameters(domain, new Org.BouncyCastle.Security.SecureRandom()));
generator.Init(new Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(domain, new Org.BouncyCastle.Security.SecureRandom()));
var pair = generator.GenerateKeyPair();
var privInfo = Org.BouncyCastle.Asn1.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
var privInfo = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
var pem = Convert.ToBase64String(privInfo.GetDerEncoded());
var path = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllText(path, "-----BEGIN PRIVATE KEY-----\n" + pem + "\n-----END PRIVATE KEY-----\n");

View File

@@ -1,9 +1,25 @@
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Text.Json;
namespace MongoDB.Bson
{
public enum BsonType
{
Double,
String,
Document,
Array,
Binary,
ObjectId,
Boolean,
DateTime,
Null,
Int32,
Int64
}
public class BsonValue : IEquatable<BsonValue?>
{
protected object? RawValue;
@@ -13,16 +29,38 @@ namespace MongoDB.Bson
RawValue = value;
}
public bool IsString => RawValue is string;
public bool IsBoolean => RawValue is bool;
public bool IsBsonDocument => RawValue is BsonDocument;
public bool IsBsonArray => RawValue is BsonArray;
public virtual BsonType BsonType => RawValue switch
{
null => BsonType.Null,
BsonDocument => BsonType.Document,
BsonArray => BsonType.Array,
string => BsonType.String,
bool => BsonType.Boolean,
int => BsonType.Int32,
long => BsonType.Int64,
double or float or decimal => BsonType.Double,
DateTime or DateTimeOffset => BsonType.DateTime,
ObjectId => BsonType.ObjectId,
byte[] => BsonType.Binary,
Guid => BsonType.String,
_ => BsonType.String
};
public bool IsString => BsonType == BsonType.String;
public bool IsBoolean => BsonType == BsonType.Boolean;
public bool IsBsonDocument => BsonType == BsonType.Document;
public bool IsBsonArray => BsonType == BsonType.Array;
public bool IsBsonNull => BsonType == BsonType.Null;
public bool IsBsonDateTime => BsonType == BsonType.DateTime;
public bool IsInt32 => BsonType == BsonType.Int32;
public bool IsInt64 => BsonType == BsonType.Int64;
public string AsString => RawValue switch
{
null => string.Empty,
string s => s,
Guid g => g.ToString(),
ObjectId o => o.ToString(),
_ => Convert.ToString(RawValue, CultureInfo.InvariantCulture) ?? string.Empty
};
@@ -44,6 +82,17 @@ namespace MongoDB.Bson
_ => 0
};
public int AsInt32 => ToInt32();
public long AsInt64 => RawValue switch
{
long l => l,
int i => i,
double d => (long)d,
string s when long.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var l) => l,
_ => 0L
};
public Guid AsGuid => RawValue switch
{
Guid g => g,
@@ -58,6 +107,24 @@ namespace MongoDB.Bson
_ => ObjectId.Empty
};
public byte[]? AsByteArray => RawValue as byte[];
public DateTimeOffset AsDateTimeOffset => RawValue switch
{
DateTimeOffset dto => dto.ToUniversalTime(),
DateTime dt => DateTime.SpecifyKind(dt, DateTimeKind.Utc),
string s when DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dto) => dto.ToUniversalTime(),
_ => DateTimeOffset.MinValue
};
public DateTime ToUniversalTime() => RawValue switch
{
DateTime dt => dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime(),
DateTimeOffset dto => dto.UtcDateTime,
string s when DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dto) => dto.UtcDateTime,
_ => DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc)
};
public BsonDocument AsBsonDocument => RawValue as BsonDocument ?? (this as BsonDocument ?? new BsonDocument());
public BsonArray AsBsonArray => RawValue as BsonArray ?? (this as BsonArray ?? new BsonArray());
@@ -69,6 +136,8 @@ namespace MongoDB.Bson
public override bool Equals(object? obj) => obj is BsonValue other && Equals(other);
public override int GetHashCode() => RawValue?.GetHashCode() ?? 0;
public static BsonValue Create(object? value) => BsonDocument.ToBsonValue(value);
public static implicit operator BsonValue(string value) => new(value);
public static implicit operator BsonValue(Guid value) => new(value);
public static implicit operator BsonValue(int value) => new(value);
@@ -76,6 +145,38 @@ namespace MongoDB.Bson
public static implicit operator BsonValue(bool value) => new(value);
public static implicit operator BsonValue(double value) => new(value);
public static implicit operator BsonValue(DateTimeOffset value) => new(value);
public static implicit operator BsonValue(DateTime value) => new(value);
public static implicit operator BsonValue(byte[] value) => new(value);
}
public sealed class BsonNull : BsonValue
{
public static BsonNull Value { get; } = new();
private BsonNull()
: base(null)
{
}
public override BsonType BsonType => BsonType.Null;
}
public sealed class BsonString : BsonValue
{
public BsonString(string value) : base(value)
{
}
}
public sealed class BsonBinaryData : BsonValue
{
public BsonBinaryData(byte[] bytes)
: base(bytes)
{
Bytes = bytes ?? Array.Empty<byte>();
}
public byte[] Bytes { get; }
}
public sealed class BsonDocument : BsonValue, IDictionary<string, BsonValue>
@@ -97,6 +198,27 @@ namespace MongoDB.Bson
}
}
public BsonDocument(IEnumerable<KeyValuePair<string, BsonValue>> values)
: this()
{
foreach (var kvp in values)
{
_values[kvp.Key] = kvp.Value ?? new BsonValue();
}
}
public BsonDocument(string key, BsonValue value)
: this()
{
Add(key, value);
}
public BsonDocument(string key, object? value)
: this()
{
Add(key, value);
}
public int ElementCount => _values.Count;
public BsonValue this[string key]
@@ -117,6 +239,7 @@ namespace MongoDB.Bson
public void Clear() => _values.Clear();
public bool Contains(KeyValuePair<string, BsonValue> item) => _values.Contains(item);
public bool ContainsKey(string key) => _values.ContainsKey(key);
public bool Contains(string key) => ContainsKey(key);
public void CopyTo(KeyValuePair<string, BsonValue>[] array, int arrayIndex) => ((IDictionary<string, BsonValue>)_values).CopyTo(array, arrayIndex);
public IEnumerator<KeyValuePair<string, BsonValue>> GetEnumerator() => _values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
@@ -126,6 +249,9 @@ namespace MongoDB.Bson
public BsonValue GetValue(string key) => _values[key];
public BsonValue GetValue(string key, BsonValue defaultValue)
=> _values.TryGetValue(key, out var value) ? value : defaultValue;
public BsonDocument DeepClone()
{
var copy = new BsonDocument();
@@ -177,7 +303,7 @@ namespace MongoDB.Bson
return array;
}
internal static BsonValue ToBsonValue(object? value)
public static BsonValue ToBsonValue(object? value)
{
return value switch
{
@@ -190,9 +316,11 @@ namespace MongoDB.Bson
bool b => new BsonValue(b),
double d => new BsonValue(d),
float f => new BsonValue(f),
decimal dec => new BsonValue((double)dec),
DateTime dt => new BsonValue(dt),
DateTimeOffset dto => new BsonValue(dto),
IEnumerable<object?> enumerable => new BsonArray(enumerable.Select(ToBsonValue)),
byte[] bytes => new BsonBinaryData(bytes),
IEnumerable enumerable when value is not string => new BsonArray(enumerable.Cast<object?>().Select(ToBsonValue)),
_ => new BsonValue(value)
};
}
@@ -216,6 +344,15 @@ namespace MongoDB.Bson
_items.AddRange(items);
}
public BsonArray(IEnumerable<object?> items)
: this()
{
foreach (var item in items)
{
Add(item);
}
}
public BsonValue this[int index]
{
get => _items[index];
@@ -227,6 +364,13 @@ namespace MongoDB.Bson
public void Add(BsonValue item) => _items.Add(item ?? new BsonValue());
public void Add(object? item) => _items.Add(BsonDocument.ToBsonValue(item));
public void AddRange(IEnumerable<object?> items)
{
foreach (var item in items)
{
Add(item);
}
}
public void Clear() => _items.Clear();
public bool Contains(BsonValue item) => _items.Contains(item);
public void CopyTo(BsonValue[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
@@ -259,6 +403,26 @@ namespace MongoDB.Bson
public override bool Equals(object? obj) => obj is ObjectId other && Equals(other);
public override int GetHashCode() => _value?.GetHashCode(StringComparison.Ordinal) ?? 0;
}
public static class BsonTypeMapper
{
public static object? MapToDotNetValue(BsonValue value)
{
if (value is null) return null;
return value.BsonType switch
{
BsonType.Document => value.AsBsonDocument.ToDictionary(static kvp => kvp.Key, static kvp => MapToDotNetValue(kvp.Value)),
BsonType.Array => value.AsBsonArray.Select(MapToDotNetValue).ToArray(),
BsonType.Null => null,
BsonType.Boolean => value.AsBoolean,
BsonType.Int32 => value.AsInt32,
BsonType.Int64 => value.AsInt64,
BsonType.DateTime => value.ToUniversalTime(),
_ => value.AsString
};
}
}
}
namespace MongoDB.Bson.Serialization.Attributes