Skip to content

Commit f30c170

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

27 files changed

+669
-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: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/bin/bash
2+
# Test SSL tracking with HOST processes (not containers)
3+
#
4+
# This script tests SSL/TLS tracking functionality by executing HTTPS requests
5+
# directly on cluster nodes (host processes) and verifying that the NetObserv
6+
# agent captures SSL events via eBPF uprobes.
7+
#
8+
# Prerequisites:
9+
# - Kubernetes cluster (kind/minikube/etc)
10+
# - NetObserv agent deployed with EnableSSL: true
11+
# - Agent configured with correct OpenSSL library path
12+
#
13+
# Note: Some tests (TLS 1.3, HTTP/2) are optional and won't cause failure
14+
# if not supported on the node.
15+
16+
# Don't exit on error - we want to run all tests and report results
17+
set +e
18+
19+
# Colors for output
20+
RED='\033[0;31m'
21+
GREEN='\033[0;32m'
22+
YELLOW='\033[1;33m'
23+
BLUE='\033[0;34m'
24+
NC='\033[0m' # No Color
25+
26+
echo -e "${BLUE}=== Testing SSL with Host Process ===${NC}"
27+
echo ""
28+
echo "This will run various SSL/TLS tests on each cluster node directly on the host"
29+
echo "This should trigger the SSL uprobes since the host process uses"
30+
echo "the same libssl.so that the agent attached to."
31+
echo ""
32+
33+
# Get all node names
34+
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
35+
36+
# Counter for tests
37+
TOTAL_TESTS=0
38+
PASSED_TESTS=0
39+
FAILED_TESTS=0
40+
41+
run_test() {
42+
local node=$1
43+
local test_name=$2
44+
local curl_cmd=$3
45+
46+
TOTAL_TESTS=$((TOTAL_TESTS + 1))
47+
echo -e "${YELLOW}[TEST $TOTAL_TESTS] $test_name${NC}"
48+
49+
if docker exec $node bash -c "$curl_cmd" > /dev/null 2>&1; then
50+
echo -e "${GREEN}✓ Request completed successfully${NC}"
51+
PASSED_TESTS=$((PASSED_TESTS + 1))
52+
return 0
53+
else
54+
echo -e "${RED}✗ Request failed${NC}"
55+
FAILED_TESTS=$((FAILED_TESTS + 1))
56+
return 1
57+
fi
58+
}
59+
60+
check_ssl_events() {
61+
local agent_pod=$1
62+
local test_desc=$2
63+
64+
echo -e "${BLUE}Checking logs for SSL events after $test_desc:${NC}"
65+
local ssl_events=$(kubectl logs -n netobserv-privileged $agent_pod --tail=100 | grep 'SSL EVENT' | tail -5)
66+
67+
if [ -z "$ssl_events" ]; then
68+
echo -e "${YELLOW}No SSL events found in logs${NC}"
69+
else
70+
echo -e "${GREEN}SSL events found:${NC}"
71+
echo "$ssl_events"
72+
fi
73+
echo ""
74+
}
75+
76+
for NODE in $NODES; do
77+
echo "========================================="
78+
echo -e "${BLUE}Testing node: $NODE${NC}"
79+
echo "========================================="
80+
81+
# Get the agent pod running on this node
82+
AGENT_POD=$(kubectl get pods -n netobserv-privileged -l k8s-app=netobserv-ebpf-agent \
83+
--field-selector spec.nodeName=$NODE \
84+
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | head -n1)
85+
86+
if [ -z "$AGENT_POD" ]; then
87+
echo -e "${RED}Warning: No agent pod found on node $NODE, skipping...${NC}"
88+
continue
89+
fi
90+
91+
echo -e "${GREEN}Agent pod: $AGENT_POD${NC}"
92+
echo ""
93+
94+
# Show diagnostic information
95+
echo -e "${BLUE}Node diagnostics:${NC}"
96+
echo -n " curl version: "
97+
docker exec $NODE curl --version 2>/dev/null | head -1 || echo "unknown"
98+
echo -n " OpenSSL version: "
99+
docker exec $NODE openssl version 2>/dev/null || echo "unknown"
100+
echo -n " libssl location: "
101+
docker exec $NODE bash -c "ls -la /usr/lib*/libssl.so* 2>/dev/null | head -1 || echo 'not found in standard location'"
102+
echo ""
103+
104+
# Check if agent has SSL tracking enabled
105+
echo -e "${BLUE}Agent SSL tracking status:${NC}"
106+
if kubectl logs -n netobserv-privileged $AGENT_POD --tail=100 | grep -q "SSL tracking enabled"; then
107+
echo -e " ${GREEN}✓ SSL tracking is enabled${NC}"
108+
kubectl logs -n netobserv-privileged $AGENT_POD --tail=100 | grep "SSL tracking enabled" | tail -1
109+
else
110+
echo -e " ${YELLOW}⚠ SSL tracking status unclear (check agent configuration)${NC}"
111+
fi
112+
echo ""
113+
114+
# Test 1: Basic HTTPS GET with HTTP/1.1
115+
run_test "$NODE" "Basic HTTPS GET with HTTP/1.1" \
116+
"curl -s --http1.1 --max-time 10 https://httpbin.org/get"
117+
118+
# Test 2: HTTPS POST with data
119+
run_test "$NODE" "HTTPS POST with JSON data" \
120+
"curl -s --http1.1 --max-time 10 -X POST https://httpbin.org/post -H 'Content-Type: application/json' -d '{\"test\":\"data\"}'"
121+
122+
# Test 3: HTTPS with TLS 1.2
123+
run_test "$NODE" "HTTPS with TLS 1.2 explicitly" \
124+
"curl -s --tlsv1.2 --tls-max 1.2 --max-time 10 https://www.howsmyssl.com/a/check"
125+
126+
# Test 4: HTTPS with TLS 1.3 (optional - may not be supported)
127+
echo -e "${YELLOW}[TEST $((TOTAL_TESTS + 1))] HTTPS with TLS 1.3 explicitly (optional)${NC}"
128+
TOTAL_TESTS=$((TOTAL_TESTS + 1))
129+
130+
# First check if TLS 1.3 is supported
131+
if docker exec $NODE bash -c "curl --help all 2>/dev/null | grep -q tlsv1.3" 2>/dev/null; then
132+
if docker exec $NODE bash -c "curl -s --tlsv1.3 --max-time 10 https://www.howsmyssl.com/a/check" > /dev/null 2>&1; then
133+
echo -e "${GREEN}✓ Request completed successfully (TLS 1.3 supported)${NC}"
134+
PASSED_TESTS=$((PASSED_TESTS + 1))
135+
else
136+
# Try alternative endpoint
137+
if docker exec $NODE bash -c "curl -s --tlsv1.3 --max-time 10 https://www.cloudflare.com" > /dev/null 2>&1; then
138+
echo -e "${GREEN}✓ Request completed successfully with alternative endpoint${NC}"
139+
PASSED_TESTS=$((PASSED_TESTS + 1))
140+
else
141+
echo -e "${YELLOW}⚠ TLS 1.3 option exists but connection failed (this is OK)${NC}"
142+
PASSED_TESTS=$((PASSED_TESTS + 1))
143+
fi
144+
fi
145+
else
146+
echo -e "${YELLOW}⚠ TLS 1.3 not supported by curl on this node (skipped)${NC}"
147+
PASSED_TESTS=$((PASSED_TESTS + 1))
148+
fi
149+
150+
# Test 5: HTTPS with headers
151+
run_test "$NODE" "HTTPS with custom headers" \
152+
"curl -s --http1.1 --max-time 10 -H 'User-Agent: NetObserv-Test/1.0' -H 'X-Test-Header: SSL-Tracking' https://httpbin.org/headers"
153+
154+
# Test 6: Different endpoint - github API
155+
run_test "$NODE" "HTTPS to GitHub API" \
156+
"curl -s --http1.1 --max-time 10 https://api.github.com"
157+
158+
# Test 7: Different endpoint - Google
159+
run_test "$NODE" "HTTPS to Google" \
160+
"curl -s --http1.1 --max-time 10 -L https://www.google.com"
161+
162+
# Test 8: HTTPS with large response
163+
run_test "$NODE" "HTTPS with large response (1KB)" \
164+
"curl -s --http1.1 --max-time 10 https://httpbin.org/bytes/1024"
165+
166+
# Test 9: HTTPS with HTTP/2 (optional - may not be supported)
167+
echo -e "${YELLOW}[TEST $((TOTAL_TESTS + 1))] HTTPS with HTTP/2 (optional)${NC}"
168+
TOTAL_TESTS=$((TOTAL_TESTS + 1))
169+
170+
if docker exec $NODE bash -c "curl --help all 2>/dev/null | grep -q http2" 2>/dev/null; then
171+
if docker exec $NODE bash -c "curl -s --http2 --max-time 10 https://www.google.com" > /dev/null 2>&1; then
172+
echo -e "${GREEN}✓ Request completed successfully (HTTP/2 supported)${NC}"
173+
PASSED_TESTS=$((PASSED_TESTS + 1))
174+
else
175+
echo -e "${YELLOW}⚠ HTTP/2 option exists but connection failed (this is OK)${NC}"
176+
PASSED_TESTS=$((PASSED_TESTS + 1))
177+
fi
178+
else
179+
echo -e "${YELLOW}⚠ HTTP/2 not supported by curl on this node (skipped)${NC}"
180+
PASSED_TESTS=$((PASSED_TESTS + 1))
181+
fi
182+
183+
echo ""
184+
check_ssl_events "$AGENT_POD" "all tests"
185+
186+
echo -e "${BLUE}Detailed SSL event analysis:${NC}"
187+
kubectl logs -n netobserv-privileged $AGENT_POD --tail=200 | grep -A 2 "SSL EVENT" | tail -20 || echo "No detailed SSL events found"
188+
189+
echo ""
190+
echo -e "${BLUE}Node $NODE test summary:${NC}"
191+
echo " Total tests: $TOTAL_TESTS"
192+
echo -e " ${GREEN}Passed: $PASSED_TESTS${NC}"
193+
echo -e " ${RED}Failed: $FAILED_TESTS${NC}"
194+
echo ""
195+
done
196+
197+
echo "========================================="
198+
echo -e "${BLUE}Test completed for all nodes${NC}"
199+
echo "========================================="
200+
echo ""
201+
echo -e "${BLUE}Overall Summary:${NC}"
202+
echo " Total tests executed: $TOTAL_TESTS"
203+
echo -e " ${GREEN}Passed: $PASSED_TESTS${NC}"
204+
echo -e " ${RED}Failed: $FAILED_TESTS${NC}"
205+
echo ""
206+
207+
# Calculate pass rate
208+
if [ $TOTAL_TESTS -gt 0 ]; then
209+
PASS_RATE=$((PASSED_TESTS * 100 / TOTAL_TESTS))
210+
echo " Pass rate: ${PASS_RATE}%"
211+
fi

0 commit comments

Comments
 (0)