finish off sprint advisories and sprints

This commit is contained in:
master
2026-01-24 00:12:43 +02:00
parent 726d70dc7f
commit c70e83719e
266 changed files with 46699 additions and 1328 deletions

View File

@@ -0,0 +1,208 @@
// -----------------------------------------------------------------------------
// RuntimeObservationTests.cs
// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
// Task: EBPF-001 - Add ProbeType field to RuntimeObservation
// Description: Unit tests for RuntimeObservation and EbpfProbeType
// -----------------------------------------------------------------------------
using StellaOps.RuntimeInstrumentation.Tetragon;
using Xunit;
namespace StellaOps.RuntimeInstrumentation.Tetragon.Tests;
public class RuntimeObservationTests
{
[Fact]
public void RuntimeObservation_WithoutProbeType_CreatesValidInstance()
{
// Arrange & Act - backward compatibility: ProbeType is optional
var observation = new RuntimeObservation
{
ObservedAt = DateTimeOffset.UtcNow,
SourceType = RuntimeObservationSourceType.Tetragon,
ContainerId = "container123"
};
// Assert
Assert.Null(observation.ProbeType);
Assert.Null(observation.FunctionName);
Assert.Null(observation.FunctionAddress);
Assert.Equal(RuntimeObservationSourceType.Tetragon, observation.SourceType);
}
[Fact]
public void RuntimeObservation_WithProbeType_SetsFieldCorrectly()
{
// Arrange & Act
var observation = new RuntimeObservation
{
ObservedAt = DateTimeOffset.UtcNow,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = EbpfProbeType.Uprobe,
FunctionName = "SSL_connect",
FunctionAddress = 0x7f1234567890
};
// Assert
Assert.Equal(EbpfProbeType.Uprobe, observation.ProbeType);
Assert.Equal("SSL_connect", observation.FunctionName);
Assert.Equal(0x7f1234567890, observation.FunctionAddress);
}
[Theory]
[InlineData(EbpfProbeType.Kprobe)]
[InlineData(EbpfProbeType.Kretprobe)]
[InlineData(EbpfProbeType.Uprobe)]
[InlineData(EbpfProbeType.Uretprobe)]
[InlineData(EbpfProbeType.Tracepoint)]
[InlineData(EbpfProbeType.Usdt)]
[InlineData(EbpfProbeType.Fentry)]
[InlineData(EbpfProbeType.Fexit)]
public void RuntimeObservation_WithAllProbeTypes_Succeeds(EbpfProbeType probeType)
{
// Arrange & Act
var observation = new RuntimeObservation
{
ObservedAt = DateTimeOffset.UtcNow,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = probeType
};
// Assert
Assert.Equal(probeType, observation.ProbeType);
}
[Fact]
public void RuntimeObservation_RecordEquality_WorksWithNewFields()
{
// Arrange
var timestamp = DateTimeOffset.UtcNow;
var obs1 = new RuntimeObservation
{
ObservedAt = timestamp,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = EbpfProbeType.Uprobe,
FunctionName = "SSL_connect",
FunctionAddress = 0x12345678
};
var obs2 = new RuntimeObservation
{
ObservedAt = timestamp,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = EbpfProbeType.Uprobe,
FunctionName = "SSL_connect",
FunctionAddress = 0x12345678
};
// Assert - records with same values are equal
Assert.Equal(obs1, obs2);
}
[Fact]
public void RuntimeObservation_RecordInequality_WithDifferentProbeType()
{
// Arrange
var timestamp = DateTimeOffset.UtcNow;
var obs1 = new RuntimeObservation
{
ObservedAt = timestamp,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = EbpfProbeType.Uprobe
};
var obs2 = new RuntimeObservation
{
ObservedAt = timestamp,
SourceType = RuntimeObservationSourceType.Tetragon,
ProbeType = EbpfProbeType.Uretprobe
};
// Assert - different probe types are not equal
Assert.NotEqual(obs1, obs2);
}
[Fact]
public void RuntimeObservation_WithAllFields_CreatesCompleteInstance()
{
// Arrange
var timestamp = DateTimeOffset.UtcNow;
var observationId = Guid.NewGuid().ToString();
// Act
var observation = new RuntimeObservation
{
ObservedAt = timestamp,
ObservationCount = 5,
StackSampleHash = "sha256:abc123",
ProcessId = 12345,
ContainerId = "container123",
PodName = "my-pod",
Namespace = "production",
SourceType = RuntimeObservationSourceType.Tetragon,
ObservationId = observationId,
ProbeType = EbpfProbeType.Uprobe,
FunctionName = "crypto_encrypt",
FunctionAddress = 0x7f1234567890
};
// Assert - all fields set correctly
Assert.Equal(timestamp, observation.ObservedAt);
Assert.Equal(5, observation.ObservationCount);
Assert.Equal("sha256:abc123", observation.StackSampleHash);
Assert.Equal(12345, observation.ProcessId);
Assert.Equal("container123", observation.ContainerId);
Assert.Equal("my-pod", observation.PodName);
Assert.Equal("production", observation.Namespace);
Assert.Equal(RuntimeObservationSourceType.Tetragon, observation.SourceType);
Assert.Equal(observationId, observation.ObservationId);
Assert.Equal(EbpfProbeType.Uprobe, observation.ProbeType);
Assert.Equal("crypto_encrypt", observation.FunctionName);
Assert.Equal(0x7f1234567890, observation.FunctionAddress);
}
}
public class EbpfProbeTypeTests
{
[Fact]
public void EbpfProbeType_HasAllExpectedValues()
{
// Assert - enum has all expected probe types
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Kprobe));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Kretprobe));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Uprobe));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Uretprobe));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Tracepoint));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Usdt));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Fentry));
Assert.True(Enum.IsDefined(typeof(EbpfProbeType), EbpfProbeType.Fexit));
}
[Fact]
public void EbpfProbeType_EnumCount_Is8()
{
// Assert - exactly 8 probe types as specified in EBPF-001
var values = Enum.GetValues<EbpfProbeType>();
Assert.Equal(8, values.Length);
}
[Theory]
[InlineData("Kprobe", EbpfProbeType.Kprobe)]
[InlineData("Kretprobe", EbpfProbeType.Kretprobe)]
[InlineData("Uprobe", EbpfProbeType.Uprobe)]
[InlineData("Uretprobe", EbpfProbeType.Uretprobe)]
[InlineData("Tracepoint", EbpfProbeType.Tracepoint)]
[InlineData("Usdt", EbpfProbeType.Usdt)]
[InlineData("Fentry", EbpfProbeType.Fentry)]
[InlineData("Fexit", EbpfProbeType.Fexit)]
public void EbpfProbeType_ParsesFromString(string name, EbpfProbeType expected)
{
// Act
var parsed = Enum.Parse<EbpfProbeType>(name);
// Assert
Assert.Equal(expected, parsed);
}
}

