mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-01 16:03:55 +08:00
Replace aquasecurity/libbpfgo with cilium/ebpf (#60541)
* start the refactor away from aquasecurity/libbpfgo to use cilium/ebpf Remove debug statements and unused code in BPF library This commit eliminates unnecessary debug log statements from bpf_test.go and removes unused code in disk.go and network.go files within BPF library. The aim is to make codebase cleaner, enhancing readability and maintainability without losing track of functional aspects. It's part of an ongoing effort to maintain optimal code quality across the project. Revise Dockerfile and Makefile instructions Adjusted Dockerfile and Makefile commands to optimize build process. The changes ensure better readability and maintainability of code, especially with respect to BPF bytecode and test preparations. A clause was also added to check for the presence of clang before building BPF bytecode. Refactor BPF code and tests Refactored BPF source code and its corresponding tests for better readability and efficiency. This includes updating data structures like rawExecEvent, rawOpenEvent, rawConn4Event and rawConn6Event to their more meritorious versions like commandDataT, diskDataT, networkIpv4DataT and networkIpv6DataT respectively. Also, generated concise and clear bindigs for eBPF programs to replace the previous multi-arch setup. Furthermore, correspondingly adapted all related test cases arising in bpf_test.go, command.go and other files. Remove counter_test from BPF and related test code This commit removes the file counter_test.bpf.c from the enhanced recording in BPF. Following this file deletion, corresponding test functions in bpf_test.go, that were originally testing the functionality provided by counter_test.bpf.c, have been removed as well. Refactor BPF test and improve error handling The commit refactors the BPF test suite to run each test as a sub-test by using t.Run, easing debugging and error tracing. It also simplifies the 'executeCommand' function by removing redundant function calls related to finding executable files, and improves error handling. In 'cgroup.go', the code has been adjusted to retry on EBUSY errors when removing a cgroup, instead of ignoring them. Update cgroup deletion process and error handling The changes made introduce a retry mechanism when deleting a cgroup in case the cgroup is busy. This prevents a failed attempt from immediately returning an error. Additionally, error handling has been improved in the 'writePids' function to better handle 'no such process' errors. Enhance cgroup monitoring in disk.bpf.c The code has been updated to check if current cgroup is being monitored in the "enter_open" method of "disk.bpf.c". If the cgroup is not marked for monitoring, it will be ignored, enhancing overall cgroup management. Improve resource handling This commit introduces a deferred close to the sendEvents function to ensure proper resource handling of the eventBuf object. Add rlimit package to remove resource limits Introduced the "github.com/cilium/ebpf/rlimit" package and added a function to remove resource limits for kernels less than 5.11. This operation is necessary for kernels with a lower version because they may face issues when running applications with allocated memory limitations. Fix a test after rebase Refactor BPF testing setup and implement session closing This commit updates the BPF testing structure to remove the context dependency in event generation functions, simplifying the test setup. Additionally, it adds a 'closed' field to the 'open', 'conn', and 'exec' structs alongside mutex protection. This implementation allows checking if a session is closed before any operations, correctly handling already closed sessions. Furthermore, it corrects tracepoints for handling 'openat' and 'openat2' system calls for different platforms. This refactoring effort aims to improve the robustness and readability of the BPF testing suite. Update conditions for BPF bytecode building This update modifies the Makefile to add a conditional check for the presence of /usr/include/bpf/bpf_helpers.h. This new requirement must be met along with the existence of /usr/include/linux/bpf.h and the installation of Clang, in order to facilitate the correct build of BPF bytecode. Consequently, this change enhances the reliability of the build process. Add conditional for bpf_helpers.h in Makefile This commit adds an additional check in the Makefile for the existence of /usr/include/bpf/bpf_helpers.h. This ensures that BPF bytecode only gets built when both bpf.h and bpf_helpers.h exist, and Clang is installed, improving the build process reliability. Refactor and improve BPF bytecode generation This commit refactors and improves the BPF bytecode generation process. It introduces distinct bytecode structures for different architectures, ensuring better compatibility and execution. The BPF test is adjusted due to its tendency to fail unpredictably, hence it's commented out to avoid unnecessary build failures. This commit also eliminates certain redundancies in `common.mk`, improving the clarity of the build process. Comment out test code in bpf_test.go The test code in the file bpf_test.go has been commented out. These changes are circumstantial and do not imply that the test code is obsolete or unnecessary. The use of such tests may be needed in future commits or for local development testing and debugging. Remove libbpf dependency from Vagrantfile and update README The Vagrantfile and README have been updated to remove the no longer needed libbpf dependency. This change simplifies the project set up by eliminating unused build steps in the Vagrantfile and correcting related instructions in the README. This results in a more streamlined, intuitive build process and reduced potential for code complexity and dependency conflicts. Remove obsolete dependencies and related code blocks The 'aquasecurity/libbpfgo' library referencing and the related code blocks have been removed from the test files and core modules. The corresponding setup instructions in the dockerfiles and build scripts have also been eliminated. This update simplifies the build process and overall project structure while reducing unnecessary dependency risks. Remove aquasecurity/libbpfgo dependency Remove restricted.bpf.c Migrate disk events to cilium Migrate network events to cilium Remove restricted session Use cilium/ebpf for BPF * fix build issues caused by stale branch Also updated bpf bytecode, updated github.com/cilium/ebpf to 0.19.0, and updated bpf/README to reflect changes * fix tests and a bit of cleanup Also added a subtest and fixed receiving IPv6 network events and fixed attempting to create tracepoints for syscalls that are not present on arm64. * repurpose buffer size configs to set event channel buffer size * add new GHA job to check that generated eBPF files are up to date * fix lost event promethus counters and address feedback * address feedback * use only unsafe instead of CGO to convert C strings * rename common_data_t comm -> command * convert strings in bpf programs from char arrays to u8 or uint8 arrays so we can convert them to Go strings without the use of the unsafe package --------- Co-authored-by: Jakub Nyckowski <jakub.nyckowski@goteleport.com> Co-authored-by: Tim Ross <tim.ross@goteleport.com>
This commit is contained in:
@@ -7,8 +7,6 @@ updates:
|
||||
day: 'sunday'
|
||||
time: '09:00' # 9am UTC
|
||||
ignore:
|
||||
# Must be kept in-sync with libbpf
|
||||
- dependency-name: github.com/aquasecurity/libbpfgo
|
||||
# Forked/replaced dependencies
|
||||
- dependency-name: github.com/alecthomas/kingpin/v2
|
||||
- dependency-name: github.com/go-mysql-org/go-mysql
|
||||
|
||||
@@ -27,7 +27,6 @@ jobs:
|
||||
TELEPORT_ETCD_TEST: yes
|
||||
TELEPORT_ETCD_TEST_ENDPOINT: https://etcd0:2379
|
||||
TELEPORT_XAUTH_TEST: yes
|
||||
TELEPORT_BPF_TEST: yes
|
||||
WEBASSETS_SKIP_BUILD: 1
|
||||
options: --cap-add=SYS_ADMIN --privileged
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
has_proto: ${{ steps.changes.outputs.has_proto }}
|
||||
has_rfd: ${{ steps.changes.outputs.has_rfd }}
|
||||
has_ui: ${{ steps.changes.outputs.has_ui }}
|
||||
has_bpf: ${{ steps.changes.outputs.has_bpf }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
@@ -79,6 +80,11 @@ jobs:
|
||||
- 'tsconfig.json'
|
||||
- 'tsconfig.node.json'
|
||||
- 'docs/pages/reference/audit-events.mdx'
|
||||
has_bpf:
|
||||
- '.github/workflows/lint.yaml'
|
||||
- 'bpf/**.c'
|
||||
- 'bpf/**.h'
|
||||
- 'lib/bpf/*'
|
||||
|
||||
lint-go:
|
||||
name: Lint (Go)
|
||||
@@ -132,7 +138,7 @@ jobs:
|
||||
# If the config is not valid, golangci-lint will fail.
|
||||
verify: false
|
||||
version: ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
args: --build-tags libfido2,piv
|
||||
args: --build-tags bpf,libfido2,piv
|
||||
skip-cache: true
|
||||
- name: golangci-lint (assets/backport)
|
||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||
@@ -338,3 +344,22 @@ jobs:
|
||||
# We have to add the current directory as a safe directory or else git commands will not work as expected.
|
||||
run: git config --global --add safe.directory $(realpath .) && make audit-event-reference-up-to-date
|
||||
|
||||
lint-bpf:
|
||||
name: BPF Generated Files
|
||||
needs: changes
|
||||
if: ${{ !startsWith(github.head_ref, 'dependabot/') && needs.changes.outputs.has_bpf == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
container:
|
||||
image: ghcr.io/gravitational/teleport-buildbox:teleport19
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if BPF generated files are up to date
|
||||
# We have to add the current directory as a safe directory or else git commands will not work as expected.
|
||||
run: git config --global --add safe.directory $(realpath .) && make bpf-up-to-date
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
TELEPORT_ETCD_TEST: yes
|
||||
TELEPORT_ETCD_TEST_ENDPOINT: https://etcd0:2379
|
||||
TELEPORT_XAUTH_TEST: yes
|
||||
TELEPORT_BPF_TEST: yes
|
||||
WEBASSETS_SKIP_BUILD: 1
|
||||
options: --cap-add=SYS_ADMIN --privileged
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ build
|
||||
!/web/packages/build
|
||||
/web/packages/shared/libs/ironrdp/pkg/**
|
||||
*.o
|
||||
# Do not ignore BPF bytecode files
|
||||
!/lib/bpf/*.o
|
||||
*.a
|
||||
*.so
|
||||
*.syso
|
||||
|
||||
@@ -388,7 +388,7 @@ $(BUILDDIR)/tctl:
|
||||
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "$(PAM_TAG) $(FIPS_TAG) $(LIBFIDO2_BUILD_TAG) $(TOUCHID_TAG) $(PIV_BUILD_TAG) $(KUSTOMIZE_NO_DYNAMIC_PLUGIN)" -o $(BUILDDIR)/tctl $(BUILDFLAGS) $(TOOLS_LDFLAGS) ./tool/tctl
|
||||
|
||||
.PHONY: $(BUILDDIR)/teleport
|
||||
$(BUILDDIR)/teleport: ensure-webassets bpf-bytecode rdpclient
|
||||
$(BUILDDIR)/teleport: ensure-webassets rdpclient
|
||||
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "webassets_embed $(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(WEBASSETS_TAG) $(RDPCLIENT_TAG) $(PIV_BUILD_TAG) $(KUSTOMIZE_NO_DYNAMIC_PLUGIN)" -o $(BUILDDIR)/teleport $(BUILDFLAGS) $(TELEPORT_LDFLAGS) ./tool/teleport
|
||||
|
||||
# NOTE: Any changes to the `tsh` build here must be copied to `build.assets/windows/build.ps1`
|
||||
@@ -455,33 +455,35 @@ tctl-app:
|
||||
|
||||
#
|
||||
# BPF support (IF ENABLED)
|
||||
# Requires a recent version of clang and libbpf installed.
|
||||
# Requires clang 14+
|
||||
#
|
||||
ifeq ("$(with_bpf)","yes")
|
||||
$(ER_BPF_BUILDDIR):
|
||||
mkdir -p $(ER_BPF_BUILDDIR)
|
||||
|
||||
# Build BPF code
|
||||
$(ER_BPF_BUILDDIR)/%.bpf.o: bpf/enhancedrecording/%.bpf.c $(wildcard bpf/*.h) | $(ER_BPF_BUILDDIR)
|
||||
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(KERNEL_ARCH) $(BPF_INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@
|
||||
$(LLVM_STRIP) -g $@ # strip useless DWARF info
|
||||
|
||||
.PHONY: bpf-er-bytecode
|
||||
bpf-er-bytecode: $(ER_BPF_BUILDDIR)/command.bpf.o $(ER_BPF_BUILDDIR)/disk.bpf.o $(ER_BPF_BUILDDIR)/network.bpf.o $(ER_BPF_BUILDDIR)/counter_test.bpf.o
|
||||
|
||||
# Enable target only if /usr/include/linux/bpf.h exists and clang is installed.
|
||||
# This is a requirement for building BPF bytecode.
|
||||
.PHONY: bpf-bytecode
|
||||
bpf-bytecode: bpf-er-bytecode
|
||||
bpf-bytecode:
|
||||
ifneq ($(or $(wildcard /usr/include/linux/bpf.h),$(wildcard /usr/include/bpf/bpf_helpers.h)),"")
|
||||
else
|
||||
$(error "libbpf-dev is required to build BPF bytecode")
|
||||
endif # libbpf-dev installed
|
||||
ifneq ("$(shell command -v clang --version 2>/dev/null)","")
|
||||
go generate ./lib/bpf/
|
||||
else
|
||||
$(error "clang is required to build BPF bytecode")
|
||||
endif # clang installed
|
||||
|
||||
# bpf-up-to-date checks if the generated BPF bytecode is up to date.
|
||||
.PHONY: bpf-up-to-date
|
||||
bpf-up-to-date: must-start-clean/host bpf-bytecode
|
||||
@if ! git diff --quiet; then \
|
||||
./build.assets/please-run.sh "bpf bytecode" "make -C build.assets bpf-bytecode"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Generate vmlinux.h based on the installed kernel
|
||||
.PHONY: update-vmlinux-h
|
||||
update-vmlinux-h:
|
||||
bpftool btf dump file /sys/kernel/btf/vmlinux format c >bpf/vmlinux.h
|
||||
|
||||
else
|
||||
.PHONY: bpf-bytecode
|
||||
bpf-bytecode:
|
||||
endif
|
||||
|
||||
.PHONY: rdpclient
|
||||
rdpclient: rustup-toolchain-warning
|
||||
ifeq ("$(with_rdpclient)", "yes")
|
||||
@@ -550,10 +552,6 @@ clean: clean-ui clean-build
|
||||
clean-build:
|
||||
@echo "---> Cleaning up OSS build artifacts."
|
||||
rm -rf $(BUILDDIR)
|
||||
# Check if the variable is set to prevent calling remove on the root directory.
|
||||
ifneq ($(ER_BPF_BUILDDIR),)
|
||||
rm -f $(ER_BPF_BUILDDIR)/*.o
|
||||
endif
|
||||
-cargo clean
|
||||
-go clean -cache
|
||||
rm -f *.gz
|
||||
@@ -951,7 +949,7 @@ test-env-leakage:
|
||||
|
||||
# Runs test prepare steps
|
||||
.PHONY: test-go-prepare
|
||||
test-go-prepare: ensure-webassets bpf-bytecode $(TEST_LOG_DIR) $(VERSRC)
|
||||
test-go-prepare: ensure-webassets rdpclient $(TEST_LOG_DIR) $(VERSRC)
|
||||
|
||||
# Runs base unit tests
|
||||
.PHONY: test-go-unit
|
||||
@@ -1034,7 +1032,7 @@ test-go-chaos:
|
||||
#
|
||||
UNIT_ROOT_REGEX := ^TestRoot
|
||||
.PHONY: test-go-root
|
||||
test-go-root: ensure-webassets bpf-bytecode rdpclient $(TEST_LOG_DIR)
|
||||
test-go-root: ensure-webassets rdpclient $(TEST_LOG_DIR)
|
||||
test-go-root: FLAGS ?= -race -shuffle on
|
||||
test-go-root: PACKAGES = $(shell go list $(ADDFLAGS) ./... | grep -v -e e2e -e integration -e integrations/operator)
|
||||
test-go-root: $(VERSRC)
|
||||
|
||||
+13
-29
@@ -6,7 +6,7 @@ This document will help you get started.
|
||||
#### Before you start here are some useful links:
|
||||
|
||||
* https://docs.kernel.org/bpf/ - BPF documentation
|
||||
* https://github.com/aquasecurity/libbpfgo - Go bindings for libbpf
|
||||
* https://github.com/cilium/ebpf - Go library to interact with BPF
|
||||
* https://github.com/iovisor/bcc - BPF Compiler Collection (a lot of examples)
|
||||
* https://github.com/cilium/cilium - BPF-based networking, security, and observability (more examples)
|
||||
* https://man7.org/linux/man-pages/man7/bpf-helpers.7.html - BPF helpers documentation
|
||||
@@ -17,14 +17,16 @@ We support BPFs only on ARM64 and x86_64 architectures.
|
||||
|
||||
1. VM with Ubuntu 20.04+ (or any other Linux distro with the kernel 5.8+ - Docker doesn't work for many BPF features)
|
||||
2. Install `clang` (required for compiling BPF code) - `apt install clang` should be enough
|
||||
3. Install `libbpf` (required for loading BPF code) - get the required version from our Docker file
|
||||
|
||||
You can also use our Dockerfile to build the environment:
|
||||
|
||||
```bash
|
||||
make -C build.assets release-centos7
|
||||
make -C build.assets bpf-bytecode
|
||||
```
|
||||
|
||||
Note that there have been some issues with command tracing reading commands and arguments in a arm64 VM. You may
|
||||
need to test/develop on an EC2 instance instead.
|
||||
|
||||
## BPF code structure
|
||||
|
||||
BPF code can be divided into two parts:
|
||||
@@ -34,37 +36,24 @@ BPF code can be divided into two parts:
|
||||
|
||||
BPF programs are located in `bpf` directory in our repository. They are C like programs with some limitations.
|
||||
The most important limitation is that you can't use any system calls in BPF programs. You can use only BPF helpers.
|
||||
The BPF programs are compiled using `clang` 10+, GCC is not supported.
|
||||
The BPF programs are compiled using `clang` 12+, GCC is not supported.
|
||||
|
||||
User space code is located in `lib/bpf` directory. It's written in Go and uses the `aquasecurity/libbpfgo` library to load BPF programs into the kernel.
|
||||
`aquasecurity/libbpfgo` is a Go wrapper around the `libbpf` library. It's a low-level library that allows you to load BPF programs into the kernel.
|
||||
|
||||
Note: `aquasecurity/libbpfgo` is not backwards compatible, you need to use the
|
||||
exact `libbpf` version it requires. For that reason `aquasecurity/libbpfgo`
|
||||
doesn't use semantic versioning, instead the tags have the format
|
||||
`v0.4.5-libbpf-1.0.1`. The first part is the `aquasecurity/libbpfgo` version,
|
||||
the second part is the `libbpf` version. Using the wrong version of `libbpf`
|
||||
results in a runtime/compilation error.
|
||||
|
||||
### libbpf installation
|
||||
|
||||
`aquasecurity/libbpfgo` requires `libbpf` to be installed on the system. The most up-to-date installation instructions can be found
|
||||
in our Docker file: https://github.com/gravitational/teleport/blob/2fd8f75e38eebf5c6826ef594433e5165c3bfbe1/build.assets/Dockerfile-centos7#L108-L131
|
||||
|
||||
Remember that the library must be installed in `/opt` directory not in `/usr/local` or `/usr`.
|
||||
User space code is located in `lib/bpf` directory. It's written in Go and uses the `cilium/ebpf` library to load BPF programs into the kernel. `cilium/ebpf ` is a pure-Go library to read, modify and load eBPF programs and attach them to various hooks in the Linux kernel.
|
||||
|
||||
### BPF license
|
||||
|
||||
BPF programs are compiled into ELF files. ELF files have a license field that is used to verify that the BPF program
|
||||
is allowed to run in the kernel. The license field is set to `GPL` by default. Teleport uses `Dual BSD/GPL` which
|
||||
is allowed to run in the kernel. The license field is set to `GPL` by default. Teleport uses `Dual BSD/GPL` which
|
||||
disables some BPF features (like logging). To enable all BPF features set the license to `GPL` in the BPF program
|
||||
and revert back before merging the code.
|
||||
|
||||
### Logging
|
||||
|
||||
BPF programs can log messages to the kernel log. To enable logging, you need to set the license to `GPL`. Then you
|
||||
can use `bpf_printk` helper to log messages. The messages will be logged to the kernel log. You can get the messages
|
||||
from `/sys/kernel/debug/tracing/trace_pipe`. Here is the best explanation that I found so far https://nakryiko.com/posts/bpf-tips-printk/.
|
||||
BPF programs can log messages to the kernel log. Then you use `bpf_printk` helper to log messages. The messages will be logged to the kernel log. You can get the messages
|
||||
from `/sys/kernel/debug/tracing/trace_pipe`. Uncomment the line in `common.h` the defines `PRINT_DEBUG_MSGS` or else `bpf_printk` calls
|
||||
will be compiled out.
|
||||
|
||||
Here is the best explanation that I found so far https://nakryiko.com/posts/bpf-tips-printk/.
|
||||
|
||||
### Communication between BPF programs and user space
|
||||
|
||||
@@ -72,10 +61,6 @@ BPF programs can communicate with user space using maps. Maps are key-value stor
|
||||
BPF programs and user space. BPF programs can only access maps using BPF helpers. User space can access maps using
|
||||
`aquasecurity/libbpfgo` library. Maps are defined in BPF programs and can be referenced by name from user space code.
|
||||
|
||||
Example:
|
||||
|
||||
* BPF: https://github.com/gravitational/teleport/blob/2fd8f75e38eebf5c6826ef594433e5165c3bfbe1/bpf/enhancedrecording/command.bpf.c#L18
|
||||
* User space: https://github.com/gravitational/teleport/blob/2fd8f75e38eebf5c6826ef594433e5165c3bfbe1/lib/bpf/common_linux.go#L40-L51
|
||||
|
||||
## BPF in Teleport
|
||||
|
||||
@@ -86,4 +71,3 @@ the kernel 5.8+. Enhanced session recording records all:
|
||||
* network connections
|
||||
|
||||
All events are recorded in the audit log. See https://goteleport.com/docs/enroll-resources/server-access/guides/bpf-session-recording/.
|
||||
|
||||
|
||||
Vendored
-21
@@ -30,8 +30,6 @@ Vagrant.configure("2") do |config|
|
||||
#
|
||||
# * clang: required to build BPF
|
||||
# * go: 1.21.x (Snap, any version >= 1.21 works)
|
||||
# * libbpf-dev: 0.5.0 (not installed)
|
||||
# * libelf, zlib: required by libbpf
|
||||
# * linux-tools-generic: bpftool
|
||||
# * llvm: llvm-strip
|
||||
config.vm.provision "shell", name: "packages", inline: <<-SHELL
|
||||
@@ -40,7 +38,6 @@ Vagrant.configure("2") do |config|
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clang \
|
||||
libelf-dev \
|
||||
"linux-tools-$(uname -r)" \
|
||||
linux-tools-generic \
|
||||
llvm \
|
||||
@@ -51,24 +48,6 @@ Vagrant.configure("2") do |config|
|
||||
go version
|
||||
SHELL
|
||||
|
||||
# Clone, build and install libbpf.
|
||||
#
|
||||
# Needs to be installed at "/usr/libbpf-$VERSION" for Teleport to be happy.
|
||||
# Eg, "/usr/libbpf-1.0.1".
|
||||
config.vm.provision "shell", name: "libbpf", privileged: false, inline: <<-SHELL
|
||||
LIBBPF_VERSION="$(grep LIBBPF_VERSION /vagrant/build.assets/versions.mk | awk '{print $3}')"
|
||||
DEST="/usr/libbpf-$LIBBPF_VERSION"
|
||||
echo "LIBBPF_VERSION = $LIBBPF_VERSION" # eg, 1.0.1
|
||||
|
||||
cd ~
|
||||
[[ ! -d libbpf ]] && git clone --depth 1 https://github.com/libbpf/libbpf.git -b "v$LIBBPF_VERSION"
|
||||
cd libbpf/src
|
||||
make
|
||||
sudo make install DESTDIR="/opt/libbpf"
|
||||
sudo rm -fr "$DEST"
|
||||
sudo mv /opt/libbpf/usr "$DEST" && sudo rm -fr /opt/libbpf
|
||||
SHELL
|
||||
|
||||
# Start sessions at /vagrant.
|
||||
config.vm.provision "shell", name: "user", privileged: false, inline: <<-SHELL
|
||||
if ! grep -q 'cd /vagrant' ~/.bashrc; then
|
||||
|
||||
@@ -31,7 +31,7 @@ enum event_type {
|
||||
struct common_data_t {
|
||||
u64 pid;
|
||||
u64 ppid;
|
||||
char comm[TASK_COMM_LEN];
|
||||
u8 command[TASK_COMM_LEN];
|
||||
u64 cgroup;
|
||||
};
|
||||
|
||||
@@ -40,13 +40,22 @@ struct data_t {
|
||||
u64 pid;
|
||||
// ppid is the userspace term (i.e task->real_parent->tgid in kernel).
|
||||
u64 ppid;
|
||||
char comm[TASK_COMM_LEN];
|
||||
// Command is the executable.
|
||||
u8 command[TASK_COMM_LEN];
|
||||
// Type is the type of event.
|
||||
enum event_type type;
|
||||
char argv[ARGSIZE];
|
||||
// Argv is the list of arguments to the program.
|
||||
u8 argv[ARGSIZE];
|
||||
// ReturnCode is the return code of execve.
|
||||
int retval;
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
u64 cgroup;
|
||||
};
|
||||
|
||||
// Force emitting struct data_t into the ELF. bpf2go needs this
|
||||
// to generate the Go bindings.
|
||||
const struct data_t *unused __attribute__((unused));
|
||||
|
||||
BPF_RING_BUF(execve_events, EVENTS_BUF_SIZE);
|
||||
|
||||
BPF_COUNTER(lost);
|
||||
@@ -67,7 +76,7 @@ static int __submit_arg(void *ptr, struct common_data_t *common)
|
||||
data->pid = common->pid;
|
||||
data->cgroup = common->cgroup;
|
||||
for (int i = 0; i < TASK_COMM_LEN; i++)
|
||||
data->comm[i] = common->comm[i];
|
||||
data->command[i] = common->command[i];
|
||||
|
||||
bpf_ringbuf_submit(data, 0);
|
||||
return 1;
|
||||
@@ -104,7 +113,7 @@ static int enter_execve(const char *filename,
|
||||
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
common.ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
bpf_get_current_comm(&common.comm, sizeof(common.comm));
|
||||
bpf_get_current_comm(&common.command, sizeof(common.command));
|
||||
|
||||
if(__submit_arg((void *)filename, &common) < 0) {
|
||||
INCR_COUNTER(lost);
|
||||
@@ -156,7 +165,7 @@ static int exit_execve(int ret)
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
data->ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
|
||||
bpf_get_current_comm(&data->comm, sizeof(data->comm));
|
||||
bpf_get_current_comm(&data->command, sizeof(data->command));
|
||||
data->type = EVENT_RET;
|
||||
data->retval = ret;
|
||||
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
#ifndef BPF_COMMON_H
|
||||
#define BPF_COMMON_H
|
||||
|
||||
// Uncomment to enable debug messages via bpf_printk
|
||||
// #define PRINT_DEBUG_MSGS
|
||||
|
||||
// Maximum monitored sessions.
|
||||
#define MAX_MONITORED_SESSIONS 1024
|
||||
|
||||
#endif // BPF_COMMON_H
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
// Easier to use bpf_printk taken from https://nakryiko.com/posts/bpf-tips-printk/
|
||||
|
||||
// Define our own struct definition if our vmlinux.h is outdated; this empty
|
||||
// definition will not conflict because of the ___x but anything after the
|
||||
// triple underscore will get ignored by BPF CO-RE.
|
||||
struct trace_event_raw_bpf_trace_printk___x {};
|
||||
|
||||
#undef bpf_printk
|
||||
|
||||
#ifdef PRINT_DEBUG_MSGS
|
||||
#define bpf_printk(fmt, ...) \
|
||||
({ \
|
||||
static char ____fmt[] = fmt "\0"; \
|
||||
if (bpf_core_type_exists(struct trace_event_raw_bpf_trace_printk___x)) \
|
||||
{ \
|
||||
bpf_trace_printk(____fmt, sizeof(____fmt) - 1, ##__VA_ARGS__); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
____fmt[sizeof(____fmt) - 2] = '\n'; \
|
||||
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
|
||||
} \
|
||||
})
|
||||
#else
|
||||
#define bpf_printk(fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif // BPF_COMMON_H
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "../vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h> /* most used helpers: SEC, __always_inline, etc */
|
||||
#include <bpf/bpf_core_read.h> /* for BPF CO-RE helpers */
|
||||
#include <bpf/bpf_tracing.h> /* for getting kprobe arguments */
|
||||
|
||||
#include "../helpers.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
BPF_COUNTER(test_counter);
|
||||
|
||||
SEC("tp/syscalls/sys_close")
|
||||
int tracepoint__syscalls__sys_enter_close(struct syscall_trace_enter *tp)
|
||||
{
|
||||
int fd = (int)tp->args[0];
|
||||
|
||||
// Special bad FD we trigger upon
|
||||
if (fd == 1234) {
|
||||
INCR_COUNTER(test_counter);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tp/syscalls/sys_exit_close")
|
||||
int tracepoint__syscalls__sys_exit_close(struct syscall_trace_exit *tp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -25,14 +25,24 @@ struct val_t {
|
||||
};
|
||||
|
||||
struct data_t {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
u64 cgroup;
|
||||
// PID is the ID of the process.
|
||||
u64 pid;
|
||||
int ret;
|
||||
char comm[TASK_COMM_LEN];
|
||||
char fname[NAME_MAX];
|
||||
// Return_code is the return code of open.
|
||||
int return_code;
|
||||
// Command is name of the executable opening the file.
|
||||
u8 command[TASK_COMM_LEN];
|
||||
// File_path is the full path to the file being opened.
|
||||
u8 file_path[NAME_MAX];
|
||||
// Flags are the flags passed to open.
|
||||
int flags;
|
||||
};
|
||||
|
||||
// Force emitting struct data_t into the ELF. bpf2go needs this
|
||||
// to generate the Go bindings.
|
||||
const struct data_t *unused __attribute__((unused));
|
||||
|
||||
BPF_HASH(infotmp, u64, struct val_t, INFLIGHT_MAX);
|
||||
|
||||
// hashmap keeps all cgroups id that should be monitored by Teleport.
|
||||
@@ -46,6 +56,15 @@ BPF_COUNTER(lost);
|
||||
static int enter_open(const char *filename, int flags) {
|
||||
struct val_t val = {};
|
||||
u64 id = bpf_get_current_pid_tgid();
|
||||
u64 cgroup = bpf_get_current_cgroup_id();
|
||||
u64 *is_monitored;
|
||||
|
||||
// Check if the cgroup should be monitored.
|
||||
is_monitored = bpf_map_lookup_elem(&monitored_cgroups, &cgroup);
|
||||
if (is_monitored == NULL) {
|
||||
// cgroup has not been marked for monitoring, ignore.
|
||||
return 0;
|
||||
}
|
||||
|
||||
val.pid = id >> 32;
|
||||
val.fname = filename;
|
||||
@@ -76,15 +95,15 @@ static int exit_open(int ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) != 0) {
|
||||
data.comm[0] = '\0';
|
||||
if (bpf_get_current_comm(&data.command, sizeof(data.command)) != 0) {
|
||||
data.command[0] = '\0';
|
||||
}
|
||||
|
||||
bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)valp->fname);
|
||||
bpf_probe_read_user(&data.file_path, sizeof(data.file_path), (void *)valp->fname);
|
||||
|
||||
data.pid = valp->pid;
|
||||
data.flags = valp->flags;
|
||||
data.ret = ret;
|
||||
data.return_code = ret;
|
||||
data.cgroup = cgroup;
|
||||
|
||||
if (bpf_ringbuf_output(&open_events, &data, sizeof(data), 0) != 0)
|
||||
@@ -110,9 +129,6 @@ int tracepoint__syscalls__sys_exit_creat(struct syscall_trace_exit *tp)
|
||||
return exit_open(tp->ret);
|
||||
}
|
||||
|
||||
// ARM64 does not implement sys_enter_open only sys_enter_openat. x86 implements it for legacy reasons.
|
||||
#ifndef __TARGET_ARCH_arm64
|
||||
|
||||
SEC("tp/syscalls/sys_enter_open")
|
||||
int tracepoint__syscalls__sys_enter_open(struct syscall_trace_enter *tp)
|
||||
{
|
||||
@@ -122,8 +138,6 @@ int tracepoint__syscalls__sys_enter_open(struct syscall_trace_enter *tp)
|
||||
return enter_open(filename, flags);
|
||||
};
|
||||
|
||||
#endif // __aarch64__
|
||||
|
||||
SEC("tp/syscalls/sys_exit_open")
|
||||
int tracepoint__syscalls__sys_exit_open(struct syscall_trace_exit *tp)
|
||||
{
|
||||
|
||||
@@ -26,27 +26,49 @@ BPF_HASH(monitored_cgroups, u64, int64_t, MAX_MONITORED_SESSIONS);
|
||||
|
||||
// separate data structs for ipv4 and ipv6
|
||||
struct ipv4_data_t {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
u64 cgroup;
|
||||
// Version is the version of TCP (4 or 6).
|
||||
u64 ip;
|
||||
// PID is the process ID.
|
||||
u32 pid;
|
||||
// SrcAddr is the source IP address.
|
||||
u32 saddr;
|
||||
// DstAddr is the destination IP address.
|
||||
u32 daddr;
|
||||
// DstPort is the port the connection is being made to.
|
||||
u16 dport;
|
||||
char task[TASK_COMM_LEN];
|
||||
// Command is name of the executable making the connection.
|
||||
u8 command[TASK_COMM_LEN];
|
||||
};
|
||||
BPF_RING_BUF(ipv4_events, EVENTS_BUF_SIZE);
|
||||
|
||||
// Force emitting struct ipv4_data_t into the ELF. bpf2go needs this
|
||||
// to generate the Go bindings.
|
||||
const struct ipv4_data_t *unused_ipv4_data_t __attribute__((unused));
|
||||
|
||||
struct ipv6_data_t {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
u64 cgroup;
|
||||
// Version is the version of TCP (4 or 6).
|
||||
u64 ip;
|
||||
// PID is the process ID.
|
||||
u32 pid;
|
||||
// SrcAddr is the source IP address.
|
||||
struct in6_addr saddr;
|
||||
// DstAddr is the destination IP address.
|
||||
struct in6_addr daddr;
|
||||
// DstPort is the port the connection is being made to.
|
||||
u16 dport;
|
||||
char task[TASK_COMM_LEN];
|
||||
// Command is name of the executable making the connection.
|
||||
u8 command[TASK_COMM_LEN];
|
||||
};
|
||||
BPF_RING_BUF(ipv6_events, EVENTS_BUF_SIZE);
|
||||
|
||||
// Force emitting struct ipv6_data_t into the ELF. bpf2go needs this
|
||||
// to generate the Go bindings.
|
||||
const struct ipv6_data_t *unused_ipv6_data_t __attribute__((unused));
|
||||
|
||||
BPF_COUNTER(lost);
|
||||
|
||||
static int trace_connect_entry(struct sock *sk)
|
||||
@@ -75,7 +97,7 @@ static int trace_connect_return(int ret, short ipver)
|
||||
|
||||
struct sock **skpp;
|
||||
skpp = bpf_map_lookup_elem(&currsock, &id);
|
||||
if (skpp == 0) {
|
||||
if (skpp == NULL) {
|
||||
return 0; // missed entry
|
||||
}
|
||||
|
||||
@@ -96,7 +118,7 @@ static int trace_connect_return(int ret, short ipver)
|
||||
data4.daddr = BPF_CORE_READ(skp, __sk_common.skc_daddr);
|
||||
data4.dport = __builtin_bswap16(dport);
|
||||
data4.cgroup = bpf_get_current_cgroup_id();
|
||||
bpf_get_current_comm(&data4.task, sizeof(data4.task));
|
||||
bpf_get_current_comm(&data4.command, sizeof(data4.command));
|
||||
if (bpf_ringbuf_output(&ipv4_events, &data4, sizeof(data4), 0) != 0)
|
||||
INCR_COUNTER(lost);
|
||||
|
||||
@@ -108,7 +130,7 @@ static int trace_connect_return(int ret, short ipver)
|
||||
|
||||
data6.dport = __builtin_bswap16(dport);
|
||||
data6.cgroup = bpf_get_current_cgroup_id();
|
||||
bpf_get_current_comm(&data6.task, sizeof(data6.task));
|
||||
bpf_get_current_comm(&data6.command, sizeof(data6.command));
|
||||
if (bpf_ringbuf_output(&ipv6_events, &data6, sizeof(data6), 0) != 0)
|
||||
INCR_COUNTER(lost);
|
||||
}
|
||||
|
||||
@@ -69,26 +69,6 @@ RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.15.0 && \
|
||||
make install && \
|
||||
make clean
|
||||
|
||||
## LIBBPF #####################################################################
|
||||
|
||||
FROM buildpack-deps:22.04 AS libbpf
|
||||
|
||||
# Install required dependencies
|
||||
RUN apt-get update -y --fix-missing && \
|
||||
apt-get -q -y upgrade && \
|
||||
apt-get install -q -y --no-install-recommends \
|
||||
libelf-dev
|
||||
|
||||
ARG LIBBPF_VERSION
|
||||
# BUILD_STATIC_ONLY - builds only static libraries without shared ones
|
||||
# EXTRA_CFLAGS - additional CFLAGS to pass to the compiler. fPIC is required so the library code can be moved around in memory
|
||||
# DESTDIR - where to install the library
|
||||
# V=1 - verbose build
|
||||
RUN mkdir -p /opt && cd /opt && \
|
||||
curl -fsSL https://github.com/libbpf/libbpf/archive/refs/tags/v${LIBBPF_VERSION}.tar.gz | tar xz && \
|
||||
cd /opt/libbpf-${LIBBPF_VERSION}/src && \
|
||||
BUILD_STATIC_ONLY=y EXTRA_CFLAGS=-fPIC DESTDIR=/opt/libbpf V=1 make install install_uapi_headers
|
||||
|
||||
## BUILDBOX ###################################################################
|
||||
#
|
||||
# Image layers are ordered according to how slow that layer takes to build and
|
||||
@@ -333,10 +313,6 @@ ARG BUF_VERSION # eg, "v1.26.1"
|
||||
RUN VERSION="$BUF_VERSION"; \
|
||||
go install "github.com/bufbuild/buf/cmd/buf@$VERSION"
|
||||
|
||||
# Copy BPF libraries.
|
||||
ARG LIBBPF_VERSION
|
||||
COPY --from=libbpf /opt/libbpf/usr /usr/libbpf-${LIBBPF_VERSION}
|
||||
|
||||
# Copy libfido2 libraries.
|
||||
# Do this near the end to take better advantage of the multi-stage build.
|
||||
COPY --from=libfido2 /usr/local/include/ /usr/local/include/
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Minimal container for generating BPF bytecode
|
||||
# This Dockerfile is used to generate BPF bytecode used
|
||||
# by enhanced recording.
|
||||
FROM docker.io/library/debian:12
|
||||
|
||||
ENV LANGUAGE="en_US.UTF-8" \
|
||||
LANG="en_US.UTF-8" \
|
||||
LC_ALL="en_US.UTF-8" \
|
||||
LC_CTYPE="en_US.UTF-8" \
|
||||
DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
# BUILDARCH is automatically set by DOCKER when building the image with Build Kit (MacOS by default).
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
ARG BUILDARCH
|
||||
|
||||
RUN apt -y update && \
|
||||
apt install -q -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clang \
|
||||
curl \
|
||||
libbpf-dev \
|
||||
llvm && \
|
||||
apt -y clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Go.
|
||||
ARG GOLANG_VERSION
|
||||
RUN mkdir -p /opt && \
|
||||
cd /opt && \
|
||||
curl -fsSL "https://storage.googleapis.com/golang/$GOLANG_VERSION.linux-$BUILDARCH.tar.gz" | tar xz && \
|
||||
mkdir -p /go/src/github.com/gravitational/teleport && \
|
||||
chmod a+w /go && \
|
||||
chmod a+w /var/lib && \
|
||||
chmod a-w /
|
||||
ENV GOPATH="/go" \
|
||||
GOROOT="/opt/go" \
|
||||
PATH="$PATH:/opt/go/bin:/go/bin:/go/src/github.com/gravitational/teleport/build"
|
||||
|
||||
ARG UID
|
||||
ARG GID
|
||||
RUN groupadd bpf --gid=$GID -o && \
|
||||
useradd bpf --uid=$UID --gid=$GID --create-home --shell=/bin/sh
|
||||
|
||||
USER bpf
|
||||
@@ -153,32 +153,6 @@ RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.15.0 && \
|
||||
make install && \
|
||||
make clean
|
||||
|
||||
## LIBBPF #####################################################################
|
||||
|
||||
FROM --platform=$BUILDPLATFORM base AS libbpf
|
||||
|
||||
# Install additional required dependencies.
|
||||
RUN yum install -y \
|
||||
elfutils-libelf-devel && \
|
||||
yum clean all
|
||||
|
||||
# Install custom package with -fPIC.
|
||||
COPY --from=teleport-buildbox-centos7-assets /opt/custom-packages /opt/custom-packages
|
||||
RUN if [ "${BUILDARCH}" = "arm64" ]; then export BUILDARCH="aarch64"; fi && \
|
||||
rpm -ivh /opt/custom-packages/elfutils-libelf-devel-static-*.el*.${BUILDARCH}.rpm && \
|
||||
rm -rf /opt/custom-packages
|
||||
|
||||
# Install libbpf - compile with a newer GCC. The one installed by default is not able to compile it.
|
||||
# BUILD_STATIC_ONLY - builds only static libraries without shared ones
|
||||
# EXTRA_CFLAGS - additional CFLAGS to pass to the compiler. fPIC is required so the library code can be moved around in memory
|
||||
# DESTDIR - where to install the library
|
||||
# V=1 - verbose build
|
||||
ARG LIBBPF_VERSION
|
||||
RUN mkdir -p /opt && cd /opt && \
|
||||
curl -fsSL https://github.com/libbpf/libbpf/archive/refs/tags/v${LIBBPF_VERSION}.tar.gz | tar xz && \
|
||||
cd /opt/libbpf-${LIBBPF_VERSION}/src && \
|
||||
scl enable ${DEVTOOLSET} "BUILD_STATIC_ONLY=y EXTRA_CFLAGS=-fPIC DESTDIR=/opt/libbpf V=1 make install install_uapi_headers"
|
||||
|
||||
## LIBPCSCLITE #####################################################################
|
||||
|
||||
FROM --platform=$BUILDPLATFORM base AS libpcsclite
|
||||
@@ -320,10 +294,6 @@ COPY --from=libpcsclite \
|
||||
# Set LIBRARY_PATH so that libpcsclite can be found by the linker.
|
||||
ENV LIBRARY_PATH=/usr/local/lib:${LIBRARY_PATH}
|
||||
|
||||
# Copy libbpf into the final image.
|
||||
ARG LIBBPF_VERSION
|
||||
COPY --from=libbpf /opt/libbpf/usr /usr/libbpf-${LIBBPF_VERSION}
|
||||
|
||||
# Copy the pre-built CentOS 7 assets with clang. Needed to build BoringSSL and BPF tools.
|
||||
COPY --from=teleport-buildbox-centos7-assets /opt/llvm /opt/llvm
|
||||
# Bring in our custom ninja build, needed for BorinSSL.
|
||||
|
||||
+12
-2
@@ -187,7 +187,6 @@ buildbox:
|
||||
--build-arg WASM_OPT_VERSION=$(WASM_OPT_VERSION) \
|
||||
--build-arg WASM_BINDGEN_VERSION=$(WASM_BINDGEN_VERSION) \
|
||||
--build-arg NODE_VERSION=$(NODE_VERSION) \
|
||||
--build-arg LIBBPF_VERSION=$(LIBBPF_VERSION) \
|
||||
--build-arg BUF_VERSION=$(BUF_VERSION) \
|
||||
--build-arg GOGO_PROTO_TAG=$(GOGO_PROTO_TAG) \
|
||||
--build-arg NODE_GRPC_TOOLS_VERSION=$(NODE_GRPC_TOOLS_VERSION) \
|
||||
@@ -229,7 +228,6 @@ buildbox-centos7:
|
||||
--build-arg GOLANG_VERSION=$(GOLANG_VERSION) \
|
||||
--build-arg RUST_VERSION=$(RUST_VERSION) \
|
||||
--build-arg DEVTOOLSET=$(DEVTOOLSET) \
|
||||
--build-arg LIBBPF_VERSION=$(LIBBPF_VERSION) \
|
||||
--build-arg LIBPCSCLITE_VERSION=$(LIBPCSCLITE_VERSION) \
|
||||
--cache-to type=inline \
|
||||
--cache-from $(BUILDBOX_CENTOS7) \
|
||||
@@ -356,6 +354,18 @@ fix-imports: buildbox
|
||||
$(DOCKERFLAGS) -t $(BUILDBOX) \
|
||||
make -C /go/src/github.com/gravitational/teleport fix-imports/host
|
||||
|
||||
# Generate BPF bytecode in a container, so local setup is not required.
|
||||
.PHONY: bpf-bytecode
|
||||
bpf-bytecode:
|
||||
docker build \
|
||||
--build-arg UID=$(UID) \
|
||||
--build-arg GID=$(GID) \
|
||||
--build-arg BUILDARCH=$(RUNTIME_ARCH) \
|
||||
--build-arg GOLANG_VERSION=$(GOLANG_VERSION) \
|
||||
-f Dockerfile-bpf -t bpf-builder .
|
||||
docker run --rm -v "$$(pwd)/../":/teleport -w /teleport bpf-builder \
|
||||
make bpf-bytecode
|
||||
|
||||
#
|
||||
# Removes the docker image
|
||||
#
|
||||
|
||||
@@ -9,7 +9,6 @@ GOLANGCI_LINT_VERSION ?= v2.4.0
|
||||
NODE_VERSION ?= 22.21.0
|
||||
|
||||
WASM_OPT_VERSION ?= 0.116.1
|
||||
LIBBPF_VERSION ?= 1.2.2
|
||||
LIBPCSCLITE_VERSION ?= 1.9.9-teleport
|
||||
|
||||
DEVTOOLSET ?= devtoolset-12
|
||||
|
||||
@@ -12,100 +12,43 @@
|
||||
PKGCONF := PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(firstword $(shell which pkgconf pkg-config false 2>/dev/null))
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# libbpf detection
|
||||
#
|
||||
# Requirements for building with BPF support:
|
||||
# * clang and llvm-strip programs
|
||||
# * linux on amd64 or amd64
|
||||
# * libbpf 1.2.2 (either in /usr/libbpf-1.2.2 or pkg-config)
|
||||
# * The centos7 buildbox puts this in /usr/libbpf-1.2.2
|
||||
# * The ng buildbox has a pkg-config file for it
|
||||
# * Native/local builds have a pkg-config file for it
|
||||
# * Either using a cross-compiling buildbox or is a native build
|
||||
#
|
||||
# The default is without BPF support unless all the critera are met.
|
||||
#
|
||||
# TODO(camh): Remove /usr/libbpf-1.2.2 when old buildboxes are replaced by ng
|
||||
|
||||
with_bpf := no
|
||||
BPF_MESSAGE := without-BPF-support
|
||||
|
||||
CLANG ?= $(shell which clang || which clang-12)
|
||||
LLVM_STRIP ?= $(shell which llvm-strip || which llvm-strip-12)
|
||||
|
||||
# libbpf version required by the build.
|
||||
LIBBPF_VER := 1.2.2
|
||||
|
||||
FOUND_LIBBPF :=
|
||||
|
||||
ifneq (,$(wildcard /usr/libbpf-$(LIBBPF_VER)))
|
||||
FOUND_LIBBPF := true
|
||||
LIBBPF_INCLUDES := -I/usr/libbpf-$(LIBBPF_VER)/include
|
||||
LIBBPF_LIBS := -L/usr/libbpf-$(LIBBPF_VER)/lib64 -lbpf
|
||||
# libbpf needs libelf. Try to find it with pkg-config/pkgconf and fallback to
|
||||
# hard-coded defaults if pkg-config says nothing.
|
||||
LIBBPF_LIBS += $(or $(shell $(PKGCONF) --silence-errors --static --libs libelf),-lelf -lz)
|
||||
else ifneq (,$(shell $(PKGCONF) --exists 'libbpf = $(LIBBPF_VER)' && echo true))
|
||||
FOUND_LIBBPF := true
|
||||
LIBBPF_INCLUDES := $(shell $(PKGCONF) --cflags libbpf)
|
||||
LIBBPF_LIBS := $(shell $(PKGCONF) --libs --static libbpf)
|
||||
endif
|
||||
|
||||
# Is this build targeting the same OS & architecture it is being compiled on, or
|
||||
# will it require cross-compilation? We need to know this (especially for ARM) so we
|
||||
# can set the cross-compiler path (and possibly feature flags) correctly.
|
||||
IS_NATIVE_BUILD ?= $(filter $(ARCH),$(shell go env GOARCH))
|
||||
IS_CROSS_COMPILE_BB = $(filter $(BUILDBOX_MODE),cross)
|
||||
|
||||
# Only build with BPF if clang and llvm-strip are installed.
|
||||
ifneq (,$(and $(CLANG),$(LLVM_STRIP)))
|
||||
|
||||
# Only build with BPF for linux/amd64 and linux/arm64.
|
||||
# Other builds have compilation issues that require fixing.
|
||||
ifneq (,$(filter $(OS)/$(ARCH),linux/amd64 linux/arm64))
|
||||
|
||||
# Only build with BPF if we found the right version installed
|
||||
ifneq (,$(FOUND_LIBBPF))
|
||||
|
||||
# Only build with BPF if its a native build or in a cross-compiling buildbox.
|
||||
ifneq (,$(or $(IS_NATIVE_BUILD),$(IS_CROSS_COMPILE_BB)))
|
||||
|
||||
with_bpf := yes
|
||||
BPF_TAG := bpf
|
||||
BPF_MESSAGE := with-BPF-support
|
||||
KERNEL_ARCH := $(shell uname -m | sed 's/x86_64/x86/g; s/aarch64/arm64/g')
|
||||
ER_BPF_BUILDDIR := lib/bpf/bytecode
|
||||
BPF_INCLUDES := $(LIBBPF_INCLUDES)
|
||||
STATIC_LIBS += $(LIBBPF_LIBS)
|
||||
INCLUDES :=
|
||||
|
||||
# Get Clang's default includes on this system. We'll explicitly add these dirs
|
||||
# to the includes list when compiling with `-target bpf` because otherwise some
|
||||
# architecture-specific dirs will be "missing" on some architectures/distros -
|
||||
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
|
||||
# sys/cdefs.h etc. might be missing.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
CLANG_BPF_SYS_INCLUDES = $(shell $(CLANG) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
|
||||
|
||||
# Link static version of libraries required by Teleport (bpf, pcsc) to reduce
|
||||
# system dependencies. Avoid dependencies on dynamic libraries if we already
|
||||
# link the static version using --as-needed.
|
||||
CGOFLAG = CGO_ENABLED=1 CGO_CFLAGS="$(BPF_INCLUDES)" CGO_LDFLAGS="-Wl,-Bstatic $(STATIC_LIBS) -Wl,-Bdynamic -Wl,--as-needed"
|
||||
# Link static version of libraries required by Teleport (pcsc) to reduce system dependencies. Avoid dependencies on dynamic libraries if we already link the static version using --as-needed.
|
||||
CGOFLAG = CGO_ENABLED=1 CGO_LDFLAGS="-Wl,-Bstatic $(STATIC_LIBS) -Wl,-Bdynamic -Wl,--as-needed"
|
||||
CGOFLAG_TSH = CGO_ENABLED=1 CGO_LDFLAGS="-Wl,-Bstatic $(STATIC_LIBS_TSH) -Wl,-Bdynamic -Wl,--as-needed"
|
||||
|
||||
endif # IS_NATIVE_BUILD || IS_CROSS_COMPILE_BB
|
||||
endif # libbpf found
|
||||
endif # OS/ARCH == linux/amd64 OR linux/arm64
|
||||
endif # clang and llvm-strip found
|
||||
|
||||
.PHONY: diag-bpf-vars
|
||||
diag-bpf-vars:
|
||||
@echo clang: $(CLANG)
|
||||
@echo llvm-strip: $(LLVM_STRIP)
|
||||
@echo os/arch: $(OS)-$(ARCH)
|
||||
@echo found bpf: $(FOUND_LIBBPF)
|
||||
@echo is-native: $(IS_NATIVE_BUILD)
|
||||
@echo is-cross: $(IS_CROSS_COMPILE_BB)
|
||||
@echo buildbox-mode: $(BUILDBOX_MODE)
|
||||
|
||||
@@ -53,7 +53,6 @@ require (
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 // replaced
|
||||
github.com/alicebob/miniredis/v2 v2.35.0
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16
|
||||
@@ -106,6 +105,7 @@ require (
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/huh v0.8.0
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/cilium/ebpf v0.19.0
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/coreos/go-systemd/v22 v22.6.0
|
||||
github.com/creack/pty v1.1.24
|
||||
|
||||
@@ -778,8 +778,6 @@ github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4x
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
|
||||
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 h1:Y7QB6jUsMyr0Bd+rAj67X2/ezNYLuxwp3kkjw5M3Q+4=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -1009,6 +1007,8 @@ github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
|
||||
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -1274,6 +1274,8 @@ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
|
||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0 h1:xamQ/fR4MJiw/Ndbk6yi7MVwhjrwlnDAPuaH9zcGb+I=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
@@ -1711,6 +1713,8 @@ github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
||||
@@ -80,7 +80,6 @@ require (
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.8 // indirect
|
||||
@@ -152,6 +151,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/charlievieth/strcase v0.0.5 // indirect
|
||||
github.com/cilium/ebpf v0.19.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/containerd/containerd v1.7.29 // indirect
|
||||
|
||||
@@ -785,8 +785,6 @@ github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJe
|
||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 h1:Y7QB6jUsMyr0Bd+rAj67X2/ezNYLuxwp3kkjw5M3Q+4=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -975,6 +973,8 @@ github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
|
||||
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
@@ -1231,6 +1231,8 @@ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
|
||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0 h1:xamQ/fR4MJiw/Ndbk6yi7MVwhjrwlnDAPuaH9zcGb+I=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
@@ -1696,6 +1698,8 @@ github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
||||
@@ -84,7 +84,6 @@ require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apparentlymart/go-textseg v1.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.8 // indirect
|
||||
@@ -156,6 +155,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/charlievieth/strcase v0.0.5 // indirect
|
||||
github.com/cilium/ebpf v0.19.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/containerd/containerd v1.7.29 // indirect
|
||||
|
||||
@@ -802,8 +802,6 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 h1:Y7QB6jUsMyr0Bd+rAj67X2/ezNYLuxwp3kkjw5M3Q+4=
|
||||
github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
@@ -998,6 +996,8 @@ github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
|
||||
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
@@ -1263,6 +1263,8 @@ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
|
||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0 h1:xamQ/fR4MJiw/Ndbk6yi7MVwhjrwlnDAPuaH9zcGb+I=
|
||||
github.com/go-piv/piv-go/v2 v2.4.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
@@ -1750,6 +1752,8 @@ github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
||||
+75
-103
@@ -20,21 +20,19 @@
|
||||
|
||||
package bpf
|
||||
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/gravitational/trace"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
ossteleport "github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
@@ -45,45 +43,17 @@ import (
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
//go:embed bytecode
|
||||
var embedFS embed.FS
|
||||
|
||||
// ArgsCacheSize is the number of args events to store before dropping args
|
||||
// events.
|
||||
const ArgsCacheSize = 1024
|
||||
|
||||
// SessionWatch is a map of cgroup IDs that the BPF service is watching and
|
||||
// emitting events for.
|
||||
type SessionWatch struct {
|
||||
watch map[uint64]*SessionContext
|
||||
mu sync.Mutex
|
||||
type sessionEnder interface {
|
||||
endSession(cgroupID uint64) error
|
||||
}
|
||||
|
||||
func NewSessionWatch() SessionWatch {
|
||||
return SessionWatch{
|
||||
watch: make(map[uint64]*SessionContext),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SessionWatch) Get(cgroupID uint64) (ctx *SessionContext, ok bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
ctx, ok = w.watch[cgroupID]
|
||||
return
|
||||
}
|
||||
|
||||
func (w *SessionWatch) Add(cgroupID uint64, ctx *SessionContext) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.watch[cgroupID] = ctx
|
||||
}
|
||||
|
||||
func (w *SessionWatch) Remove(cgroupID uint64) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
delete(w.watch, cgroupID)
|
||||
type cgroupRegister interface {
|
||||
startSession(cgroupID uint64) error
|
||||
endSession(cgroupID uint64) error
|
||||
}
|
||||
|
||||
// Service manages BPF and control groups orchestration.
|
||||
@@ -92,7 +62,7 @@ type Service struct {
|
||||
|
||||
// watch is a map of cgroup IDs that the BPF service is watching and
|
||||
// emitting events for.
|
||||
watch SessionWatch
|
||||
watch utils.SyncMap[uint64, *SessionContext]
|
||||
|
||||
// argsCache holds the arguments to execve because they come a different
|
||||
// event than the result.
|
||||
@@ -123,18 +93,17 @@ func New(config *servicecfg.BPFConfig) (bpf BPF, err error) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
closeContext, closeFunc := context.WithCancel(context.Background())
|
||||
|
||||
// If BPF-based auditing is not enabled, don't configure anything return
|
||||
// right away.
|
||||
if !config.Enabled {
|
||||
logger.DebugContext(closeContext, "Enhanced session recording is not enabled, skipping")
|
||||
logger.DebugContext(context.Background(), "Enhanced session recording is not enabled, skipping")
|
||||
return &NOP{}, nil
|
||||
}
|
||||
|
||||
closeContext, closeFunc := context.WithCancel(context.Background())
|
||||
|
||||
s := &Service{
|
||||
BPFConfig: config,
|
||||
watch: NewSessionWatch(),
|
||||
closeContext: closeContext,
|
||||
closeFunc: closeFunc,
|
||||
}
|
||||
@@ -165,20 +134,18 @@ func New(config *servicecfg.BPFConfig) (bpf BPF, err error) {
|
||||
start := time.Now()
|
||||
logger.DebugContext(closeContext, "Starting enhanced session recording")
|
||||
|
||||
// Compile and start BPF programs if they are enabled (buffer size given).
|
||||
// Compile and start BPF programs (buffer size given).
|
||||
s.exec, err = startExec(*config.CommandBufferSize)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil, trace.Wrap(err, "failed to load command hooks")
|
||||
}
|
||||
s.open, err = startOpen(*config.DiskBufferSize)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil, trace.Wrap(err, "failed to load disk hooks")
|
||||
}
|
||||
|
||||
// Load network BPF modules only when required.
|
||||
s.conn, err = startConn(*config.NetworkBufferSize)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil, trace.Wrap(err, "failed to load network hooks")
|
||||
}
|
||||
|
||||
logger.DebugContext(closeContext, "Started enhanced session recording",
|
||||
@@ -208,9 +175,7 @@ func (s *Service) Close(restarting bool) error {
|
||||
// Unload the BPF programs.
|
||||
s.exec.close()
|
||||
s.open.close()
|
||||
if s.conn != nil {
|
||||
s.conn.close()
|
||||
}
|
||||
s.conn.close()
|
||||
|
||||
// Close cgroup service. We should not unmount the cgroup filesystem if
|
||||
// we're restarting.
|
||||
@@ -239,7 +204,7 @@ func (s *Service) OpenSession(ctx *SessionContext) (uint64, error) {
|
||||
}
|
||||
|
||||
// initializedModClosures holds all already opened modules closures.
|
||||
initializedModClosures := make([]interface{ endSession(uint64) error }, 0)
|
||||
initializedModClosures := make([]sessionEnder, 0)
|
||||
for _, module := range []cgroupRegister{
|
||||
s.open,
|
||||
s.exec,
|
||||
@@ -259,7 +224,7 @@ func (s *Service) OpenSession(ctx *SessionContext) (uint64, error) {
|
||||
}
|
||||
|
||||
// Start watching for any events that come from this cgroup.
|
||||
s.watch.Add(cgroupID, ctx)
|
||||
s.watch.Store(cgroupID, ctx)
|
||||
|
||||
// Place requested PID into cgroup.
|
||||
err = s.cgroup.Place(ctx.SessionID, ctx.PID)
|
||||
@@ -279,7 +244,7 @@ func (s *Service) CloseSession(ctx *SessionContext) error {
|
||||
}
|
||||
|
||||
// Stop watching for events from this PID.
|
||||
s.watch.Remove(cgroupID)
|
||||
s.watch.Delete(cgroupID)
|
||||
|
||||
var errs []error
|
||||
// Move all PIDs to the root cgroup and remove the cgroup created for this
|
||||
@@ -288,7 +253,7 @@ func (s *Service) CloseSession(ctx *SessionContext) error {
|
||||
errs = append(errs, trace.Wrap(err))
|
||||
}
|
||||
|
||||
for _, module := range []interface{ endSession(cgroupID uint64) error }{
|
||||
for _, module := range []sessionEnder{
|
||||
s.open,
|
||||
s.exec,
|
||||
s.conn,
|
||||
@@ -306,6 +271,24 @@ func (s *Service) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func sendEvents(bpfEvents chan []byte, eventBuf *ringbuf.Reader) {
|
||||
defer eventBuf.Close()
|
||||
|
||||
for {
|
||||
rec, err := eventBuf.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, ringbuf.ErrClosed) {
|
||||
logger.DebugContext(context.Background(), "Received signal, exiting")
|
||||
return
|
||||
}
|
||||
logger.ErrorContext(context.Background(), "Error reading from ring buffer", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
bpfEvents <- rec.RawSample[:]
|
||||
}
|
||||
}
|
||||
|
||||
// processAccessEvents pulls events off the perf ring buffer, parses them, and emits them to
|
||||
// the audit log.
|
||||
func (s *Service) processAccessEvents() {
|
||||
@@ -343,7 +326,7 @@ func (s *Service) processNetworkEvents() {
|
||||
// emitCommandEvent will parse and emit command events to the Audit Log.
|
||||
func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
// Unmarshal raw event bytes.
|
||||
var event rawExecEvent
|
||||
var event commandDataT
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
if err != nil {
|
||||
logger.DebugContext(s.closeContext, "Failed to read binary data", "error", err)
|
||||
@@ -351,7 +334,7 @@ func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
}
|
||||
|
||||
// If the event comes from a unmonitored process/cgroup, don't process it.
|
||||
ctx, ok := s.watch.Get(event.CgroupID)
|
||||
ctx, ok := s.watch.Load(event.Cgroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -366,7 +349,7 @@ func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
// Args are sent in their own event by execsnoop to save stack space. Store
|
||||
// the args in a ttlmap, so they can be retrieved when the return event arrives.
|
||||
case eventArg:
|
||||
key := strconv.FormatUint(event.PID, 10)
|
||||
key := strconv.FormatUint(event.Pid, 10)
|
||||
|
||||
args, err := utils.FnCacheGet(s.closeContext, s.argsCache, key, func(ctx context.Context) ([]string, error) {
|
||||
return make([]string, 0), nil
|
||||
@@ -376,19 +359,17 @@ func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
args = []string{}
|
||||
}
|
||||
|
||||
argv := (*C.char)(unsafe.Pointer(&event.Argv))
|
||||
args = append(args, C.GoString(argv))
|
||||
args = append(args, ConvertString(event.Argv[:]))
|
||||
|
||||
s.argsCache.SetWithTTL(key, args, 24*time.Hour)
|
||||
// The event has returned, emit the fully parsed event.
|
||||
case eventRet:
|
||||
// The args should have come in a previous event, find them by PID.
|
||||
key := strconv.FormatUint(event.PID, 10)
|
||||
key := strconv.FormatUint(event.Pid, 10)
|
||||
|
||||
args, err := utils.FnCacheGet(s.closeContext, s.argsCache, key, func(ctx context.Context) ([]string, error) {
|
||||
return nil, trace.NotFound("args missing")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.DebugContext(s.closeContext, "Got event with missing args, skipping")
|
||||
lostCommandEvents.Add(float64(1))
|
||||
@@ -418,12 +399,12 @@ func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
UserTraits: ctx.UserTraits.Clone(),
|
||||
},
|
||||
BPFMetadata: apievents.BPFMetadata{
|
||||
CgroupID: event.CgroupID,
|
||||
Program: ConvertString(unsafe.Pointer(&event.Command)),
|
||||
PID: event.PID,
|
||||
CgroupID: event.Cgroup,
|
||||
Program: ConvertString(event.Command[:]),
|
||||
PID: event.Pid,
|
||||
},
|
||||
PPID: event.PPID,
|
||||
ReturnCode: event.ReturnCode,
|
||||
PPID: event.Ppid,
|
||||
ReturnCode: event.Retval,
|
||||
Path: args[0],
|
||||
Argv: args[1:],
|
||||
}
|
||||
@@ -439,7 +420,7 @@ func (s *Service) emitCommandEvent(eventBytes []byte) {
|
||||
// emitDiskEvent will parse and emit disk events to the Audit Log.
|
||||
func (s *Service) emitDiskEvent(eventBytes []byte) {
|
||||
// Unmarshal raw event bytes.
|
||||
var event rawOpenEvent
|
||||
var event diskDataT
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
if err != nil {
|
||||
logger.DebugContext(s.closeContext, "Failed to read binary data", "error", err)
|
||||
@@ -447,12 +428,12 @@ func (s *Service) emitDiskEvent(eventBytes []byte) {
|
||||
}
|
||||
|
||||
// If the event comes from a unmonitored process/cgroup, don't process it.
|
||||
ctx, ok := s.watch.Get(event.CgroupID)
|
||||
ctx, ok := s.watch.Load(event.Cgroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// If the network event is not being monitored, don't process it.
|
||||
// If the disk event is not being monitored, don't process it.
|
||||
_, ok = ctx.Events[constants.EnhancedRecordingDisk]
|
||||
if !ok {
|
||||
return
|
||||
@@ -480,12 +461,12 @@ func (s *Service) emitDiskEvent(eventBytes []byte) {
|
||||
UserTraits: ctx.UserTraits.Clone(),
|
||||
},
|
||||
BPFMetadata: apievents.BPFMetadata{
|
||||
CgroupID: event.CgroupID,
|
||||
Program: ConvertString(unsafe.Pointer(&event.Command)),
|
||||
PID: event.PID,
|
||||
CgroupID: event.Cgroup,
|
||||
Program: ConvertString(event.Command[:]),
|
||||
PID: event.Pid,
|
||||
},
|
||||
Flags: event.Flags,
|
||||
Path: ConvertString(unsafe.Pointer(&event.Path)),
|
||||
Path: ConvertString(event.FilePath[:]),
|
||||
ReturnCode: event.ReturnCode,
|
||||
}
|
||||
// Logs can be DoS by event failures here
|
||||
@@ -495,7 +476,7 @@ func (s *Service) emitDiskEvent(eventBytes []byte) {
|
||||
// emit4NetworkEvent will parse and emit IPv4 events to the Audit Log.
|
||||
func (s *Service) emit4NetworkEvent(eventBytes []byte) {
|
||||
// Unmarshal raw event bytes.
|
||||
var event rawConn4Event
|
||||
var event networkIpv4DataT
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
if err != nil {
|
||||
logger.DebugContext(s.closeContext, "Failed to read binary data", "error", err)
|
||||
@@ -503,7 +484,7 @@ func (s *Service) emit4NetworkEvent(eventBytes []byte) {
|
||||
}
|
||||
|
||||
// If the event comes from an unmonitored process/cgroup, don't process it.
|
||||
ctx, ok := s.watch.Get(event.CgroupID)
|
||||
ctx, ok := s.watch.Load(event.Cgroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -514,8 +495,8 @@ func (s *Service) emit4NetworkEvent(eventBytes []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
srcAddr := ipv4HostToIP(event.SrcAddr)
|
||||
dstAddr := ipv4HostToIP(event.DstAddr)
|
||||
srcAddr := ipv4HostToIP(event.Saddr)
|
||||
dstAddr := ipv4HostToIP(event.Daddr)
|
||||
sessionNetworkEvent := &apievents.SessionNetwork{
|
||||
Metadata: apievents.Metadata{
|
||||
Type: events.SessionNetworkEvent,
|
||||
@@ -538,11 +519,11 @@ func (s *Service) emit4NetworkEvent(eventBytes []byte) {
|
||||
UserTraits: ctx.UserTraits.Clone(),
|
||||
},
|
||||
BPFMetadata: apievents.BPFMetadata{
|
||||
CgroupID: event.CgroupID,
|
||||
Program: ConvertString(unsafe.Pointer(&event.Command)),
|
||||
PID: uint64(event.PID),
|
||||
CgroupID: event.Cgroup,
|
||||
Program: ConvertString(event.Command[:]),
|
||||
PID: uint64(event.Pid),
|
||||
},
|
||||
DstPort: int32(event.DstPort),
|
||||
DstPort: int32(event.Dport),
|
||||
DstAddr: dstAddr.String(),
|
||||
SrcAddr: srcAddr.String(),
|
||||
TCPVersion: 4,
|
||||
@@ -555,7 +536,7 @@ func (s *Service) emit4NetworkEvent(eventBytes []byte) {
|
||||
// emit6NetworkEvent will parse and emit IPv6 events to the Audit Log.
|
||||
func (s *Service) emit6NetworkEvent(eventBytes []byte) {
|
||||
// Unmarshal raw event bytes.
|
||||
var event rawConn6Event
|
||||
var event networkIpv6DataT
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
if err != nil {
|
||||
logger.DebugContext(s.closeContext, "Failed to read binary data", "error", err)
|
||||
@@ -563,7 +544,7 @@ func (s *Service) emit6NetworkEvent(eventBytes []byte) {
|
||||
}
|
||||
|
||||
// If the event comes from an unmonitored process/cgroup, don't process it.
|
||||
ctx, ok := s.watch.Get(event.CgroupID)
|
||||
ctx, ok := s.watch.Load(event.Cgroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -574,8 +555,8 @@ func (s *Service) emit6NetworkEvent(eventBytes []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
srcAddr := ipv6HostToIP(event.SrcAddr)
|
||||
dstAddr := ipv6HostToIP(event.DstAddr)
|
||||
srcAddr := net.IP(event.Saddr.In6U.U6Addr8[:])
|
||||
dstAddr := net.IP(event.Daddr.In6U.U6Addr8[:])
|
||||
sessionNetworkEvent := &apievents.SessionNetwork{
|
||||
Metadata: apievents.Metadata{
|
||||
Type: events.SessionNetworkEvent,
|
||||
@@ -598,11 +579,11 @@ func (s *Service) emit6NetworkEvent(eventBytes []byte) {
|
||||
UserTraits: ctx.UserTraits.Clone(),
|
||||
},
|
||||
BPFMetadata: apievents.BPFMetadata{
|
||||
CgroupID: event.CgroupID,
|
||||
Program: ConvertString(unsafe.Pointer(&event.Command)),
|
||||
PID: uint64(event.PID),
|
||||
CgroupID: event.Cgroup,
|
||||
Program: ConvertString(event.Command[:]),
|
||||
PID: uint64(event.Pid),
|
||||
},
|
||||
DstPort: int32(event.DstPort),
|
||||
DstPort: int32(event.Dport),
|
||||
DstAddr: dstAddr.String(),
|
||||
SrcAddr: srcAddr.String(),
|
||||
TCPVersion: 6,
|
||||
@@ -618,15 +599,6 @@ func ipv4HostToIP(addr uint32) net.IP {
|
||||
return val
|
||||
}
|
||||
|
||||
func ipv6HostToIP(addr [4]uint32) net.IP {
|
||||
val := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint32(val[0:], addr[0])
|
||||
binary.LittleEndian.PutUint32(val[4:], addr[1])
|
||||
binary.LittleEndian.PutUint32(val[8:], addr[2])
|
||||
binary.LittleEndian.PutUint32(val[12:], addr[3])
|
||||
return val
|
||||
}
|
||||
|
||||
// unmarshalEvent will unmarshal the perf event.
|
||||
func unmarshalEvent(data []byte, v interface{}) error {
|
||||
err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, v)
|
||||
@@ -636,9 +608,9 @@ func unmarshalEvent(data []byte, v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertString converts a C string to a Go string.
|
||||
func ConvertString(s unsafe.Pointer) string {
|
||||
return C.GoString((*C.char)(s))
|
||||
// ConvertString converts a NUL-terminated string to a Go string.
|
||||
func ConvertString(s []byte) string {
|
||||
return unix.ByteSliceToString(s)
|
||||
}
|
||||
|
||||
// SystemHasBPF returns true if the binary was build with support for BPF
|
||||
|
||||
+223
-330
@@ -23,25 +23,19 @@ package bpf
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
osexec "os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
@@ -56,8 +50,10 @@ import (
|
||||
const (
|
||||
// reexecInCGroupCmd is a cmd used to re-exec the test binary and call arbitrary program.
|
||||
reexecInCGroupCmd = "reexecCgroup"
|
||||
// networkInCgroupCmd is a cmd used to re-exec the test binary and make HTTP call.
|
||||
networkInCgroupCmd = "networkCgroup"
|
||||
// networkIPv4InCgroupCmd is a cmd used to re-exec the test binary and make HTTP call using an IPv4 address.
|
||||
networkIPv4InCgroupCmd = "networkCgroupIPv4"
|
||||
// networkIPv6InCgroupCmd is a cmd used to re-exec the test binary and make HTTP call using an IPv6 address.
|
||||
networkIPv6InCgroupCmd = "networkCgroupIPv6"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -73,16 +69,18 @@ func TestMain(m *testing.M) {
|
||||
cmd := os.Args[2]
|
||||
|
||||
err = waitAndRun(cmd)
|
||||
case networkInCgroupCmd:
|
||||
// Get the endpoint to call.
|
||||
case networkIPv4InCgroupCmd:
|
||||
endpoint := os.Args[2]
|
||||
|
||||
err = callEndpoint(endpoint)
|
||||
err = getEndpoint(endpoint, false)
|
||||
case networkIPv6InCgroupCmd:
|
||||
endpoint := os.Args[2]
|
||||
err = getEndpoint(endpoint, true)
|
||||
default:
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("rexec failed: %v\n", err)
|
||||
// Something went wrong, exit with error.
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -101,23 +99,24 @@ func waitAndRun(cmd string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return osexec.Command(cmd).Run()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return osexec.CommandContext(ctx, cmd).Run()
|
||||
}
|
||||
|
||||
// callEndpoint wait for continue signal to be generated an executes HTTP GET
|
||||
// on provided endpoint.
|
||||
func callEndpoint(endpoint string) error {
|
||||
// getEndpoint wait for continue signal to be generated then creates an
|
||||
// HTTP GET request on provided endpoint.
|
||||
func getEndpoint(endpoint string, ipv6 bool) error {
|
||||
if err := waitForContinue(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Get(endpoint)
|
||||
if resp != nil {
|
||||
// Close the body to make our linter happy.
|
||||
_ = resp.Body.Close()
|
||||
forceIPVersion := "-4"
|
||||
if ipv6 {
|
||||
forceIPVersion = "-6"
|
||||
}
|
||||
|
||||
return err
|
||||
return osexec.Command("curl", forceIPVersion, endpoint).Run()
|
||||
}
|
||||
|
||||
// waitForContinue opens FD 3 and waits the signal from parent process that
|
||||
@@ -135,17 +134,9 @@ func waitForContinue() error {
|
||||
}
|
||||
|
||||
func TestRootWatch(t *testing.T) {
|
||||
// TODO(jakule): Find a way to run this test in CI. Disable for now to not block all BPF tests.
|
||||
t.Skip("this test always fails when running inside a CGroup/Docker")
|
||||
|
||||
// This test must be run as root and the host has to be capable of running
|
||||
// BPF programs.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
checkBPF(t)
|
||||
|
||||
// Create temporary directory where cgroup2 hierarchy will be mounted.
|
||||
cgroupPath := t.TempDir()
|
||||
@@ -221,169 +212,128 @@ func TestRootWatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRootObfuscate checks if execsnoop can capture Obfuscated commands.
|
||||
func TestRootObfuscate(t *testing.T) {
|
||||
t.Skip("flaky test, disable now")
|
||||
// TestRootScripts checks if execsnoop can capture commands executed
|
||||
// in scripts, whether they are obfuscated or not.
|
||||
func TestRootScripts(t *testing.T) {
|
||||
// This test must be run as root and the host has to be capable of running
|
||||
// BPF programs.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
checkBPF(t)
|
||||
|
||||
// Create temporary directory where cgroup2 hierarchy will be mounted.
|
||||
cgroupPath := t.TempDir()
|
||||
|
||||
// Create BPF service.
|
||||
service, err := New(&servicecfg.BPFConfig{
|
||||
Enabled: true,
|
||||
CgroupPath: cgroupPath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
const restarting = false
|
||||
require.NoError(t, service.Close(restarting))
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scriptContents string
|
||||
expectedCommand string
|
||||
usedCommands []string
|
||||
}{
|
||||
{
|
||||
name: "normal script",
|
||||
scriptContents: "ls -la",
|
||||
expectedCommand: "ls -la",
|
||||
usedCommands: []string{"ls"},
|
||||
},
|
||||
{
|
||||
name: "base64 encoded",
|
||||
scriptContents: fmt.Sprintf("echo %s | base64 --decode | /bin/sh", base64.StdEncoding.EncodeToString([]byte("ls -la"))),
|
||||
expectedCommand: "ls -la",
|
||||
usedCommands: []string{"ls"},
|
||||
},
|
||||
}
|
||||
|
||||
// Find the programs needed to run these tests on the host.
|
||||
decoderPath, err := osexec.LookPath("base64")
|
||||
require.NoError(t, err)
|
||||
shellPath, err := osexec.LookPath("sh")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start execsnoop.
|
||||
execsnoop, err := startExec(8192)
|
||||
defer execsnoop.close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create obfuscated script.
|
||||
shellContents := fmt.Sprintf("#!%v\necho bHM= | %v --decode | %v",
|
||||
shellPath, decoderPath, shellPath)
|
||||
|
||||
// Write script to a temporary folder.
|
||||
fileName := filepath.Join(t.TempDir(), "test-script")
|
||||
err = os.WriteFile(fileName, []byte(shellContents), 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
// Start a goroutine that writes a script which will execute "ls"
|
||||
// in a loop. Then waits for an exec event to show up the reports "ls"
|
||||
// has been executed.
|
||||
go func() {
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
runCmd(t, reexecInCGroupCmd, fileName, execsnoop, require.NoError)
|
||||
case <-done:
|
||||
return
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
usedCommands := append(tt.usedCommands, "bash")
|
||||
for _, cmd := range usedCommands {
|
||||
// Find the programs needed to run these tests on the host.
|
||||
_, err := osexec.LookPath(cmd)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for an event to arrive from execsnoop. If an event does not arrive
|
||||
// within 10 seconds, timeout.
|
||||
for {
|
||||
select {
|
||||
case eventBytes := <-execsnoop.events():
|
||||
var event rawExecEvent
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
// Create a fake audit log that can be used to capture the events emitted.
|
||||
emitter := eventstest.NewChannelEmitter(8)
|
||||
|
||||
scx := &SessionContext{
|
||||
Context: t.Context(),
|
||||
Namespace: apidefaults.Namespace,
|
||||
SessionID: uuid.New().String(),
|
||||
ServerID: uuid.New().String(),
|
||||
ServerHostname: "hostname",
|
||||
Login: "foo",
|
||||
User: "foo@example.com",
|
||||
PID: os.Getpid(),
|
||||
Emitter: emitter,
|
||||
Events: map[string]bool{
|
||||
constants.EnhancedRecordingCommand: true,
|
||||
},
|
||||
}
|
||||
_, err := service.OpenSession(scx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the event is what we expect, in this case "ls".
|
||||
if ConvertString(unsafe.Pointer(&event.Command)) == "ls" {
|
||||
return
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out waiting for an event.")
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Cleanup(func() { service.CloseSession(scx) })
|
||||
|
||||
// TestRootScript checks if execsnoop can capture what a script executes.
|
||||
func TestRootScript(t *testing.T) {
|
||||
t.Skip("flaky test, disable now")
|
||||
// This test must be run as root and the host has to be capable of running
|
||||
// BPF programs.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
|
||||
// Write script to a temporary folder.
|
||||
fileName := filepath.Join(t.TempDir(), "test-script")
|
||||
err := os.WriteFile(fileName, []byte("#!/bin/sh\nls"), 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start execsnoop.
|
||||
execsnoop, err := startExec(8192)
|
||||
defer execsnoop.close()
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
// Start a goroutine that writes a script which will execute "ls"
|
||||
// in a loop. Then waits for an exec event to show up the reports "ls"
|
||||
// has been executed.
|
||||
go func() {
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
// Run script in a cgroup.
|
||||
runCmd(t, reexecInCGroupCmd, fileName, execsnoop, require.NoError)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for an event to arrive from execsnoop. If an event does not arrive
|
||||
// within 10 seconds, timeout.
|
||||
for {
|
||||
select {
|
||||
case eventBytes := <-execsnoop.events():
|
||||
var event rawExecEvent
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
// Write script to a temporary folder.
|
||||
fileName := filepath.Join(t.TempDir(), "test-script")
|
||||
scriptContents := "#!/bin/bash\n" + tt.scriptContents
|
||||
err = os.WriteFile(fileName, []byte(scriptContents), 0o700)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the event is what we expect, in this case "ls".
|
||||
if ConvertString(unsafe.Pointer(&event.Command)) == "ls" {
|
||||
return
|
||||
err = osexec.CommandContext(t.Context(), fileName).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for an event to arrive from execsnoop. If an event does not arrive
|
||||
// within 10 seconds, timeout.
|
||||
for {
|
||||
select {
|
||||
case event := <-emitter.C():
|
||||
cmdEvent, ok := event.(*apievents.SessionCommand)
|
||||
require.True(t, ok, "expected SessionCommand event, got %T", event)
|
||||
|
||||
args := append([]string{cmdEvent.BPFMetadata.Program}, cmdEvent.Argv...)
|
||||
cmd := strings.Join(args, " ")
|
||||
t.Logf("got event: %q", cmd)
|
||||
if cmd == tt.expectedCommand {
|
||||
return
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out waiting for an event.")
|
||||
}
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out waiting for an event.")
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRootPrograms tests execsnoop, opensnoop, and tcpconnect to make sure they
|
||||
// run and receive events.
|
||||
func TestRootPrograms(t *testing.T) {
|
||||
t.Skip("flaky test, disable now")
|
||||
// This test must be run as root. Only root can create cgroups.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
|
||||
// Start a debug server that tcpconnect will connect to.
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "hello, world")
|
||||
}))
|
||||
defer ts.Close()
|
||||
checkBPF(t)
|
||||
|
||||
// Start execsnoop.
|
||||
execsnoop, err := startExec(8192)
|
||||
execsnoop, err := startExec(8)
|
||||
require.NoError(t, err)
|
||||
defer execsnoop.close()
|
||||
|
||||
// Start opensnoop.
|
||||
opensnoop, err := startOpen(8192)
|
||||
opensnoop, err := startOpen(8)
|
||||
require.NoError(t, err)
|
||||
defer opensnoop.close()
|
||||
|
||||
// Start tcpconnect.
|
||||
tcpconnect, err := startConn(8192)
|
||||
tcpconnect, err := startConn(8)
|
||||
require.NoError(t, err)
|
||||
defer tcpconnect.close()
|
||||
|
||||
@@ -392,20 +342,20 @@ func TestRootPrograms(t *testing.T) {
|
||||
tests := []struct {
|
||||
inName string
|
||||
inEventCh <-chan []byte
|
||||
genEvents func(t *testing.T, ctx context.Context)
|
||||
genEvents func(t *testing.T)
|
||||
verifyFn func(event []byte) bool
|
||||
}{
|
||||
// Run execsnoop with "ls".
|
||||
{
|
||||
inName: "execsnoop",
|
||||
inEventCh: execsnoop.events(),
|
||||
genEvents: func(t *testing.T, ctx context.Context) {
|
||||
executeCommand(t, ctx, "ls", execsnoop)
|
||||
genEvents: func(t *testing.T) {
|
||||
executeCommand(t, "ls", execsnoop)
|
||||
},
|
||||
verifyFn: func(event []byte) bool {
|
||||
var e rawExecEvent
|
||||
var e commandDataT
|
||||
err := unmarshalEvent(event, &e)
|
||||
return err == nil && ConvertString(unsafe.Pointer(&e.Command)) == "ls"
|
||||
return err == nil && ConvertString(e.Command[:]) == "ls"
|
||||
},
|
||||
},
|
||||
// Run opensnoop with "ls". This is fine because "ls" will open some
|
||||
@@ -413,127 +363,75 @@ func TestRootPrograms(t *testing.T) {
|
||||
{
|
||||
inName: "opensnoop",
|
||||
inEventCh: opensnoop.events(),
|
||||
genEvents: func(t *testing.T, ctx context.Context) {
|
||||
executeCommand(t, ctx, "ls", opensnoop)
|
||||
genEvents: func(t *testing.T) {
|
||||
executeCommand(t, "ls", opensnoop)
|
||||
},
|
||||
verifyFn: func(event []byte) bool {
|
||||
var e rawOpenEvent
|
||||
var e diskDataT
|
||||
err := unmarshalEvent(event, &e)
|
||||
return err == nil
|
||||
return err == nil && ConvertString(e.Command[:]) == "ls"
|
||||
},
|
||||
},
|
||||
// Run tcpconnect with netcat.
|
||||
// Run tcpconnect with curl forcing IPv4.
|
||||
{
|
||||
inName: "tcpconnect",
|
||||
inName: "tcpconnect ipv4",
|
||||
inEventCh: tcpconnect.v4Events(),
|
||||
genEvents: func(t *testing.T, ctx context.Context) {
|
||||
executeHTTP(t, ctx, ts.URL, tcpconnect)
|
||||
genEvents: func(t *testing.T) {
|
||||
executeHTTP(t, "http://google.com", false, tcpconnect)
|
||||
},
|
||||
verifyFn: func(event []byte) bool {
|
||||
var e rawConn4Event
|
||||
var e networkIpv4DataT
|
||||
err := unmarshalEvent(event, &e)
|
||||
return err == nil
|
||||
|
||||
return err == nil && ConvertString(e.Command[:]) == "curl"
|
||||
},
|
||||
},
|
||||
// Run tcpconnect with curl forcing IPv6.
|
||||
{
|
||||
inName: "tcpconnect ipv6",
|
||||
inEventCh: tcpconnect.v6Events(),
|
||||
genEvents: func(t *testing.T) {
|
||||
executeHTTP(t, "http://google.com", true, tcpconnect)
|
||||
},
|
||||
verifyFn: func(event []byte) bool {
|
||||
var e networkIpv6DataT
|
||||
err := unmarshalEvent(event, &e)
|
||||
|
||||
return err == nil && ConvertString(e.Command[:]) == "curl"
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Create a context that will be used to signal that an event has been recieved.
|
||||
doneContext, doneFunc := context.WithCancel(context.Background())
|
||||
t.Run(tt.inName, func(t *testing.T) {
|
||||
// Create a context that will be used to signal that an event has been received.
|
||||
doneContext, doneFunc := context.WithCancel(context.Background())
|
||||
t.Cleanup(doneFunc)
|
||||
|
||||
// Start two goroutines. The first will wait for the BPF program event to
|
||||
// arrive, and once it has, signal over the context that it's complete. The
|
||||
// second will continue to execute or an HTTP GET in a processAccessEvents attempting to
|
||||
// trigger an event.
|
||||
go waitForEvent(doneContext, doneFunc, tt.inEventCh, tt.verifyFn)
|
||||
// Start two goroutines. The first will wait for the BPF program event to
|
||||
// arrive, and once it has, signal over the context that it's complete. The
|
||||
// second will continue to execute or an HTTP GET in a processAccessEvents attempting to
|
||||
// trigger an event.
|
||||
go waitForEvent(doneContext, doneFunc, tt.inEventCh, tt.verifyFn)
|
||||
|
||||
go tt.genEvents(t, doneContext)
|
||||
go tt.genEvents(t)
|
||||
|
||||
// Wait for an event to arrive from execsnoop. If an event does not arrive
|
||||
// within 10 seconds, timeout.
|
||||
select {
|
||||
case <-doneContext.Done():
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out waiting for an %v event.", tt.inName)
|
||||
}
|
||||
// Wait for an event to arrive from execsnoop. If an event does not arrive
|
||||
// within 10 seconds, timeout.
|
||||
select {
|
||||
case <-doneContext.Done():
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out waiting for an %v event.", tt.inName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRootBPFCounter tests that BPF-to-Prometheus counter works ok
|
||||
func TestRootBPFCounter(t *testing.T) {
|
||||
t.Skip("flaky test, disable now")
|
||||
// This test must be run as root. Only root can create cgroups.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
|
||||
counterTestBPF, err := embedFS.ReadFile("bytecode/counter_test.bpf.o")
|
||||
if err != nil {
|
||||
t.Skip(fmt.Sprintf("Tests for package bpf can not be run: %v.", err))
|
||||
}
|
||||
|
||||
module, err := libbpfgo.NewModuleFromBuffer(counterTestBPF, "counter_test")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load into the kernel
|
||||
err = module.BPFLoadObject()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = AttachSyscallTracepoint(module, "close")
|
||||
require.NoError(t, err)
|
||||
|
||||
promCounter := prometheus.NewCounter(prometheus.CounterOpts{Name: "test"})
|
||||
|
||||
counter, err := NewCounter(module, "test_counter", promCounter)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make sure the counter starts with 0
|
||||
require.Zero(t, testutil.ToFloat64(promCounter))
|
||||
|
||||
// close(1234) will cause the counter to get incremented.
|
||||
magicFD := 1234
|
||||
|
||||
// First do it a few times as to no overflow the doorbell buffer
|
||||
gentleBumps := 10
|
||||
for i := 0; i < gentleBumps; i++ {
|
||||
syscall.Close(magicFD)
|
||||
}
|
||||
|
||||
// Not ideal but no other good way to know that the counter was updated
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Make sure all are accounted for
|
||||
require.Equal(t, float64(gentleBumps), testutil.ToFloat64(promCounter))
|
||||
|
||||
// Next, pound the counter to hopefully overflow the doorbell.
|
||||
poundingBumps := 100000
|
||||
for i := 0; i < poundingBumps; i++ {
|
||||
syscall.Close(magicFD)
|
||||
}
|
||||
|
||||
// Not ideal but no other good way to know that the counter was updated
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Make sure all are accounted for
|
||||
require.Equal(t, float64(gentleBumps+poundingBumps), testutil.ToFloat64(promCounter))
|
||||
|
||||
counter.Close()
|
||||
}
|
||||
|
||||
// TestRootLargeCommands given commands with higher amount of characters
|
||||
// (length), ensure the command events are generated correctly.
|
||||
func TestRootLargeCommands(t *testing.T) {
|
||||
t.Skip("flaky test, disable now")
|
||||
// This test must be run as root and the host has to be capable of running
|
||||
// BPF programs.
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled")
|
||||
}
|
||||
if !isRoot() {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
checkBPF(t)
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
cmd string
|
||||
@@ -555,19 +453,20 @@ func TestRootLargeCommands(t *testing.T) {
|
||||
|
||||
// Since we're using a random command, we should expect its
|
||||
// execution will fail.
|
||||
runCmd(t, reexecInCGroupCmd, test.cmd, execsnoop, require.Error)
|
||||
err = runCmd(t, reexecInCGroupCmd, test.cmd, execsnoop)
|
||||
require.Error(t, err)
|
||||
|
||||
for {
|
||||
select {
|
||||
case eventBytes := <-execsnoop.events():
|
||||
var event rawExecEvent
|
||||
var event commandDataT
|
||||
err := unmarshalEvent(eventBytes, &event)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Since we're executing the command using the test binary,
|
||||
// the arguments return on a single event, and the path of
|
||||
// or command will come on the argv part.
|
||||
argv := ConvertString(unsafe.Pointer(&event.Argv))
|
||||
argv := ConvertString(event.Argv[:])
|
||||
if event.Type == eventArg {
|
||||
if test.expectPartialPath {
|
||||
require.Len(t, argv, ArgvMax)
|
||||
@@ -585,7 +484,6 @@ func TestRootLargeCommands(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// waitForEvent will wait for an event to arrive over the perf buffer and
|
||||
@@ -613,7 +511,7 @@ func moveIntoCgroup(t *testing.T, pid int) (uint64, error) {
|
||||
MountPath: cgroupPath,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, trace.Wrap(err)
|
||||
return 0, trace.Wrap(err, "failed to mount cgroup")
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
const skipUnmount = false
|
||||
@@ -624,13 +522,13 @@ func moveIntoCgroup(t *testing.T, pid int) (uint64, error) {
|
||||
// Put the cmd in a new cgroup.
|
||||
cgroupID, err := createCgroup(t, cgroupSrv, sessionID)
|
||||
if err != nil {
|
||||
return 0, trace.Wrap(err)
|
||||
return 0, trace.Wrap(err, "failed to create cgroup")
|
||||
}
|
||||
|
||||
// Place requested PID into cgroup.
|
||||
err = cgroupSrv.Place(sessionID, pid)
|
||||
if err != nil {
|
||||
return 0, trace.Wrap(err)
|
||||
return 0, trace.Wrap(err, "failed to place pid %d into cgroup", pid)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
@@ -659,40 +557,25 @@ func createCgroup(t *testing.T, cgroup *cgroup.Service, sessionID string,
|
||||
return cgroupID, nil
|
||||
}
|
||||
|
||||
// executeCommand will execute some command in a loop.
|
||||
func executeCommand(t *testing.T, doneContext context.Context, file string,
|
||||
traceCgroup cgroupRegister,
|
||||
) {
|
||||
// executeCommand will execute some command.
|
||||
func executeCommand(t *testing.T, file string, traceCgroup cgroupRegister) {
|
||||
t.Helper()
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
fullPath, err := osexec.LookPath(file)
|
||||
require.NoError(t, err, "Failed to find executable %q", file)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Lookup and run the requested command.
|
||||
path, err := osexec.LookPath(file)
|
||||
if err != nil {
|
||||
t.Logf("Failed to find executable %q: %v.", file, err)
|
||||
}
|
||||
|
||||
fullPath, err := osexec.LookPath(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
runCmd(t, reexecInCGroupCmd, fullPath, traceCgroup, require.NoError)
|
||||
case <-doneContext.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
err = runCmd(t, reexecInCGroupCmd, fullPath, traceCgroup)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func runCmd(t *testing.T, reexecCmd string, arg string, traceCgroup cgroupRegister, cmdReturnAssertion require.ErrorAssertionFunc) {
|
||||
func runCmd(t *testing.T, reexecCmd string, arg string, traceCgroup cgroupRegister) error {
|
||||
t.Helper()
|
||||
|
||||
// Create a pipe to communicate with the child process after re-exec.
|
||||
readP, writeP, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to create pipe")
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
readP.Close()
|
||||
@@ -701,60 +584,70 @@ func runCmd(t *testing.T, reexecCmd string, arg string, traceCgroup cgroupRegist
|
||||
|
||||
// Re-exec the test binary. We can then move the binary to a new cgroup.
|
||||
cmd := osexec.Command(os.Args[0], reexecCmd, arg)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, readP)
|
||||
|
||||
// Start the re-exec
|
||||
err = cmd.Start()
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to start command")
|
||||
}
|
||||
|
||||
cgroupID, err := moveIntoCgroup(t, cmd.Process.Pid)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to move pid %d into cgroup", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
// Register the process in the BPF module
|
||||
err = traceCgroup.startSession(cgroupID)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to register cgroup in BPF module")
|
||||
}
|
||||
|
||||
// Send one byte to continue the subprocess execution.
|
||||
_, err = writeP.Write([]byte{1})
|
||||
require.NoError(t, err)
|
||||
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to write to pipe")
|
||||
}
|
||||
// Wait for the command to exit. Otherwise, we cannot clean up the cgroup.
|
||||
cmdReturnAssertion(t, cmd.Wait())
|
||||
waitErr := trace.Wrap(cmd.Wait())
|
||||
|
||||
// Remove the registered cgroup from the BPF module. Do not call it after
|
||||
// BPF module is deregistered.
|
||||
err = traceCgroup.endSession(cgroupID)
|
||||
if err != nil {
|
||||
return trace.NewAggregate(waitErr, trace.Wrap(err, "failed to deregister cgroup in BPF module"))
|
||||
}
|
||||
|
||||
return waitErr
|
||||
}
|
||||
|
||||
// executeHTTP will perform an HTTP GET to some endpoint in a subprocess
|
||||
// that is placed into the traceCgroup cgroup so it can be tracked.
|
||||
func executeHTTP(t *testing.T, endpoint string, ipv6 bool, traceCgroup cgroupRegister) {
|
||||
t.Helper()
|
||||
|
||||
cmd := networkIPv4InCgroupCmd
|
||||
if ipv6 {
|
||||
cmd = networkIPv6InCgroupCmd
|
||||
}
|
||||
|
||||
err := runCmd(t, cmd, endpoint, traceCgroup)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// executeHTTP will perform a HTTP GET to some endpoint in a loop.
|
||||
func executeHTTP(t *testing.T, doneContext context.Context, endpoint string, traceCgroup cgroupRegister) {
|
||||
// checkBPF skips the test if BPF tests are not enabled or the test is not run
|
||||
// as root.
|
||||
func checkBPF(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Perform HTTP GET to the requested endpoint.
|
||||
if _, err := http.Get(endpoint); err != nil {
|
||||
t.Logf("HTTP request failed: %v.", err)
|
||||
}
|
||||
|
||||
runCmd(t, networkInCgroupCmd, endpoint, traceCgroup, require.NoError)
|
||||
|
||||
case <-doneContext.Done():
|
||||
return
|
||||
}
|
||||
if !bpfTestEnabled() {
|
||||
t.Skip("BPF testing is disabled. Set TELEPORT_BPF_TEST environment variable to enable.")
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("Tests for package bpf can only be run as root.")
|
||||
}
|
||||
}
|
||||
|
||||
// isRoot returns a boolean if the test is being run as root or not. Tests
|
||||
// for this package must be run as root.
|
||||
func isRoot() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
// bpfTestEnabled returns true if BPF tests should run. Tests can be enabled by
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## BPF Bytecode
|
||||
|
||||
After builds, this directory contains CO-RE BFP bytecode that is embedded within Teleport.
|
||||
+126
-71
@@ -21,9 +21,14 @@
|
||||
package bpf
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
@@ -31,49 +36,56 @@ import (
|
||||
"github.com/gravitational/teleport/lib/observability/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
lostCommandEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostCommandEvents,
|
||||
Help: "Number of lost command events.",
|
||||
},
|
||||
)
|
||||
var lostCommandEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostCommandEvents,
|
||||
Help: "Number of lost command events.",
|
||||
},
|
||||
)
|
||||
|
||||
const (
|
||||
commandEventsBuffer = "execve_events"
|
||||
)
|
||||
|
||||
// rawExecEvent is sent by the eBPF program that Teleport pulls off the perf
|
||||
// buffer.
|
||||
type rawExecEvent struct {
|
||||
// PID is the ID of the process.
|
||||
PID uint64
|
||||
|
||||
// PPID is the PID of the parent process.
|
||||
PPID uint64
|
||||
|
||||
// Command is the executable.
|
||||
Command [CommMax]byte
|
||||
|
||||
// Type is the type of event.
|
||||
Type int32
|
||||
|
||||
// Argv is the list of arguments to the program.
|
||||
Argv [ArgvMax]byte
|
||||
|
||||
// ReturnCode is the return code of execve.
|
||||
ReturnCode int32
|
||||
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
CgroupID uint64
|
||||
}
|
||||
|
||||
type exec struct {
|
||||
session
|
||||
// session
|
||||
objs commandObjects
|
||||
|
||||
eventBuf *RingBuffer
|
||||
lost *Counter
|
||||
eventBuf *ringbuf.Reader
|
||||
lost *ebpf.Map
|
||||
toClose []io.Closer
|
||||
|
||||
closed bool
|
||||
mtx sync.Mutex
|
||||
|
||||
bpfEvents chan []byte
|
||||
lostCounter *Counter
|
||||
}
|
||||
|
||||
func (e *exec) startSession(cgroupID uint64) error {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return trace.BadParameter("open session already closed")
|
||||
}
|
||||
|
||||
if err := e.objs.MonitoredCgroups.Put(cgroupID, int64(0)); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *exec) endSession(cgroupID uint64) error {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil // Ignore. If the session is closed, the cgroup is no longer monitored.
|
||||
}
|
||||
|
||||
if err := e.objs.MonitoredCgroups.Delete(&cgroupID); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startExec will load, start, and pull events off the ring buffer
|
||||
@@ -84,59 +96,102 @@ func startExec(bufferSize int) (*exec, error) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
e := &exec{}
|
||||
// Remove resource limits for kernels <5.11.
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
return nil, trace.WrapWithMessage(err, "Removing memlock")
|
||||
}
|
||||
|
||||
commandBPF, err := embedFS.ReadFile("bytecode/command.bpf.o")
|
||||
var objs commandObjects
|
||||
if err := loadCommandObjects(&objs, nil); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
lostCtr, err := NewCounter(objs.LostCounter, objs.LostDoorbell, lostCommandEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
e.session.module, err = libbpfgo.NewModuleFromBuffer(commandBPF, "command")
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
toClose := make([]io.Closer, 0)
|
||||
|
||||
tracePoints := []struct {
|
||||
name string
|
||||
tracepoint *ebpf.Program
|
||||
}{
|
||||
{
|
||||
name: "sys_enter_execve",
|
||||
tracepoint: objs.TracepointSyscallsSysEnterExecve,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_execve",
|
||||
tracepoint: objs.TracepointSyscallsSysExitExecve,
|
||||
},
|
||||
{
|
||||
name: "sys_enter_execveat",
|
||||
tracepoint: objs.TracepointSyscallsSysEnterExecveat,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_execveat",
|
||||
tracepoint: objs.TracepointSyscallsSysExitExecveat,
|
||||
},
|
||||
}
|
||||
|
||||
// Resizing the ring buffer must be done here, after the module
|
||||
// was created but before it's loaded into the kernel.
|
||||
if err = ResizeMap(e.session.module, commandEventsBuffer, uint32(bufferSize*pageSize)); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Load into the kernel
|
||||
if err = e.session.module.BPFLoadObject(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
syscalls := []string{"execve", "execveat"}
|
||||
|
||||
for _, syscall := range syscalls {
|
||||
if err = AttachSyscallTracepoint(e.session.module, syscall); err != nil {
|
||||
for _, tp := range tracePoints {
|
||||
tp, err := link.Tracepoint("syscalls", tp.name, tp.tracepoint, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
toClose = append(toClose, tp)
|
||||
}
|
||||
|
||||
e.eventBuf, err = NewRingBuffer(e.session.module, commandEventsBuffer)
|
||||
eventBuf, err := ringbuf.NewReader(objs.ExecveEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
e.lost, err = NewCounter(e.session.module, "lost", lostCommandEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
bpfEvents := make(chan []byte, bufferSize)
|
||||
go sendEvents(bpfEvents, eventBuf)
|
||||
|
||||
return e, nil
|
||||
return &exec{
|
||||
objs: objs,
|
||||
eventBuf: eventBuf,
|
||||
lost: objs.LostCounter,
|
||||
toClose: toClose,
|
||||
bpfEvents: bpfEvents,
|
||||
lostCounter: lostCtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// close will stop reading events off the ring buffer and unload the BPF
|
||||
// program. The ring buffer is closed as part of the module being closed.
|
||||
func (e *exec) close() {
|
||||
e.lost.Close()
|
||||
e.eventBuf.Close()
|
||||
e.session.module.Close()
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return
|
||||
}
|
||||
|
||||
e.closed = true
|
||||
|
||||
for _, link := range e.toClose {
|
||||
if err := link.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close link", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.objs.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close command objects", "error", err)
|
||||
}
|
||||
|
||||
if err := e.lostCounter.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close command lost counter", "error", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(context.Background(), "Closed command BPF module")
|
||||
}
|
||||
|
||||
// events contains raw events off the perf buffer.
|
||||
func (e *exec) events() <-chan []byte {
|
||||
return e.eventBuf.EventCh
|
||||
return e.bpfEvents
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build arm64 && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type commandDataT struct {
|
||||
_ structs.HostLayout
|
||||
Pid uint64
|
||||
Ppid uint64
|
||||
Command [16]uint8
|
||||
Type uint32
|
||||
Argv [1024]uint8
|
||||
Retval int32
|
||||
Cgroup uint64
|
||||
}
|
||||
|
||||
// loadCommand returns the embedded CollectionSpec for command.
|
||||
func loadCommand() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_CommandBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load command: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadCommandObjects loads command and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *commandObjects
|
||||
// *commandPrograms
|
||||
// *commandMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadCommandObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// commandSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandSpecs struct {
|
||||
commandProgramSpecs
|
||||
commandMapSpecs
|
||||
commandVariableSpecs
|
||||
}
|
||||
|
||||
// commandProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandProgramSpecs struct {
|
||||
TracepointSyscallsSysEnterExecve *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_execve"`
|
||||
TracepointSyscallsSysEnterExecveat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_execveat"`
|
||||
TracepointSyscallsSysExitExecve *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_execve"`
|
||||
TracepointSyscallsSysExitExecveat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_execveat"`
|
||||
}
|
||||
|
||||
// commandMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandMapSpecs struct {
|
||||
ExecveEvents *ebpf.MapSpec `ebpf:"execve_events"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
// commandVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandVariableSpecs struct {
|
||||
Unused *ebpf.VariableSpec `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// commandObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandObjects struct {
|
||||
commandPrograms
|
||||
commandMaps
|
||||
commandVariables
|
||||
}
|
||||
|
||||
func (o *commandObjects) Close() error {
|
||||
return _CommandClose(
|
||||
&o.commandPrograms,
|
||||
&o.commandMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// commandMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandMaps struct {
|
||||
ExecveEvents *ebpf.Map `ebpf:"execve_events"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
func (m *commandMaps) Close() error {
|
||||
return _CommandClose(
|
||||
m.ExecveEvents,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
)
|
||||
}
|
||||
|
||||
// commandVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandVariables struct {
|
||||
Unused *ebpf.Variable `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// commandPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandPrograms struct {
|
||||
TracepointSyscallsSysEnterExecve *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_execve"`
|
||||
TracepointSyscallsSysEnterExecveat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_execveat"`
|
||||
TracepointSyscallsSysExitExecve *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_execve"`
|
||||
TracepointSyscallsSysExitExecveat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_execveat"`
|
||||
}
|
||||
|
||||
func (p *commandPrograms) Close() error {
|
||||
return _CommandClose(
|
||||
p.TracepointSyscallsSysEnterExecve,
|
||||
p.TracepointSyscallsSysEnterExecveat,
|
||||
p.TracepointSyscallsSysExitExecve,
|
||||
p.TracepointSyscallsSysExitExecveat,
|
||||
)
|
||||
}
|
||||
|
||||
func _CommandClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed command_arm64_bpfel.o
|
||||
var _CommandBytes []byte
|
||||
Binary file not shown.
@@ -0,0 +1,165 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build (386 || amd64) && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type commandDataT struct {
|
||||
_ structs.HostLayout
|
||||
Pid uint64
|
||||
Ppid uint64
|
||||
Command [16]uint8
|
||||
Type uint32
|
||||
Argv [1024]uint8
|
||||
Retval int32
|
||||
Cgroup uint64
|
||||
}
|
||||
|
||||
// loadCommand returns the embedded CollectionSpec for command.
|
||||
func loadCommand() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_CommandBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load command: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadCommandObjects loads command and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *commandObjects
|
||||
// *commandPrograms
|
||||
// *commandMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadCommandObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// commandSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandSpecs struct {
|
||||
commandProgramSpecs
|
||||
commandMapSpecs
|
||||
commandVariableSpecs
|
||||
}
|
||||
|
||||
// commandProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandProgramSpecs struct {
|
||||
TracepointSyscallsSysEnterExecve *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_execve"`
|
||||
TracepointSyscallsSysEnterExecveat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_execveat"`
|
||||
TracepointSyscallsSysExitExecve *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_execve"`
|
||||
TracepointSyscallsSysExitExecveat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_execveat"`
|
||||
}
|
||||
|
||||
// commandMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandMapSpecs struct {
|
||||
ExecveEvents *ebpf.MapSpec `ebpf:"execve_events"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
// commandVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type commandVariableSpecs struct {
|
||||
Unused *ebpf.VariableSpec `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// commandObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandObjects struct {
|
||||
commandPrograms
|
||||
commandMaps
|
||||
commandVariables
|
||||
}
|
||||
|
||||
func (o *commandObjects) Close() error {
|
||||
return _CommandClose(
|
||||
&o.commandPrograms,
|
||||
&o.commandMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// commandMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandMaps struct {
|
||||
ExecveEvents *ebpf.Map `ebpf:"execve_events"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
func (m *commandMaps) Close() error {
|
||||
return _CommandClose(
|
||||
m.ExecveEvents,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
)
|
||||
}
|
||||
|
||||
// commandVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandVariables struct {
|
||||
Unused *ebpf.Variable `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// commandPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadCommandObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type commandPrograms struct {
|
||||
TracepointSyscallsSysEnterExecve *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_execve"`
|
||||
TracepointSyscallsSysEnterExecveat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_execveat"`
|
||||
TracepointSyscallsSysExitExecve *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_execve"`
|
||||
TracepointSyscallsSysExitExecveat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_execveat"`
|
||||
}
|
||||
|
||||
func (p *commandPrograms) Close() error {
|
||||
return _CommandClose(
|
||||
p.TracepointSyscallsSysEnterExecve,
|
||||
p.TracepointSyscallsSysEnterExecveat,
|
||||
p.TracepointSyscallsSysExitExecve,
|
||||
p.TracepointSyscallsSysExitExecveat,
|
||||
)
|
||||
}
|
||||
|
||||
func _CommandClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed command_x86_bpfel.o
|
||||
var _CommandBytes []byte
|
||||
Binary file not shown.
@@ -1,65 +0,0 @@
|
||||
//go:build bpf && !386
|
||||
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
const monitoredCGroups = "monitored_cgroups"
|
||||
|
||||
type session struct {
|
||||
module *libbpfgo.Module
|
||||
}
|
||||
|
||||
// startSession registers the given cgroup in the BPF module. Only registered
|
||||
// cgroups will return events to the userspace.
|
||||
func (s *session) startSession(cgroupID uint64) error {
|
||||
cgroupMap, err := s.module.GetMap(monitoredCGroups)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
dummyVal := 0
|
||||
err = cgroupMap.Update(unsafe.Pointer(&cgroupID), unsafe.Pointer(&dummyVal))
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// endSession removes the previously registered cgroup from the BPF module.
|
||||
func (s *session) endSession(cgroupID uint64) error {
|
||||
cgroupMap, err := s.module.GetMap(monitoredCGroups)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := cgroupMap.DeleteKey(unsafe.Pointer(&cgroupID)); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func TestBPFConfig_CheckAndSetDefaults(t *testing.T) {
|
||||
openPerfBufferPageCount := defaults.OpenPerfBufferPageCount
|
||||
zeroPageCount := 0
|
||||
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
got *servicecfg.BPFConfig
|
||||
want *servicecfg.BPFConfig
|
||||
@@ -60,8 +60,8 @@ func TestBPFConfig_CheckAndSetDefaults(t *testing.T) {
|
||||
CgroupPath: "/my/cgroup/",
|
||||
},
|
||||
want: &servicecfg.BPFConfig{
|
||||
CommandBufferSize: &zeroPageCount,
|
||||
DiskBufferSize: &zeroPageCount,
|
||||
CommandBufferSize: &perfBufferPageCount,
|
||||
DiskBufferSize: &openPerfBufferPageCount,
|
||||
NetworkBufferSize: &perfBufferPageCount,
|
||||
CgroupPath: "/my/cgroup/",
|
||||
},
|
||||
|
||||
+144
-76
@@ -21,10 +21,15 @@
|
||||
package bpf
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"context"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
@@ -32,51 +37,23 @@ import (
|
||||
"github.com/gravitational/teleport/lib/observability/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
lostDiskEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostDiskEvents,
|
||||
Help: "Number of lost disk events.",
|
||||
},
|
||||
)
|
||||
var lostDiskEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostDiskEvents,
|
||||
Help: "Number of lost disk events.",
|
||||
},
|
||||
)
|
||||
|
||||
const (
|
||||
diskEventsBuffer = "open_events"
|
||||
)
|
||||
|
||||
// rawOpenEvent is sent by the eBPF program that Teleport pulls off the perf
|
||||
// buffer.
|
||||
type rawOpenEvent struct {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
CgroupID uint64
|
||||
|
||||
// PID is the ID of the process.
|
||||
PID uint64
|
||||
|
||||
// ReturnCode is the return code of open.
|
||||
ReturnCode int32
|
||||
|
||||
// Command is name of the executable opening the file.
|
||||
Command [CommMax]byte
|
||||
|
||||
// Path is the full path to the file being opened.
|
||||
Path [PathMax]byte
|
||||
|
||||
// Flags are the flags passed to open.
|
||||
Flags int32
|
||||
}
|
||||
|
||||
type cgroupRegister interface {
|
||||
startSession(cgroupID uint64) error
|
||||
endSession(cgroupID uint64) error
|
||||
}
|
||||
|
||||
type open struct {
|
||||
session
|
||||
objs diskObjects
|
||||
|
||||
eventBuf *RingBuffer
|
||||
lost *Counter
|
||||
eventBuf chan []byte
|
||||
toClose []io.Closer
|
||||
|
||||
closed bool
|
||||
mtx sync.Mutex
|
||||
|
||||
lostCounter *Counter
|
||||
}
|
||||
|
||||
// startOpen will compile, load, start, and pull events off the perf buffer
|
||||
@@ -84,67 +61,158 @@ type open struct {
|
||||
func startOpen(bufferSize int) (*open, error) {
|
||||
err := metrics.RegisterPrometheusCollectors(lostDiskEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "registering prometheus collectors: %v", err)
|
||||
}
|
||||
|
||||
// Remove resource limits for kernels <5.11.
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
logger.ErrorContext(context.Background(), "Removing memlock failed", "error", err)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
o := &open{}
|
||||
var objs diskObjects
|
||||
if err := loadDiskObjects(&objs, nil); err != nil {
|
||||
return nil, trace.Wrap(err, "loading disk objects: %v", err)
|
||||
}
|
||||
|
||||
diskBPF, err := embedFS.ReadFile("bytecode/disk.bpf.o")
|
||||
lostCtr, err := NewCounter(objs.LostCounter, objs.LostDoorbell, lostDiskEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
o.session.module, err = libbpfgo.NewModuleFromBuffer(diskBPF, "disk")
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
trs := []struct {
|
||||
name string
|
||||
prog *ebpf.Program
|
||||
}{
|
||||
{
|
||||
name: "sys_enter_openat",
|
||||
prog: objs.TracepointSyscallsSysEnterOpenat,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_openat",
|
||||
prog: objs.TracepointSyscallsSysExitOpenat,
|
||||
},
|
||||
{
|
||||
name: "sys_enter_openat2",
|
||||
prog: objs.TracepointSyscallsSysEnterOpenat2,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_openat2",
|
||||
prog: objs.TracepointSyscallsSysExitOpenat2,
|
||||
},
|
||||
}
|
||||
|
||||
// Resizing the ring buffer must be done here, after the module
|
||||
// was created but before it's loaded into the kernel.
|
||||
if err = ResizeMap(o.session.module, diskEventsBuffer, uint32(bufferSize*pageSize)); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Load into the kernel
|
||||
if err = o.session.module.BPFLoadObject(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
syscalls := []string{"openat", "openat2"}
|
||||
|
||||
if runtime.GOARCH != "arm64" {
|
||||
// open is not implemented on arm64.
|
||||
syscalls = append(syscalls, "open")
|
||||
// creat and open are not implemented on arm64.
|
||||
trs = append(trs, []struct {
|
||||
name string
|
||||
prog *ebpf.Program
|
||||
}{
|
||||
{
|
||||
name: "sys_enter_creat",
|
||||
prog: objs.TracepointSyscallsSysEnterCreat,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_creat",
|
||||
prog: objs.TracepointSyscallsSysExitCreat,
|
||||
},
|
||||
{
|
||||
name: "sys_enter_open",
|
||||
prog: objs.TracepointSyscallsSysEnterOpen,
|
||||
},
|
||||
{
|
||||
name: "sys_exit_open",
|
||||
prog: objs.TracepointSyscallsSysExitOpen,
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, syscall := range syscalls {
|
||||
if err = AttachSyscallTracepoint(o.session.module, syscall); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
toClose := make([]io.Closer, 0, len(trs))
|
||||
for _, tr := range trs {
|
||||
tp, err := link.Tracepoint("syscalls", tr.name, tr.prog, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "linking %q tracepoint: %v", tr.name, err)
|
||||
}
|
||||
|
||||
toClose = append(toClose, tp)
|
||||
}
|
||||
|
||||
o.eventBuf, err = NewRingBuffer(o.session.module, diskEventsBuffer)
|
||||
eventBuf, err := ringbuf.NewReader(objs.OpenEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil, trace.Wrap(err, "creating ring buffer reader: %v", err)
|
||||
}
|
||||
|
||||
o.lost, err = NewCounter(o.session.module, "lost", lostDiskEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
bpfEvents := make(chan []byte, bufferSize)
|
||||
go sendEvents(bpfEvents, eventBuf)
|
||||
|
||||
return &open{
|
||||
objs: objs,
|
||||
eventBuf: bpfEvents,
|
||||
toClose: toClose,
|
||||
lostCounter: lostCtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *open) startSession(cgroupID uint64) error {
|
||||
o.mtx.Lock()
|
||||
defer o.mtx.Unlock()
|
||||
|
||||
if o.closed {
|
||||
return trace.BadParameter("open session already closed")
|
||||
}
|
||||
|
||||
return o, nil
|
||||
if err := o.objs.MonitoredCgroups.Put(cgroupID, int64(0)); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *open) endSession(cgroupID uint64) error {
|
||||
o.mtx.Lock()
|
||||
defer o.mtx.Unlock()
|
||||
|
||||
if o.closed {
|
||||
return nil // Ignore. If the session is closed, the cgroup is no longer monitored.
|
||||
}
|
||||
|
||||
if err := o.objs.MonitoredCgroups.Delete(&cgroupID); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// close will stop reading events off the ring buffer and unload the BPF
|
||||
// program. The ring buffer is closed as part of the module being closed.
|
||||
func (o *open) close() {
|
||||
o.lost.Close()
|
||||
o.eventBuf.Close()
|
||||
o.session.module.Close()
|
||||
o.mtx.Lock()
|
||||
defer o.mtx.Unlock()
|
||||
|
||||
if o.closed {
|
||||
return
|
||||
}
|
||||
|
||||
o.closed = true
|
||||
|
||||
for _, toClose := range o.toClose {
|
||||
if err := toClose.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close link", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := o.objs.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close disk objects", "error", err)
|
||||
}
|
||||
|
||||
if err := o.lostCounter.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close disk lost counter", "error", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(context.Background(), "Closed disk BPF module")
|
||||
}
|
||||
|
||||
// events contains raw events off the perf buffer.
|
||||
func (o *open) events() <-chan []byte {
|
||||
return o.eventBuf.EventCh
|
||||
return o.eventBuf
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build arm64 && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type diskDataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Pid uint64
|
||||
ReturnCode int32
|
||||
Command [16]uint8
|
||||
FilePath [255]uint8
|
||||
_ [1]byte
|
||||
Flags int32
|
||||
}
|
||||
|
||||
// loadDisk returns the embedded CollectionSpec for disk.
|
||||
func loadDisk() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_DiskBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load disk: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadDiskObjects loads disk and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *diskObjects
|
||||
// *diskPrograms
|
||||
// *diskMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadDiskObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadDisk()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// diskSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskSpecs struct {
|
||||
diskProgramSpecs
|
||||
diskMapSpecs
|
||||
diskVariableSpecs
|
||||
}
|
||||
|
||||
// diskProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskProgramSpecs struct {
|
||||
TracepointSyscallsSysEnterCreat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_creat"`
|
||||
TracepointSyscallsSysEnterOpen *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_open"`
|
||||
TracepointSyscallsSysEnterOpenat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_openat"`
|
||||
TracepointSyscallsSysEnterOpenat2 *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_openat2"`
|
||||
TracepointSyscallsSysExitCreat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_creat"`
|
||||
TracepointSyscallsSysExitOpen *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_open"`
|
||||
TracepointSyscallsSysExitOpenat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_openat"`
|
||||
TracepointSyscallsSysExitOpenat2 *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_openat2"`
|
||||
}
|
||||
|
||||
// diskMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskMapSpecs struct {
|
||||
Infotmp *ebpf.MapSpec `ebpf:"infotmp"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
OpenEvents *ebpf.MapSpec `ebpf:"open_events"`
|
||||
}
|
||||
|
||||
// diskVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskVariableSpecs struct {
|
||||
Unused *ebpf.VariableSpec `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// diskObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskObjects struct {
|
||||
diskPrograms
|
||||
diskMaps
|
||||
diskVariables
|
||||
}
|
||||
|
||||
func (o *diskObjects) Close() error {
|
||||
return _DiskClose(
|
||||
&o.diskPrograms,
|
||||
&o.diskMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// diskMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskMaps struct {
|
||||
Infotmp *ebpf.Map `ebpf:"infotmp"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
OpenEvents *ebpf.Map `ebpf:"open_events"`
|
||||
}
|
||||
|
||||
func (m *diskMaps) Close() error {
|
||||
return _DiskClose(
|
||||
m.Infotmp,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
m.OpenEvents,
|
||||
)
|
||||
}
|
||||
|
||||
// diskVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskVariables struct {
|
||||
Unused *ebpf.Variable `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// diskPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskPrograms struct {
|
||||
TracepointSyscallsSysEnterCreat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_creat"`
|
||||
TracepointSyscallsSysEnterOpen *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_open"`
|
||||
TracepointSyscallsSysEnterOpenat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_openat"`
|
||||
TracepointSyscallsSysEnterOpenat2 *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_openat2"`
|
||||
TracepointSyscallsSysExitCreat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_creat"`
|
||||
TracepointSyscallsSysExitOpen *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_open"`
|
||||
TracepointSyscallsSysExitOpenat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_openat"`
|
||||
TracepointSyscallsSysExitOpenat2 *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_openat2"`
|
||||
}
|
||||
|
||||
func (p *diskPrograms) Close() error {
|
||||
return _DiskClose(
|
||||
p.TracepointSyscallsSysEnterCreat,
|
||||
p.TracepointSyscallsSysEnterOpen,
|
||||
p.TracepointSyscallsSysEnterOpenat,
|
||||
p.TracepointSyscallsSysEnterOpenat2,
|
||||
p.TracepointSyscallsSysExitCreat,
|
||||
p.TracepointSyscallsSysExitOpen,
|
||||
p.TracepointSyscallsSysExitOpenat,
|
||||
p.TracepointSyscallsSysExitOpenat2,
|
||||
)
|
||||
}
|
||||
|
||||
func _DiskClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed disk_arm64_bpfel.o
|
||||
var _DiskBytes []byte
|
||||
Binary file not shown.
@@ -0,0 +1,180 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build (386 || amd64) && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type diskDataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Pid uint64
|
||||
ReturnCode int32
|
||||
Command [16]uint8
|
||||
FilePath [255]uint8
|
||||
_ [1]byte
|
||||
Flags int32
|
||||
}
|
||||
|
||||
// loadDisk returns the embedded CollectionSpec for disk.
|
||||
func loadDisk() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_DiskBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load disk: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadDiskObjects loads disk and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *diskObjects
|
||||
// *diskPrograms
|
||||
// *diskMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadDiskObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadDisk()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// diskSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskSpecs struct {
|
||||
diskProgramSpecs
|
||||
diskMapSpecs
|
||||
diskVariableSpecs
|
||||
}
|
||||
|
||||
// diskProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskProgramSpecs struct {
|
||||
TracepointSyscallsSysEnterCreat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_creat"`
|
||||
TracepointSyscallsSysEnterOpen *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_open"`
|
||||
TracepointSyscallsSysEnterOpenat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_openat"`
|
||||
TracepointSyscallsSysEnterOpenat2 *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_enter_openat2"`
|
||||
TracepointSyscallsSysExitCreat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_creat"`
|
||||
TracepointSyscallsSysExitOpen *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_open"`
|
||||
TracepointSyscallsSysExitOpenat *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_openat"`
|
||||
TracepointSyscallsSysExitOpenat2 *ebpf.ProgramSpec `ebpf:"tracepoint__syscalls__sys_exit_openat2"`
|
||||
}
|
||||
|
||||
// diskMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskMapSpecs struct {
|
||||
Infotmp *ebpf.MapSpec `ebpf:"infotmp"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
OpenEvents *ebpf.MapSpec `ebpf:"open_events"`
|
||||
}
|
||||
|
||||
// diskVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type diskVariableSpecs struct {
|
||||
Unused *ebpf.VariableSpec `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// diskObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskObjects struct {
|
||||
diskPrograms
|
||||
diskMaps
|
||||
diskVariables
|
||||
}
|
||||
|
||||
func (o *diskObjects) Close() error {
|
||||
return _DiskClose(
|
||||
&o.diskPrograms,
|
||||
&o.diskMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// diskMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskMaps struct {
|
||||
Infotmp *ebpf.Map `ebpf:"infotmp"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
OpenEvents *ebpf.Map `ebpf:"open_events"`
|
||||
}
|
||||
|
||||
func (m *diskMaps) Close() error {
|
||||
return _DiskClose(
|
||||
m.Infotmp,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
m.OpenEvents,
|
||||
)
|
||||
}
|
||||
|
||||
// diskVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskVariables struct {
|
||||
Unused *ebpf.Variable `ebpf:"unused"`
|
||||
}
|
||||
|
||||
// diskPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadDiskObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type diskPrograms struct {
|
||||
TracepointSyscallsSysEnterCreat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_creat"`
|
||||
TracepointSyscallsSysEnterOpen *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_open"`
|
||||
TracepointSyscallsSysEnterOpenat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_openat"`
|
||||
TracepointSyscallsSysEnterOpenat2 *ebpf.Program `ebpf:"tracepoint__syscalls__sys_enter_openat2"`
|
||||
TracepointSyscallsSysExitCreat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_creat"`
|
||||
TracepointSyscallsSysExitOpen *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_open"`
|
||||
TracepointSyscallsSysExitOpenat *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_openat"`
|
||||
TracepointSyscallsSysExitOpenat2 *ebpf.Program `ebpf:"tracepoint__syscalls__sys_exit_openat2"`
|
||||
}
|
||||
|
||||
func (p *diskPrograms) Close() error {
|
||||
return _DiskClose(
|
||||
p.TracepointSyscallsSysEnterCreat,
|
||||
p.TracepointSyscallsSysEnterOpen,
|
||||
p.TracepointSyscallsSysEnterOpenat,
|
||||
p.TracepointSyscallsSysEnterOpenat2,
|
||||
p.TracepointSyscallsSysExitCreat,
|
||||
p.TracepointSyscallsSysExitOpen,
|
||||
p.TracepointSyscallsSysExitOpenat,
|
||||
p.TracepointSyscallsSysExitOpenat2,
|
||||
)
|
||||
}
|
||||
|
||||
func _DiskClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed disk_x86_bpfel.o
|
||||
var _DiskBytes []byte
|
||||
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2025 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bpf
|
||||
|
||||
// Multi-arch setup, as mentioned in https://github.com/cilium/ebpf/issues/305
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 -cflags "-D__TARGET_ARCH_x86" -tags bpf -type data_t command ../../bpf/enhancedrecording/command.bpf.c
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 -cflags "-D__TARGET_ARCH_x86" -tags bpf -type data_t -no-global-types disk ../../bpf/enhancedrecording/disk.bpf.c
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 -cflags "-D__TARGET_ARCH_x86" -tags bpf -type ipv4_data_t -type ipv6_data_t network ../../bpf/enhancedrecording/network.bpf.c
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target arm64 -cflags "-D__TARGET_ARCH_arm64" -tags bpf -type data_t command ../../bpf/enhancedrecording/command.bpf.c
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target arm64 -cflags "-D__TARGET_ARCH_arm64" -tags bpf -no-global-types -type data_t disk ../../bpf/enhancedrecording/disk.bpf.c
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target arm64 -cflags "-D__TARGET_ARCH_arm64" -tags bpf -type ipv4_data_t -type ipv6_data_t network ../../bpf/enhancedrecording/network.bpf.c
|
||||
+73
-207
@@ -22,12 +22,11 @@ package bpf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"errors"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
@@ -37,206 +36,6 @@ import (
|
||||
|
||||
var logger = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentBPF)
|
||||
|
||||
const (
|
||||
kprobeProgPrefix = "kprobe__"
|
||||
kretprobeProgPrefix = "kretprobe__"
|
||||
tracepointProgPrefix = "tracepoint__"
|
||||
syscallsCategory = "syscalls"
|
||||
syscallEnterPrefix = "sys_enter_"
|
||||
syscallExitPrefix = "sys_exit_"
|
||||
counterSuffix = "_counter"
|
||||
doorbellSuffix = "_doorbell"
|
||||
)
|
||||
|
||||
var pageSize = os.Getpagesize()
|
||||
|
||||
// ResizeMap resizes (changes max number of entries) the
|
||||
// map to the specified value. This function must be called
|
||||
// before BPFLoadObject has been called.
|
||||
func ResizeMap(mod *libbpfgo.Module, mapName string, value uint32) error {
|
||||
m, err := mod.GetMap(mapName)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err = m.Resize(value); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachKprobe attaches both a kprobe and kretprobe for the
|
||||
// function identified by "name". The BPF C functions must be
|
||||
// called "kprobe__NAME" and "kretprobe__NAME" where NAME
|
||||
// the name of the kernel function to be hooked.
|
||||
func AttachKprobe(mod *libbpfgo.Module, name string) error {
|
||||
prog, err := mod.GetProgram(kprobeProgPrefix + name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = prog.AttachKprobe(name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
prog, err = mod.GetProgram(kretprobeProgPrefix + name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = prog.AttachKretprobe(name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachTracepoint attaches a tracepoint identified by category:name.
|
||||
// The BPF C function must be called "tracepoint__CATEGORY__NAME" where
|
||||
// CATEGORY and NAME are the tracepoint's category and name.
|
||||
func AttachTracepoint(mod *libbpfgo.Module, category, name string) error {
|
||||
prog, err := mod.GetProgram(tracepointProgPrefix + category + "__" + name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = prog.AttachTracepoint(category, name)
|
||||
return err
|
||||
}
|
||||
|
||||
// AttachSyscallTracepoint hooks a syscall using the tracepoint mechanism.
|
||||
// Like Kprobe, it hooks the entry and exit points. Unlike the Kprobe, it
|
||||
// is stable and not sensitive to the kernel renaming its functions.
|
||||
// The BPF C functions must be called "tracepoint__sys_enter__SYSCALL" and
|
||||
// "tracepoint__sys_exit__SYSCALL" where SYSCALL is the name of the syscalled
|
||||
// to be hooked.
|
||||
//
|
||||
// For more details, see https://www.kernel.org/doc/html/v5.8/trace/events.html.
|
||||
func AttachSyscallTracepoint(mod *libbpfgo.Module, syscall string) error {
|
||||
if err := AttachTracepoint(mod, syscallsCategory, syscallEnterPrefix+syscall); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := AttachTracepoint(mod, syscallsCategory, syscallExitPrefix+syscall); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RingBuffer wraps a BPF ring buffer with a channel that will
|
||||
// receive the data from the ring buffer.
|
||||
type RingBuffer struct {
|
||||
buf *libbpfgo.RingBuffer
|
||||
EventCh chan []byte
|
||||
}
|
||||
|
||||
// NewRingBuffer creates a RingBuffer object identified by "name". The messages
|
||||
// from the ring buffer will be available on the EventCh channel.
|
||||
func NewRingBuffer(mod *libbpfgo.Module, name string) (*RingBuffer, error) {
|
||||
rb := &RingBuffer{
|
||||
EventCh: make(chan []byte, chanSize),
|
||||
}
|
||||
|
||||
var err error
|
||||
rb.buf, err = mod.InitRingBuf(name, rb.EventCh)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
rb.buf.Start()
|
||||
|
||||
return rb, nil
|
||||
}
|
||||
|
||||
// Close will stop receiving messages from the kernel and putting them
|
||||
// into EventCh channel.
|
||||
func (rb *RingBuffer) Close() {
|
||||
rb.buf.Stop()
|
||||
// don't Close ring buffers as they'll be closed as part of Module.Close()
|
||||
}
|
||||
|
||||
// Counter allows a BPF program to increment a Prometheus counter.
|
||||
// The counter value is stored in a one element BPF array (map).
|
||||
// When it's incremented, the BPF program also rings the doorbell
|
||||
// via a ring buffer.
|
||||
type Counter struct {
|
||||
// doorbellBuf contains dummy bytes and is used for signaling the userspace
|
||||
doorbellBuf *libbpfgo.RingBuffer
|
||||
// doorbellCh is the chan corresponding to doorbellBuf
|
||||
doorbellCh chan []byte
|
||||
|
||||
// arr is a one element array containing the value
|
||||
arr *libbpfgo.BPFMap
|
||||
// lastCnt keeps the last read counter value
|
||||
lastCnt uint64
|
||||
|
||||
// wg is used to wait for the loop goroutine to finish
|
||||
wg sync.WaitGroup
|
||||
|
||||
// counter is the associated Prometheus counter to increment
|
||||
counter prometheus.Counter
|
||||
}
|
||||
|
||||
// NewCounter starts tracking the lost messages and updating the Prometheus counter.
|
||||
func NewCounter(mod *libbpfgo.Module, name string, counter prometheus.Counter) (*Counter, error) {
|
||||
c := &Counter{
|
||||
doorbellCh: make(chan []byte, chanSize),
|
||||
counter: counter,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
c.arr, err = mod.GetMap(name + counterSuffix)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.doorbellBuf, err = mod.InitRingBuf(name+doorbellSuffix, c.doorbellCh)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.doorbellBuf.Start()
|
||||
|
||||
c.wg.Add(1)
|
||||
go c.loop()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close will stop tracking and release the resources.
|
||||
func (c *Counter) Close() {
|
||||
c.doorbellBuf.Stop()
|
||||
// don't Close ring buffers as they'll be closed as part of Module.Close()
|
||||
|
||||
// wait for lostLoop to finish
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
func (c *Counter) loop() {
|
||||
for range c.doorbellCh {
|
||||
var key int32 = 0
|
||||
cntBytes, err := c.arr.GetValue(unsafe.Pointer(&key))
|
||||
if err != nil {
|
||||
logger.ErrorContext(context.Background(), "Error reading array value at index 0")
|
||||
continue
|
||||
}
|
||||
|
||||
cnt := binary.LittleEndian.Uint64(cntBytes)
|
||||
if delta := cnt - c.lastCnt; delta > 0 {
|
||||
c.counter.Add(float64(delta))
|
||||
}
|
||||
|
||||
c.lastCnt = cnt
|
||||
}
|
||||
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
const (
|
||||
// CommMax is the maximum length of a command from linux/sched.h.
|
||||
CommMax = 16
|
||||
@@ -252,7 +51,74 @@ const (
|
||||
|
||||
// eventRet holds the return value and other data about an event.
|
||||
eventRet = 1
|
||||
|
||||
// chanSize is the size of the event channels.
|
||||
chanSize = 1024
|
||||
)
|
||||
|
||||
// Counter allows a BPF program to increment a Prometheus counter.
|
||||
// The counter value is stored in a one element BPF array (map).
|
||||
// When it's incremented, the BPF program also rings the doorbell
|
||||
// via a ring buffer.
|
||||
type Counter struct {
|
||||
// doorbellBuf contains dummy bytes and is used for signaling the userspace
|
||||
doorbellBuf *ringbuf.Reader
|
||||
|
||||
// arr is a one element array containing the value
|
||||
arr *ebpf.Map
|
||||
// lastCnt keeps the last read counter value
|
||||
lastCnt uint64
|
||||
|
||||
// wg is used to wait for the loop goroutine to finish
|
||||
wg sync.WaitGroup
|
||||
|
||||
// counter is the associated Prometheus counter to increment
|
||||
counter prometheus.Counter
|
||||
}
|
||||
|
||||
// NewCounter starts tracking the lost messages and updating the Prometheus counter.
|
||||
func NewCounter(counter, doorbell *ebpf.Map, promCounter prometheus.Counter) (*Counter, error) {
|
||||
doorbellBuf, err := ringbuf.NewReader(doorbell)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c := &Counter{
|
||||
doorbellBuf: doorbellBuf,
|
||||
arr: counter,
|
||||
counter: promCounter,
|
||||
}
|
||||
|
||||
c.wg.Go(c.loop)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close will stop tracking and release the resources.
|
||||
func (c *Counter) Close() error {
|
||||
err := c.doorbellBuf.Close()
|
||||
// wait for lostLoop to finish
|
||||
c.wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Counter) loop() {
|
||||
for {
|
||||
_, err := c.doorbellBuf.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, ringbuf.ErrClosed) {
|
||||
return
|
||||
}
|
||||
logger.ErrorContext(context.Background(), "Error reading from ring buffer", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
var key int32 = 0
|
||||
var count uint64
|
||||
if err := c.arr.Lookup(&key, &count); err != nil {
|
||||
logger.ErrorContext(context.Background(), "Error reading array value at index 0", "error", err)
|
||||
continue
|
||||
}
|
||||
if delta := count - c.lastCnt; delta > 0 {
|
||||
c.counter.Add(float64(delta))
|
||||
}
|
||||
c.lastCnt = count
|
||||
}
|
||||
}
|
||||
|
||||
+144
-108
@@ -21,9 +21,14 @@
|
||||
package bpf
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/aquasecurity/libbpfgo"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
@@ -31,77 +36,24 @@ import (
|
||||
"github.com/gravitational/teleport/lib/observability/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
lostNetworkEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostNetworkEvents,
|
||||
Help: "Number of lost network events.",
|
||||
},
|
||||
)
|
||||
var lostNetworkEvents = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: teleport.MetricLostNetworkEvents,
|
||||
Help: "Number of lost network events.",
|
||||
},
|
||||
)
|
||||
|
||||
const (
|
||||
network4EventsBuffer = "ipv4_events"
|
||||
network6EventsBuffer = "ipv6_events"
|
||||
)
|
||||
|
||||
// rawConn4Event is sent by the eBPF program that Teleport pulls off the perf
|
||||
// buffer.
|
||||
type rawConn4Event struct {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
CgroupID uint64
|
||||
|
||||
// Version is the version of TCP (4 or 6).
|
||||
Version uint64
|
||||
|
||||
// PID is the process ID.
|
||||
PID uint32
|
||||
|
||||
// SrcAddr is the source IP address.
|
||||
SrcAddr uint32
|
||||
|
||||
// DstAddr is the destination IP address.
|
||||
DstAddr uint32
|
||||
|
||||
// DstPort is the port the connection is being made to.
|
||||
DstPort uint16
|
||||
|
||||
// Command is name of the executable making the connection.
|
||||
Command [CommMax]byte
|
||||
}
|
||||
|
||||
// rawConn6Event is sent by the eBPF program that Teleport pulls off the perf
|
||||
// buffer.
|
||||
type rawConn6Event struct {
|
||||
// CgroupID is the internal cgroupv2 ID of the event.
|
||||
CgroupID uint64
|
||||
|
||||
// Version is the version of TCP (4 or 6).
|
||||
Version uint64
|
||||
|
||||
// PID is the process ID.
|
||||
PID uint32
|
||||
|
||||
// SrcAddr is the source IP address.
|
||||
SrcAddr [4]uint32
|
||||
|
||||
// DstAddr is the destination IP address.
|
||||
DstAddr [4]uint32
|
||||
|
||||
// DstPort is the port the connection is being made to.
|
||||
DstPort uint16
|
||||
|
||||
// Command is name of the executable making the connection.
|
||||
Command [CommMax]byte
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
session
|
||||
objs *networkObjects
|
||||
|
||||
event4Buf *RingBuffer
|
||||
event6Buf *RingBuffer
|
||||
event4Chan chan []byte
|
||||
event6Chan chan []byte
|
||||
toClose []io.Closer
|
||||
|
||||
lost *Counter
|
||||
closed bool
|
||||
mtx sync.Mutex
|
||||
|
||||
lostCounter *Counter
|
||||
}
|
||||
|
||||
func startConn(bufferSize int) (*conn, error) {
|
||||
@@ -110,74 +62,158 @@ func startConn(bufferSize int) (*conn, error) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c := &conn{}
|
||||
// Remove resource limits for kernels <5.11.
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
return nil, trace.WrapWithMessage(err, "Removing memlock")
|
||||
}
|
||||
|
||||
networkBPF, err := embedFS.ReadFile("bytecode/network.bpf.o")
|
||||
var objs networkObjects
|
||||
if err := loadNetworkObjects(&objs, nil); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
lostCtr, err := NewCounter(objs.LostCounter, objs.LostDoorbell, lostNetworkEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.session.module, err = libbpfgo.NewModuleFromBuffer(networkBPF, "network")
|
||||
kprobes := []struct {
|
||||
symbol string
|
||||
prog *ebpf.Program
|
||||
}{
|
||||
{
|
||||
symbol: "tcp_v4_connect",
|
||||
prog: objs.KprobeTcpV4Connect,
|
||||
},
|
||||
{
|
||||
symbol: "tcp_v6_connect",
|
||||
prog: objs.KprobeTcpV6Connect,
|
||||
},
|
||||
}
|
||||
|
||||
kretProbes := []struct {
|
||||
symbol string
|
||||
prog *ebpf.Program
|
||||
}{
|
||||
{
|
||||
symbol: "tcp_v4_connect",
|
||||
prog: objs.KretprobeTcpV4Connect,
|
||||
},
|
||||
{
|
||||
symbol: "tcp_v6_connect",
|
||||
prog: objs.KretprobeTcpV6Connect,
|
||||
},
|
||||
}
|
||||
|
||||
toClose := make([]io.Closer, 0)
|
||||
for _, kprobe := range kprobes {
|
||||
kp, err := link.Kprobe(kprobe.symbol, kprobe.prog, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
toClose = append(toClose, kp)
|
||||
}
|
||||
|
||||
for _, kretprobe := range kretProbes {
|
||||
kret, err := link.Kretprobe(kretprobe.symbol, kretprobe.prog, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
toClose = append(toClose, kret)
|
||||
}
|
||||
|
||||
eventBufV4, err := ringbuf.NewReader(objs.Ipv4Events)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
eventBufV6, err := ringbuf.NewReader(objs.Ipv6Events)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Resizing the ring buffer must be done here, after the module
|
||||
// was created but before it's loaded into the kernel.
|
||||
if err = ResizeMap(c.session.module, network4EventsBuffer, uint32(bufferSize*pageSize)); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
bpfv4Events := make(chan []byte, bufferSize)
|
||||
go sendEvents(bpfv4Events, eventBufV4)
|
||||
|
||||
bpfv6Events := make(chan []byte, bufferSize)
|
||||
go sendEvents(bpfv6Events, eventBufV6)
|
||||
|
||||
return &conn{
|
||||
objs: &objs,
|
||||
event4Chan: bpfv4Events,
|
||||
event6Chan: bpfv6Events,
|
||||
toClose: toClose,
|
||||
lostCounter: lostCtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *conn) startSession(cgroupID uint64) error {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return trace.BadParameter("connection is closed")
|
||||
}
|
||||
|
||||
if err = ResizeMap(c.session.module, network6EventsBuffer, uint32(bufferSize*pageSize)); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if err := c.objs.MonitoredCgroups.Put(cgroupID, int64(0)); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Load into the kernel
|
||||
if err = c.session.module.BPFLoadObject(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) endSession(cgroupID uint64) error {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil // Ignore. If the session is closed, the cgroup is no longer monitored.
|
||||
}
|
||||
|
||||
if err = AttachKprobe(c.session.module, "tcp_v4_connect"); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if err := c.objs.MonitoredCgroups.Delete(&cgroupID); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err = AttachKprobe(c.session.module, "tcp_v6_connect"); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.event4Buf, err = NewRingBuffer(c.session.module, network4EventsBuffer)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.event6Buf, err = NewRingBuffer(c.session.module, network6EventsBuffer)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
c.lost, err = NewCounter(c.session.module, "lost", lostNetworkEvents)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// close will stop reading events off the ring buffer and unload the BPF
|
||||
// program. The ring buffer is closed as part of the module being closed.
|
||||
func (c *conn) close() {
|
||||
c.lost.Close()
|
||||
c.event4Buf.Close()
|
||||
c.event6Buf.Close()
|
||||
c.session.module.Close()
|
||||
// c.lost.Close()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
c.closed = true
|
||||
|
||||
for _, link := range c.toClose {
|
||||
if err := link.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close link", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.objs.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close network objects", "error", err)
|
||||
}
|
||||
|
||||
if err := c.lostCounter.Close(); err != nil {
|
||||
logger.WarnContext(context.Background(), "failed to close network lost counter", "error", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(context.Background(), "Closed network BPF module")
|
||||
}
|
||||
|
||||
// v4Events contains raw events off the perf buffer.
|
||||
func (c *conn) v4Events() <-chan []byte {
|
||||
return c.event4Buf.EventCh
|
||||
return c.event4Chan
|
||||
}
|
||||
|
||||
// v6Events contains raw events off the perf buffer.
|
||||
func (c *conn) v6Events() <-chan []byte {
|
||||
return c.event6Buf.EventCh
|
||||
return c.event6Chan
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build arm64 && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type networkIpv4DataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Ip uint64
|
||||
Pid uint32
|
||||
Saddr uint32
|
||||
Daddr uint32
|
||||
Dport uint16
|
||||
Command [16]uint8
|
||||
_ [2]byte
|
||||
}
|
||||
|
||||
type networkIpv6DataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Ip uint64
|
||||
Pid uint32
|
||||
Saddr struct {
|
||||
_ structs.HostLayout
|
||||
In6U struct {
|
||||
_ structs.HostLayout
|
||||
U6Addr8 [16]uint8
|
||||
}
|
||||
}
|
||||
Daddr struct {
|
||||
_ structs.HostLayout
|
||||
In6U struct {
|
||||
_ structs.HostLayout
|
||||
U6Addr8 [16]uint8
|
||||
}
|
||||
}
|
||||
Dport uint16
|
||||
Command [16]uint8
|
||||
_ [2]byte
|
||||
}
|
||||
|
||||
// loadNetwork returns the embedded CollectionSpec for network.
|
||||
func loadNetwork() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_NetworkBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load network: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadNetworkObjects loads network and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *networkObjects
|
||||
// *networkPrograms
|
||||
// *networkMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadNetworkObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// networkSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkSpecs struct {
|
||||
networkProgramSpecs
|
||||
networkMapSpecs
|
||||
networkVariableSpecs
|
||||
}
|
||||
|
||||
// networkProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkProgramSpecs struct {
|
||||
KprobeTcpV4Connect *ebpf.ProgramSpec `ebpf:"kprobe__tcp_v4_connect"`
|
||||
KprobeTcpV6Connect *ebpf.ProgramSpec `ebpf:"kprobe__tcp_v6_connect"`
|
||||
KretprobeTcpV4Connect *ebpf.ProgramSpec `ebpf:"kretprobe__tcp_v4_connect"`
|
||||
KretprobeTcpV6Connect *ebpf.ProgramSpec `ebpf:"kretprobe__tcp_v6_connect"`
|
||||
}
|
||||
|
||||
// networkMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkMapSpecs struct {
|
||||
Currsock *ebpf.MapSpec `ebpf:"currsock"`
|
||||
Ipv4Events *ebpf.MapSpec `ebpf:"ipv4_events"`
|
||||
Ipv6Events *ebpf.MapSpec `ebpf:"ipv6_events"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
// networkVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkVariableSpecs struct {
|
||||
UnusedIpv4DataT *ebpf.VariableSpec `ebpf:"unused_ipv4_data_t"`
|
||||
UnusedIpv6DataT *ebpf.VariableSpec `ebpf:"unused_ipv6_data_t"`
|
||||
}
|
||||
|
||||
// networkObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkObjects struct {
|
||||
networkPrograms
|
||||
networkMaps
|
||||
networkVariables
|
||||
}
|
||||
|
||||
func (o *networkObjects) Close() error {
|
||||
return _NetworkClose(
|
||||
&o.networkPrograms,
|
||||
&o.networkMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// networkMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkMaps struct {
|
||||
Currsock *ebpf.Map `ebpf:"currsock"`
|
||||
Ipv4Events *ebpf.Map `ebpf:"ipv4_events"`
|
||||
Ipv6Events *ebpf.Map `ebpf:"ipv6_events"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
func (m *networkMaps) Close() error {
|
||||
return _NetworkClose(
|
||||
m.Currsock,
|
||||
m.Ipv4Events,
|
||||
m.Ipv6Events,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
)
|
||||
}
|
||||
|
||||
// networkVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkVariables struct {
|
||||
UnusedIpv4DataT *ebpf.Variable `ebpf:"unused_ipv4_data_t"`
|
||||
UnusedIpv6DataT *ebpf.Variable `ebpf:"unused_ipv6_data_t"`
|
||||
}
|
||||
|
||||
// networkPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkPrograms struct {
|
||||
KprobeTcpV4Connect *ebpf.Program `ebpf:"kprobe__tcp_v4_connect"`
|
||||
KprobeTcpV6Connect *ebpf.Program `ebpf:"kprobe__tcp_v6_connect"`
|
||||
KretprobeTcpV4Connect *ebpf.Program `ebpf:"kretprobe__tcp_v4_connect"`
|
||||
KretprobeTcpV6Connect *ebpf.Program `ebpf:"kretprobe__tcp_v6_connect"`
|
||||
}
|
||||
|
||||
func (p *networkPrograms) Close() error {
|
||||
return _NetworkClose(
|
||||
p.KprobeTcpV4Connect,
|
||||
p.KprobeTcpV6Connect,
|
||||
p.KretprobeTcpV4Connect,
|
||||
p.KretprobeTcpV6Connect,
|
||||
)
|
||||
}
|
||||
|
||||
func _NetworkClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed network_arm64_bpfel.o
|
||||
var _NetworkBytes []byte
|
||||
Binary file not shown.
@@ -0,0 +1,198 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build (386 || amd64) && bpf
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"structs"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type networkIpv4DataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Ip uint64
|
||||
Pid uint32
|
||||
Saddr uint32
|
||||
Daddr uint32
|
||||
Dport uint16
|
||||
Command [16]uint8
|
||||
_ [2]byte
|
||||
}
|
||||
|
||||
type networkIpv6DataT struct {
|
||||
_ structs.HostLayout
|
||||
Cgroup uint64
|
||||
Ip uint64
|
||||
Pid uint32
|
||||
Saddr struct {
|
||||
_ structs.HostLayout
|
||||
In6U struct {
|
||||
_ structs.HostLayout
|
||||
U6Addr8 [16]uint8
|
||||
}
|
||||
}
|
||||
Daddr struct {
|
||||
_ structs.HostLayout
|
||||
In6U struct {
|
||||
_ structs.HostLayout
|
||||
U6Addr8 [16]uint8
|
||||
}
|
||||
}
|
||||
Dport uint16
|
||||
Command [16]uint8
|
||||
_ [2]byte
|
||||
}
|
||||
|
||||
// loadNetwork returns the embedded CollectionSpec for network.
|
||||
func loadNetwork() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_NetworkBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load network: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadNetworkObjects loads network and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *networkObjects
|
||||
// *networkPrograms
|
||||
// *networkMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadNetworkObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// networkSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkSpecs struct {
|
||||
networkProgramSpecs
|
||||
networkMapSpecs
|
||||
networkVariableSpecs
|
||||
}
|
||||
|
||||
// networkProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkProgramSpecs struct {
|
||||
KprobeTcpV4Connect *ebpf.ProgramSpec `ebpf:"kprobe__tcp_v4_connect"`
|
||||
KprobeTcpV6Connect *ebpf.ProgramSpec `ebpf:"kprobe__tcp_v6_connect"`
|
||||
KretprobeTcpV4Connect *ebpf.ProgramSpec `ebpf:"kretprobe__tcp_v4_connect"`
|
||||
KretprobeTcpV6Connect *ebpf.ProgramSpec `ebpf:"kretprobe__tcp_v6_connect"`
|
||||
}
|
||||
|
||||
// networkMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkMapSpecs struct {
|
||||
Currsock *ebpf.MapSpec `ebpf:"currsock"`
|
||||
Ipv4Events *ebpf.MapSpec `ebpf:"ipv4_events"`
|
||||
Ipv6Events *ebpf.MapSpec `ebpf:"ipv6_events"`
|
||||
LostCounter *ebpf.MapSpec `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.MapSpec `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.MapSpec `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
// networkVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type networkVariableSpecs struct {
|
||||
UnusedIpv4DataT *ebpf.VariableSpec `ebpf:"unused_ipv4_data_t"`
|
||||
UnusedIpv6DataT *ebpf.VariableSpec `ebpf:"unused_ipv6_data_t"`
|
||||
}
|
||||
|
||||
// networkObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkObjects struct {
|
||||
networkPrograms
|
||||
networkMaps
|
||||
networkVariables
|
||||
}
|
||||
|
||||
func (o *networkObjects) Close() error {
|
||||
return _NetworkClose(
|
||||
&o.networkPrograms,
|
||||
&o.networkMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// networkMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkMaps struct {
|
||||
Currsock *ebpf.Map `ebpf:"currsock"`
|
||||
Ipv4Events *ebpf.Map `ebpf:"ipv4_events"`
|
||||
Ipv6Events *ebpf.Map `ebpf:"ipv6_events"`
|
||||
LostCounter *ebpf.Map `ebpf:"lost_counter"`
|
||||
LostDoorbell *ebpf.Map `ebpf:"lost_doorbell"`
|
||||
MonitoredCgroups *ebpf.Map `ebpf:"monitored_cgroups"`
|
||||
}
|
||||
|
||||
func (m *networkMaps) Close() error {
|
||||
return _NetworkClose(
|
||||
m.Currsock,
|
||||
m.Ipv4Events,
|
||||
m.Ipv6Events,
|
||||
m.LostCounter,
|
||||
m.LostDoorbell,
|
||||
m.MonitoredCgroups,
|
||||
)
|
||||
}
|
||||
|
||||
// networkVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkVariables struct {
|
||||
UnusedIpv4DataT *ebpf.Variable `ebpf:"unused_ipv4_data_t"`
|
||||
UnusedIpv6DataT *ebpf.Variable `ebpf:"unused_ipv6_data_t"`
|
||||
}
|
||||
|
||||
// networkPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadNetworkObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type networkPrograms struct {
|
||||
KprobeTcpV4Connect *ebpf.Program `ebpf:"kprobe__tcp_v4_connect"`
|
||||
KprobeTcpV6Connect *ebpf.Program `ebpf:"kprobe__tcp_v6_connect"`
|
||||
KretprobeTcpV4Connect *ebpf.Program `ebpf:"kretprobe__tcp_v4_connect"`
|
||||
KretprobeTcpV6Connect *ebpf.Program `ebpf:"kretprobe__tcp_v6_connect"`
|
||||
}
|
||||
|
||||
func (p *networkPrograms) Close() error {
|
||||
return _NetworkClose(
|
||||
p.KprobeTcpV4Connect,
|
||||
p.KprobeTcpV6Connect,
|
||||
p.KretprobeTcpV4Connect,
|
||||
p.KretprobeTcpV6Connect,
|
||||
)
|
||||
}
|
||||
|
||||
func _NetworkClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed network_x86_bpfel.o
|
||||
var _NetworkBytes []byte
|
||||
Binary file not shown.
+9
-10
@@ -31,6 +31,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -142,12 +143,10 @@ func (s *Service) Remove(sessionID string) error {
|
||||
|
||||
// Move all PIDs to the root controller. This has to be done before a cgroup
|
||||
// can be removed.
|
||||
err = writePids(filepath.Join(s.MountPath, cgroupProcs), pids)
|
||||
if err != nil {
|
||||
if err = writePids(filepath.Join(s.MountPath, cgroupProcs), pids); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// The rmdir syscall is used to remove a cgroup.
|
||||
err = unix.Rmdir(filepath.Join(s.teleportRoot, sessionID))
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
@@ -209,7 +208,9 @@ func writePids(path string, pids []string) error {
|
||||
|
||||
for _, pid := range pids {
|
||||
_, err := f.WriteString(pid + "\n")
|
||||
if err != nil {
|
||||
// ignore no such process that can be returned if the process has already
|
||||
// exited.
|
||||
if err != nil && !errors.Is(err, unix.ESRCH) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
@@ -217,7 +218,7 @@ func writePids(path string, pids []string) error {
|
||||
return trace.Wrap(f.Sync())
|
||||
}
|
||||
|
||||
// cleanupHierarchy removes any cgroups for any exisiting sessions.
|
||||
// cleanupHierarchy removes any cgroups for any existing sessions.
|
||||
func (s *Service) cleanupHierarchy() error {
|
||||
var sessions []string
|
||||
|
||||
@@ -377,15 +378,13 @@ func (s *Service) ID(sessionID string) (uint64, error) {
|
||||
return fh.CgroupID, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// pattern matches cgroup process files.
|
||||
pattern = regexp.MustCompile(`cgroup\.procs$`)
|
||||
)
|
||||
// pattern matches cgroup process files.
|
||||
var pattern = regexp.MustCompile(`cgroup\.procs$`)
|
||||
|
||||
const (
|
||||
// fileMode is the mode files and directories are created in within the
|
||||
// cgroup filesystem.
|
||||
fileMode = 0555
|
||||
fileMode = 0o555
|
||||
|
||||
// teleportRoot is the prefix of the root cgroup that holds all other
|
||||
// Teleport cgroups.
|
||||
|
||||
@@ -542,13 +542,11 @@ func ReadableDatabaseProtocol(p string) string {
|
||||
}
|
||||
|
||||
const (
|
||||
// PerfBufferPageCount is the size of the perf ring buffer in number of pages.
|
||||
// Must be power of 2.
|
||||
PerfBufferPageCount = 8
|
||||
// PerfBufferPageCount is the size of the perf ring buffer.
|
||||
PerfBufferPageCount = 64
|
||||
|
||||
// OpenPerfBufferPageCount is the page count for the perf buffer. Open
|
||||
// events generate many events so this buffer needs to be extra large.
|
||||
// Must be power of 2.
|
||||
OpenPerfBufferPageCount = 128
|
||||
|
||||
// CgroupPath is where the cgroupv2 hierarchy will be mounted.
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
|
||||
package servicecfg
|
||||
|
||||
import "github.com/gravitational/teleport/lib/defaults"
|
||||
import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
)
|
||||
|
||||
// BPFConfig holds configuration for the BPF service.
|
||||
type BPFConfig struct {
|
||||
@@ -43,17 +47,27 @@ type BPFConfig struct {
|
||||
|
||||
// CheckAndSetDefaults checks BPF configuration.
|
||||
func (c *BPFConfig) CheckAndSetDefaults() error {
|
||||
var perfBufferPageCount = defaults.PerfBufferPageCount
|
||||
var openPerfBufferPageCount = defaults.OpenPerfBufferPageCount
|
||||
perfBufferPageCount := defaults.PerfBufferPageCount
|
||||
openPerfBufferPageCount := defaults.OpenPerfBufferPageCount
|
||||
|
||||
if c.CommandBufferSize == nil {
|
||||
// Set defaults for buffer sizes if they are unset or zero.
|
||||
// A zero value was accepted before but is undesirable now as it
|
||||
// will result in blocking event channels, so we set it to a sane
|
||||
// default to maintain backwards compatibility.
|
||||
if c.CommandBufferSize == nil || *c.CommandBufferSize == 0 {
|
||||
c.CommandBufferSize = &perfBufferPageCount
|
||||
} else if *c.CommandBufferSize < 0 {
|
||||
return trace.BadParameter("CommandBufferSize must not be negative")
|
||||
}
|
||||
if c.DiskBufferSize == nil {
|
||||
if c.DiskBufferSize == nil || *c.DiskBufferSize == 0 {
|
||||
c.DiskBufferSize = &openPerfBufferPageCount
|
||||
} else if *c.DiskBufferSize < 0 {
|
||||
return trace.BadParameter("DiskBufferSize must not be negative")
|
||||
}
|
||||
if c.NetworkBufferSize == nil {
|
||||
if c.NetworkBufferSize == nil || *c.NetworkBufferSize == 0 {
|
||||
c.NetworkBufferSize = &perfBufferPageCount
|
||||
} else if *c.NetworkBufferSize < 0 {
|
||||
return trace.BadParameter("NetworkBufferSize must not be negative")
|
||||
}
|
||||
if c.CgroupPath == "" {
|
||||
c.CgroupPath = defaults.CgroupPath
|
||||
|
||||
Reference in New Issue
Block a user