Skip to content

Commit f5957da

Browse files
committed
Initial PR to implement openSSL userspace tracker
Signed-off-by: Mohamed S. Mahmoud <[email protected]>
1 parent b165fb3 commit f5957da

27 files changed

+481
-23
lines changed

.mk/bc.mk

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ define PROGRAMS
2222
"xfrm_input_kprobe": "kprobe",
2323
"xfrm_input_kretprobe": "kretprobe",
2424
"xfrm_output_kprobe": "kprobe",
25-
"xfrm_output_kretprobe": "kretprobe"
25+
"xfrm_output_kretprobe": "kretprobe",
26+
"probe_entry_SSL_write":"uprobe",
2627
}
2728
endef
2829

@@ -38,7 +39,8 @@ define MAPS
3839
"filter_map":"lpm_trie",
3940
"peer_filter_map":"lpm_trie",
4041
"ipsec_ingress_map":"hash",
41-
"ipsec_egress_map":"hash"
42+
"ipsec_egress_map":"hash",
43+
"ssl_data_event_map":"ringbuf"
4244
}
4345
endef
4446

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ ARG TARGETARCH
33
# Build the manager binary
44
FROM docker.io/library/golang:1.24 as builder
55

6-
ARG TARGETARCH
76
ARG LDFLAGS
87

98
WORKDIR /opt/app-root

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ create-and-deploy-kind-cluster: prereqs ## Create a kind cluster and deploy the
205205

206206
.PHONY: destroy-kind-cluster
207207
destroy-kind-cluster: ## Destroy the kind cluster.
208-
oc delete -f scripts/agent.yml
208+
kubectl delete -f scripts/agent.yml
209209
kind delete cluster
210210

211211
##@ Images

bpf/configs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ volatile const u8 enable_network_events_monitoring = 0;
1515
volatile const u8 network_events_monitoring_groupid = 0;
1616
volatile const u8 enable_pkt_translation_tracking = 0;
1717
volatile const u8 enable_ipsec = 0;
18+
volatile const u8 enable_ssl = 0;
1819
#endif //__CONFIGS_H__

bpf/flows.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
*/
5858
#include "ipsec.h"
5959

