Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .mk/bc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ define PROGRAMS
"xfrm_input_kprobe": "kprobe",
"xfrm_input_kretprobe": "kretprobe",
"xfrm_output_kprobe": "kprobe",
"xfrm_output_kretprobe": "kretprobe"
"xfrm_output_kretprobe": "kretprobe",
"probe_entry_SSL_write":"uprobe",
}
endef

Expand All @@ -38,7 +39,8 @@ define MAPS
"filter_map":"lpm_trie",
"peer_filter_map":"lpm_trie",
"ipsec_ingress_map":"hash",
"ipsec_egress_map":"hash"
"ipsec_egress_map":"hash",
"ssl_data_event_map":"ringbuf"
}
endef

Expand Down
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ ARG TARGETARCH
# Build the manager binary
FROM docker.io/library/golang:1.24 as builder

ARG TARGETARCH
ARG LDFLAGS

WORKDIR /opt/app-root
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ create-and-deploy-kind-cluster: prereqs ## Create a kind cluster and deploy the

.PHONY: destroy-kind-cluster
destroy-kind-cluster: ## Destroy the kind cluster.
oc delete -f scripts/agent.yml
kubectl delete -f scripts/agent.yml
kind delete cluster

##@ Images
Expand Down
1 change: 1 addition & 0 deletions bpf/configs.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ volatile const u8 enable_network_events_monitoring = 0;
volatile const u8 network_events_monitoring_groupid = 0;
volatile const u8 enable_pkt_translation_tracking = 0;
volatile const u8 enable_ipsec = 0;
volatile const u8 enable_ssl = 0;
#endif //__CONFIGS_H__
5 changes: 5 additions & 0 deletions bpf/flows.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
*/
#include "ipsec.h"

/*
* Defines ssl tracker
*/
#include "openssl_tracker.h"

// return 0 on success, 1 if capacity reached
static __always_inline int add_observed_intf(flow_metrics *value, pkt_info *pkt, u32 if_index,
u8 direction) {
Expand Down
7 changes: 7 additions & 0 deletions bpf/maps_definition.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,11 @@ struct {
__uint(pinning, LIBBPF_PIN_BY_NAME);
} ipsec_egress_map SEC(".maps");

// Ringbuf for SSL data events
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 27); // 16KB * 1000 events/sec * 5sec "eviction time" = ~128MB
__uint(pinning, LIBBPF_PIN_BY_NAME);
} ssl_data_event_map SEC(".maps");

#endif //__MAPS_DEFINITION_H__
65 changes: 65 additions & 0 deletions bpf/openssl_tracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* OpenSSL monitoring uprobe/uretprobe eBPF hook.
*/

#ifndef __OPENSSL_TRACKER_H__
#define __OPENSSL_TRACKER_H__

#include "utils.h"

static inline void generate_SSL_data_event(struct pt_regs *ctx, u64 pid_tgid, u8 ssl_type,
const char *buf, int len) {
if (len <= 0) {
return;
}

struct ssl_data_event_t *event;
event = bpf_ringbuf_reserve(&ssl_data_event_map, sizeof(*event), 0);
if (!event) {
return;
}
event->timestamp_ns = bpf_ktime_get_ns();
event->pid_tgid = pid_tgid;
event->ssl_type = ssl_type;
event->data_len = len < MAX_DATA_SIZE_OPENSSL ? len : MAX_DATA_SIZE_OPENSSL;
bpf_probe_read_user(&event->data, event->data_len, buf);
bpf_ringbuf_submit(event, 0);
}

// int SSL_write(SSL *ssl, const void *buf, int num);
// https://github.com/openssl/openssl/blob/master/ssl/ssl_lib.c#L2666
SEC("uprobe/SSL_write")
int probe_entry_SSL_write(struct pt_regs *ctx) {
if (enable_ssl == 0) {
return 0;
}

u64 pid_tgid = bpf_get_current_pid_tgid();

BPF_PRINTK("openssl uprobe/SSL_write pid: %d\n", pid_tgid);
// https://github.com/openssl/openssl/blob/master/ssl/ssl_local.h#L1233
void *ssl = (void *)PT_REGS_PARM1(ctx);

u32 ssl_type;
int ret;

ret = bpf_probe_read_user(&ssl_type, sizeof(ssl_type), (u32 *)ssl);
if (ret) {
BPF_PRINTK("(OPENSSL) bpf_probe_read ssl_type_ptr failed, ret: %d\n", ret);
return 0;
}
const char *buf = (const char *)PT_REGS_PARM2(ctx);
int num = (int)PT_REGS_PARM3(ctx); // Third parameter: number of bytes to write

BPF_PRINTK("openssl uprobe/SSL_write type: %d, buf: %p, num: %d\n", ssl_type, buf, num);

// Read the data immediately in the uprobe (before SSL_write processes it)
// This captures the plaintext before encryption
if (num > 0) {
generate_SSL_data_event(ctx, pid_tgid, ssl_type, buf, num);
}

return 0;
}