View File

@@ -0,0 +1,253 @@
// -----------------------------------------------------------------------------
// TetragonEventAdapterProbeTypeTests.cs
// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
// Task: EBPF-002 - Update Tetragon event parser to populate ProbeType
// Description: Integration tests for Tetragon event parser probe type mapping
// -----------------------------------------------------------------------------
using Moq;
using StellaOps.RuntimeInstrumentation.Tetragon;
using Xunit;
namespace StellaOps.RuntimeInstrumentation.Tetragon.Tests;
public class TetragonEventAdapterProbeTypeTests
{
private readonly Mock<ISymbolResolver> _mockSymbolResolver;
private readonly Mock<IHotSymbolIndex> _mockHotSymbolIndex;
private readonly Mock<ILogger<TetragonEventAdapter>> _mockLogger;
private readonly TetragonEventAdapter _adapter;
public TetragonEventAdapterProbeTypeTests()
{
_mockSymbolResolver = new Mock<ISymbolResolver>();
_mockHotSymbolIndex = new Mock<IHotSymbolIndex>();
_mockLogger = new Mock<ILogger<TetragonEventAdapter>>();
var options = new TestOptions<TetragonAdapterOptions>(new TetragonAdapterOptions
{
UpdateHotSymbolIndex = false // Disable for these tests
});
_adapter = new TetragonEventAdapter(
_mockSymbolResolver.Object,
_mockHotSymbolIndex.Object,
options,
_mockLogger.Object);
}
[Theory]
[InlineData(TetragonEventType.Kprobe, EbpfProbeType.Kprobe)]
[InlineData(TetragonEventType.Kretprobe, EbpfProbeType.Kretprobe)]
[InlineData(TetragonEventType.Uprobe, EbpfProbeType.Uprobe)]
[InlineData(TetragonEventType.Uretprobe, EbpfProbeType.Uretprobe)]
[InlineData(TetragonEventType.Tracepoint, EbpfProbeType.Tracepoint)]
[InlineData(TetragonEventType.Usdt, EbpfProbeType.Usdt)]
[InlineData(TetragonEventType.Fentry, EbpfProbeType.Fentry)]
[InlineData(TetragonEventType.Fexit, EbpfProbeType.Fexit)]
public async Task AdaptAsync_WithEbpfProbeType_MapsCorrectly(
TetragonEventType tetragonType,
EbpfProbeType expectedProbeType)
{
// Arrange
var tetragonEvent = CreateTestEvent(tetragonType, "test_function", 0x12345678);
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedProbeType, result.ProbeType);
}
[Theory]
[InlineData(TetragonEventType.ProcessExec)]
[InlineData(TetragonEventType.ProcessExit)]
public async Task AdaptAsync_WithProcessEvent_ReturnsNullProbeType(TetragonEventType tetragonType)
{
// Arrange
var tetragonEvent = CreateTestEvent(tetragonType, null, null);
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Null(result.ProbeType);
}
[Fact]
public async Task AdaptAsync_WithFunctionAddress_PopulatesFunctionAddress()
{
// Arrange
const ulong expectedAddress = 0x7f1234567890;
var tetragonEvent = CreateTestEvent(TetragonEventType.Uprobe, "SSL_connect", expectedAddress);
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedAddress, result.FunctionAddress);
}
[Fact]
public async Task AdaptAsync_WithoutStackTrace_HasNullFunctionAddress()
{
// Arrange
var tetragonEvent = new TetragonEvent
{
Type = TetragonEventType.Uprobe,
Time = DateTimeOffset.UtcNow,
FunctionName = "SSL_connect",
Process = new TetragonProcess { Pid = 1234 },
StackTrace = null // No stack trace
};
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Null(result.FunctionAddress);
}
[Theory]
[InlineData(TetragonEventType.Kprobe, RuntimeEventSource.Syscall)]
[InlineData(TetragonEventType.Kretprobe, RuntimeEventSource.Syscall)]
[InlineData(TetragonEventType.Fentry, RuntimeEventSource.Syscall)]
[InlineData(TetragonEventType.Fexit, RuntimeEventSource.Syscall)]
[InlineData(TetragonEventType.Uprobe, RuntimeEventSource.LibraryLoad)]
[InlineData(TetragonEventType.Uretprobe, RuntimeEventSource.LibraryLoad)]
[InlineData(TetragonEventType.Tracepoint, RuntimeEventSource.Tracepoint)]
[InlineData(TetragonEventType.Usdt, RuntimeEventSource.Tracepoint)]
public async Task AdaptAsync_MapsSourceCorrectly(
TetragonEventType tetragonType,
RuntimeEventSource expectedSource)
{
// Arrange
var tetragonEvent = CreateTestEvent(tetragonType, "test_func", 0x12345678);
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedSource, result.Source);
}
[Fact]
public async Task AdaptAsync_WithFullContext_PopulatesAllFields()
{
// Arrange
var timestamp = DateTimeOffset.UtcNow;
var tetragonEvent = new TetragonEvent
{
Type = TetragonEventType.Uprobe,
Time = timestamp,
FunctionName = "SSL_connect",
Process = new TetragonProcess
{
Pid = 1234,
Tid = 5678,
Binary = "/usr/lib/libssl.so",
Docker = "container123",
Pod = new TetragonPod
{
Name = "my-pod",
Namespace = "production",
Container = new TetragonContainer { Id = "container123", Name = "app" }
}
},
StackTrace = new TetragonStackTrace
{
Frames = new List<TetragonStackFrame>
{
new()
{
Address = 0x7f1234567890,
Offset = 0x100,
Module = "libssl.so",
Symbol = "SSL_connect",
Flags = StackFrameFlags.User
}
}
}
};
_mockSymbolResolver
.Setup(r => r.ResolveAsync(It.IsAny<ulong>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ResolvedSymbol
{
Name = "SSL_connect",
DemangledName = "SSL_connect",
Confidence = 1.0
});
// Act
var result = await _adapter.AdaptAsync(tetragonEvent);
// Assert
Assert.NotNull(result);
Assert.Equal(EbpfProbeType.Uprobe, result.ProbeType);
Assert.Equal(0x7f1234567890ul, result.FunctionAddress);
Assert.Equal("SSL_connect", result.SyscallName);
Assert.Equal(1234, result.ProcessId);
Assert.Equal(5678, result.ThreadId);
Assert.Equal("container123", result.ContainerId);
Assert.Equal("my-pod", result.PodName);
Assert.Equal("production", result.Namespace);
}
[Fact]
public async Task AdaptAsync_NullEvent_ReturnsNull()
{
// Act
var result = await _adapter.AdaptAsync(null!);
// Assert
Assert.Null(result);
}
private static TetragonEvent CreateTestEvent(
TetragonEventType type,
string? functionName,
ulong? address)
{
return new TetragonEvent
{
Type = type,
Time = DateTimeOffset.UtcNow,
FunctionName = functionName,
Process = new TetragonProcess
{
Pid = 1234,
Tid = 5678,
Binary = "/usr/bin/test"
},
StackTrace = address.HasValue
? new TetragonStackTrace
{
Frames = new List<TetragonStackFrame>
{
new()
{
Address = address.Value,
Offset = 0x100,
Module = "test",
Symbol = functionName,
Flags = StackFrameFlags.User
}
}
}
: null
};
}
// Helper class for options
private sealed class TestOptions<T> : IOptions<T> where T : class
{
public TestOptions(T value) => Value = value;
public T Value { get; }
}
}

