Rename Concelier Source modules to Connector

This commit is contained in:
2025-10-18 20:11:18 +03:00
parent 0137856fdb
commit 6524626230
789 changed files with 1489 additions and 1489 deletions

View File

@@ -0,0 +1,2 @@
"suse-su-2025_0001-1.json","2025-01-21T10:00:00Z"
"suse-su-2025_0002-1.json","2025-01-22T08:30:00Z"
1 suse-su-2025_0001-1.json 2025-01-21T10:00:00Z
2 suse-su-2025_0002-1.json 2025-01-22T08:30:00Z

View File

@@ -0,0 +1,63 @@
{
"document": {
"title": "openssl - security update",
"tracking": {
"id": "SUSE-SU-2025:0001-1",
"initial_release_date": "2025-01-21T00:00:00Z",
"current_release_date": "2025-01-21T00:00:00Z"
},
"references": [
{
"category": "self",
"summary": "SUSE notice",
"url": "https://www.suse.com/security/cve/CVE-2025-0001/"
}
],
"notes": [
{
"category": "summary",
"text": "Security update for openssl"
}
]
},
"product_tree": {
"branches": [
{
"category": "vendor",
"name": "SUSE",
"branches": [
{
"category": "product_family",
"name": "SUSE Linux Enterprise Server 15 SP5",
"branches": [
{
"category": "architecture",
"name": "x86_64",
"branches": [
{
"category": "product_version",
"name": "openssl-1.1.1w-150500.17.25.1.x86_64",
"product": {
"name": "openssl-1.1.1w-150500.17.25.1.x86_64",
"product_id": "SUSE Linux Enterprise Server 15 SP5:openssl-1.1.1w-150500.17.25.1.x86_64"
}
}
]
}
]
}
]
}
]
},
"vulnerabilities": [
{
"cve": "CVE-2025-0001",
"product_status": {
"recommended": [
"SUSE Linux Enterprise Server 15 SP5:openssl-1.1.1w-150500.17.25.1.x86_64"
]
}
}
]
}

View File