#endif /* __OPENSSL_TRACKER_H__ */
13 changes: 13 additions & 0 deletions bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,17 @@ struct filter_value_t {
// Force emitting enums/structs into the ELF
const static struct filter_value_t *unused12 __attribute__((unused));

#define MAX_DATA_SIZE_OPENSSL 1024 * 16
// SSL data event
struct ssl_data_event_t {
u64 timestamp_ns;
u64 pid_tgid;
s32 data_len;
u8 ssl_type;
char data[MAX_DATA_SIZE_OPENSSL];
} ssl_data_event;

// Force emitting enums/structs into the ELF
const static struct ssl_data_event_t *unused13 __attribute__((unused));

#endif /* __TYPES_H__ */
216 changes: 216 additions & 0 deletions examples/test-ssl-host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#!/bin/bash
# Test SSL tracking with HOST processes (not containers)
#
# This script tests SSL/TLS tracking functionality by executing HTTPS requests
# directly on cluster nodes (host processes) and verifying that the NetObserv
# agent captures SSL events via eBPF uprobes.
#
# Prerequisites:
# - Kubernetes cluster (kind/minikube/etc)
# - NetObserv agent deployed with EnableSSL: true
# - Agent configured with correct OpenSSL library path
#
# Note: Some tests (TLS 1.3, HTTP/2) are optional and won't cause failure
# if not supported on the node.

# Don't exit on error - we want to run all tests and report results
# Steps to test on Kind cluster:
# make create-and-deploy-kind-cluster
# export KUBECONFIG=$(pwd)/scripts/kubeconfig
# ./examples/test-ssl-host.sh

set +e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}=== Testing SSL with Host Process ===${NC}"
echo ""
echo "This will run various SSL/TLS tests on each cluster node directly on the host"
echo "This should trigger the SSL uprobes since the host process uses"
echo "the same libssl.so that the agent attached to."
echo ""

# Get all node names
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')

# Counter for tests
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0

run_test() {
local node=$1
local test_name=$2
local curl_cmd=$3

TOTAL_TESTS=$((TOTAL_TESTS + 1))
echo -e "${YELLOW}[TEST $TOTAL_TESTS] $test_name${NC}"

if docker exec $node bash -c "$curl_cmd" > /dev/null 2>&1; then
echo -e "${GREEN}✓ Request completed successfully${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
echo -e "${RED}✗ Request failed${NC}"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}

check_ssl_events() {
local agent_pod=$1
local test_desc=$2

echo -e "${BLUE}Checking logs for SSL events after $test_desc:${NC}"
local ssl_events=$(kubectl logs -n netobserv-privileged $agent_pod --tail=100 | grep 'SSL EVENT' | tail -5)

if [ -z "$ssl_events" ]; then
echo -e "${YELLOW}No SSL events found in logs${NC}"
else
echo -e "${GREEN}SSL events found:${NC}"
echo "$ssl_events"
fi
echo ""
}

for NODE in $NODES; do
echo "========================================="
echo -e "${BLUE}Testing node: $NODE${NC}"
echo "========================================="

# Get the agent pod running on this node
AGENT_POD=$(kubectl get pods -n netobserv-privileged -l k8s-app=netobserv-ebpf-agent \
--field-selector spec.nodeName=$NODE \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | head -n1)

if [ -z "$AGENT_POD" ]; then
echo -e "${RED}Warning: No agent pod found on node $NODE, skipping...${NC}"
continue
fi

echo -e "${GREEN}Agent pod: $AGENT_POD${NC}"
echo ""

# Show diagnostic information
echo -e "${BLUE}Node diagnostics:${NC}"
echo -n " curl version: "
docker exec $NODE curl --version 2>/dev/null | head -1 || echo "unknown"
echo -n " OpenSSL version: "
docker exec $NODE openssl version 2>/dev/null || echo "unknown"
echo -n " libssl location: "
docker exec $NODE bash -c "ls -la /usr/lib*/libssl.so* 2>/dev/null | head -1 || echo 'not found in standard location'"
echo ""

# Check if agent has SSL tracking enabled
echo -e "${BLUE}Agent SSL tracking status:${NC}"
if kubectl logs -n netobserv-privileged $AGENT_POD --tail=100 | grep -q "SSL tracking enabled"; then
echo -e " ${GREEN}✓ SSL tracking is enabled${NC}"
kubectl logs -n netobserv-privileged $AGENT_POD --tail=100 | grep "SSL tracking enabled" | tail -1
else
echo -e " ${YELLOW}⚠ SSL tracking status unclear (check agent configuration)${NC}"
fi
echo ""

