test fixes and new product advisories work
This commit is contained in:
211
tests/reachability/fixtures/ebpf/README.md
Normal file
211
tests/reachability/fixtures/ebpf/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# eBPF Reachability Test Fixtures
|
||||
|
||||
This directory contains frozen test fixtures for determinism validation of the eBPF evidence collection system.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
fixtures/ebpf/
|
||||
├── README.md # This file
|
||||
├── proc/ # Mock /proc filesystem data
|
||||
│ ├── {pid}-maps.txt # Memory map files
|
||||
│ └── {pid}-cgroup.txt # Cgroup membership
|
||||
├── elf/ # Mock ELF symbol data
|
||||
│ └── *-symbols.json # Symbol tables in JSON format
|
||||
├── events/ # Simulated eBPF events
|
||||
│ └── *-events.json # Event sequences by type
|
||||
└── golden/ # Expected NDJSON output
|
||||
└── *-golden.ndjson # Canonical output for comparison
|
||||
```
|
||||
|
||||
## Fixture Types
|
||||
|
||||
### /proc Filesystem Fixtures (`proc/`)
|
||||
|
||||
Mock `/proc/{pid}/maps` and `/proc/{pid}/cgroup` files for testing symbol resolution and container identification.
|
||||
|
||||
**Format for maps:**
|
||||
```
|
||||
start-end permissions offset device inode pathname
|
||||
7f0000000000-7f0000001000 r--p 00000000 fd:01 12345678 /lib/libc.so.6
|
||||
```
|
||||
|
||||
**Format for cgroup:**
|
||||
```
|
||||
0::/system.slice/containerd.service/container-id
|
||||
```
|
||||
|
||||
### ELF Symbol Fixtures (`elf/`)
|
||||
|
||||
JSON representation of ELF symbol tables for testing address resolution.
|
||||
|
||||
**Schema:**
|
||||
```json
|
||||
{
|
||||
"path": "/path/to/binary",
|
||||
"format": "ELF64",
|
||||
"symbols": [
|
||||
{ "name": "symbol_name", "address": "0x1000", "size": 256, "type": "FUNC" }
|
||||
],
|
||||
"buildId": "hexstring"
|
||||
}
|
||||
```
|
||||
|
||||
### Event Fixtures (`events/`)
|
||||
|
||||
JSON arrays of simulated eBPF events with binary-compatible structures.
|
||||
|
||||
**Event Types:**
|
||||
- `1` - File access (sys_enter_openat)
|
||||
- `2` - Process exec (sched_process_exec)
|
||||
- `3` - TCP state (inet_sock_set_state)
|
||||
- `4` - Network op (uprobe connect/accept)
|
||||
- `5` - SSL op (uprobe SSL_read/SSL_write)
|
||||
- `6` - Symbol call (uprobe function)
|
||||
|
||||
### Golden Output Fixtures (`golden/`)
|
||||
|
||||
Canonical NDJSON files representing expected output when processing event fixtures with enrichment.
|
||||
|
||||
**Requirements:**
|
||||
- Sorted JSON keys (alphabetical)
|
||||
- No trailing whitespace
|
||||
- Single newline per record
|
||||
- No pretty-printing
|
||||
|
||||
## Determinism Testing
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run determinism tests
|
||||
dotnet test --filter "Category=Determinism"
|
||||
|
||||
# Verify against golden files
|
||||
dotnet test --filter "FullyQualifiedName~GoldenFileTests"
|
||||
```
|
||||
|
||||
### Updating Golden Files
|
||||
|
||||
**IMPORTANT:** Only update golden files when schema changes are intentional.
|
||||
|
||||
1. Set environment variable to enable updates:
|
||||
```bash
|
||||
export STELLAOPS_UPDATE_FIXTURES=true
|
||||
```
|
||||
|
||||
2. Run the test that generates output:
|
||||
```bash
|
||||
dotnet test --filter "FullyQualifiedName~GenerateGoldenOutput"
|
||||
```
|
||||
|
||||
3. Review changes carefully:
|
||||
```bash
|
||||
git diff tests/reachability/fixtures/ebpf/golden/
|
||||
```
|
||||
|
||||
4. Commit with explanation:
|
||||
```bash
|
||||
git add tests/reachability/fixtures/ebpf/golden/
|
||||
git commit -m "Update golden files: <reason for change>"
|
||||
```
|
||||
|
||||
### CI Integration
|
||||
|
||||
The CI workflow runs determinism tests automatically:
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/test.yaml
|
||||
- name: Determinism Tests
|
||||
run: dotnet test --filter "Category=Determinism"
|
||||
env:
|
||||
STELLAOPS_UPDATE_FIXTURES: "false" # Never auto-update in CI
|
||||
```
|
||||
|
||||
If tests fail:
|
||||
1. Check if schema changes were intentional
|
||||
2. If intentional, update golden files locally and commit
|
||||
3. If unintentional, fix the determinism bug
|
||||
|
||||
## Adding New Fixtures
|
||||
|
||||
### Adding a New Event Type
|
||||
|
||||
1. Create input fixture in `events/`:
|
||||
```json
|
||||
// events/new-type-events.json
|
||||
[
|
||||
{
|
||||
"event_type": 7,
|
||||
"timestamp_ns": 1000000000000,
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
2. Generate golden output:
|
||||
```bash
|
||||
STELLAOPS_UPDATE_FIXTURES=true dotnet test --filter "GenerateNewTypeGolden"
|
||||
```
|
||||
|
||||
3. Verify output is deterministic:
|
||||
```bash
|
||||
# Run multiple times, output should be identical
|
||||
for i in {1..3}; do
|
||||
dotnet test --filter "NewTypeGolden" 2>/dev/null
|
||||
sha256sum golden/new-type-golden.ndjson
|
||||
done
|
||||
```
|
||||
|
||||
### Adding a New Process Fixture
|
||||
|
||||
1. Create mock `/proc` files:
|
||||
```bash
|
||||
# proc/{pid}-maps.txt
|
||||
# proc/{pid}-cgroup.txt
|
||||
```
|
||||
|
||||
2. Create corresponding ELF symbols if needed:
|
||||
```bash
|
||||
# elf/{binary}-symbols.json
|
||||
```
|
||||
|
||||
3. Update container mapping in test setup
|
||||
|
||||
## Validation
|
||||
|
||||
Golden files must satisfy these constraints:
|
||||
|
||||
1. **Byte-identical output**: Same input always produces same output
|
||||
2. **Sorted keys**: JSON keys in alphabetical order
|
||||
3. **No floating point**: Use integers for all numeric values
|
||||
4. **UTF-8 NFC**: All strings in NFC normalization form
|
||||
5. **No null values**: Omit fields with null values
|
||||
6. **Consistent timestamps**: Use nanoseconds since boot
|
||||
|
||||
Run validation:
|
||||
```bash
|
||||
# Validate golden file format
|
||||
stella evidence validate tests/reachability/fixtures/ebpf/golden/*.ndjson
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Test Fails: "Output differs from golden file"
|
||||
|
||||
1. Check for unintended schema changes
|
||||
2. Verify timestamp handling is deterministic
|
||||
3. Check for locale-dependent formatting
|
||||
4. Ensure random values aren't leaking into output
|
||||
|
||||
### Test Fails: "Cannot load fixture"
|
||||
|
||||
1. Verify fixture file exists and is valid JSON
|
||||
2. Check file permissions
|
||||
3. Ensure no BOM in JSON files
|
||||
|
||||
### Test Fails: "Symbol resolution failed"
|
||||
|
||||
1. Verify mock maps file has correct format
|
||||
2. Check address ranges don't overlap
|
||||
3. Ensure symbol addresses are within mapped regions
|
||||
16
tests/reachability/fixtures/ebpf/elf/libc-symbols.json
Normal file
16
tests/reachability/fixtures/ebpf/elf/libc-symbols.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
|
||||
"format": "ELF64",
|
||||
"symbols": [
|
||||
{ "name": "connect", "address": "0x120000", "size": 128, "type": "FUNC" },
|
||||
{ "name": "accept", "address": "0x120080", "size": 96, "type": "FUNC" },
|
||||
{ "name": "accept4", "address": "0x1200e0", "size": 112, "type": "FUNC" },
|
||||
{ "name": "read", "address": "0x121000", "size": 64, "type": "FUNC" },
|
||||
{ "name": "write", "address": "0x121040", "size": 64, "type": "FUNC" },
|
||||
{ "name": "open", "address": "0x122000", "size": 96, "type": "FUNC" },
|
||||
{ "name": "openat", "address": "0x122060", "size": 112, "type": "FUNC" },
|
||||
{ "name": "close", "address": "0x1220d0", "size": 48, "type": "FUNC" }
|
||||
],
|
||||
"version": "2.35",
|
||||
"buildId": "fedcba9876543210fedcba9876543210fedcba98"
|
||||
}
|
||||
14
tests/reachability/fixtures/ebpf/elf/libssl-symbols.json
Normal file
14
tests/reachability/fixtures/ebpf/elf/libssl-symbols.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"path": "/usr/lib/x86_64-linux-gnu/libssl.so.3",
|
||||
"format": "ELF64",
|
||||
"symbols": [
|
||||
{ "name": "SSL_read", "address": "0x15000", "size": 256, "type": "FUNC" },
|
||||
{ "name": "SSL_write", "address": "0x15100", "size": 256, "type": "FUNC" },
|
||||
{ "name": "SSL_connect", "address": "0x15200", "size": 384, "type": "FUNC" },
|
||||
{ "name": "SSL_accept", "address": "0x15380", "size": 320, "type": "FUNC" },
|
||||
{ "name": "SSL_get_fd", "address": "0x154c0", "size": 64, "type": "FUNC" },
|
||||
{ "name": "SSL_get_peer_certificate", "address": "0x15500", "size": 128, "type": "FUNC" }
|
||||
],
|
||||
"version": "3.0.0",
|
||||
"buildId": "112233445566778899aabbccddeeff0011223344"
|
||||
}
|
||||
19
tests/reachability/fixtures/ebpf/elf/myapp-symbols.json
Normal file
19
tests/reachability/fixtures/ebpf/elf/myapp-symbols.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"path": "/app/bin/myapp",
|
||||
"format": "ELF64",
|
||||
"symbols": [
|
||||
{ "name": "main", "address": "0x1000", "size": 256, "type": "FUNC" },
|
||||
{ "name": "parse_json", "address": "0x1100", "size": 512, "type": "FUNC" },
|
||||
{ "name": "vulnerable_parse_json", "address": "0x1300", "size": 384, "type": "FUNC" },
|
||||
{ "name": "handle_request", "address": "0x1480", "size": 640, "type": "FUNC" },
|
||||
{ "name": "process_config", "address": "0x1700", "size": 192, "type": "FUNC" },
|
||||
{ "name": "connect_database", "address": "0x17c0", "size": 448, "type": "FUNC" },
|
||||
{ "name": "send_response", "address": "0x1980", "size": 320, "type": "FUNC" }
|
||||
],
|
||||
"sections": {
|
||||
".text": { "address": "0x1000", "size": "0x2000", "offset": "0x1000" },
|
||||
".rodata": { "address": "0x3000", "size": "0x800", "offset": "0x3000" },
|
||||
".data": { "address": "0x4000", "size": "0x200", "offset": "0x4000" }
|
||||
},
|
||||
"buildId": "abc123def456789012345678901234567890"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"event_type": 1,
|
||||
"timestamp_ns": 1000000000000,
|
||||
"pid": 1234,
|
||||
"tid": 1234,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"dfd": -100,
|
||||
"flags": 0,
|
||||
"mode": 0,
|
||||
"filename": "/etc/myapp/config.yaml"
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_type": 1,
|
||||
"timestamp_ns": 1000100000000,
|
||||
"pid": 1234,
|
||||
"tid": 1235,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"dfd": -100,
|
||||
"flags": 1,
|
||||
"mode": 420,
|
||||
"filename": "/var/log/myapp/access.log"
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_type": 1,
|
||||
"timestamp_ns": 1000200000000,
|
||||
"pid": 5678,
|
||||
"tid": 5678,
|
||||
"cgroup_id": 9876543210,
|
||||
"comm": "nginx",
|
||||
"payload": {
|
||||
"dfd": -100,
|
||||
"flags": 0,
|
||||
"mode": 0,
|
||||
"filename": "/etc/nginx/nginx.conf"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"event_type": 2,
|
||||
"timestamp_ns": 900000000000,
|
||||
"pid": 1234,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"ppid": 1,
|
||||
"filename": "/app/bin/myapp",
|
||||
"argv": ["myapp", "--config", "/etc/myapp/config.yaml"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_type": 2,
|
||||
"timestamp_ns": 850000000000,
|
||||
"pid": 5678,
|
||||
"cgroup_id": 9876543210,
|
||||
"comm": "nginx",
|
||||
"payload": {
|
||||
"ppid": 1,
|
||||
"filename": "/usr/bin/nginx",
|
||||
"argv": ["nginx", "-g", "daemon off;"]
|
||||
}
|
||||
}
|
||||
]
|
||||
28
tests/reachability/fixtures/ebpf/events/ssl-events.json
Normal file
28
tests/reachability/fixtures/ebpf/events/ssl-events.json
Normal file
@@ -0,0 +1,28 @@
|
||||
[
|
||||
{
|
||||
"event_type": 5,
|
||||
"timestamp_ns": 1000500000000,
|
||||
"pid": 1234,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"operation": 1,
|
||||
"requested_bytes": 1024,
|
||||
"actual_bytes": 1024,
|
||||
"ssl_ptr": 140234567890
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_type": 5,
|
||||
"timestamp_ns": 1000600000000,
|
||||
"pid": 1234,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"operation": 0,
|
||||
"requested_bytes": 4096,
|
||||
"actual_bytes": 2048,
|
||||
"ssl_ptr": 140234567890
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"event_type": 3,
|
||||
"timestamp_ns": 1000300000000,
|
||||
"pid": 1234,
|
||||
"cgroup_id": 1234567890,
|
||||
"comm": "myapp",
|
||||
"payload": {
|
||||
"family": 2,
|
||||
"old_state": 2,
|
||||
"new_state": 1,
|
||||
"sport": 45678,
|
||||
"dport": 5432,
|
||||
"saddr_v4": "10.0.0.5",
|
||||
"daddr_v4": "10.0.1.100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_type": 3,
|
||||
"timestamp_ns": 1000400000000,
|
||||
"pid": 5678,
|
||||
"cgroup_id": 9876543210,
|
||||
"comm": "nginx",
|
||||
"payload": {
|
||||
"family": 2,
|
||||
"old_state": 0,
|
||||
"new_state": 1,
|
||||
"sport": 80,
|
||||
"dport": 54321,
|
||||
"saddr_v4": "0.0.0.0",
|
||||
"daddr_v4": "192.168.1.50"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"access":"read","flags":0,"mode":0,"path":"/etc/myapp/config.yaml","type":"file_access"},"pid":1234,"src":"tracepoint:syscalls:sys_enter_openat","tid":1234,"ts_ns":1000000000000}
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"access":"write","flags":1,"mode":420,"path":"/var/log/myapp/access.log","type":"file_access"},"pid":1234,"src":"tracepoint:syscalls:sys_enter_openat","tid":1235,"ts_ns":1000100000000}
|
||||
{"cgroup_id":9876543210,"comm":"nginx","container_id":"fedcba9876543210fedcba9876543210fedcba9876543210fedcba98765432","event":{"access":"read","flags":0,"mode":0,"path":"/etc/nginx/nginx.conf","type":"file_access"},"pid":5678,"src":"tracepoint:syscalls:sys_enter_openat","tid":5678,"ts_ns":1000200000000}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"argv":["myapp","--config","/etc/myapp/config.yaml"],"filename":"/app/bin/myapp","ppid":1,"type":"process_exec"},"pid":1234,"src":"tracepoint:sched:sched_process_exec","ts_ns":900000000000}
|
||||
{"cgroup_id":9876543210,"comm":"nginx","container_id":"fedcba9876543210fedcba9876543210fedcba9876543210fedcba98765432","event":{"argv":["nginx","-g","daemon off;"],"filename":"/usr/bin/nginx","ppid":1,"type":"process_exec"},"pid":5678,"src":"tracepoint:sched:sched_process_exec","ts_ns":850000000000}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"actual_bytes":1024,"operation":"write","requested_bytes":1024,"ssl_ptr":140234567890,"type":"ssl_op"},"pid":1234,"src":"uprobe:libssl.so.3:SSL_write","ts_ns":1000500000000}
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"actual_bytes":2048,"operation":"read","requested_bytes":4096,"ssl_ptr":140234567890,"type":"ssl_op"},"pid":1234,"src":"uprobe:libssl.so.3:SSL_read","ts_ns":1000600000000}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"cgroup_id":1234567890,"comm":"myapp","container_id":"abc123def456789012345678901234567890123456789012345678901234","event":{"dst_addr":"10.0.1.100","dst_port":5432,"family":"ipv4","new_state":"ESTABLISHED","old_state":"SYN_SENT","src_addr":"10.0.0.5","src_port":45678,"type":"tcp_state"},"pid":1234,"src":"tracepoint:sock:inet_sock_set_state","ts_ns":1000300000000}
|
||||
{"cgroup_id":9876543210,"comm":"nginx","container_id":"fedcba9876543210fedcba9876543210fedcba9876543210fedcba98765432","event":{"dst_addr":"192.168.1.50","dst_port":54321,"family":"ipv4","new_state":"ESTABLISHED","old_state":"CLOSED","src_addr":"0.0.0.0","src_port":80,"type":"tcp_state"},"pid":5678,"src":"tracepoint:sock:inet_sock_set_state","ts_ns":1000400000000}
|
||||
1
tests/reachability/fixtures/ebpf/proc/1234-cgroup.txt
Normal file
1
tests/reachability/fixtures/ebpf/proc/1234-cgroup.txt
Normal file
@@ -0,0 +1 @@
|
||||
0::/system.slice/containerd.service/kubepods-besteffort-pod12345678.slice:cri-containerd:abc123def456789012345678901234567890123456789012345678901234
|
||||
14
tests/reachability/fixtures/ebpf/proc/1234-maps.txt
Normal file
14
tests/reachability/fixtures/ebpf/proc/1234-maps.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
7f0000000000-7f0000001000 r--p 00000000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f0000001000-7f0000180000 r-xp 00001000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f0000180000-7f00001d8000 r--p 00180000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f00001d8000-7f00001dc000 r--p 001d7000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f00001dc000-7f00001de000 rw-p 001db000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f0000200000-7f0000210000 r--p 00000000 fd:01 87654321 /usr/lib/x86_64-linux-gnu/libssl.so.3
|
||||
7f0000210000-7f0000280000 r-xp 00010000 fd:01 87654321 /usr/lib/x86_64-linux-gnu/libssl.so.3
|
||||
7f0000280000-7f00002a0000 r--p 00080000 fd:01 87654321 /usr/lib/x86_64-linux-gnu/libssl.so.3
|
||||
7f0000400000-7f0000450000 r-xp 00000000 fd:01 11223344 /app/bin/myapp
|
||||
7f0000450000-7f0000460000 r--p 00050000 fd:01 11223344 /app/bin/myapp
|
||||
7f0000460000-7f0000461000 rw-p 00060000 fd:01 11223344 /app/bin/myapp
|
||||
7ffff7ff0000-7ffff7ff4000 r--p 00000000 00:00 0 [vvar]
|
||||
7ffff7ff4000-7ffff7ff6000 r-xp 00000000 00:00 0 [vdso]
|
||||
7ffff7ff6000-7ffff7ff8000 rw-p 00000000 00:00 0 [stack]
|
||||
1
tests/reachability/fixtures/ebpf/proc/5678-cgroup.txt
Normal file
1
tests/reachability/fixtures/ebpf/proc/5678-cgroup.txt
Normal file
@@ -0,0 +1 @@
|
||||
0::/docker/fedcba9876543210fedcba9876543210fedcba9876543210fedcba98765432
|
||||
4
tests/reachability/fixtures/ebpf/proc/5678-maps.txt
Normal file
4
tests/reachability/fixtures/ebpf/proc/5678-maps.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
7f1000000000-7f1000001000 r--p 00000000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f1000001000-7f1000180000 r-xp 00001000 fd:01 12345678 /usr/lib/x86_64-linux-gnu/libc.so.6
|
||||
7f1000200000-7f1000300000 r-xp 00000000 fd:01 99887766 /usr/bin/nginx
|
||||
7f1000300000-7f1000310000 r--p 00100000 fd:01 99887766 /usr/bin/nginx
|
||||
Reference in New Issue
Block a user