compose and authority fixes. finish sprints.

This commit is contained in:
master
2026-02-17 21:59:47 +02:00
parent fb46a927ad
commit 49cdebe2f1
187 changed files with 23189 additions and 1439 deletions

View File

@@ -6,3 +6,4 @@ Source of truth: `docs/implplan/SPRINT_20260112_003_BE_csproj_audit_pending_appl
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-HOTLIST-SIGNALS-0001 | DONE | Hotlist apply for `src/Signals/StellaOps.Signals/StellaOps.Signals.csproj`; audit tracker updated. |
| MWD-001 | DONE | Implemented deterministic BTF fallback selection and metadata emission for runtime eBPF collection (`source_kind`, `source_path`, `source_digest`, `selection_reason`); verified with Signals and Scanner tests. |

View File

@@ -119,7 +119,8 @@ public static class ServiceCollectionExtensions
{
var logger = sp.GetRequiredService<ILogger<RuntimeSignalCollector>>();
var probeLoader = sp.GetRequiredService<IEbpfProbeLoader>();
return new RuntimeSignalCollector(logger, probeLoader);
var btfSelector = RuntimeBtfSourceSelector.CreateDefault(options.BtfSelectionOptions);
return new RuntimeSignalCollector(logger, probeLoader, btfSelector);
});
return services;
@@ -187,4 +188,9 @@ public sealed class EbpfEvidenceOptions
/// Collector options.
/// </summary>
public RuntimeEvidenceCollectorOptions CollectorOptions { get; set; } = new();
/// <summary>
/// Runtime BTF selection options.
/// </summary>
public RuntimeBtfSelectionOptions BtfSelectionOptions { get; set; } = new();
}

View File

@@ -50,6 +50,11 @@ public interface IRuntimeSignalCollector
/// <returns>True if eBPF probes can be loaded.</returns>
bool IsSupported();
/// <summary>
/// Gets deterministic BTF source metadata used for runtime collection.
/// </summary>
RuntimeBtfSelection GetBtfSelection();
/// <summary>
/// Gets available probe types on this system.
/// </summary>

View File

@@ -0,0 +1,251 @@
// <copyright file="RuntimeBtfSourceSelector.cs" company="StellaOps">
// SPDX-License-Identifier: BUSL-1.1
// </copyright>
using StellaOps.Signals.Ebpf.Schema;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace StellaOps.Signals.Ebpf.Services;
/// <summary>
/// Configuration for deterministic BTF source selection.
/// </summary>
public sealed record RuntimeBtfSelectionOptions
{
/// <summary>
/// Candidate full-kernel BTF (vmlinux) paths in deterministic order.
/// </summary>
public IReadOnlyList<string> ExternalVmlinuxPaths { get; init; } = [];
/// <summary>
/// Candidate split-BTF root directories in deterministic order.
/// </summary>
public IReadOnlyList<string> SplitBtfDirectories { get; init; } = [];
}
/// <summary>
/// Selects a deterministic BTF source for runtime eBPF collection.
/// </summary>
public sealed class RuntimeBtfSourceSelector
{
private const string KernelBtfPath = "/sys/kernel/btf/vmlinux";
private static readonly string[] DefaultSplitBtfDirectories =
[
"/var/lib/stellaops/btf/split",
"/usr/share/stellaops/btf/split",
"/usr/lib/stellaops/btf/split",
];
private readonly RuntimeBtfSelectionOptions _options;
private readonly Func<bool> _isLinuxPlatform;
private readonly Func<string, bool> _fileExists;
private readonly Func<string, byte[]> _readAllBytes;
private readonly Func<string> _kernelReleaseProvider;
private readonly Func<string> _kernelArchProvider;
/// <summary>
/// Creates a selector with explicit environment probes.
/// </summary>
public RuntimeBtfSourceSelector(
RuntimeBtfSelectionOptions? options = null,
Func<bool>? isLinuxPlatform = null,
Func<string, bool>? fileExists = null,
Func<string, byte[]>? readAllBytes = null,
Func<string>? kernelReleaseProvider = null,
Func<string>? kernelArchProvider = null)
{
_options = options ?? new RuntimeBtfSelectionOptions();
_isLinuxPlatform = isLinuxPlatform ?? (() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux));
_fileExists = fileExists ?? File.Exists;
_readAllBytes = readAllBytes ?? File.ReadAllBytes;
_kernelReleaseProvider = kernelReleaseProvider ?? GetKernelRelease;
_kernelArchProvider = kernelArchProvider ?? GetKernelArch;
}
/// <summary>
/// Creates a selector using host runtime probes.
/// </summary>
public static RuntimeBtfSourceSelector CreateDefault(RuntimeBtfSelectionOptions? options = null)
=> new(options: options);
/// <summary>
/// Resolves the BTF source with deterministic precedence.
/// </summary>
public RuntimeBtfSelection Resolve()
{
var kernelRelease = _kernelReleaseProvider();
var kernelArch = _kernelArchProvider();
if (!_isLinuxPlatform())
{
return new RuntimeBtfSelection
{
SourceKind = "unsupported",
SourcePath = null,
SourceDigest = null,
SelectionReason = "platform_not_linux",
KernelRelease = kernelRelease,
KernelArch = kernelArch,
};
}
if (TryCreateSelection(
sourceKind: "kernel",
sourcePath: KernelBtfPath,
selectionReason: "kernel_btf_present",
kernelRelease,
kernelArch,
out var kernelSelection))
{
return kernelSelection;
}
foreach (var candidate in NormalizePaths(_options.ExternalVmlinuxPaths))
{
if (TryCreateSelection(
sourceKind: "external-vmlinux",
sourcePath: candidate,
selectionReason: "external_vmlinux_configured",
kernelRelease,
kernelArch,
out var externalSelection))
{
return externalSelection;
}
}
foreach (var candidate in BuildSplitBtfCandidates(kernelRelease, kernelArch))
{
if (TryCreateSelection(
sourceKind: "split-btf",
sourcePath: candidate,
selectionReason: "split_btf_fallback",
kernelRelease,
kernelArch,
out var splitSelection))
{
return splitSelection;
}
}
return new RuntimeBtfSelection
{
SourceKind = "unavailable",
SourcePath = null,
SourceDigest = null,
SelectionReason = "no_btf_source_found",
KernelRelease = kernelRelease,
KernelArch = kernelArch,
};
}
private static IEnumerable<string> NormalizePaths(IEnumerable<string>? paths)
{
var seen = new HashSet<string>(StringComparer.Ordinal);
if (paths is null)
{
yield break;
}
foreach (var path in paths)
{
if (string.IsNullOrWhiteSpace(path))
{
continue;
}
var trimmed = path.Trim();
if (seen.Add(trimmed))
{
yield return trimmed;
}
}
}
private IEnumerable<string> BuildSplitBtfCandidates(string kernelRelease, string kernelArch)
{
var splitRoots = NormalizePaths(
_options.SplitBtfDirectories.Concat(DefaultSplitBtfDirectories));
foreach (var root in splitRoots)
{
var releaseArchDir = Path.Combine(root, kernelRelease, kernelArch);
yield return Path.Combine(releaseArchDir, "vmlinux.btf");
yield return Path.Combine(root, kernelRelease, $"{kernelArch}.btf");
yield return Path.Combine(root, kernelRelease, "vmlinux.btf");
yield return Path.Combine(root, $"{kernelRelease}.btf");
}
}
private bool TryCreateSelection(
string sourceKind,
string sourcePath,
string selectionReason,
string kernelRelease,
string kernelArch,
out RuntimeBtfSelection selection)
{
selection = null!;
if (!_fileExists(sourcePath))
{
return false;
}
try
{
var digest = "sha256:" + Convert.ToHexStringLower(SHA256.HashData(_readAllBytes(sourcePath)));
selection = new RuntimeBtfSelection
{
SourceKind = sourceKind,
SourcePath = sourcePath,
SourceDigest = digest,
SelectionReason = selectionReason,
KernelRelease = kernelRelease,
KernelArch = kernelArch,
};
return true;
}
catch
{
return false;
}
}
private string GetKernelRelease()
{
const string releasePath = "/proc/sys/kernel/osrelease";
if (_fileExists(releasePath))
{
try
{
var release = Encoding.UTF8.GetString(_readAllBytes(releasePath)).Trim();
if (!string.IsNullOrWhiteSpace(release))
{
return release;
}
}
catch
{
// Fall through to runtime description.
}
}
return RuntimeInformation.OSDescription.Trim();
}
private static string GetKernelArch()
{
return RuntimeInformation.OSArchitecture switch
{
Architecture.X64 => "x86_64",
Architecture.X86 => "x86",
Architecture.Arm64 => "arm64",
Architecture.Arm => "arm",
_ => RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(),
};
}
}

View File

@@ -7,7 +7,6 @@ using StellaOps.Reachability.Core;
using StellaOps.Signals.Ebpf.Probes;
using StellaOps.Signals.Ebpf.Schema;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
@@ -27,16 +26,20 @@ public sealed class RuntimeSignalCollector : IRuntimeSignalCollector, IDisposabl
private readonly IEbpfProbeLoader _probeLoader;
private readonly ConcurrentDictionary<Guid, CollectionSession> _activeSessions;
private readonly bool _isSupported;
private readonly RuntimeBtfSelection _btfSelection;
private bool _disposed;
public RuntimeSignalCollector(
ILogger<RuntimeSignalCollector> logger,
IEbpfProbeLoader probeLoader)
IEbpfProbeLoader probeLoader,
RuntimeBtfSourceSelector? btfSourceSelector = null)
{
_logger = logger;
_probeLoader = probeLoader;
_activeSessions = new ConcurrentDictionary<Guid, CollectionSession>();
_isSupported = CheckEbpfSupport();
var selector = btfSourceSelector ?? RuntimeBtfSourceSelector.CreateDefault();
_btfSelection = selector.Resolve();
_isSupported = !string.IsNullOrWhiteSpace(_btfSelection.SourcePath);
}
/// <inheritdoc />
@@ -51,7 +54,7 @@ public sealed class RuntimeSignalCollector : IRuntimeSignalCollector, IDisposabl
if (!_isSupported)
{
throw new PlatformNotSupportedException(
"eBPF is not supported on this platform. Linux 5.8+ with BTF enabled is required.");
$"eBPF is not supported on this platform. Selection reason: {_btfSelection.SelectionReason}");
}
_logger.LogInformation(
@@ -180,6 +183,7 @@ public sealed class RuntimeSignalCollector : IRuntimeSignalCollector, IDisposabl
ObservedNodeHashes = observedNodeHashes,
ObservedPathHashes = observedPathHashes,
CombinedPathHash = combinedPathHash,
BtfSelection = _btfSelection,
};
}
@@ -219,6 +223,9 @@ public sealed class RuntimeSignalCollector : IRuntimeSignalCollector, IDisposabl
/// <inheritdoc />
public bool IsSupported() => _isSupported;
/// <inheritdoc />
public RuntimeBtfSelection GetBtfSelection() => _btfSelection;
/// <inheritdoc />
public IReadOnlyList<ProbeType> GetSupportedProbeTypes()
{
@@ -256,18 +263,6 @@ public sealed class RuntimeSignalCollector : IRuntimeSignalCollector, IDisposabl
_disposed = true;
}
private static bool CheckEbpfSupport()
{
// eBPF is only supported on Linux 5.8+ with BTF
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return false;
}
// Check for BTF support by looking for /sys/kernel/btf/vmlinux
return File.Exists("/sys/kernel/btf/vmlinux");
}
private async Task ProcessEventsAsync(CollectionSession session, CancellationToken ct)
{
var rateLimiter = new RateLimiter(session.Options.MaxEventsPerSecond);

View File

@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Signals.Ebpf.Probes;
using StellaOps.Signals.Ebpf.Schema;
using StellaOps.Signals.Ebpf.Services;
using System.Text;
using Xunit;
/// <summary>
@@ -36,6 +37,86 @@ public sealed class RuntimeSignalCollectorTests
Assert.True(isSupported == false || Environment.OSVersion.Platform == PlatformID.Unix);
}
[Fact]
public void GetBtfSelection_PrefersKernelBtfOverConfiguredFallback()
{
var external = "/opt/stellaops/btf/vmlinux-6.8.0-test";
var collector = CreateCollectorWithFiles(
CreateFileMap("/sys/kernel/btf/vmlinux", external),
new RuntimeBtfSelectionOptions
{
ExternalVmlinuxPaths = [external],
});
var selection = collector.GetBtfSelection();
Assert.True(collector.IsSupported());
Assert.Equal("kernel", selection.SourceKind);
Assert.Equal("/sys/kernel/btf/vmlinux", selection.SourcePath);
Assert.Equal("kernel_btf_present", selection.SelectionReason);
Assert.StartsWith("sha256:", selection.SourceDigest);
}
[Fact]
public void GetBtfSelection_UsesExternalVmlinuxWhenKernelBtfMissing()
{
var external = "/opt/stellaops/btf/vmlinux-6.8.0-test";
var collector = CreateCollectorWithFiles(
CreateFileMap(external),
new RuntimeBtfSelectionOptions
{
ExternalVmlinuxPaths = [external],
});
var selection = collector.GetBtfSelection();
Assert.True(collector.IsSupported());
Assert.Equal("external-vmlinux", selection.SourceKind);
Assert.Equal(external, selection.SourcePath);
Assert.Equal("external_vmlinux_configured", selection.SelectionReason);
}
[Fact]
public void GetBtfSelection_UsesSplitBtfFallbackWhenConfigured()
{
var splitRoot = "/var/lib/stellaops/btf/split";
var splitCandidate = Path.Combine(splitRoot, "6.8.0-test", "x86_64", "vmlinux.btf");
var collector = CreateCollectorWithFiles(
CreateFileMap(splitCandidate),
new RuntimeBtfSelectionOptions
{
SplitBtfDirectories = [splitRoot],
});
var selection = collector.GetBtfSelection();
Assert.True(collector.IsSupported());
Assert.Equal("split-btf", selection.SourceKind);
Assert.Equal(splitCandidate, selection.SourcePath);
Assert.Equal("split_btf_fallback", selection.SelectionReason);
Assert.Equal("6.8.0-test", selection.KernelRelease);
Assert.Equal("x86_64", selection.KernelArch);
}
[Fact]
public async Task StopCollectionAsync_EmitsBtfSelectionMetadata()
{
var external = "/opt/stellaops/btf/vmlinux-6.8.0-test";
var collector = CreateCollectorWithFiles(
CreateFileMap(external),
new RuntimeBtfSelectionOptions
{
ExternalVmlinuxPaths = [external],
});
var handle = await collector.StartCollectionAsync("container-1", new RuntimeSignalOptions());
var summary = await collector.StopCollectionAsync(handle);
Assert.NotNull(summary.BtfSelection);
Assert.Equal("external-vmlinux", summary.BtfSelection!.SourceKind);
Assert.Equal(external, summary.BtfSelection.SourcePath);
}
[Fact]
public void GetSupportedProbeTypes_ReturnsEmptyOnUnsupportedPlatform()
{
@@ -206,4 +287,33 @@ public sealed class RuntimeSignalCollectorTests
public IReadOnlyList<ProbeType> GetSupportedProbeTypes() => [];
}
private static RuntimeSignalCollector CreateCollectorWithFiles(
IReadOnlyDictionary<string, byte[]> files,
RuntimeBtfSelectionOptions options)
{
var selector = new RuntimeBtfSourceSelector(
options,
isLinuxPlatform: () => true,
fileExists: path => files.ContainsKey(path),
readAllBytes: path => files[path],
kernelReleaseProvider: () => "6.8.0-test",
kernelArchProvider: () => "x86_64");
return new RuntimeSignalCollector(
NullLogger<RuntimeSignalCollector>.Instance,
new MockProbeLoader(),
selector);
}
private static IReadOnlyDictionary<string, byte[]> CreateFileMap(params string[] paths)
{
var map = new Dictionary<string, byte[]>(StringComparer.Ordinal);
foreach (var path in paths)
{
map[path] = Encoding.UTF8.GetBytes($"btf:{path}");
}
return map;
}
}