@@ -0,0 +1,66 @@
{
"document": {
"title": "postgresql - investigation update",
"tracking": {
"id": "SUSE-SU-2025:0002-1",
"initial_release_date": "2025-01-22T00:00:00Z",
"current_release_date": "2025-01-22T00:00:00Z"
},
"references": [
{
"category": "external",
"summary": "Upstream CVE",
"url": "https://www.postgresql.org/support/security/CVE-2025-0002/"
}
],
"notes": [
{
"category": "summary",
"text": "Investigation ongoing for postgresql security issue."
}
]
},
"product_tree": {
"branches": [
{
"category": "vendor",
"name": "SUSE",
"branches": [
{
"category": "product_family",
"name": "openSUSE Tumbleweed",
"branches": [
{
"category": "architecture",
"name": "x86_64",
"branches": [
{
"category": "product_version",
"name": "postgresql16-16.3-2.1.x86_64",
"product": {
"name": "postgresql16-16.3-2.1.x86_64",
"product_id": "openSUSE Tumbleweed:postgresql16-16.3-2.1.x86_64"
}
}
]
}
]
}
]
}
]
},
"vulnerabilities": [
{
"cve": "CVE-2025-0002",
"product_status": {
"known_affected": [
"openSUSE Tumbleweed:postgresql16-16.3-2.1.x86_64"
],
"under_investigation": [
"openSUSE Tumbleweed:postgresql16-16.3-2.1.x86_64"
]
}
}
]
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Concelier.Connector.Distro.Suse/StellaOps.Concelier.Connector.Distro.Suse.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Source\Distro\Suse\Fixtures\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,168 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Distro.Suse;
using StellaOps.Concelier.Connector.Distro.Suse.Configuration;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using StellaOps.Concelier.Testing;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Concelier.Connector.Distro.Suse.Tests;
[Collection("mongo-fixture")]
public sealed class SuseConnectorTests : IAsyncLifetime
{
private static readonly Uri ChangesUri = new("https://ftp.suse.com/pub/projects/security/csaf/changes.csv");
private static readonly Uri AdvisoryResolvedUri = new("https://ftp.suse.com/pub/projects/security/csaf/suse-su-2025_0001-1.json");
private static readonly Uri AdvisoryOpenUri = new("https://ftp.suse.com/pub/projects/security/csaf/suse-su-2025_0002-1.json");
private readonly MongoIntegrationFixture _fixture;
private readonly FakeTimeProvider _timeProvider;
private readonly CannedHttpMessageHandler _handler;
public SuseConnectorTests(MongoIntegrationFixture fixture, ITestOutputHelper output)
{
_fixture = fixture;
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 22, 0, 0, 0, TimeSpan.Zero));
_handler = new CannedHttpMessageHandler();
}
[Fact]
public async Task FetchParseMap_ProcessesResolvedAndOpenNotices()
{
await using var provider = await BuildServiceProviderAsync();
SeedInitialResponses();
var connector = provider.GetRequiredService<SuseConnector>();
await connector.FetchAsync(provider, CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
await connector.ParseAsync(provider, CancellationToken.None);
await connector.MapAsync(provider, CancellationToken.None);
var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
Assert.Equal(2, advisories.Count);
var resolved = advisories.Single(a => a.AdvisoryKey == "SUSE-SU-2025:0001-1");
var resolvedPackage = Assert.Single(resolved.AffectedPackages);
var resolvedRange = Assert.Single(resolvedPackage.VersionRanges);
Assert.Equal("nevra", resolvedRange.RangeKind);
Assert.NotNull(resolvedRange.Primitives);
Assert.NotNull(resolvedRange.Primitives!.Nevra?.Fixed);
var open = advisories.Single(a => a.AdvisoryKey == "SUSE-SU-2025:0002-1");
var openPackage = Assert.Single(open.AffectedPackages);
Assert.Equal(AffectedPackageStatusCatalog.UnderInvestigation, openPackage.Statuses.Single().Status);
SeedNotModifiedResponses();
await connector.FetchAsync(provider, CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
await connector.ParseAsync(provider, CancellationToken.None);
await connector.MapAsync(provider, CancellationToken.None);
advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
Assert.Equal(2, advisories.Count);
_handler.AssertNoPendingResponses();
}
private async Task<ServiceProvider> BuildServiceProviderAsync()
{
await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName);
_handler.Clear();
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
services.AddSingleton<TimeProvider>(_timeProvider);
services.AddSingleton(_handler);
services.AddMongoStorage(options =>
{
options.ConnectionString = _fixture.Runner.ConnectionString;
options.DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName;
options.CommandTimeout = TimeSpan.FromSeconds(5);
});
services.AddSourceCommon();
services.AddSuseConnector(options =>
{
options.ChangesEndpoint = ChangesUri;
options.AdvisoryBaseUri = new Uri("https://ftp.suse.com/pub/projects/security/csaf/");
options.MaxAdvisoriesPerFetch = 5;
options.RequestDelay = TimeSpan.Zero;
});
services.Configure<HttpClientFactoryOptions>(SuseOptions.HttpClientName, builderOptions =>
{
builderOptions.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.PrimaryHandler = _handler;
});
});
var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<MongoBootstrapper>();
await bootstrapper.InitializeAsync(CancellationToken.None);
return provider;
}
private void SeedInitialResponses()
{
_handler.AddResponse(ChangesUri, () => BuildResponse(HttpStatusCode.OK, "suse-changes.csv", "\"changes-v1\""));
_handler.AddResponse(AdvisoryResolvedUri, () => BuildResponse(HttpStatusCode.OK, "suse-su-2025_0001-1.json", "\"adv-1\""));
_handler.AddResponse(AdvisoryOpenUri, () => BuildResponse(HttpStatusCode.OK, "suse-su-2025_0002-1.json", "\"adv-2\""));
}
private void SeedNotModifiedResponses()
{
_handler.AddResponse(ChangesUri, () => BuildResponse(HttpStatusCode.NotModified, "suse-changes.csv", "\"changes-v1\""));
}
private HttpResponseMessage BuildResponse(HttpStatusCode statusCode, string fixture, string etag)
{
var response = new HttpResponseMessage(statusCode);
if (statusCode == HttpStatusCode.OK)
{
var contentType = fixture.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) ? "text/csv" : "application/json";
response.Content = new StringContent(ReadFixture(Path.Combine("Source", "Distro", "Suse", "Fixtures", fixture)), Encoding.UTF8, contentType);
}
response.Headers.ETag = new EntityTagHeaderValue(etag);
return response;
}
private static string ReadFixture(string relativePath)
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar));
if (!File.Exists(path))
{
throw new FileNotFoundException($"Fixture '{relativePath}' not found.", path);
}
return File.ReadAllText(path);
}
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync() => Task.CompletedTask;
}

View File