View File

@@ -51,17 +51,28 @@ public sealed class TetragonEventAdapter : ITetragonEventAdapter
TetragonEventType.ProcessExec => RuntimeEventSource.ProcessExec,
TetragonEventType.ProcessExit => RuntimeEventSource.ProcessExit,
TetragonEventType.Kprobe => RuntimeEventSource.Syscall,
TetragonEventType.Kretprobe => RuntimeEventSource.Syscall,
TetragonEventType.Uprobe => RuntimeEventSource.LibraryLoad,
TetragonEventType.Uretprobe => RuntimeEventSource.LibraryLoad,
TetragonEventType.Tracepoint => RuntimeEventSource.Tracepoint,
TetragonEventType.Usdt => RuntimeEventSource.Tracepoint,
TetragonEventType.Fentry => RuntimeEventSource.Syscall,
TetragonEventType.Fexit => RuntimeEventSource.Syscall,
_ => RuntimeEventSource.Unknown
};
// EBPF-002: Map Tetragon event type to EbpfProbeType
var probeType = MapToEbpfProbeType(tetragonEvent.Type);
// Extract and canonicalize stack frames
var frames = await CanonicalizeStackFramesAsync(
tetragonEvent.StackTrace,
tetragonEvent.Process?.Binary,
ct);
// Extract function address from the first stack frame if available
ulong? functionAddress = tetragonEvent.StackTrace?.Frames?.FirstOrDefault()?.Address;
// Build RuntimeCallEvent compatible with existing infrastructure
var runtimeEvent = new RuntimeCallEvent
{
@@ -77,7 +88,10 @@ public sealed class TetragonEventAdapter : ITetragonEventAdapter
Frames = frames,
SyscallName = tetragonEvent.FunctionName,
SyscallArgs = tetragonEvent.Args?.Select(a => a.ToString()).ToList(),
ReturnValue = tetragonEvent.Return?.IntValue
ReturnValue = tetragonEvent.Return?.IntValue,
// EBPF-002: Populate eBPF probe type fields
ProbeType = probeType,
FunctionAddress = functionAddress
};
// Update hot symbol index for reachability analysis
@@ -89,6 +103,30 @@ public sealed class TetragonEventAdapter : ITetragonEventAdapter
return runtimeEvent;
}
/// <summary>
/// Maps Tetragon event type to EbpfProbeType.
/// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
/// Task: EBPF-002
/// </summary>
private static EbpfProbeType? MapToEbpfProbeType(TetragonEventType eventType)
{
return eventType switch
{
TetragonEventType.Kprobe => EbpfProbeType.Kprobe,
TetragonEventType.Kretprobe => EbpfProbeType.Kretprobe,
TetragonEventType.Uprobe => EbpfProbeType.Uprobe,
TetragonEventType.Uretprobe => EbpfProbeType.Uretprobe,
TetragonEventType.Tracepoint => EbpfProbeType.Tracepoint,
TetragonEventType.Usdt => EbpfProbeType.Usdt,
TetragonEventType.Fentry => EbpfProbeType.Fentry,
TetragonEventType.Fexit => EbpfProbeType.Fexit,
// Process events are not eBPF probe types
TetragonEventType.ProcessExec => null,
TetragonEventType.ProcessExit => null,
_ => null
};
}
/// <inheritdoc />
public async IAsyncEnumerable<RuntimeCallEvent> AdaptStreamAsync(
IAsyncEnumerable<TetragonEvent> eventStream,
@@ -225,6 +263,8 @@ public sealed record TetragonEvent
/// <summary>
/// Tetragon event type.
/// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
/// Task: EBPF-002 - Extended with all eBPF probe types
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum TetragonEventType
@@ -232,8 +272,13 @@ public enum TetragonEventType
ProcessExec,
ProcessExit,
Kprobe,
Kretprobe,
Uprobe,
Tracepoint
Uretprobe,
Tracepoint,
Usdt,
Fentry,
Fexit
}
/// <summary>
@@ -355,6 +400,19 @@ public sealed record RuntimeCallEvent
public string? SyscallName { get; init; }
public IReadOnlyList<string>? SyscallArgs { get; init; }
public long? ReturnValue { get; init; }
// EBPF-002: Added for eBPF probe type tracking
// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
/// <summary>
/// Type of eBPF probe that generated this event.
/// </summary>
public EbpfProbeType? ProbeType { get; init; }
/// <summary>
/// Address of the probed function.
/// </summary>
public ulong? FunctionAddress { get; init; }
}
/// <summary>