# Test 1: Basic HTTPS GET with HTTP/1.1
run_test "$NODE" "Basic HTTPS GET with HTTP/1.1" \
"curl -s --http1.1 --max-time 10 https://httpbin.org/get"

# Test 2: HTTPS POST with data
run_test "$NODE" "HTTPS POST with JSON data" \
"curl -s --http1.1 --max-time 10 -X POST https://httpbin.org/post -H 'Content-Type: application/json' -d '{\"test\":\"data\"}'"

# Test 3: HTTPS with TLS 1.2
run_test "$NODE" "HTTPS with TLS 1.2 explicitly" \
"curl -s --tlsv1.2 --tls-max 1.2 --max-time 10 https://www.howsmyssl.com/a/check"

# Test 4: HTTPS with TLS 1.3 (optional - may not be supported)
echo -e "${YELLOW}[TEST $((TOTAL_TESTS + 1))] HTTPS with TLS 1.3 explicitly (optional)${NC}"
TOTAL_TESTS=$((TOTAL_TESTS + 1))

# First check if TLS 1.3 is supported
if docker exec $NODE bash -c "curl --help all 2>/dev/null | grep -q tlsv1.3" 2>/dev/null; then
if docker exec $NODE bash -c "curl -s --tlsv1.3 --max-time 10 https://www.howsmyssl.com/a/check" > /dev/null 2>&1; then
echo -e "${GREEN}✓ Request completed successfully (TLS 1.3 supported)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
# Try alternative endpoint
if docker exec $NODE bash -c "curl -s --tlsv1.3 --max-time 10 https://www.cloudflare.com" > /dev/null 2>&1; then
echo -e "${GREEN}✓ Request completed successfully with alternative endpoint${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo -e "${YELLOW}⚠ TLS 1.3 option exists but connection failed (this is OK)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
fi
fi
else
echo -e "${YELLOW}⚠ TLS 1.3 not supported by curl on this node (skipped)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
fi

# Test 5: HTTPS with headers
run_test "$NODE" "HTTPS with custom headers" \
"curl -s --http1.1 --max-time 10 -H 'User-Agent: NetObserv-Test/1.0' -H 'X-Test-Header: SSL-Tracking' https://httpbin.org/headers"

# Test 6: Different endpoint - github API
run_test "$NODE" "HTTPS to GitHub API" \
"curl -s --http1.1 --max-time 10 https://api.github.com"

# Test 7: Different endpoint - Google
run_test "$NODE" "HTTPS to Google" \
"curl -s --http1.1 --max-time 10 -L https://www.google.com"

# Test 8: HTTPS with large response
run_test "$NODE" "HTTPS with large response (1KB)" \
"curl -s --http1.1 --max-time 10 https://httpbin.org/bytes/1024"

# Test 9: HTTPS with HTTP/2 (optional - may not be supported)
echo -e "${YELLOW}[TEST $((TOTAL_TESTS + 1))] HTTPS with HTTP/2 (optional)${NC}"
TOTAL_TESTS=$((TOTAL_TESTS + 1))

if docker exec $NODE bash -c "curl --help all 2>/dev/null | grep -q http2" 2>/dev/null; then
if docker exec $NODE bash -c "curl -s --http2 --max-time 10 https://www.google.com" > /dev/null 2>&1; then
echo -e "${GREEN}✓ Request completed successfully (HTTP/2 supported)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo -e "${YELLOW}⚠ HTTP/2 option exists but connection failed (this is OK)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
fi
else
echo -e "${YELLOW}⚠ HTTP/2 not supported by curl on this node (skipped)${NC}"
PASSED_TESTS=$((PASSED_TESTS + 1))
fi

echo ""
check_ssl_events "$AGENT_POD" "all tests"

echo -e "${BLUE}Detailed SSL event analysis:${NC}"
kubectl logs -n netobserv-privileged $AGENT_POD --tail=200 | grep -A 2 "SSL EVENT" | tail -20 || echo "No detailed SSL events found"

echo ""
echo -e "${BLUE}Node $NODE test summary:${NC}"
echo " Total tests: $TOTAL_TESTS"
echo -e " ${GREEN}Passed: $PASSED_TESTS${NC}"
echo -e " ${RED}Failed: $FAILED_TESTS${NC}"
echo ""
done

echo "========================================="
echo -e "${BLUE}Test completed for all nodes${NC}"
echo "========================================="
echo ""
echo -e "${BLUE}Overall Summary:${NC}"
echo " Total tests executed: $TOTAL_TESTS"
echo -e " ${GREEN}Passed: $PASSED_TESTS${NC}"
echo -e " ${RED}Failed: $FAILED_TESTS${NC}"
echo ""

# Calculate pass rate
if [ $TOTAL_TESTS -gt 0 ]; then
PASS_RATE=$((PASSED_TESTS * 100 / TOTAL_TESTS))
echo " Pass rate: ${PASS_RATE}%"
fi
Loading