license switch agpl -> busl1, sprints work, new product advisories

This commit is contained in:
master
2026-01-20 15:32:20 +02:00
parent 4903395618
commit c32fff8f86
1835 changed files with 38630 additions and 4359 deletions

View File

@@ -1,5 +1,5 @@
// <copyright file="TemporalCacheTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_001_TEST_time_skew_idempotency
// Task: TSKW-010

View File

@@ -1,5 +1,5 @@
// <copyright file="ConcelierConfigDiffTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
// Task: CCUT-020

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraConnectorTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System;

View File

@@ -0,0 +1,549 @@
// -----------------------------------------------------------------------------
// ParsedSbomParserTests.cs
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM parsing tests
// Description: Unit tests for enriched SBOM parsing
// -----------------------------------------------------------------------------
using System.Text;
using System.Linq;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using StellaOps.Concelier.SbomIntegration.Models;
using StellaOps.Concelier.SbomIntegration.Parsing;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.SbomIntegration.Tests;
public sealed class ParsedSbomParserTests
{
private readonly ParsedSbomParser _parser;
public ParsedSbomParserTests()
{
var loggerMock = new Mock<ILogger<ParsedSbomParser>>();
_parser = new ParsedSbomParser(loggerMock.Object);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParseAsync_CycloneDx_ExtractsMetadataComponentsAndServices()
{
var content = """
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:1234",
"metadata": {
"timestamp": "2026-01-20T00:00:00Z",
"component": {
"bom-ref": "app",
"name": "myapp",
"version": "1.0.0"
},
"tools": [
{ "name": "stella-scanner" }
],
"authors": [
{ "name": "dev@example.com" }
],
"supplier": { "name": "Acme" },
"manufacturer": { "name": "AcmeManu" }
},
"components": [
{
"bom-ref": "lib",
"name": "lib",
"version": "2.0.0",
"purl": "pkg:npm/lib@2.0.0",
"scope": "optional",
"modified": true,
"supplier": {
"name": "LibSupplier",
"url": "https://supplier.example.com",
"contact": [
{ "name": "Supplier Contact", "email": "contact@example.com" }
]
},
"manufacturer": {
"name": "LibManufacturer"
},
"evidence": {
"identity": {
"field": "purl",
"confidence": 0.9,
"value": "pkg:npm/lib@2.0.0"
},
"occurrences": [
{
"location": "src/lib.js",
"line": 10,
"offset": 4,
"symbol": "libfn",
"additionalContext": "ctx"
}
],
"callstack": {
"frames": [
{
"package": "pkg",
"module": "mod",
"function": "fn",
"parameters": ["a", "b"],
"line": 20,
"column": 2,
"fullFilename": "/src/lib.js"
}
]
},
"licenses": [
{ "expression": "MIT" }
],
"copyright": [
"Copyright 2026"
]
},
"pedigree": {
"ancestors": [
{ "bom-ref": "ancestor", "version": "0.1", "description": "base" }
],
"variants": [
{ "bom-ref": "variant", "version": "2.0" }
],
"commits": [
{ "uid": "abc123", "message": "fix" }
],
"patches": [
{ "type": "backport", "diff": { "text": "diff", "url": "https://example.com/diff" } }
],
"notes": ["note1", "note2"]
},
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "hash",
"parameterSetIdentifier": "ps1",
"curve": "P-256",
"executionEnvironment": "software",
"certificationLevel": "fips140-2",
"mode": "gcm",
"padding": "pkcs7",
"cryptoFunctions": ["digest"],
"classicalSecurityLevel": 128,
"nistQuantumSecurityLevel": 1,
"keySize": 256
},
"certificateProperties": {
"subjectName": "CN=Test",
"issuerName": "CA",
"notValidBefore": "2026-01-01T00:00:00Z",
"notValidAfter": "2027-01-01T00:00:00Z",
"signatureAlgorithmRef": "sha256",
"subjectPublicKeyRef": "key",
"certificateFormat": "x509",
"certificateExtension": "ext"
},
"protocolProperties": {
"type": "tls",
"version": "1.3",
"cipherSuites": ["TLS_AES_128_GCM_SHA256"],
"ikev2TransformTypes": ["aes"],
"cryptoRefArray": ["ref1"]
},
"relatedCryptoMaterialProperties": {
"type": "key",
"id": "key-1",
"algorithmRef": "alg-1",
"securedBy": ["sec1"],
"relatedCryptographicAssets": [
{ "type": "certificate", "ref": "cert-1" }
]
},
"oid": "1.2.3.4"
},
"modelCard": {
"bom-ref": "model-1",
"modelParameters": {
"task": "classification",
"architectureFamily": "cnn",
"modelArchitecture": "resnet",
"approach": { "type": "supervised" },
"datasets": [
{
"name": "dataset1",
"version": "1.0",
"url": "https://example.com/ds",
"hashes": [
{ "alg": "SHA-256", "content": "abcd" }
]
}
],
"inputs": [
{ "format": "image", "description": "jpg" }
],
"outputs": [
{ "format": "label", "description": "class" }
]
},
"quantitativeAnalysis": {
"performanceMetrics": [
{
"type": "accuracy",
"value": "0.9",
"slice": "overall",
"confidenceInterval": { "lowerBound": "0.88", "upperBound": "0.92" }
}
],
"graphics": {
"collection": [
{ "name": "roc", "image": "data:image/png;base64,aa", "description": "ROC" }
]
}
},
"considerations": {
"users": ["devs"],
"useCases": ["testing"],
"technicalLimitations": ["small dataset"],
"ethicalConsiderations": [
{ "name": "bias", "mitigationStrategy": "review" }
],
"fairnessAssessments": [
{ "groupAtRisk": "group1", "benefits": "benefit", "harms": "harm", "mitigationStrategy": "mit" }
],
"environmentalConsiderations": {
"energyConsumptions": [
{
"activity": "training",
"activityEnergyCost": "10kWh",
"co2CostEquivalent": "5kg",
"co2CostOffset": "1kg",
"properties": [
{ "name": "region", "value": "us" }
],
"energyProviders": [
{
"bom-ref": "prov",
"description": "provider",
"organization": { "name": "EnergyCo" },
"energySource": "solar",
"energyProvided": "5kWh",
"externalReferences": [
{ "type": "website", "url": "https://energy.example.com" }
]
}
]
}
],
"properties": [
{ "name": "note", "value": "env" }
]
}
}
},
"licenses": [
{
"license": {
"id": "MIT",
"name": "MIT License",
"url": "https://example.com/license",
"text": {
"content": "TUlU",
"encoding": "base64"
},
"licensing": {
"licensor": { "name": "Acme" },
"licensee": "Consumer",
"purchaseOrder": "PO-123"
}
}
},
{
"expression": "MIT AND (Apache-2.0 OR BSD-3-Clause)"
}
],
"externalReferences": [
{ "type": "website", "url": "https://example.com/lib", "comment": "home" }
]
}
],
"dependencies": [
{
"ref": "app",
"dependsOn": ["lib"]
}
],
"services": [
{
"bom-ref": "svc",
"name": "api",
"version": "1.0.0",
"authenticated": true,
"x-trust-boundary": true,
"endpoints": ["https://api.example.com"],
"licenses": [
{ "expression": "Apache-2.0" }
],
"externalReferences": [
{ "type": "documentation", "url": "https://example.com/api-docs" }
]
}
],
"formulation": [
{
"bom-ref": "form-1",
"components": [
"lib",
{
"ref": "app",
"properties": [
{ "name": "stage", "value": "build" }
]
}
],
"workflows": [
{
"name": "build",
"description": "build pipeline",
"inputs": ["src"],
"outputs": ["artifact"],
"tasks": [
{
"name": "compile",
"description": "compile sources",
"inputs": ["src"],
"outputs": ["bin"],
"parameters": [
{ "name": "opt", "value": "O2" }
],
"properties": [
{ "name": "runner", "value": "msbuild" }
]
}
],
"properties": [
{ "name": "workflow", "value": "ci" }
]
}
],
"tasks": [
{
"name": "package",
"description": "package app",
"inputs": ["bin"],
"outputs": ["artifact"],
"parameters": [
{ "name": "format", "value": "zip" }
]
}
],
"properties": [
{ "name": "formulation", "value": "v1" }
]
}
]
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
var result = await _parser.ParseAsync(stream, SbomFormat.CycloneDX);
result.Format.Should().Be("cyclonedx");
result.SpecVersion.Should().Be("1.7");
result.SerialNumber.Should().Be("urn:uuid:1234");
result.Metadata.Name.Should().Be("myapp");
result.Metadata.Version.Should().Be("1.0.0");
result.Metadata.Supplier.Should().Be("Acme");
result.Metadata.Manufacturer.Should().Be("AcmeManu");
result.Components.Should().Contain(c => c.BomRef == "app");
result.Components.Should().Contain(c => c.Purl == "pkg:npm/lib@2.0.0");
var lib = result.Components.Single(c => c.BomRef == "lib");
lib.ExternalReferences.Should().ContainSingle(r =>
r.Type == "website" && r.Url == "https://example.com/lib");
lib.Scope.Should().Be(ComponentScope.Optional);
lib.Modified.Should().BeTrue();
lib.Supplier.Should().NotBeNull();
lib.Supplier!.Name.Should().Be("LibSupplier");
lib.Supplier!.Url.Should().Be("https://supplier.example.com");
lib.Manufacturer.Should().NotBeNull();
lib.Manufacturer!.Name.Should().Be("LibManufacturer");
lib.Evidence.Should().NotBeNull();
lib.Evidence!.Identity.Should().NotBeNull();
lib.Evidence!.Identity!.Field.Should().Be("purl");
lib.Evidence!.Identity!.Confidence.Should().Be(0.9);
lib.Evidence!.Occurrences.Should().ContainSingle(o => o.Location == "src/lib.js");
lib.Evidence!.Callstack.Should().NotBeNull();
lib.Evidence!.Callstack!.Frames.Should().ContainSingle(f => f.Function == "fn");
lib.Evidence!.Licenses.Should().ContainSingle(l => l.Expression != null);
lib.Evidence!.Copyrights.Should().ContainSingle(c => c == "Copyright 2026");
lib.Pedigree.Should().NotBeNull();
lib.Pedigree!.Ancestors.Should().ContainSingle(a => a.BomRef == "ancestor");
lib.Pedigree!.Variants.Should().ContainSingle(v => v.BomRef == "variant");
lib.Pedigree!.Commits.Should().ContainSingle(c => c.BomRef == "abc123");
lib.Pedigree!.Patches.Should().ContainSingle(p => p.Type == "backport");
lib.Pedigree!.Notes.Should().Contain(n => n == "note1");
lib.CryptoProperties.Should().NotBeNull();
lib.CryptoProperties!.AssetType.Should().Be(CryptoAssetType.Algorithm);
lib.CryptoProperties!.AlgorithmProperties.Should().NotBeNull();
lib.CryptoProperties!.AlgorithmProperties!.Primitive.Should().Be(CryptoPrimitive.Hash);
lib.CryptoProperties!.CertificateProperties.Should().NotBeNull();
lib.CryptoProperties!.CertificateProperties!.SubjectName.Should().Be("CN=Test");
lib.CryptoProperties!.ProtocolProperties.Should().NotBeNull();
lib.CryptoProperties!.ProtocolProperties!.Type.Should().Be("tls");
lib.CryptoProperties!.RelatedCryptoMaterial.Should().NotBeNull();
lib.CryptoProperties!.RelatedCryptoMaterial!.Reference.Should().Be("key-1");
lib.CryptoProperties!.RelatedCryptoMaterial!.MaterialRefs.Should().Contain("cert-1");
lib.ModelCard.Should().NotBeNull();
lib.ModelCard!.BomRef.Should().Be("model-1");
lib.ModelCard!.ModelParameters.Should().NotBeNull();
lib.ModelCard!.ModelParameters!.Task.Should().Be("classification");
lib.ModelCard!.ModelParameters!.Datasets.Should().ContainSingle(d => d.Name == "dataset1");
lib.ModelCard!.QuantitativeAnalysis.Should().NotBeNull();
lib.ModelCard!.QuantitativeAnalysis!.PerformanceMetrics.Should().ContainSingle(m => m.Type == "accuracy");
lib.ModelCard!.Considerations.Should().NotBeNull();
lib.ModelCard!.Considerations!.Users.Should().ContainSingle(u => u == "devs");
lib.Licenses.Should().HaveCount(2);
lib.Licenses[0].SpdxId.Should().Be("MIT");
lib.Licenses[0].Name.Should().Be("MIT License");
lib.Licenses[0].Url.Should().Be("https://example.com/license");
lib.Licenses[0].Text.Should().Be("MIT");
lib.Licenses[0].Licensing.Should().NotBeNull();
lib.Licenses[0].Licensing!.Licensor.Should().Be("Acme");
lib.Licenses[0].Licensing!.Licensee.Should().Be("Consumer");
lib.Licenses[0].Licensing!.PurchaseOrder.Should().Be("PO-123");
lib.Licenses[1].Expression.Should().BeOfType<ConjunctiveSet>();
var andExpr = (ConjunctiveSet)lib.Licenses[1].Expression!;
andExpr.Members.Should().HaveCount(2);
andExpr.Members[0].Should().BeOfType<SimpleLicense>()
.Which.Id.Should().Be("MIT");
andExpr.Members[1].Should().BeOfType<DisjunctiveSet>();
var orExpr = (DisjunctiveSet)andExpr.Members[1];
orExpr.Members.OfType<SimpleLicense>()
.Should()
.ContainSingle(license => license.Id == "Apache-2.0");
result.Dependencies.Should().ContainSingle(d => d.SourceRef == "app");
result.Services.Should().ContainSingle(s => s.Name == "api");
result.Services[0].Authenticated.Should().BeTrue();
result.Services[0].CrossesTrustBoundary.Should().BeTrue();
result.Services[0].Licenses.Should().ContainSingle();
result.Services[0].ExternalReferences.Should().ContainSingle(r =>
r.Type == "documentation" && r.Url == "https://example.com/api-docs");
result.Formulation.Should().NotBeNull();
result.Formulation!.BomRef.Should().Be("form-1");
result.Formulation.Components.Should().HaveCount(2);
result.Formulation.Components.Should().ContainSingle(c => c.BomRef == "lib");
result.Formulation.Components.Should().ContainSingle(c =>
c.BomRef == "app" && c.Properties.ContainsKey("stage"));
result.Formulation.Workflows.Should().ContainSingle(w => w.Name == "build");
var workflow = result.Formulation.Workflows.Single(w => w.Name == "build");
workflow.InputRefs.Should().Contain("src");
workflow.OutputRefs.Should().Contain("artifact");
workflow.Tasks.Should().ContainSingle(t => t.Name == "compile");
workflow.Tasks[0].Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
workflow.Tasks[0].Properties.Should().ContainKey("runner").WhoseValue.Should().Be("msbuild");
result.Formulation.Tasks.Should().ContainSingle(t => t.Name == "package");
result.Formulation.Tasks[0].Parameters.Should().ContainKey("format").WhoseValue.Should().Be("zip");
result.Formulation.Properties.Should().ContainKey("formulation").WhoseValue.Should().Be("v1");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParseAsync_Spdx3_ExtractsDocumentAndPackageMetadata()
{
var content = """
{
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"@type": "SpdxDocument",
"spdxId": "urn:doc",
"name": "sbom-doc",
"creationInfo": {
"specVersion": "3.0.1",
"created": "2026-01-20T00:00:00Z",
"createdBy": ["org:Acme"],
"createdUsing": ["tool:stella"],
"profile": ["https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType/core"]
},
"rootElement": ["spdx:pkg:root"],
"namespaceMap": [
{ "prefix": "ex", "namespace": "https://example.com" }
],
"import": [
{ "externalSpdxId": "urn:ext" }
]
},
{
"@type": "build_Build",
"spdxId": "spdx:build:1",
"buildId": "build-123",
"buildType": "release",
"buildStartTime": "2026-01-20T01:00:00Z",
"buildEndTime": "2026-01-20T01:30:00Z",
"configSourceEntrypoint": "build.yaml",
"configSourceDigest": "sha256:abcd",
"configSourceUri": "https://example.com/build.yaml",
"environment": [
{ "name": "OS", "value": "linux" }
],
"parameters": {
"opt": "O2",
"threads": 8
}
},
{
"@type": "software_Package",
"spdxId": "spdx:pkg:root",
"name": "root",
"software_packageVersion": "1.0.0",
"packageUrl": "pkg:npm/root@1.0.0",
"simplelicensing_licenseExpression": "Apache-2.0 WITH LLVM-exception",
"verifiedUsing": [
{ "algorithm": "SHA256", "hashValue": "abc" }
],
"externalRef": [
{ "externalRefType": "purl", "locator": "pkg:npm/root@1.0.0", "comment": "source" }
],
"externalIdentifier": [
{ "externalIdentifierType": "cpe23", "identifier": "cpe:2.3:a:root" }
]
}
]
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
var result = await _parser.ParseAsync(stream, SbomFormat.SPDX);
result.Format.Should().Be("spdx");
result.SpecVersion.Should().Be("3.0.1");
result.SerialNumber.Should().Be("urn:doc");
result.Metadata.Name.Should().Be("sbom-doc");
result.Metadata.RootComponentRef.Should().Be("spdx:pkg:root");
result.Metadata.NamespaceMap.Should().ContainSingle(map => map.Prefix == "ex");
result.BuildInfo.Should().NotBeNull();
result.BuildInfo!.BuildId.Should().Be("build-123");
result.BuildInfo.BuildType.Should().Be("release");
result.BuildInfo.BuildStartTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:00:00Z"));
result.BuildInfo.BuildEndTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:30:00Z"));
result.BuildInfo.ConfigSourceEntrypoint.Should().Be("build.yaml");
result.BuildInfo.ConfigSourceDigest.Should().Be("sha256:abcd");
result.BuildInfo.ConfigSourceUri.Should().Be("https://example.com/build.yaml");
result.BuildInfo.Environment.Should().ContainKey("OS").WhoseValue.Should().Be("linux");
result.BuildInfo.Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
result.BuildInfo.Parameters.Should().ContainKey("threads").WhoseValue.Should().Be("8");
var root = result.Components.Single(c => c.Purl == "pkg:npm/root@1.0.0");
root.Cpe.Should().Be("cpe:2.3:a:root");
root.Hashes.Should().ContainSingle(h => h.Algorithm == "SHA256" && h.Value == "abc");
root.ExternalReferences.Should().ContainSingle(r =>
r.Type == "purl" && r.Url == "pkg:npm/root@1.0.0");
root.Licenses.Should().ContainSingle();
root.Licenses[0].Expression.Should().BeOfType<WithException>();
var withExpr = (WithException)root.Licenses[0].Expression!;
withExpr.Exception.Should().Be("LLVM-exception");
withExpr.License.Should().BeOfType<SimpleLicense>()
.Which.Id.Should().Be("Apache-2.0");
}
}

View File

@@ -1,5 +1,5 @@
// <copyright file="ConcelierSchemaEvolutionTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
// Task: CCUT-010