feat: Implement DefaultCryptoHmac for compliance-aware HMAC operations

- Added DefaultCryptoHmac class implementing ICryptoHmac interface.
- Introduced purpose-based HMAC computation methods.
- Implemented verification methods for HMACs with constant-time comparison.
- Created HmacAlgorithms and HmacPurpose classes for well-known identifiers.
- Added compliance profile support for HMAC algorithms.
- Included asynchronous methods for HMAC computation from streams.
This commit is contained in:
StellaOps Bot
2025-12-06 00:41:04 +02:00
parent 43c281a8b2
commit f0662dd45f
362 changed files with 8441 additions and 22338 deletions

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
<ProjectReference Include="..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
<ProjectReference Include="..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,6 +2,7 @@ using System.Buffers;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using StellaOps.Cryptography;
namespace StellaOps.AdvisoryAI.Vectorization;
@@ -10,22 +11,23 @@ internal interface IVectorEncoder
float[] Encode(string text);
}
internal sealed class DeterministicHashVectorEncoder : IVectorEncoder, IDisposable
internal sealed class DeterministicHashVectorEncoder : IVectorEncoder
{
private const int DefaultDimensions = 64;
private static readonly Regex TokenRegex = new("[A-Za-z0-9]+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private readonly IncrementalHash _hash;
private readonly ICryptoHash _cryptoHash;
private readonly int _dimensions;
public DeterministicHashVectorEncoder(int dimensions = DefaultDimensions)
public DeterministicHashVectorEncoder(ICryptoHash cryptoHash, int dimensions = DefaultDimensions)
{
ArgumentNullException.ThrowIfNull(cryptoHash);
if (dimensions <= 0)
{
throw new ArgumentOutOfRangeException(nameof(dimensions));
}
_cryptoHash = cryptoHash;
_dimensions = dimensions;
_hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
}
public float[] Encode(string text)
@@ -39,15 +41,12 @@ internal sealed class DeterministicHashVectorEncoder : IVectorEncoder, IDisposab
return vector;
}
Span<byte> hash = stackalloc byte[32];
foreach (Match match in tokenMatches)
{
var token = match.Value.ToLowerInvariant();
var bytes = Encoding.UTF8.GetBytes(token);
_hash.AppendData(bytes);
_hash.GetHashAndReset(hash);
var index = (int)(BitConverter.ToUInt32(hash[..4]) % (uint)_dimensions);
var hash = _cryptoHash.ComputeHashForPurpose(bytes, HashPurpose.Content);
var index = (int)(BitConverter.ToUInt32(hash.AsSpan(0, 4)) % (uint)_dimensions);
vector[index] += 1f;
}
@@ -69,9 +68,4 @@ internal sealed class DeterministicHashVectorEncoder : IVectorEncoder, IDisposab
vector[i] /= length;
}
}
public void Dispose()
{
_hash.Dispose();
}
}

View File