View File

@@ -397,6 +397,25 @@ public sealed record RuntimeObservation
public string? Namespace { get; init; }
public required RuntimeObservationSourceType SourceType { get; init; }
public string? ObservationId { get; init; }
// EBPF-001: New fields for eBPF probe type categorization
// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
/// <summary>
/// Type of eBPF probe that generated this observation.
/// Only set when SourceType is Tetragon or another eBPF-based source.
/// </summary>
public EbpfProbeType? ProbeType { get; init; }
/// <summary>
/// Name of the function being probed (e.g., "SSL_connect", "crypto_encrypt").
/// </summary>
public string? FunctionName { get; init; }
/// <summary>
/// Address of the function in the binary (for symbol resolution/verification).
/// </summary>
public long? FunctionAddress { get; init; }
}
/// <summary>
@@ -410,3 +429,35 @@ public enum RuntimeObservationSourceType
Tracer,
Custom
}
/// <summary>
/// eBPF probe type for runtime observations.
/// Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type
/// Task: EBPF-001 - Add ProbeType field to RuntimeObservation
/// </summary>
public enum EbpfProbeType
{
/// <summary>Kernel function entry probe.</summary>
Kprobe,
/// <summary>Kernel function return probe.</summary>
Kretprobe,
/// <summary>User-space function entry probe.</summary>
Uprobe,
/// <summary>User-space function return probe.</summary>
Uretprobe,
/// <summary>Kernel tracepoint.</summary>
Tracepoint,
/// <summary>User Statically Defined Tracing probe.</summary>
Usdt,
/// <summary>Fast kernel function entry (BTF-based).</summary>
Fentry,
/// <summary>Fast kernel function exit (BTF-based).</summary>
Fexit
}