60+
/*
61+
* Defines ssl tracker
62+
*/
63+
#include "openssl_tracker.h"
64+
6065
// return 0 on success, 1 if capacity reached
6166
static __always_inline int add_observed_intf(flow_metrics *value, pkt_info *pkt, u32 if_index,
6267
u8 direction) {

bpf/maps_definition.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,11 @@ struct {
9797
__uint(pinning, LIBBPF_PIN_BY_NAME);
9898
} ipsec_egress_map SEC(".maps");
9999

100+
// Ringbuf for SSL data events
101+
struct {
102+
__uint(type, BPF_MAP_TYPE_RINGBUF);
103+
__uint(max_entries, 1 << 27); // 16KB * 1000 events/sec * 5sec "eviction time" = ~128MB
104+
__uint(pinning, LIBBPF_PIN_BY_NAME);
105+
} ssl_data_event_map SEC(".maps");
106+
100107
#endif //__MAPS_DEFINITION_H__

bpf/openssl_tracker.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* OpenSSL monitoring uprobe/uretprobe eBPF hook.
3+
*/
4+
5+
#ifndef __OPENSSL_TRACKER_H__
6+
#define __OPENSSL_TRACKER_H__
7+
8+
#include "utils.h"
9+
10+
static inline void generate_SSL_data_event(struct pt_regs *ctx, u64 pid_tgid, u8 ssl_type,
11+
const char *buf, int len) {
12+
if (len <= 0) {
13+
return;
14+
}
15+
16+
struct ssl_data_event_t *event;
17+
event = bpf_ringbuf_reserve(&ssl_data_event_map, sizeof(*event), 0);
18+
if (!event) {
19+
return;
20+
}
21+
event->timestamp_ns = bpf_ktime_get_ns();
22+
event->pid_tgid = pid_tgid;
23+
event->ssl_type = ssl_type;
24+
event->data_len = len < MAX_DATA_SIZE_OPENSSL ? len : MAX_DATA_SIZE_OPENSSL;
25+
bpf_probe_read_user(&event->data, event->data_len, buf);
26+
bpf_ringbuf_submit(event, 0);
27+
}
28+
29+
// int SSL_write(SSL *ssl, const void *buf, int num);
30+
// https://github.com/openssl/openssl/blob/master/ssl/ssl_lib.c#L2666
31+
SEC("uprobe/SSL_write")
32+
int probe_entry_SSL_write(struct pt_regs *ctx) {
33+
if (enable_ssl == 0) {
34+
return 0;
35+
}
36+
37+
u64 pid_tgid = bpf_get_current_pid_tgid();
38+
39+
BPF_PRINTK("openssl uprobe/SSL_write pid: %d\n", pid_tgid);
40+
// https://github.com/openssl/openssl/blob/master/ssl/ssl_local.h#L1233
41+
void *ssl = (void *)PT_REGS_PARM1(ctx);
42+
43+
u32 ssl_type;
44+
int ret;
45+
46+
ret = bpf_probe_read_user(&ssl_type, sizeof(ssl_type), (u32 *)ssl);
47+
if (ret) {
48+
BPF_PRINTK("(OPENSSL) bpf_probe_read ssl_type_ptr failed, ret: %d\n", ret);
49+
return 0;
50+
}
51+
const char *buf = (const char *)PT_REGS_PARM2(ctx);
52+
int num = (int)PT_REGS_PARM3(ctx); // Third parameter: number of bytes to write
53+
54+
BPF_PRINTK("openssl uprobe/SSL_write type: %d, buf: %p, num: %d\n", ssl_type, buf, num);
55+
56+
// Read the data immediately in the uprobe (before SSL_write processes it)
57+
// This captures the plaintext before encryption
58+
if (num > 0) {
59+
generate_SSL_data_event(ctx, pid_tgid, ssl_type, buf, num);
60+
}
61+
62+
return 0;
63+
}
64+
65+
#endif /* __OPENSSL_TRACKER_H__ */

bpf/types.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,17 @@ struct filter_value_t {
277277
// Force emitting enums/structs into the ELF
278278
const static struct filter_value_t *unused12 __attribute__((unused));
279279

280+
#define MAX_DATA_SIZE_OPENSSL 1024 * 16
281+
// SSL data event
282+
struct ssl_data_event_t {
283+
u64 timestamp_ns;
284+
u64 pid_tgid;
285+
s32 data_len;
286+
u8 ssl_type;
287+
char data[MAX_DATA_SIZE_OPENSSL];
288+
} ssl_data_event;
289+
290+
// Force emitting enums/structs into the ELF
291+
const static struct ssl_data_event_t *unused14 __attribute__((unused));
292+
280293
#endif /* __TYPES_H__ */

examples/test-ssl-host.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
# Test SSL tracking with HOST processes (not containers)
3+
4+
echo "=== Testing SSL with Host Process ==="
5+
echo ""
6+
echo "This will SSH to a cluster node and run curl directly on the host"
7+
echo "This should trigger the SSL uprobes since the host process uses"
8+
echo "the same libssl.so that the agent attached to."
9+
echo ""
10+
11+
# Get node name
12+
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
13+
echo "Target node: $NODE"
14+
echo ""
15+
16+
# Run curl on the host (via docker exec into the kind node)
17+
echo "Running curl with HTTP/1.1 (--http1.1) on host..."
18+
docker exec $NODE curl -s --http1.1 https://httpbin.org/get > /dev/null
19+
echo "Request completed"
20+
echo ""
21+
22+
echo "Check agent logs for SSL events:"
23+
kubectl logs -n netobserv-privileged -l k8s-app=netobserv-ebpf-agent | grep 'SSL EVENT'

pkg/agent/agent.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ type Flows struct {
8686
promoServer *http.Server
8787
sampleDecoder *ovnobserv.SampleDecoder
8888

89-
metrics *metrics.Metrics
89+
metrics *metrics.Metrics
90+
rbSSLTracer *flow.RingBufTracer
9091
}
9192

9293
// ebpfFlowFetcher abstracts the interface of ebpf.FlowFetcher to allow dependency injection in tests
@@ -97,6 +98,7 @@ type ebpfFlowFetcher interface {
9798
LookupAndDeleteMap(*metrics.Metrics) map[ebpf.BpfFlowId]model.BpfFlowContent
9899
DeleteMapsStaleEntries(timeOut time.Duration)
99100
ReadRingBuf() (ringbuf.Record, error)
101+
ReadSSLRingBuf() (ringbuf.Record, error)
100102
}
101103

102104
// FlowsAgent instantiates a new agent, given a configuration.
@@ -175,6 +177,8 @@ func FlowsAgent(cfg *config.Agent) (*Flows, error) {
175177
BpfManBpfFSPath: cfg.BpfManBpfFSPath,
176178
EnableIPsecTracker: cfg.EnableIPsecTracking,
177179
FilterConfig: filterRules,
180+
EnableSSL: cfg.EnableSSL,
181+
OpenSSLPath: cfg.OpenSSLPath,
178182
}
179183

180184
fetcher, err := tracer.NewFlowFetcher(ebpfConfig, m)
@@ -206,6 +210,10 @@ func flowsAgent(
206210

207211
mapTracer := flow.NewMapTracer(fetcher, cfg.CacheActiveTimeout, cfg.StaleEntriesEvictTimeout, m, s, cfg.EnableUDNMapping)
208212
rbTracer := flow.NewRingBufTracer(fetcher, mapTracer, cfg.CacheActiveTimeout, m)
213+
var rbSSLTracer *flow.RingBufTracer
214+
if cfg.EnableSSL {
215+
rbSSLTracer = flow.NewSSLRingBufTracer(fetcher, mapTracer, cfg.CacheActiveTimeout, m)
216+
}
209217
accounter := flow.NewAccounter(cfg.CacheMaxFlows, cfg.CacheActiveTimeout, time.Now, monotime.Now, m, s, cfg.EnableUDNMapping)
210218
limiter := flow.NewCapacityLimiter(m)
211219

@@ -222,6 +230,7 @@ func flowsAgent(
222230
informer: informer,
223231
promoServer: promoServer,
224232
metrics: m,
233+
rbSSLTracer: rbSSLTracer,
225234
}, nil
226235
}
227236

@@ -392,6 +401,10 @@ func (f *Flows) buildAndStartPipeline(ctx context.Context) (*node.Terminal[[]*mo
392401
alog.Debug("connecting flows processing graph")
393402
mapTracer := node.AsStart(f.mapTracer.TraceLoop(ctx, f.cfg.ForceGC))
394403
rbTracer := node.AsStart(f.rbTracer.TraceLoop(ctx))
404+
var rbSSLTracer *node.Start[*model.RawRecord]
405+
if f.cfg.EnableSSL {
406+
rbSSLTracer = node.AsStart(f.rbSSLTracer.TraceLoop(ctx))
407+
}
395408

396409
accounter := node.AsMiddle(f.accounter.Account,
397410
node.ChannelBufferLen(f.cfg.BuffersLength))
@@ -408,6 +421,9 @@ func (f *Flows) buildAndStartPipeline(ctx context.Context) (*node.Terminal[[]*mo
408421
node.ChannelBufferLen(ebl))
409422

410423
rbTracer.SendsTo(accounter)
424+
if rbSSLTracer != nil {
425+
rbSSLTracer.SendsTo(accounter)
426+
}
411427

412428
mapTracer.SendsTo(limiter)
413429
accounter.SendsTo(limiter)
@@ -416,6 +432,9 @@ func (f *Flows) buildAndStartPipeline(ctx context.Context) (*node.Terminal[[]*mo
416432
alog.Debug("starting graph")
417433
mapTracer.Start()
418434
rbTracer.Start()
435+
if rbSSLTracer != nil {
436+
rbSSLTracer.Start()
437+
}
419438
return export, nil
420439
}
421440

0 commit comments

Comments
 (0)