@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using StellaOps.Concelier.Connector.Distro.Suse.Internal;
using Xunit;
namespace StellaOps.Concelier.Connector.Distro.Suse.Tests;
public sealed class SuseCsafParserTests
{
[Fact]
public void Parse_ProducesRecommendedAndAffectedPackages()
{
var json = ReadFixture("Source/Distro/Suse/Fixtures/suse-su-2025_0001-1.json");
var dto = SuseCsafParser.Parse(json);
Assert.Equal("SUSE-SU-2025:0001-1", dto.AdvisoryId);
Assert.Contains("CVE-2025-0001", dto.CveIds);
var package = Assert.Single(dto.Packages);
Assert.Equal("openssl", package.Package);
Assert.Equal("resolved", package.Status);
Assert.NotNull(package.FixedVersion);
Assert.Equal("SUSE Linux Enterprise Server 15 SP5", package.Platform);
Assert.Equal("openssl-1.1.1w-150500.17.25.1.x86_64", package.CanonicalNevra);
}
[Fact]
public void Parse_HandlesOpenInvestigation()
{
var json = ReadFixture("Source/Distro/Suse/Fixtures/suse-su-2025_0002-1.json");
var dto = SuseCsafParser.Parse(json);
Assert.Equal("SUSE-SU-2025:0002-1", dto.AdvisoryId);
Assert.Contains("CVE-2025-0002", dto.CveIds);
var package = Assert.Single(dto.Packages);
Assert.Equal("open", package.Status);
Assert.Equal("postgresql16", package.Package);
Assert.NotNull(package.LastAffectedVersion);
}
private static string ReadFixture(string relativePath)
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar));
if (!File.Exists(path))
{
throw new FileNotFoundException($"Fixture '{relativePath}' not found.", path);
}
return File.ReadAllText(path);
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Distro.Suse;
using StellaOps.Concelier.Connector.Distro.Suse.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using Xunit;
namespace StellaOps.Concelier.Connector.Distro.Suse.Tests;
public sealed class SuseMapperTests
{
[Fact]
public void Map_BuildsNevraRangePrimitives()
{
var json = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Source", "Distro", "Suse", "Fixtures", "suse-su-2025_0001-1.json"));
var dto = SuseCsafParser.Parse(json);
var document = new DocumentRecord(
Guid.NewGuid(),
SuseConnectorPlugin.SourceName,
"https://ftp.suse.com/pub/projects/security/csaf/suse-su-2025_0001-1.json",
DateTimeOffset.UtcNow,
"sha256",
DocumentStatuses.PendingParse,
"application/json",
Headers: null,
Metadata: new Dictionary<string, string>(StringComparer.Ordinal)
{
["suse.id"] = dto.AdvisoryId
},
Etag: "adv-1",
LastModified: DateTimeOffset.UtcNow,
GridFsId: ObjectId.Empty);
var mapped = SuseMapper.Map(dto, document, DateTimeOffset.UtcNow);
Assert.Equal(dto.AdvisoryId, mapped.AdvisoryKey);
var package = Assert.Single(mapped.AffectedPackages);
Assert.Equal(AffectedPackageTypes.Rpm, package.Type);
var range = Assert.Single(package.VersionRanges);
Assert.Equal("nevra", range.RangeKind);
Assert.NotNull(range.Primitives);
Assert.NotNull(range.Primitives!.Nevra);
Assert.NotNull(range.Primitives.Nevra!.Fixed);
Assert.Equal("openssl", range.Primitives.Nevra.Fixed!.Name);
Assert.Equal("SUSE Linux Enterprise Server 15 SP5", package.Platform);
var normalizedRule = Assert.Single(package.NormalizedVersions);
Assert.Equal(NormalizedVersionSchemes.Nevra, normalizedRule.Scheme);
Assert.Equal($"suse:{package.Platform}", normalizedRule.Notes);
var primitives = range.Primitives.Nevra;
if (primitives!.Introduced is not null)
{
Assert.Equal(primitives.Introduced.ToCanonicalString(), normalizedRule.Min);
}
if (primitives.Fixed is not null)
{
Assert.Equal(primitives.Fixed.ToCanonicalString(), normalizedRule.Max);
}
if (primitives.LastAffected is not null && normalizedRule.Type is NormalizedVersionRuleTypes.Range or NormalizedVersionRuleTypes.LessThanOrEqual)
{
Assert.Equal(primitives.LastAffected.ToCanonicalString(), normalizedRule.Max);
}
}
}