@@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
<ProjectReference Include="..\..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
<ProjectReference Include="..\..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="TestData/*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -11,21 +11,25 @@ using StellaOps.Attestor.Core.Options;
using StellaOps.Attestor.Core.Storage;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Core.Verification;
using StellaOps.Cryptography;
namespace StellaOps.Attestor.Verify;
public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
{
private readonly IDsseCanonicalizer _canonicalizer;
private readonly ICryptoHash _cryptoHash;
private readonly AttestorOptions _options;
private readonly ILogger<AttestorVerificationEngine> _logger;
public AttestorVerificationEngine(
IDsseCanonicalizer canonicalizer,
ICryptoHash cryptoHash,
IOptions<AttestorOptions> options,
ILogger<AttestorVerificationEngine> logger)
{
_canonicalizer = canonicalizer ?? throw new ArgumentNullException(nameof(canonicalizer));
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -126,7 +130,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
});
}
var computedHash = Convert.ToHexString(SHA256.HashData(canonicalBundle)).ToLowerInvariant();
var computedHash = _cryptoHash.ComputeHashHexForPurpose(canonicalBundle, HashPurpose.Attestation);
if (!string.Equals(computedHash, entry.BundleSha256, StringComparison.OrdinalIgnoreCase))
{
signatureIssues.Add("bundle_hash_mismatch");
@@ -806,14 +810,13 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
return buffer;
}
private static byte[] HashInternal(byte[] left, byte[] right)
private byte[] HashInternal(byte[] left, byte[] right)
{
using var sha = SHA256.Create();
var buffer = new byte[1 + left.Length + right.Length];
buffer[0] = 0x01;
Buffer.BlockCopy(left, 0, buffer, 1, left.Length);
Buffer.BlockCopy(right, 0, buffer, 1 + left.Length, right.Length);
return sha.ComputeHash(buffer);
return _cryptoHash.ComputeHashForPurpose(buffer, HashPurpose.Merkle);
}
private static bool TryDecodeSecret(string value, out byte[] bytes)

View File

@@ -8,5 +8,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\\StellaOps.Attestor\\StellaOps.Attestor.Core\\StellaOps.Attestor.Core.csproj" />
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@@ -14,13 +14,13 @@
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Kms\StellaOps.Cryptography.Kms.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
<PackageReference Include="AWSSDK.S3" Version="3.7.307.6" />

View File

@@ -8,7 +8,7 @@
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />

View File

@@ -8,7 +8,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />

View File

@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -32,7 +32,7 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0" PrivateAssets="All" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />

View File

@@ -34,7 +34,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="OpenIddict.Abstractions" Version="6.4.0" />
</ItemGroup>

View File

@@ -9,9 +9,9 @@
<IsAuthorityPlugin>true</IsAuthorityPlugin>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="8.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>

View File

@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority.Plugin.Standard\StellaOps.Authority.Plugin.Standard.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority.Plugin.Standard\StellaOps.Authority.Plugin.Standard.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
</Project>

View File

@@ -9,9 +9,9 @@
<IsAuthorityPlugin>true</IsAuthorityPlugin>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -15,9 +15,9 @@
<WriteLinesToFile File="$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildEditorConfig.editorconfig" Lines="" Overwrite="false" />
</Target>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />

View File

@@ -1,21 +1,21 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority\StellaOps.Authority.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Auth.Security/StellaOps.Auth.Security.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
<ItemGroup>
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority\StellaOps.Authority.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Auth.Security/StellaOps.Auth.Security.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="../../../../tests/shared/OpenSslLegacyShim.cs" Link="Infrastructure/OpenSslLegacyShim.cs" />
<None Include="../../../../tests/native/openssl-1.1/linux-x64/*" Link="native/linux-x64/%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
</ItemGroup>
</Project>

View File

@@ -13,8 +13,8 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">

View File

@@ -1,28 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.LinkNotMerge.Vex\StellaOps.Bench.LinkNotMerge.Vex.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.LinkNotMerge.Vex\StellaOps.Bench.LinkNotMerge.Vex.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,28 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.LinkNotMerge\StellaOps.Bench.LinkNotMerge.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.LinkNotMerge\StellaOps.Bench.LinkNotMerge.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,27 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.Notify\StellaOps.Bench.Notify.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.Notify\StellaOps.Bench.Notify.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.ScannerAnalyzers\StellaOps.Bench.ScannerAnalyzers.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Bench.ScannerAnalyzers\StellaOps.Bench.ScannerAnalyzers.csproj" />
</ItemGroup>
</Project>

View File

@@ -13,6 +13,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Services.Models;
using StellaOps.Cryptography;
namespace StellaOps.Cli.Services;
@@ -29,11 +30,13 @@ internal sealed partial class PromotionAssembler : IPromotionAssembler
};
private readonly HttpClient _httpClient;
private readonly ICryptoHash _cryptoHash;
private readonly ILogger<PromotionAssembler> _logger;
public PromotionAssembler(HttpClient httpClient, ILogger<PromotionAssembler> logger)
public PromotionAssembler(HttpClient httpClient, ICryptoHash cryptoHash, ILogger<PromotionAssembler> logger)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -289,11 +292,10 @@ internal sealed partial class PromotionAssembler : IPromotionAssembler
return null;
}
private static async Task<string> ComputeFileDigestAsync(string filePath, CancellationToken cancellationToken)
private async Task<string> ComputeFileDigestAsync(string filePath, CancellationToken cancellationToken)
{
await using var stream = File.OpenRead(filePath);
var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
return Convert.ToHexString(hash).ToLowerInvariant();
return await _cryptoHash.ComputeHashHexForPurposeAsync(stream, HashPurpose.Content, cancellationToken).ConfigureAwait(false);
}
private static (string name, string? tag) ParseImageRef(string imageRef)

View File

@@ -1,69 +1,69 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.local.json" Condition="Exists('appsettings.local.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.yaml" Condition="Exists('appsettings.yaml')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.local.yaml" Condition="Exists('appsettings.local.yaml')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.Pkcs11Gost/StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.EntryTrace/StellaOps.Scanner.EntryTrace.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Python/StellaOps.Scanner.Analyzers.Lang.Python.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Ruby/StellaOps.Scanner.Analyzers.Lang.Ruby.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Surface.Env/StellaOps.Scanner.Surface.Env.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Surface.Validation/StellaOps.Scanner.Surface.Validation.csproj" />
<ProjectReference Include="../../Policy/StellaOps.PolicyDsl/StellaOps.PolicyDsl.csproj" />
<ProjectReference Include="../../Policy/__Libraries/StellaOps.Policy/StellaOps.Policy.csproj" />
<ProjectReference Include="../../Policy/StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
<ProjectReference Include="../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
<ProjectReference Include="../../Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/StellaOps.Cryptography.Plugin.CryptoPro.csproj" />
</ItemGroup>
</Project>
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.local.json" Condition="Exists('appsettings.local.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.yaml" Condition="Exists('appsettings.yaml')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.local.yaml" Condition="Exists('appsettings.local.yaml')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.Pkcs11Gost/StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.EntryTrace/StellaOps.Scanner.EntryTrace.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Python/StellaOps.Scanner.Analyzers.Lang.Python.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Ruby/StellaOps.Scanner.Analyzers.Lang.Ruby.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Surface.Env/StellaOps.Scanner.Surface.Env.csproj" />
<ProjectReference Include="../../Scanner/__Libraries/StellaOps.Scanner.Surface.Validation/StellaOps.Scanner.Surface.Validation.csproj" />
<ProjectReference Include="../../Policy/StellaOps.PolicyDsl/StellaOps.PolicyDsl.csproj" />
<ProjectReference Include="../../Policy/__Libraries/StellaOps.Policy/StellaOps.Policy.csproj" />
<ProjectReference Include="../../Policy/StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
<ProjectReference Include="../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
<ProjectReference Include="../../Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/StellaOps.Cryptography.Plugin.CryptoPro.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,22 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PluginOutputDirectory>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\\..\\plugins\\cli\\StellaOps.Cli.Plugins.NonCore\\'))</PluginOutputDirectory>
</PropertyGroup>
<ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PluginOutputDirectory>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\\..\\plugins\\cli\\StellaOps.Cli.Plugins.NonCore\\'))</PluginOutputDirectory>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Cli\StellaOps.Cli.csproj" />
</ItemGroup>
<Target Name="CopyPluginBinaries" AfterTargets="Build">
<MakeDir Directories="$(PluginOutputDirectory)" />
<Copy SourceFiles="$(TargetDir)$(TargetFileName)" DestinationFolder="$(PluginOutputDirectory)" />
<Copy SourceFiles="$(TargetDir)$(TargetName).pdb"
DestinationFolder="$(PluginOutputDirectory)"
Condition="Exists('$(TargetDir)$(TargetName).pdb')" />
</Target>
</Project>
</ItemGroup>
<Target Name="CopyPluginBinaries" AfterTargets="Build">
<MakeDir Directories="$(PluginOutputDirectory)" />
<Copy SourceFiles="$(TargetDir)$(TargetFileName)" DestinationFolder="$(PluginOutputDirectory)" />
<Copy SourceFiles="$(TargetDir)$(TargetName).pdb"
DestinationFolder="$(PluginOutputDirectory)"
Condition="Exists('$(TargetDir)$(TargetName).pdb')" />
</Target>
</Project>

View File

@@ -9,6 +9,8 @@ public sealed class ConcelierOptions
{
public StorageOptions Storage { get; set; } = new();
public PostgresStorageOptions? PostgresStorage { get; set; }
public PluginOptions Plugins { get; set; } = new();
public TelemetryOptions Telemetry { get; set; } = new();
@@ -36,6 +38,63 @@ public sealed class ConcelierOptions
public int CommandTimeoutSeconds { get; set; } = 30;
}
/// <summary>
/// PostgreSQL storage options for the LNM linkset cache.
/// </summary>
public sealed class PostgresStorageOptions
{
/// <summary>
/// Enable PostgreSQL storage for LNM linkset cache.
/// When true, the linkset cache is stored in PostgreSQL instead of MongoDB.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// PostgreSQL connection string.
/// </summary>
public string ConnectionString { get; set; } = string.Empty;
/// <summary>
/// Command timeout in seconds. Default is 30 seconds.
/// </summary>
public int CommandTimeoutSeconds { get; set; } = 30;
/// <summary>
/// Maximum number of connections in the pool. Default is 100.
/// </summary>
public int MaxPoolSize { get; set; } = 100;
/// <summary>
/// Minimum number of connections in the pool. Default is 1.
/// </summary>
public int MinPoolSize { get; set; } = 1;
/// <summary>
/// Connection idle lifetime in seconds. Default is 300 seconds (5 minutes).
/// </summary>
public int ConnectionIdleLifetimeSeconds { get; set; } = 300;
/// <summary>
/// Enable connection pooling. Default is true.
/// </summary>
public bool Pooling { get; set; } = true;
/// <summary>
/// Schema name for LNM tables. Default is "vuln".
/// </summary>
public string SchemaName { get; set; } = "vuln";
/// <summary>
/// Enable automatic migration on startup. Default is false for production safety.
/// </summary>
public bool AutoMigrate { get; set; }
/// <summary>
/// Path to SQL migration files. Required if AutoMigrate is true.
/// </summary>
public string? MigrationsPath { get; set; }
}
public sealed class PluginOptions
{
public string? BaseDirectory { get; set; }

View File

@@ -57,6 +57,7 @@ using StellaOps.Concelier.RawModels;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Mongo.Aliases;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Provenance.Mongo;
using StellaOps.Concelier.Core.Attestation;
using AttestationClaims = StellaOps.Concelier.Core.Attestation.AttestationClaims;
@@ -195,6 +196,25 @@ else
builder.Services.RemoveAll<IMongoClient>();
builder.Services.RemoveAll<IMongoDatabase>();
}
// Add PostgreSQL storage for LNM linkset cache if configured.
// This provides a PostgreSQL-backed implementation of IAdvisoryLinksetStore for the read-through cache.
if (concelierOptions.PostgresStorage is { Enabled: true } postgresOptions)
{
builder.Services.AddConcelierPostgresStorage(pgOptions =>
{
pgOptions.ConnectionString = postgresOptions.ConnectionString;
pgOptions.CommandTimeoutSeconds = postgresOptions.CommandTimeoutSeconds;
pgOptions.MaxPoolSize = postgresOptions.MaxPoolSize;
pgOptions.MinPoolSize = postgresOptions.MinPoolSize;
pgOptions.ConnectionIdleLifetimeSeconds = postgresOptions.ConnectionIdleLifetimeSeconds;
pgOptions.Pooling = postgresOptions.Pooling;
pgOptions.SchemaName = postgresOptions.SchemaName;
pgOptions.AutoMigrate = postgresOptions.AutoMigrate;
pgOptions.MigrationsPath = postgresOptions.MigrationsPath;
});
}
builder.Services.AddOptions<AdvisoryObservationEventPublisherOptions>()
.Bind(builder.Configuration.GetSection("advisoryObservationEvents"))
.PostConfigure(options =>

View File

@@ -9,7 +9,7 @@
<RootNamespace>StellaOps.Concelier.WebService</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
@@ -24,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Storage.Postgres/StellaOps.Concelier.Storage.Postgres.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Concelier.Merge/StellaOps.Concelier.Merge.csproj" />

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonSchema.Net" Version="5.3.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="AngleSharp" Version="1.1.1" />
<PackageReference Include="UglyToad.PdfPig" Version="1.7.0-custom-5" />

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />

View File

@@ -0,0 +1,130 @@
using System.Collections.Immutable;
using System.Text.Json;
using Microsoft.Extensions.Options;
using StellaOps.Aoc;
using StellaOps.Concelier.RawModels;
namespace StellaOps.Concelier.Core.Aoc;
/// <summary>
/// Default implementation of <see cref="IAdvisorySchemaValidator"/>.
/// Per WEB-AOC-19-002, provides granular validation checks for AOC compliance testing.
/// </summary>
public sealed class AdvisorySchemaValidator : IAdvisorySchemaValidator
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private readonly IAocGuard _guard;
private readonly AocGuardOptions _options;
public AdvisorySchemaValidator(IAocGuard guard, IOptions<AocGuardOptions>? options = null)
{
_guard = guard ?? throw new ArgumentNullException(nameof(guard));
_options = options?.Value ?? AocGuardOptions.Default;
}
/// <inheritdoc />
public AocGuardResult ValidateSchema(AdvisoryRawDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var json = SerializeDocument(document);
return _guard.Validate(json, _options);
}
/// <inheritdoc />
public AocGuardResult ValidateForbiddenFields(AdvisoryRawDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var result = ValidateSchema(document);
return FilterByCode(result, AocViolationCode.ForbiddenField);
}
/// <inheritdoc />
public AocGuardResult ValidateDerivedFields(AdvisoryRawDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var result = ValidateSchema(document);
return FilterByCode(result, AocViolationCode.DerivedFindingDetected);
}
/// <inheritdoc />
public AocGuardResult ValidateAllowedFields(AdvisoryRawDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var result = ValidateSchema(document);
return FilterByCode(result, AocViolationCode.UnknownField);
}
/// <inheritdoc />
public AocGuardResult ValidateMergeAttempt(AdvisoryRawDocument document)
{
ArgumentNullException.ThrowIfNull(document);
// Merge attempts are indicated by presence of "merged_from" field,
// which is detected as ForbiddenField. We check for this specific field.
var result = ValidateSchema(document);
var mergeViolations = result.Violations
.Where(v => v.Code == AocViolationCode.ForbiddenField &&
v.Path.Contains("merged_from", StringComparison.OrdinalIgnoreCase))
.Select(v => AocViolation.Create(
AocViolationCode.MergeAttempt,
v.Path,
"Merge attempts are not allowed in AOC documents. Use Link-Not-Merge pattern."))
.ToImmutableArray();
return mergeViolations.Length > 0
? new AocGuardResult(false, mergeViolations)
: AocGuardResult.Success;
}
private static JsonElement SerializeDocument(AdvisoryRawDocument document)
{
var normalized = NormalizeDocument(document);
var serialized = JsonSerializer.Serialize(normalized, SerializerOptions);
using var jsonDoc = JsonDocument.Parse(serialized);
return jsonDoc.RootElement.Clone();
}
private static AocGuardResult FilterByCode(AocGuardResult result, AocViolationCode code)
{
var filtered = result.Violations
.Where(v => v.Code == code)
.ToImmutableArray();
return filtered.Length > 0
? new AocGuardResult(false, filtered)
: AocGuardResult.Success;
}
private static AdvisoryRawDocument NormalizeDocument(AdvisoryRawDocument document)
{
var identifiers = document.Identifiers with
{
Aliases = Normalize(document.Identifiers.Aliases)
};
var linkset = document.Linkset with
{
Aliases = Normalize(document.Linkset.Aliases),
PackageUrls = Normalize(document.Linkset.PackageUrls),
Cpes = Normalize(document.Linkset.Cpes),
References = Normalize(document.Linkset.References),
ReconciledFrom = Normalize(document.Linkset.ReconciledFrom),
Notes = Normalize(document.Linkset.Notes)
};
return document with
{
Identifiers = identifiers,
Linkset = linkset,
Links = Normalize(document.Links)
};
}
private static ImmutableArray<T> Normalize<T>(ImmutableArray<T> value) =>
value.IsDefault ? ImmutableArray<T>.Empty : value;
private static ImmutableDictionary<TKey, TValue> Normalize<TKey, TValue>(ImmutableDictionary<TKey, TValue> value)
where TKey : notnull =>
value == default ? ImmutableDictionary<TKey, TValue>.Empty : value;
}

View File

@@ -38,6 +38,14 @@ public static class AocServiceCollectionExtensions
// Append-only write guard for observations (LNM-21-004)
services.TryAddSingleton<IAdvisoryObservationWriteGuard, AdvisoryObservationWriteGuard>();
// Schema validator for granular AOC validation (WEB-AOC-19-002)
services.TryAddSingleton<IAdvisorySchemaValidator>(sp =>
{
var guard = sp.GetRequiredService<IAocGuard>();
var options = sp.GetService<IOptions<AocGuardOptions>>();
return new AdvisorySchemaValidator(guard, options);
});
return services;
}
}

View File

@@ -0,0 +1,48 @@
using StellaOps.Aoc;
using StellaOps.Concelier.RawModels;
namespace StellaOps.Concelier.Core.Aoc;
/// <summary>
/// Provides granular schema validation for advisory documents against the AOC contract.
/// Per WEB-AOC-19-002, exposes specific validation checks for test coverage.
/// </summary>
public interface IAdvisorySchemaValidator
{
/// <summary>
/// Validates the entire document schema.
/// </summary>
/// <param name="document">Raw advisory document to validate.</param>
/// <returns>Validation result with all violations.</returns>
AocGuardResult ValidateSchema(AdvisoryRawDocument document);
/// <summary>
/// Validates that no forbidden fields are present (ERR_AOC_001).
/// Forbidden fields include: severity, cvss, merged_from, consensus_provider, etc.
/// </summary>
/// <param name="document">Raw advisory document to validate.</param>
/// <returns>Validation result with forbidden field violations only.</returns>
AocGuardResult ValidateForbiddenFields(AdvisoryRawDocument document);
/// <summary>
/// Validates that no derived fields are present (ERR_AOC_006).
/// Derived fields are those prefixed with "effective_".
/// </summary>
/// <param name="document">Raw advisory document to validate.</param>
/// <returns>Validation result with derived field violations only.</returns>
AocGuardResult ValidateDerivedFields(AdvisoryRawDocument document);
/// <summary>
/// Validates that only allowed fields are present (ERR_AOC_007).
/// </summary>
/// <param name="document">Raw advisory document to validate.</param>
/// <returns>Validation result with unknown field violations only.</returns>
AocGuardResult ValidateAllowedFields(AdvisoryRawDocument document);
/// <summary>
/// Detects merge attempt indicators (ERR_AOC_002).
/// </summary>
/// <param name="document">Raw advisory document to validate.</param>
/// <returns>Validation result with merge attempt violations only.</returns>
AocGuardResult ValidateMergeAttempt(AdvisoryRawDocument document);
}

View File

@@ -45,6 +45,32 @@ public interface IVendorRiskSignalProvider
string tenantId,
string linksetId,
CancellationToken cancellationToken);
/// <summary>
/// Gets a consolidated risk signal for an advisory (merges all vendor observations).
/// Per CONCELIER-RISK-68-001, used by Policy Studio signal picker.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryId">Advisory identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Consolidated risk signal, or null if no observations exist.</returns>
Task<VendorRiskSignal?> GetSignalAsync(
string tenantId,
string advisoryId,
CancellationToken cancellationToken);
/// <summary>
/// Gets consolidated risk signals for multiple advisories in batch.
/// Per CONCELIER-RISK-68-001, used by Policy Studio signal picker for bulk operations.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryIds">Advisory identifiers.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Collection of consolidated risk signals.</returns>
Task<IReadOnlyList<VendorRiskSignal>> GetSignalsBatchAsync(
string tenantId,
IEnumerable<string> advisoryIds,
CancellationToken cancellationToken);
}
/// <summary>

View File

@@ -0,0 +1,92 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Concelier.Core.Risk.PolicyStudio;
/// <summary>
/// Interface for picking and mapping advisory signals to Policy Studio input format.
/// Per CONCELIER-RISK-68-001, all selected fields must be provenance-backed.
/// </summary>
public interface IPolicyStudioSignalPicker
{
/// <summary>
/// Picks advisory signals for a specific advisory and maps to Policy Studio input format.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryId">Advisory identifier.</param>
/// <param name="options">Options controlling field selection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Policy Studio signal input with provenance metadata.</returns>
Task<PolicyStudioSignalInput?> PickAsync(
string tenantId,
string advisoryId,
PolicyStudioSignalOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Picks advisory signals for multiple advisories in batch.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryIds">Advisory identifiers.</param>
/// <param name="options">Options controlling field selection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Dictionary mapping advisory IDs to their Policy Studio signal inputs.</returns>
Task<ImmutableDictionary<string, PolicyStudioSignalInput>> PickBatchAsync(
string tenantId,
IEnumerable<string> advisoryIds,
PolicyStudioSignalOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Maps an existing vendor risk signal to Policy Studio input format.
/// </summary>
/// <param name="signal">The vendor risk signal to map.</param>
/// <param name="options">Options controlling field selection.</param>
/// <returns>Policy Studio signal input with provenance metadata.</returns>
PolicyStudioSignalInput MapFromSignal(
VendorRiskSignal signal,
PolicyStudioSignalOptions? options = null);
}
/// <summary>
/// Options for controlling advisory signal selection.
/// </summary>
public sealed record PolicyStudioSignalOptions
{
/// <summary>
/// Include CVSS score data. Default is true.
/// </summary>
public bool IncludeCvss { get; init; } = true;
/// <summary>
/// Include KEV status data. Default is true.
/// </summary>
public bool IncludeKev { get; init; } = true;
/// <summary>
/// Include fix availability data. Default is true.
/// </summary>
public bool IncludeFixAvailability { get; init; } = true;
/// <summary>
/// Include severity derived fields. Default is true.
/// </summary>
public bool IncludeSeverity { get; init; } = true;
/// <summary>
/// Preferred CVSS version for score selection (e.g., "cvss_v31", "cvss_v40").
/// If not specified, uses the highest available version.
/// </summary>
public string? PreferredCvssVersion { get; init; }
/// <summary>
/// Include detailed provenance in the output. Default is true.
/// </summary>
public bool IncludeProvenance { get; init; } = true;
/// <summary>
/// Default options instance.
/// </summary>
public static PolicyStudioSignalOptions Default { get; } = new();
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.Core.Risk.PolicyStudio;
/// <summary>
/// Policy Studio input model for advisory signals.
/// Per CONCELIER-RISK-68-001, all fields are provenance-backed.
/// This model is designed to be serialized to JSON for Policy Studio consumption
/// per CONTRACT-POLICY-STUDIO-007.
/// </summary>
public sealed record PolicyStudioSignalInput
{
/// <summary>
/// Tenant identifier.
/// </summary>
[JsonPropertyName("tenant_id")]
public required string TenantId { get; init; }
/// <summary>
/// Advisory identifier (e.g., CVE-2024-1234, GHSA-xxx).
/// </summary>
[JsonPropertyName("advisory_id")]
public required string AdvisoryId { get; init; }
/// <summary>
/// CVSS score (highest available based on options).
/// </summary>
[JsonPropertyName("cvss")]
public double? Cvss { get; init; }
/// <summary>
/// CVSS version for the reported score.
/// </summary>
[JsonPropertyName("cvss_version")]
public string? CvssVersion { get; init; }
/// <summary>
/// CVSS vector string.
/// </summary>
[JsonPropertyName("cvss_vector")]
public string? CvssVector { get; init; }
/// <summary>
/// Severity tier (critical, high, medium, low, informational).
/// </summary>
[JsonPropertyName("severity")]
public string? Severity { get; init; }
/// <summary>
/// Indicates if the vulnerability is in the KEV (Known Exploited Vulnerabilities) list.
/// </summary>
[JsonPropertyName("kev")]
public bool? Kev { get; init; }
/// <summary>
/// Date the vulnerability was added to KEV, if applicable.
/// </summary>
[JsonPropertyName("kev_date_added")]
public DateTimeOffset? KevDateAdded { get; init; }
/// <summary>
/// KEV remediation due date, if applicable.
/// </summary>
[JsonPropertyName("kev_due_date")]
public DateTimeOffset? KevDueDate { get; init; }
/// <summary>
/// Indicates if a fix is available for any affected package.
/// </summary>
[JsonPropertyName("fix_available")]
public bool? FixAvailable { get; init; }
/// <summary>
/// Fixed version(s) if a fix is available.
/// </summary>
[JsonPropertyName("fixed_versions")]
public ImmutableArray<string>? FixedVersions { get; init; }
/// <summary>
/// Date the signal was extracted from source observations.
/// </summary>
[JsonPropertyName("extracted_at")]
public DateTimeOffset ExtractedAt { get; init; }
/// <summary>
/// Provenance metadata for policy audit trail.
/// </summary>
[JsonPropertyName("provenance")]
public PolicyStudioSignalProvenance? Provenance { get; init; }
}
/// <summary>
/// Provenance metadata for Policy Studio signal input.
/// Ensures audit trail for policy evaluation decisions.
/// </summary>
public sealed record PolicyStudioSignalProvenance
{
/// <summary>
/// Source observation IDs that contributed to this signal.
/// </summary>
[JsonPropertyName("observation_ids")]
public ImmutableArray<string> ObservationIds { get; init; } = ImmutableArray<string>.Empty;
/// <summary>
/// Source vendors/feeds that provided the data.
/// </summary>
[JsonPropertyName("sources")]
public ImmutableArray<string> Sources { get; init; } = ImmutableArray<string>.Empty;
/// <summary>
/// Observation hashes for integrity verification.
/// </summary>
[JsonPropertyName("observation_hashes")]
public ImmutableArray<string> ObservationHashes { get; init; } = ImmutableArray<string>.Empty;
/// <summary>
/// Provenance details for the CVSS score field.
/// </summary>
[JsonPropertyName("cvss_provenance")]
public PolicyStudioFieldProvenance? CvssProvenance { get; init; }
/// <summary>
/// Provenance details for the KEV status field.
/// </summary>
[JsonPropertyName("kev_provenance")]
public PolicyStudioFieldProvenance? KevProvenance { get; init; }
/// <summary>
/// Provenance details for the fix availability field.
/// </summary>
[JsonPropertyName("fix_provenance")]
public PolicyStudioFieldProvenance? FixProvenance { get; init; }
}
/// <summary>
/// Field-level provenance for individual signal fields.
/// </summary>
public sealed record PolicyStudioFieldProvenance
{
/// <summary>
/// Vendor that provided this field's data.
/// </summary>
[JsonPropertyName("vendor")]
public required string Vendor { get; init; }
/// <summary>
/// Source feed/API that provided the data.
/// </summary>
[JsonPropertyName("source")]
public required string Source { get; init; }
/// <summary>
/// Observation hash for the data.
/// </summary>
[JsonPropertyName("observation_hash")]
public required string ObservationHash { get; init; }
/// <summary>
/// When the data was fetched from the source.
/// </summary>
[JsonPropertyName("fetched_at")]
public DateTimeOffset FetchedAt { get; init; }
/// <summary>
/// Upstream identifier from the source (e.g., NVD ID, GHSA ID).
/// </summary>
[JsonPropertyName("upstream_id")]
public string? UpstreamId { get; init; }
}

View File

@@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace StellaOps.Concelier.Core.Risk.PolicyStudio;
/// <summary>
/// Default implementation of <see cref="IPolicyStudioSignalPicker"/>.
/// Per CONCELIER-RISK-68-001, all selected fields are provenance-backed.
/// </summary>
public sealed class PolicyStudioSignalPicker : IPolicyStudioSignalPicker
{
private readonly IVendorRiskSignalProvider _signalProvider;
private readonly ILogger<PolicyStudioSignalPicker> _logger;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new instance of <see cref="PolicyStudioSignalPicker"/>.
/// </summary>
public PolicyStudioSignalPicker(
IVendorRiskSignalProvider signalProvider,
ILogger<PolicyStudioSignalPicker> logger,
TimeProvider timeProvider)
{
_signalProvider = signalProvider ?? throw new ArgumentNullException(nameof(signalProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
/// <inheritdoc />
public async Task<PolicyStudioSignalInput?> PickAsync(
string tenantId,
string advisoryId,
PolicyStudioSignalOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryId);
options ??= PolicyStudioSignalOptions.Default;
_logger.LogDebug(
"Picking advisory signals for Policy Studio: tenant={TenantId}, advisory={AdvisoryId}",
tenantId, advisoryId);
var signal = await _signalProvider
.GetSignalAsync(tenantId, advisoryId, cancellationToken)
.ConfigureAwait(false);
if (signal is null)
{
_logger.LogDebug(
"No risk signal found for advisory {AdvisoryId} in tenant {TenantId}",
advisoryId, tenantId);
return null;
}
return MapFromSignal(signal, options);
}
/// <inheritdoc />
public async Task<ImmutableDictionary<string, PolicyStudioSignalInput>> PickBatchAsync(
string tenantId,
IEnumerable<string> advisoryIds,
PolicyStudioSignalOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentNullException.ThrowIfNull(advisoryIds);
options ??= PolicyStudioSignalOptions.Default;
var idList = advisoryIds.ToList();
if (idList.Count == 0)
{
return ImmutableDictionary<string, PolicyStudioSignalInput>.Empty;
}
_logger.LogDebug(
"Picking advisory signals for Policy Studio batch: tenant={TenantId}, count={Count}",
tenantId, idList.Count);
var signals = await _signalProvider
.GetSignalsBatchAsync(tenantId, idList, cancellationToken)
.ConfigureAwait(false);
var builder = ImmutableDictionary.CreateBuilder<string, PolicyStudioSignalInput>();
foreach (var signal in signals)
{
var input = MapFromSignal(signal, options);
builder[signal.AdvisoryId] = input;
}
return builder.ToImmutable();
}
/// <inheritdoc />
public PolicyStudioSignalInput MapFromSignal(
VendorRiskSignal signal,
PolicyStudioSignalOptions? options = null)
{
ArgumentNullException.ThrowIfNull(signal);
options ??= PolicyStudioSignalOptions.Default;
// Select CVSS score based on options
var cvssScore = SelectCvssScore(signal.CvssScores, options);
// Extract fix versions
ImmutableArray<string>? fixedVersions = null;
if (options.IncludeFixAvailability && !signal.FixAvailability.IsDefaultOrEmpty)
{
fixedVersions = signal.FixAvailability
.Where(f => f.Status == FixStatus.Available && !string.IsNullOrEmpty(f.FixedVersion))
.Select(f => f.FixedVersion!)
.Distinct()
.ToImmutableArray();
}
// Build provenance if requested
PolicyStudioSignalProvenance? provenance = null;
if (options.IncludeProvenance)
{
provenance = BuildProvenance(signal, cvssScore, options);
}
return new PolicyStudioSignalInput
{
TenantId = signal.TenantId,
AdvisoryId = signal.AdvisoryId,
Cvss = options.IncludeCvss ? cvssScore?.Score : null,
CvssVersion = options.IncludeCvss ? cvssScore?.NormalizedSystem : null,
CvssVector = options.IncludeCvss ? cvssScore?.Vector : null,
Severity = options.IncludeSeverity ? DetermineSeverity(signal, cvssScore) : null,
Kev = options.IncludeKev ? signal.KevStatus?.InKev : null,
KevDateAdded = options.IncludeKev ? signal.KevStatus?.DateAdded : null,
KevDueDate = options.IncludeKev ? signal.KevStatus?.DueDate : null,
FixAvailable = options.IncludeFixAvailability ? signal.HasFixAvailable : null,
FixedVersions = fixedVersions,
ExtractedAt = signal.ExtractedAt,
Provenance = provenance
};
}
private static VendorCvssScore? SelectCvssScore(
ImmutableArray<VendorCvssScore> scores,
PolicyStudioSignalOptions options)
{
if (scores.IsDefaultOrEmpty)
{
return null;
}
// If preferred version specified, try to find it
if (!string.IsNullOrEmpty(options.PreferredCvssVersion))
{
var preferred = scores.FirstOrDefault(s =>
string.Equals(s.NormalizedSystem, options.PreferredCvssVersion, StringComparison.OrdinalIgnoreCase));
if (preferred is not null)
{
return preferred;
}
}
// Otherwise, select by priority: v4.0 > v3.1 > v3.0 > v2.0
// Then by highest score within same version
return scores
.OrderByDescending(s => GetCvssVersionPriority(s.NormalizedSystem))
.ThenByDescending(s => s.Score)
.FirstOrDefault();
}
private static int GetCvssVersionPriority(string version) => version switch
{
"cvss_v40" => 4,
"cvss_v31" => 3,
"cvss_v30" => 2,
"cvss_v2" => 1,
_ => 0
};
private static string? DetermineSeverity(VendorRiskSignal signal, VendorCvssScore? cvssScore)
{
// Use KEV as highest priority indicator
if (signal.KevStatus?.InKev == true)
{
return "critical"; // KEV status implies critical severity for policy purposes
}
// Use CVSS-derived severity
return cvssScore?.EffectiveSeverity;
}
private static PolicyStudioSignalProvenance BuildProvenance(
VendorRiskSignal signal,
VendorCvssScore? cvssScore,
PolicyStudioSignalOptions options)
{
var observationIds = new HashSet<string> { signal.ObservationId };
var sources = new HashSet<string> { signal.Provenance.Source };
var hashes = new HashSet<string> { signal.Provenance.ObservationHash };
// Collect provenance from all contributing observations
foreach (var score in signal.CvssScores)
{
sources.Add(score.Provenance.Source);
hashes.Add(score.Provenance.ObservationHash);
}
if (signal.KevStatus is not null)
{
sources.Add(signal.KevStatus.Provenance.Source);
hashes.Add(signal.KevStatus.Provenance.ObservationHash);
}
foreach (var fix in signal.FixAvailability)
{
sources.Add(fix.Provenance.Source);
hashes.Add(fix.Provenance.ObservationHash);
}
return new PolicyStudioSignalProvenance
{
ObservationIds = observationIds.ToImmutableArray(),
Sources = sources.ToImmutableArray(),
ObservationHashes = hashes.ToImmutableArray(),
CvssProvenance = cvssScore is not null && options.IncludeCvss
? ToFieldProvenance(cvssScore.Provenance)
: null,
KevProvenance = signal.KevStatus is not null && options.IncludeKev
? ToFieldProvenance(signal.KevStatus.Provenance)
: null,
FixProvenance = !signal.FixAvailability.IsDefaultOrEmpty && options.IncludeFixAvailability
? ToFieldProvenance(signal.FixAvailability.First().Provenance)
: null
};
}
private static PolicyStudioFieldProvenance ToFieldProvenance(VendorRiskProvenance provenance)
{
return new PolicyStudioFieldProvenance
{
Vendor = provenance.Vendor,
Source = provenance.Source,
ObservationHash = provenance.ObservationHash,
FetchedAt = provenance.FetchedAt,
UpstreamId = provenance.UpstreamId
};
}
}

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Concelier.Core.Risk.PolicyStudio;
namespace StellaOps.Concelier.Core.Risk;
@@ -10,7 +11,7 @@ public static class RiskServiceCollectionExtensions
{
/// <summary>
/// Adds risk signal and fix-availability services to the service collection.
/// Per CONCELIER-RISK-66-002, CONCELIER-RISK-67-001, and CONCELIER-RISK-69-001.
/// Per CONCELIER-RISK-66-002, CONCELIER-RISK-67-001, CONCELIER-RISK-68-001, and CONCELIER-RISK-69-001.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
@@ -23,6 +24,9 @@ public static class RiskServiceCollectionExtensions
services.TryAddSingleton<ISourceCoverageMetricsStore, InMemorySourceCoverageMetricsStore>();
services.TryAddSingleton<ISourceCoverageMetricsPublisher, SourceCoverageMetricsPublisher>();
// Register Policy Studio signal picker (CONCELIER-RISK-68-001)
services.TryAddSingleton<IPolicyStudioSignalPicker, PolicyStudioSignalPicker>();
// Register field change notification services (CONCELIER-RISK-69-001)
services.TryAddSingleton<IAdvisoryFieldChangeNotificationPublisher, InMemoryAdvisoryFieldChangeNotificationPublisher>();
services.TryAddSingleton<IAdvisoryFieldChangeEmitter, AdvisoryFieldChangeEmitter>();

View File

@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Cronos" Version="0.10.0" />
<PackageReference Include="StellaOps.Policy.AuthSignals" Version="0.1.0-alpha" />
</ItemGroup>

View File

@@ -0,0 +1,150 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Concelier.Core.VexLens;
/// <summary>
/// Interface for providing canonical advisory keys and cross-links for VEX Lens consumption.
/// Per CONCELIER-VEXLENS-30-001, ensures advisory key consistency without merges.
/// </summary>
public interface IVexLensAdvisoryKeyProvider
{
/// <summary>
/// Gets the canonical advisory key for a given advisory ID.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryId">Advisory identifier (may be original or alias).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Canonical advisory key with cross-links, or null if not found.</returns>
Task<VexLensCanonicalKey?> GetCanonicalKeyAsync(
string tenantId,
string advisoryId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets canonical advisory keys for multiple advisory IDs in batch.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryIds">Advisory identifiers.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Dictionary mapping input IDs to their canonical keys.</returns>
Task<ImmutableDictionary<string, VexLensCanonicalKey>> GetCanonicalKeysBatchAsync(
string tenantId,
IEnumerable<string> advisoryIds,
CancellationToken cancellationToken = default);
/// <summary>
/// Resolves an advisory by alias to its canonical key.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="alias">Alias to resolve (e.g., GHSA-xxx for a CVE).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Canonical key if alias exists, or null.</returns>
Task<VexLensCanonicalKey?> ResolveByAliasAsync(
string tenantId,
string alias,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets cross-links for an advisory (all known aliases and their sources).
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="advisoryId">Advisory identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Cross-links with provenance.</returns>
Task<VexLensCrossLinks?> GetCrossLinksAsync(
string tenantId,
string advisoryId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Canonical advisory key for VEX Lens correlation.
/// Per CONTRACT-ADVISORY-KEY-001.
/// </summary>
public sealed record VexLensCanonicalKey
{
/// <summary>
/// The canonical advisory key used for correlation.
/// CVE identifiers remain as-is; others are prefixed with scope (ECO:, VND:, DST:, UNK:).
/// </summary>
public required string AdvisoryKey { get; init; }
/// <summary>
/// Scope/authority level of the advisory.
/// </summary>
public required VexLensAdvisoryScope Scope { get; init; }
/// <summary>
/// Original identifier that was canonicalized.
/// </summary>
public required string OriginalId { get; init; }
/// <summary>
/// Identifier type (cve, ghsa, rhsa, dsa, usn, msrc, other).
/// </summary>
public required string Type { get; init; }
/// <summary>
/// All known aliases for this advisory.
/// </summary>
public ImmutableArray<VexLensAdvisoryLink> Links { get; init; } = ImmutableArray<VexLensAdvisoryLink>.Empty;
/// <summary>
/// Tenant ID for scoping.
/// </summary>
public required string TenantId { get; init; }
}
/// <summary>
/// Advisory scope/authority level per CONTRACT-ADVISORY-KEY-001.
/// </summary>
public enum VexLensAdvisoryScope
{
/// <summary>Unknown or unclassified scope.</summary>
Unknown = 0,
/// <summary>Global identifiers (CVE).</summary>
Global = 1,
/// <summary>Ecosystem-specific (GHSA).</summary>
Ecosystem = 2,
/// <summary>Vendor-specific (RHSA, MSRC).</summary>
Vendor = 3,
/// <summary>Distribution-specific (DSA, USN).</summary>
Distribution = 4
}
/// <summary>
/// Link to an original or alias advisory identifier.
/// </summary>
public sealed record VexLensAdvisoryLink
{
/// <summary>
/// The advisory identifier value.
/// </summary>
public required string Identifier { get; init; }
/// <summary>
/// Identifier type (cve, ghsa, rhsa, dsa, usn, msrc, other).
/// </summary>
public required string Type { get; init; }
/// <summary>
/// True if this is the original identifier provided at ingest time.
/// </summary>
public bool IsOriginal { get; init; }
/// <summary>
/// Source that provided this identifier.
/// </summary>
public string? Source { get; init; }
/// <summary>
/// When this link was discovered.
/// </summary>
public DateTimeOffset? DiscoveredAt { get; init; }
}

View File

@@ -0,0 +1,417 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core.Linksets;
namespace StellaOps.Concelier.Core.VexLens;
/// <summary>
/// Default implementation of <see cref="IVexLensAdvisoryKeyProvider"/>.
/// Per CONCELIER-VEXLENS-30-001, provides advisory key consistency for VEX Lens consumption.
/// </summary>
public sealed partial class VexLensAdvisoryKeyProvider : IVexLensAdvisoryKeyProvider
{
private readonly IAdvisoryLinksetLookup _linksetLookup;
private readonly ILogger<VexLensAdvisoryKeyProvider> _logger;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new instance of <see cref="VexLensAdvisoryKeyProvider"/>.
/// </summary>
public VexLensAdvisoryKeyProvider(
IAdvisoryLinksetLookup linksetLookup,
ILogger<VexLensAdvisoryKeyProvider> logger,
TimeProvider timeProvider)
{
_linksetLookup = linksetLookup ?? throw new ArgumentNullException(nameof(linksetLookup));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
/// <inheritdoc />
public async Task<VexLensCanonicalKey?> GetCanonicalKeyAsync(
string tenantId,
string advisoryId,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryId);
_logger.LogDebug(
"Getting canonical key for VEX Lens: tenant={TenantId}, advisory={AdvisoryId}",
tenantId, advisoryId);
// First, canonicalize the input advisory ID
var canonicalKey = Canonicalize(advisoryId);
var scope = DetermineScope(advisoryId);
var type = DetermineType(advisoryId);
// Look up linksets to get cross-links
var linksets = await _linksetLookup.FindByTenantAsync(
tenantId,
advisoryIds: new[] { advisoryId },
sources: null,
cursor: null,
limit: 100,
cancellationToken).ConfigureAwait(false);
var links = new List<VexLensAdvisoryLink>
{
new VexLensAdvisoryLink
{
Identifier = advisoryId,
Type = type,
IsOriginal = true
}
};
// Collect aliases from linksets
foreach (var linkset in linksets)
{
// The linkset may have normalized data with additional identifiers
if (linkset.Normalized is not null)
{
// Collect any additional identifiers from normalized data
// (implementation depends on linkset structure)
}
}
return new VexLensCanonicalKey
{
AdvisoryKey = canonicalKey,
Scope = scope,
OriginalId = advisoryId,
Type = type,
Links = links.ToImmutableArray(),
TenantId = tenantId
};
}
/// <inheritdoc />
public async Task<ImmutableDictionary<string, VexLensCanonicalKey>> GetCanonicalKeysBatchAsync(
string tenantId,
IEnumerable<string> advisoryIds,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentNullException.ThrowIfNull(advisoryIds);
var idList = advisoryIds.ToList();
if (idList.Count == 0)
{
return ImmutableDictionary<string, VexLensCanonicalKey>.Empty;
}
_logger.LogDebug(
"Getting canonical keys batch for VEX Lens: tenant={TenantId}, count={Count}",
tenantId, idList.Count);
var builder = ImmutableDictionary.CreateBuilder<string, VexLensCanonicalKey>();
foreach (var advisoryId in idList)
{
var key = await GetCanonicalKeyAsync(tenantId, advisoryId, cancellationToken)
.ConfigureAwait(false);
if (key is not null)
{
builder[advisoryId] = key;
}
}
return builder.ToImmutable();
}
/// <inheritdoc />
public async Task<VexLensCanonicalKey?> ResolveByAliasAsync(
string tenantId,
string alias,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(alias);
_logger.LogDebug(
"Resolving advisory by alias for VEX Lens: tenant={TenantId}, alias={Alias}",
tenantId, alias);
// Try to find linksets that contain this alias
var linksets = await _linksetLookup.FindByTenantAsync(
tenantId,
advisoryIds: new[] { alias },
sources: null,
cursor: null,
limit: 1,
cancellationToken).ConfigureAwait(false);
if (linksets.Count == 0)
{
return null;
}
var linkset = linksets.First();
return new VexLensCanonicalKey
{
AdvisoryKey = Canonicalize(linkset.AdvisoryId),
Scope = DetermineScope(linkset.AdvisoryId),
OriginalId = linkset.AdvisoryId,
Type = DetermineType(linkset.AdvisoryId),
Links = ImmutableArray.Create(new VexLensAdvisoryLink
{
Identifier = alias,
Type = DetermineType(alias),
IsOriginal = false,
Source = linkset.Source
}),
TenantId = tenantId
};
}
/// <inheritdoc />
public async Task<VexLensCrossLinks?> GetCrossLinksAsync(
string tenantId,
string advisoryId,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryId);
_logger.LogDebug(
"Getting cross-links for VEX Lens: tenant={TenantId}, advisory={AdvisoryId}",
tenantId, advisoryId);
var linksets = await _linksetLookup.FindByTenantAsync(
tenantId,
advisoryIds: new[] { advisoryId },
sources: null,
cursor: null,
limit: 100,
cancellationToken).ConfigureAwait(false);
if (linksets.Count == 0)
{
return null;
}
var canonicalKey = Canonicalize(advisoryId);
var now = _timeProvider.GetUtcNow();
// Collect observations and sources
var observations = new List<VexLensObservationRef>();
var linksetRefs = new List<VexLensLinksetRef>();
var sourceStats = new Dictionary<string, (int count, DateTimeOffset latest)>(StringComparer.OrdinalIgnoreCase);
var identifiers = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { advisoryId };
foreach (var linkset in linksets)
{
// Add observation refs
foreach (var obsId in linkset.ObservationIds)
{
observations.Add(new VexLensObservationRef
{
ObservationId = obsId,
Source = linkset.Source,
ContentHash = linkset.Provenance?.ObservationHashes?.FirstOrDefault() ?? "unknown",
CreatedAt = linkset.CreatedAt,
UpdatedAt = linkset.CreatedAt
});
}
// Add linkset ref
linksetRefs.Add(new VexLensLinksetRef
{
LinksetId = $"{linkset.TenantId}:{linkset.Source}:{linkset.AdvisoryId}",
Source = linkset.Source,
ObservationCount = linkset.ObservationIds.Length,
Confidence = linkset.Confidence,
CreatedAt = linkset.CreatedAt
});
// Track source statistics
if (!sourceStats.TryGetValue(linkset.Source, out var stats))
{
stats = (0, DateTimeOffset.MinValue);
}
sourceStats[linkset.Source] = (
stats.count + linkset.ObservationIds.Length,
linkset.CreatedAt > stats.latest ? linkset.CreatedAt : stats.latest
);
}
var sources = sourceStats.Select(kvp => new VexLensSourceRef
{
SourceId = kvp.Key,
ObservationCount = kvp.Value.count,
LatestObservationAt = kvp.Value.latest
}).ToImmutableArray();
var identifierLinks = identifiers.Select(id => new VexLensAdvisoryLink
{
Identifier = id,
Type = DetermineType(id),
IsOriginal = string.Equals(id, advisoryId, StringComparison.OrdinalIgnoreCase)
}).ToImmutableArray();
// Compute content hash for provenance
var contentHash = ComputeContentHash(canonicalKey, observations, linksetRefs);
return new VexLensCrossLinks
{
AdvisoryKey = canonicalKey,
TenantId = tenantId,
Identifiers = identifierLinks,
Observations = observations.ToImmutableArray(),
Linksets = linksetRefs.ToImmutableArray(),
Sources = sources,
UpdatedAt = now,
Provenance = new VexLensCrossLinksProvenance
{
ContentHash = contentHash,
ComputedAt = now
}
};
}
/// <summary>
/// Canonicalizes an advisory ID per CONTRACT-ADVISORY-KEY-001.
/// </summary>
private static string Canonicalize(string advisoryId)
{
var trimmed = advisoryId.Trim().ToUpperInvariant();
// CVE identifiers remain as-is
if (CvePattern().IsMatch(trimmed))
{
return trimmed;
}
// GHSA identifiers get ECO: prefix
if (GhsaPattern().IsMatch(trimmed))
{
return $"ECO:{trimmed}";
}
// RHSA/RHBA/RHEA get VND: prefix
if (RhPattern().IsMatch(trimmed))
{
return $"VND:{trimmed}";
}
// DSA gets DST: prefix
if (DsaPattern().IsMatch(trimmed))
{
return $"DST:{trimmed}";
}
// USN gets DST: prefix
if (UsnPattern().IsMatch(trimmed))
{
return $"DST:{trimmed}";
}
// MSRC (ADV-xxxx) gets VND: prefix
if (MsrcPattern().IsMatch(trimmed))
{
return $"VND:{trimmed}";
}
// Unknown scope
return $"UNK:{trimmed}";
}
private static VexLensAdvisoryScope DetermineScope(string advisoryId)
{
var trimmed = advisoryId.Trim().ToUpperInvariant();
if (CvePattern().IsMatch(trimmed))
return VexLensAdvisoryScope.Global;
if (GhsaPattern().IsMatch(trimmed))
return VexLensAdvisoryScope.Ecosystem;
if (RhPattern().IsMatch(trimmed) || MsrcPattern().IsMatch(trimmed))
return VexLensAdvisoryScope.Vendor;
if (DsaPattern().IsMatch(trimmed) || UsnPattern().IsMatch(trimmed))
return VexLensAdvisoryScope.Distribution;
return VexLensAdvisoryScope.Unknown;
}
private static string DetermineType(string advisoryId)
{
var trimmed = advisoryId.Trim().ToUpperInvariant();
if (CvePattern().IsMatch(trimmed))
return "cve";
if (GhsaPattern().IsMatch(trimmed))
return "ghsa";
if (trimmed.StartsWith("RHSA-", StringComparison.OrdinalIgnoreCase))
return "rhsa";
if (trimmed.StartsWith("DSA-", StringComparison.OrdinalIgnoreCase))
return "dsa";
if (trimmed.StartsWith("USN-", StringComparison.OrdinalIgnoreCase))
return "usn";
if (trimmed.StartsWith("ADV-", StringComparison.OrdinalIgnoreCase))
return "msrc";
return "other";
}
private static string ComputeContentHash(
string advisoryKey,
IEnumerable<VexLensObservationRef> observations,
IEnumerable<VexLensLinksetRef> linksets)
{
var builder = new StringBuilder();
builder.Append(advisoryKey);
builder.Append('|');
foreach (var obs in observations.OrderBy(o => o.ObservationId, StringComparer.Ordinal))
{
builder.Append(obs.ObservationId);
builder.Append(':');
builder.Append(obs.ContentHash);
builder.Append('|');
}
foreach (var ls in linksets.OrderBy(l => l.LinksetId, StringComparer.Ordinal))
{
builder.Append(ls.LinksetId);
builder.Append('|');
}
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(builder.ToString()));
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
[GeneratedRegex(@"^CVE-\d{4}-\d{4,}$", RegexOptions.IgnoreCase)]
private static partial Regex CvePattern();
[GeneratedRegex(@"^GHSA-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$", RegexOptions.IgnoreCase)]
private static partial Regex GhsaPattern();
[GeneratedRegex(@"^RH[A-Z]{2}-\d{4}:\d+$", RegexOptions.IgnoreCase)]
private static partial Regex RhPattern();
[GeneratedRegex(@"^DSA-\d+(-\d+)?$", RegexOptions.IgnoreCase)]
private static partial Regex DsaPattern();
[GeneratedRegex(@"^USN-\d+(-\d+)?$", RegexOptions.IgnoreCase)]
private static partial Regex UsnPattern();
[GeneratedRegex(@"^ADV-\d{4}-\d+$", RegexOptions.IgnoreCase)]
private static partial Regex MsrcPattern();
}

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Immutable;
namespace StellaOps.Concelier.Core.VexLens;
/// <summary>
/// Cross-links between Concelier advisory observations and VEX Lens.
/// Per CONCELIER-VEXLENS-30-001, provides evidence citations without merges.
/// </summary>
public sealed record VexLensCrossLinks
{
/// <summary>
/// Canonical advisory key.
/// </summary>
public required string AdvisoryKey { get; init; }
/// <summary>
/// Tenant identifier.
/// </summary>
public required string TenantId { get; init; }
/// <summary>
/// All known identifiers for this advisory (CVE, GHSA, vendor IDs, etc.).
/// </summary>
public ImmutableArray<VexLensAdvisoryLink> Identifiers { get; init; } = ImmutableArray<VexLensAdvisoryLink>.Empty;
/// <summary>
/// Observation references from Concelier.
/// </summary>
public ImmutableArray<VexLensObservationRef> Observations { get; init; } = ImmutableArray<VexLensObservationRef>.Empty;
/// <summary>
/// Linkset references (if Link-Not-Merge is enabled).
/// </summary>
public ImmutableArray<VexLensLinksetRef> Linksets { get; init; } = ImmutableArray<VexLensLinksetRef>.Empty;
/// <summary>
/// Sources that contributed observations.
/// </summary>
public ImmutableArray<VexLensSourceRef> Sources { get; init; } = ImmutableArray<VexLensSourceRef>.Empty;
/// <summary>
/// When the cross-links were last updated.
/// </summary>
public DateTimeOffset UpdatedAt { get; init; }
/// <summary>
/// Provenance metadata for the cross-links.
/// </summary>
public VexLensCrossLinksProvenance? Provenance { get; init; }
}
/// <summary>
/// Reference to a Concelier observation for VEX Lens.
/// </summary>
public sealed record VexLensObservationRef
{
/// <summary>
/// Observation identifier.
/// </summary>
public required string ObservationId { get; init; }
/// <summary>
/// Source that provided this observation.
/// </summary>
public required string Source { get; init; }
/// <summary>
/// Content hash of the observation.
/// </summary>
public required string ContentHash { get; init; }
/// <summary>
/// When the observation was created.
/// </summary>
public DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// When the observation was last updated.
/// </summary>
public DateTimeOffset UpdatedAt { get; init; }
/// <summary>
/// Upstream ID from the source.
/// </summary>
public string? UpstreamId { get; init; }
}
/// <summary>
/// Reference to a Concelier linkset for VEX Lens.
/// </summary>
public sealed record VexLensLinksetRef
{
/// <summary>
/// Linkset identifier.
/// </summary>
public required string LinksetId { get; init; }
/// <summary>
/// Source that the linkset is scoped to.
/// </summary>
public required string Source { get; init; }
/// <summary>
/// Number of observations in the linkset.
/// </summary>
public int ObservationCount { get; init; }
/// <summary>
/// Confidence score for the linkset (0.0 - 1.0).
/// </summary>
public double? Confidence { get; init; }
/// <summary>
/// When the linkset was created.
/// </summary>
public DateTimeOffset CreatedAt { get; init; }
}
/// <summary>
/// Reference to a source that contributed observations.
/// </summary>
public sealed record VexLensSourceRef
{
/// <summary>
/// Source identifier.
/// </summary>
public required string SourceId { get; init; }
/// <summary>
/// Source display name.
/// </summary>
public string? DisplayName { get; init; }
/// <summary>
/// Source type (vendor, distribution, ecosystem).
/// </summary>
public string? Type { get; init; }
/// <summary>
/// Number of observations from this source.
/// </summary>
public int ObservationCount { get; init; }
/// <summary>
/// Most recent observation timestamp from this source.
/// </summary>
public DateTimeOffset? LatestObservationAt { get; init; }
}
/// <summary>
/// Provenance metadata for cross-links.
/// </summary>
public sealed record VexLensCrossLinksProvenance
{
/// <summary>
/// Hash of the cross-links for integrity verification.
/// </summary>
public required string ContentHash { get; init; }
/// <summary>
/// When the cross-links were computed.
/// </summary>
public required DateTimeOffset ComputedAt { get; init; }
/// <summary>
/// Version of the cross-link algorithm.
/// </summary>
public string Version { get; init; } = "1.0";
/// <summary>
/// Job ID that computed these cross-links (if applicable).
/// </summary>
public string? JobId { get; init; }
}

View File

@@ -0,0 +1,39 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace StellaOps.Concelier.Core.VexLens;
/// <summary>
/// Service collection extensions for VEX Lens integration.
/// Per CONCELIER-VEXLENS-30-001.
/// </summary>
public static class VexLensServiceCollectionExtensions
{
/// <summary>
/// Adds VEX Lens advisory key provider services to the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddConcelierVexLensServices(this IServiceCollection services)
{
services.TryAddSingleton<IVexLensAdvisoryKeyProvider, VexLensAdvisoryKeyProvider>();
// Ensure TimeProvider is registered
services.TryAddSingleton(TimeProvider.System);
return services;
}
/// <summary>
/// Adds a custom implementation of <see cref="IVexLensAdvisoryKeyProvider"/>.
/// </summary>
/// <typeparam name="TProvider">The provider implementation type.</typeparam>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddVexLensAdvisoryKeyProvider<TProvider>(this IServiceCollection services)
where TProvider : class, IVexLensAdvisoryKeyProvider
{
services.AddSingleton<IVexLensAdvisoryKeyProvider, TProvider>();
return services;
}
}

View File

@@ -16,9 +16,9 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -15,8 +15,8 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Semver" Version="2.3.0" />
<ProjectReference Include="../StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
</Project>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageReference Include="Semver" Version="2.3.0" />
<ProjectReference Include="../StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
@@ -7,7 +7,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />

View File

@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Tests\**\*.cs" />
<None Remove="Tests\**\*" />
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Tests\**\*.cs" />
<None Remove="Tests\**\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Bson" Version="3.5.0" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Bson" Version="3.5.0" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mongo2Go" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0" />
<PackageReference Include="xunit" Version="2.9.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mongo2Go" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0" />
<PackageReference Include="xunit" Version="2.9.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,308 @@
using System.Collections.Immutable;
using System.Text.Json;
using Microsoft.Extensions.Options;
using StellaOps.Aoc;
using StellaOps.Concelier.Core.Aoc;
using StellaOps.Concelier.RawModels;
namespace StellaOps.Concelier.Core.Tests.Aoc;
/// <summary>
/// Tests for <see cref="AdvisorySchemaValidator"/> per WEB-AOC-19-002.
/// Covers ERR_AOC_001 (forbidden), ERR_AOC_002 (merge), ERR_AOC_006 (derived), ERR_AOC_007 (unknown).
/// </summary>
public sealed class AdvisorySchemaValidatorTests
{
private static readonly AocGuardOptions GuardOptions = AocGuardOptions.Default;
private static AdvisoryRawDocument CreateValidDocument(string tenant = "tenant-a")
{
using var rawDocument = JsonDocument.Parse("""{"id":"demo"}""");
return new AdvisoryRawDocument(
Tenant: tenant,
Source: new RawSourceMetadata("vendor-x", "connector-y", "1.0.0"),
Upstream: new RawUpstreamMetadata(
UpstreamId: "GHSA-xxxx",
DocumentVersion: "1",
RetrievedAt: DateTimeOffset.UtcNow,
ContentHash: "sha256:abc",
Signature: new RawSignatureMetadata(false),
Provenance: ImmutableDictionary<string, string>.Empty),
Content: new RawContent(
Format: "OSV",
SpecVersion: "1.0",
Raw: rawDocument.RootElement.Clone()),
Identifiers: new RawIdentifiers(
Aliases: ImmutableArray.Create("GHSA-xxxx"),
PrimaryId: "GHSA-xxxx"),
Linkset: new RawLinkset
{
Aliases = ImmutableArray<string>.Empty,
PackageUrls = ImmutableArray<string>.Empty,
Cpes = ImmutableArray<string>.Empty,
References = ImmutableArray<RawReference>.Empty,
ReconciledFrom = ImmutableArray<string>.Empty,
Notes = ImmutableDictionary<string, string>.Empty
},
Links: ImmutableArray<RawLink>.Empty);
}
private static AdvisorySchemaValidator CreateValidator()
=> new(new AocWriteGuard(), Options.Create(GuardOptions));
[Fact]
public void ValidateSchema_AllowsValidDocument()
{
var validator = CreateValidator();
var document = CreateValidDocument();
var result = validator.ValidateSchema(document);
Assert.True(result.IsValid);
Assert.Empty(result.Violations);
}
[Fact]
public void ValidateForbiddenFields_ReturnsSuccessForValidDocument()
{
var validator = CreateValidator();
var document = CreateValidDocument();
var result = validator.ValidateForbiddenFields(document);
Assert.True(result.IsValid);
}
[Fact]
public void ValidateDerivedFields_ReturnsSuccessForValidDocument()
{
var validator = CreateValidator();
var document = CreateValidDocument();
var result = validator.ValidateDerivedFields(document);
Assert.True(result.IsValid);
}
[Fact]
public void ValidateAllowedFields_ReturnsSuccessForValidDocument()
{
var validator = CreateValidator();
var document = CreateValidDocument();
var result = validator.ValidateAllowedFields(document);
Assert.True(result.IsValid);
}
[Fact]
public void ValidateMergeAttempt_ReturnsSuccessForValidDocument()
{
var validator = CreateValidator();
var document = CreateValidDocument();
var result = validator.ValidateMergeAttempt(document);
Assert.True(result.IsValid);
}
// Direct IAocGuard tests for ERR_AOC_001, ERR_AOC_002, ERR_AOC_006, ERR_AOC_007
// These test the underlying guard behavior with arbitrary JSON
[Fact]
public void AocGuard_DetectsForbiddenField_ERR_AOC_001()
{
var guard = new AocWriteGuard();
using var jsonDoc = JsonDocument.Parse("""
{
"tenant": "test",
"severity": "high",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.ForbiddenField &&
v.ErrorCode == "ERR_AOC_001" &&
v.Path == "/severity");
}
[Fact]
public void AocGuard_DetectsMergedFromField_ERR_AOC_001()
{
var guard = new AocWriteGuard();
using var jsonDoc = JsonDocument.Parse("""
{
"tenant": "test",
"merged_from": ["obs-1", "obs-2"],
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.ForbiddenField &&
v.ErrorCode == "ERR_AOC_001" &&
v.Path == "/merged_from");
}
[Fact]
public void AocGuard_DetectsDerivedField_ERR_AOC_006()
{
var guard = new AocWriteGuard();
using var jsonDoc = JsonDocument.Parse("""
{
"tenant": "test",
"effective_status": "affected",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.DerivedFindingDetected &&
v.ErrorCode == "ERR_AOC_006" &&
v.Path == "/effective_status");
}
[Fact]
public void AocGuard_DetectsUnknownField_ERR_AOC_007()
{
var guard = new AocWriteGuard();
using var jsonDoc = JsonDocument.Parse("""
{
"tenant": "test",
"unknown_custom_field": "value",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.UnknownField &&
v.ErrorCode == "ERR_AOC_007" &&
v.Path == "/unknown_custom_field");
}
[Theory]
[InlineData("cvss")]
[InlineData("cvss_vector")]
[InlineData("consensus_provider")]
[InlineData("reachability")]
[InlineData("asset_criticality")]
[InlineData("risk_score")]
public void AocGuard_DetectsAllForbiddenFields(string forbiddenField)
{
var guard = new AocWriteGuard();
var json = $$"""
{
"tenant": "test",
"{{forbiddenField}}": "forbidden_value",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""";
using var jsonDoc = JsonDocument.Parse(json);
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.ForbiddenField &&
v.ErrorCode == "ERR_AOC_001");
}
[Theory]
[InlineData("effective_range")]
[InlineData("effective_severity")]
[InlineData("effective_cvss")]
public void AocGuard_DetectsAllDerivedFields(string derivedField)
{
var guard = new AocWriteGuard();
var json = $$"""
{
"tenant": "test",
"{{derivedField}}": "derived_value",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""";
using var jsonDoc = JsonDocument.Parse(json);
var result = guard.Validate(jsonDoc.RootElement, GuardOptions);
Assert.False(result.IsValid);
// Derived fields (effective_*) trigger both ForbiddenField and DerivedFindingDetected
// if they're in the forbidden list, otherwise just DerivedFindingDetected
Assert.Contains(result.Violations, v =>
v.Code == AocViolationCode.DerivedFindingDetected &&
v.ErrorCode == "ERR_AOC_006");
}
}

View File

@@ -6,6 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<!-- Disable Concelier Testing infra which requires Storage.Mongo -->
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
@@ -15,6 +17,6 @@
<ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
<!-- Test packages inherited from Directory.Build.props -->
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -17,13 +17,13 @@
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.7.303.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0" />
<PackageReference Include="Npgsql" Version="8.0.3" />
</ItemGroup>

View File

@@ -9,7 +9,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.EvidenceLocker.Core\StellaOps.EvidenceLocker.Core.csproj" />

View File

@@ -1,43 +1,43 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<UserSecretsId>dotnet-StellaOps.EvidenceLocker.Worker-c74bd053-c14b-412b-a177-12e15fdbe207</UserSecretsId>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.EvidenceLocker.Core\StellaOps.EvidenceLocker.Core.csproj"/>
<ProjectReference Include="..\StellaOps.EvidenceLocker.Infrastructure\StellaOps.EvidenceLocker.Infrastructure.csproj"/>
</ItemGroup>
</Project>
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<UserSecretsId>dotnet-StellaOps.EvidenceLocker.Worker-c74bd053-c14b-412b-a177-12e15fdbe207</UserSecretsId>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.EvidenceLocker.Core\StellaOps.EvidenceLocker.Core.csproj"/>
<ProjectReference Include="..\StellaOps.EvidenceLocker.Infrastructure\StellaOps.EvidenceLocker.Infrastructure.csproj"/>
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />

View File

@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.7.305.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Export\StellaOps.Excititor.Export.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.7.305.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Export\StellaOps.Excititor.Export.csproj" />
</ItemGroup>
</Project>

View File

@@ -7,9 +7,9 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />

View File

@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="..\StellaOps.Excititor.Storage.Mongo\StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />

View File

@@ -8,8 +8,8 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Attestation/StellaOps.Excititor.Attestation.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Attestation/StellaOps.Excititor.Attestation.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,22 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Connectors.Cisco.CSAF/StellaOps.Excititor.Connectors.Cisco.CSAF.csproj" />
</ItemGroup>
</Project>
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Connectors.Cisco.CSAF/StellaOps.Excititor.Connectors.Cisco.CSAF.csproj" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -1,23 +1,23 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Storage.Mongo/StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
</Project>
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Storage.Mongo/StellaOps.Excititor.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\..\..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
</ItemGroup>
</Project>

View File

@@ -12,8 +12,8 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="EphemeralMongo" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all" />

View File

@@ -1,8 +1,8 @@
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using StellaOps.Cryptography;
namespace StellaOps.ExportCenter.RiskBundles;
@@ -28,11 +28,13 @@ public sealed record RiskBundleManifestDsseSignature(
public sealed class HmacRiskBundleManifestSigner : IRiskBundleManifestSigner, IRiskBundleArchiveSigner
{
private const string DefaultPayloadType = "application/stellaops.risk-bundle.provider-manifest+json";
private readonly ICryptoHmac _cryptoHmac;
private readonly byte[] _key;
private readonly string _keyId;
public HmacRiskBundleManifestSigner(string key, string keyId)
public HmacRiskBundleManifestSigner(ICryptoHmac cryptoHmac, string key, string keyId)
{
_cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException("Signing key cannot be empty.", nameof(key));
@@ -48,7 +50,7 @@ public sealed class HmacRiskBundleManifestSigner : IRiskBundleManifestSigner, IR
cancellationToken.ThrowIfCancellationRequested();
var pae = CreatePreAuthenticationEncoding(DefaultPayloadType, manifestJson);
var signature = ComputeHmac(pae, _key);
var signature = _cryptoHmac.ComputeHmacBase64ForPurpose(_key, pae, HmacPurpose.Signing);
var document = new RiskBundleManifestSignatureDocument(
DefaultPayloadType,
@@ -58,7 +60,7 @@ public sealed class HmacRiskBundleManifestSigner : IRiskBundleManifestSigner, IR
return Task.FromResult(document);
}
public Task<string> SignArchiveAsync(Stream archiveStream, CancellationToken cancellationToken = default)
public async Task<string> SignArchiveAsync(Stream archiveStream, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(archiveStream);
cancellationToken.ThrowIfCancellationRequested();
@@ -69,16 +71,8 @@ public sealed class HmacRiskBundleManifestSigner : IRiskBundleManifestSigner, IR
}
archiveStream.Position = 0;
using var hmac = new HMACSHA256(_key);
var signature = hmac.ComputeHash(archiveStream);
var signature = await _cryptoHmac.ComputeHmacForPurposeAsync(_key, archiveStream, HmacPurpose.Signing, cancellationToken);
archiveStream.Position = 0;
return Task.FromResult(Convert.ToBase64String(signature));
}
private static string ComputeHmac(byte[] pae, byte[] key)
{
using var hmac = new HMACSHA256(key);
var signature = hmac.ComputeHash(pae);
return Convert.ToBase64String(signature);
}

View File

@@ -9,9 +9,10 @@
<ItemGroup>
<ProjectReference Include="../StellaOps.ExportCenter/StellaOps.ExportCenter.Infrastructure/StellaOps.ExportCenter.Infrastructure.csproj" />
<ProjectReference Include="../StellaOps.ExportCenter/StellaOps.ExportCenter.Core/StellaOps.ExportCenter.Core.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -7,6 +7,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Linq;
using StellaOps.Cryptography;
namespace StellaOps.ExportCenter.Core.DevPortalOffline;
@@ -51,9 +52,11 @@ public sealed class DevPortalOfflineBundleBuilder
};
private readonly TimeProvider _timeProvider;
private readonly ICryptoHash _cryptoHash;
public DevPortalOfflineBundleBuilder(TimeProvider? timeProvider = null)
public DevPortalOfflineBundleBuilder(ICryptoHash cryptoHash, TimeProvider? timeProvider = null)
{
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_timeProvider = timeProvider ?? TimeProvider.System;
}
@@ -130,7 +133,7 @@ public sealed class DevPortalOfflineBundleBuilder
entries);
var manifestJson = JsonSerializer.Serialize(manifest, SerializerOptions);
var rootHash = ComputeSha256(manifestJson);
var rootHash = ComputeContentHash(manifestJson);
var checksums = BuildChecksums(rootHash, collected);
var instructions = BuildInstructions(manifest);
var verificationScript = BuildVerificationScript();
@@ -141,7 +144,7 @@ public sealed class DevPortalOfflineBundleBuilder
return new DevPortalOfflineBundleResult(manifest, manifestJson, checksums, rootHash, bundleStream);
}
private static bool CollectDirectory(
private bool CollectDirectory(
string? directory,
string category,
string prefix,
@@ -179,36 +182,17 @@ public sealed class DevPortalOfflineBundleBuilder
return true;
}
private static FileMetadata CreateFileMetadata(string category, string canonicalPath, string sourcePath)
private FileMetadata CreateFileMetadata(string category, string canonicalPath, string sourcePath)
{
using var stream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 128 * 1024, FileOptions.SequentialScan);
using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
var buffer = ArrayPool<byte>.Shared.Rent(128 * 1024);
long totalBytes = 0;
try
{
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
hash.AppendData(buffer, 0, read);
totalBytes += read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
var sha = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
return new FileMetadata(category, canonicalPath, sourcePath, totalBytes, sha, GetContentType(sourcePath));
var fileBytes = File.ReadAllBytes(sourcePath);
var sha = _cryptoHash.ComputeHashHexForPurpose(fileBytes, HashPurpose.Content);
return new FileMetadata(category, canonicalPath, sourcePath, fileBytes.Length, sha, GetContentType(sourcePath));
}
private static string ComputeSha256(string content)
private string ComputeContentHash(string content)
{
var bytes = Encoding.UTF8.GetBytes(content);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
return _cryptoHash.ComputeHashHexForPurpose(bytes, HashPurpose.Content);
}
private static string BuildChecksums(string rootHash, IReadOnlyCollection<FileMetadata> files)

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
@@ -12,10 +12,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\TimelineIndexer\StellaOps.TimelineIndexer\StellaOps.TimelineIndexer.Core\StellaOps.TimelineIndexer.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.ExportCenter.Core.DevPortalOffline;
namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
@@ -13,15 +14,18 @@ namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObjectStore
{
private readonly IOptionsMonitor<DevPortalOfflineStorageOptions> _options;
private readonly ICryptoHash _cryptoHash;
private readonly TimeProvider _timeProvider;
private readonly ILogger<FileSystemDevPortalOfflineObjectStore> _logger;
public FileSystemDevPortalOfflineObjectStore(
IOptionsMonitor<DevPortalOfflineStorageOptions> options,
ICryptoHash cryptoHash,
TimeProvider timeProvider,
ILogger<FileSystemDevPortalOfflineObjectStore> logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -40,8 +44,9 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
content.Seek(0, SeekOrigin.Begin);
// Write the content to file
using var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None);
using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
var buffer = ArrayPool<byte>.Shared.Rent(128 * 1024);
long totalBytes = 0;
@@ -51,7 +56,6 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
while ((read = await content.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
hash.AppendData(buffer, 0, read);
totalBytes += read;
}
}
@@ -61,9 +65,11 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
}
await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false);
content.Seek(0, SeekOrigin.Begin);
var sha = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
// Compute hash from the written file
content.Seek(0, SeekOrigin.Begin);
var sha = await _cryptoHash.ComputeHashHexForPurposeAsync(content, HashPurpose.Content, cancellationToken).ConfigureAwait(false);
content.Seek(0, SeekOrigin.Begin);
var createdAt = _timeProvider.GetUtcNow();
_logger.LogDebug("Stored devportal artefact at {Path} ({Bytes} bytes).", fullPath, totalBytes);

View File

@@ -1,12 +1,12 @@
using System;
using System.Buffers.Binary;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.ExportCenter.Core.DevPortalOffline;
namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
@@ -14,15 +14,18 @@ namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManifestSigner
{
private readonly IOptionsMonitor<DevPortalOfflineManifestSigningOptions> _options;
private readonly ICryptoHmac _cryptoHmac;
private readonly TimeProvider _timeProvider;
private readonly ILogger<HmacDevPortalOfflineManifestSigner> _logger;
public HmacDevPortalOfflineManifestSigner(
IOptionsMonitor<DevPortalOfflineManifestSigningOptions> options,
ICryptoHmac cryptoHmac,
TimeProvider timeProvider,
ILogger<HmacDevPortalOfflineManifestSigner> logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -49,7 +52,7 @@ public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManife
var signedAt = _timeProvider.GetUtcNow();
var payloadBytes = Encoding.UTF8.GetBytes(manifestJson);
var pae = BuildPreAuthEncoding(options.PayloadType, payloadBytes);
var signature = ComputeSignature(options, pae);
var signature = ComputeSignature(options, pae, _cryptoHmac);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
_logger.LogDebug("Signed devportal manifest for bundle {BundleId}.", bundleId);
@@ -82,12 +85,10 @@ public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManife
}
}
private static string ComputeSignature(DevPortalOfflineManifestSigningOptions options, byte[] pae)
private static string ComputeSignature(DevPortalOfflineManifestSigningOptions options, byte[] pae, ICryptoHmac cryptoHmac)
{
var secretBytes = Convert.FromBase64String(options.Secret);
using var hmac = new HMACSHA256(secretBytes);
var signatureBytes = hmac.ComputeHash(pae);
return Convert.ToBase64String(signatureBytes);
return cryptoHmac.ComputeHmacBase64ForPurpose(secretBytes, pae, HmacPurpose.Signing);
}
private static byte[] BuildPreAuthEncoding(string payloadType, byte[] payloadBytes)

View File

@@ -11,10 +11,11 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj" />
<ProjectReference Include="..\..\..\TimelineIndexer\StellaOps.TimelineIndexer\StellaOps.TimelineIndexer.Core\StellaOps.TimelineIndexer.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,121 +1,121 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="xunit.v3" Version="3.0.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"/>
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="xunit.v3" Version="3.0.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"/>
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
@@ -123,14 +123,14 @@
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
<ProjectReference Include="..\..\StellaOps.ExportCenter.RiskBundles\StellaOps.ExportCenter.RiskBundles.csproj" />
</ItemGroup>
</Project>
</ItemGroup>
</Project>

View File

@@ -9,7 +9,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj" />

View File

@@ -1,33 +1,33 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<UserSecretsId>dotnet-StellaOps.ExportCenter.Worker-d4cfd239-79d1-4d17-91d6-bb7a78770695</UserSecretsId>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/>
</ItemGroup>
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<UserSecretsId>dotnet-StellaOps.ExportCenter.Worker-d4cfd239-79d1-4d17-91d6-bb7a78770695</UserSecretsId>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0"/>
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Findings.Ledger.Options;
namespace StellaOps.Findings.Ledger.Services;
@@ -13,11 +13,13 @@ public interface IAttachmentUrlSigner
public sealed class AttachmentUrlSigner : IAttachmentUrlSigner
{
private readonly LedgerServiceOptions.AttachmentsOptions options;
private readonly ICryptoHmac _cryptoHmac;
private readonly byte[] secretKey;
public AttachmentUrlSigner(IOptions<LedgerServiceOptions> optionsAccessor)
public AttachmentUrlSigner(IOptions<LedgerServiceOptions> optionsAccessor, ICryptoHmac cryptoHmac)
{
ArgumentNullException.ThrowIfNull(optionsAccessor);
_cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
options = optionsAccessor.Value.Attachments;
secretKey = Encoding.UTF8.GetBytes(options.SignedUrlSecret ?? string.Empty);
if (secretKey.Length == 0)
@@ -33,8 +35,9 @@ public sealed class AttachmentUrlSigner : IAttachmentUrlSigner
var expires = now.Add(lifetime);
var expiresUnix = expires.ToUnixTimeSeconds();
var payload = $"{attachmentId}|{expiresUnix}";
using var hmac = new HMACSHA256(secretKey);
var signature = Base64UrlEncode(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)));
var payloadBytes = Encoding.UTF8.GetBytes(payload);
var signatureBytes = _cryptoHmac.ComputeHmacForPurpose(secretKey, payloadBytes, HmacPurpose.Authentication);
var signature = Base64UrlEncode(signatureBytes);
var baseUrl = options.SignedUrlBase.TrimEnd('/');
var url = $"{baseUrl}/{Uri.EscapeDataString(attachmentId)}?exp={expiresUnix}&sig={signature}";

View File

@@ -18,11 +18,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Npgsql" Version="7.0.7" />
</ItemGroup>

View File

@@ -9,12 +9,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="MongoDB.Bson" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
</ItemGroup>
</Project>

View File

@@ -7,6 +7,6 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More