From 6b0109d77c032d1ddf2c6a02ed4d70115b780a93 Mon Sep 17 00:00:00 2001 From: QxBytes Date: Tue, 8 Jul 2025 15:23:41 -0700 Subject: [PATCH 1/7] add iptables monitor binary and makefile changes --- Makefile | 108 ++++++-- azure-iptables-monitor/Dockerfile | 29 +++ azure-iptables-monitor/README.md | 63 +++++ azure-iptables-monitor/go.mod | 62 +++++ azure-iptables-monitor/go.sum | 182 ++++++++++++++ azure-iptables-monitor/iptables_monitor.go | 236 ++++++++++++++++++ .../iptables_monitor_test.go | 224 +++++++++++++++++ 7 files changed, 884 insertions(+), 20 deletions(-) create mode 100644 azure-iptables-monitor/Dockerfile create mode 100644 azure-iptables-monitor/README.md create mode 100644 azure-iptables-monitor/go.mod create mode 100644 azure-iptables-monitor/go.sum create mode 100644 azure-iptables-monitor/iptables_monitor.go create mode 100644 azure-iptables-monitor/iptables_monitor_test.go diff --git a/Makefile b/Makefile index 9c34200ccb..2da9e50b71 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,11 @@ endif # Interrogate the git repo and set some variables REPO_ROOT ?= $(shell git rev-parse --show-toplevel) REVISION ?= $(shell git rev-parse --short HEAD) -ACN_VERSION ?= $(shell git describe --exclude "azure-ip-masq-merger*" --exclude "azure-ipam*" --exclude "dropgz*" --exclude "zapai*" --exclude "ipv6-hp-bpf*" --tags --always) +ACN_VERSION ?= $(shell git describe --exclude "azure-iptables-monitor*" --exclude "azure-ip-masq-merger*" --exclude "azure-ipam*" --exclude "dropgz*" --exclude "zapai*" --exclude "ipv6-hp-bpf*" --tags --always) IPV6_HP_BPF_VERSION ?= $(notdir $(shell git describe --match "ipv6-hp-bpf*" --tags --always)) AZURE_IPAM_VERSION ?= $(notdir $(shell git describe --match "azure-ipam*" --tags --always)) AZURE_IP_MASQ_MERGER_VERSION ?= $(notdir $(shell git describe --match "azure-ip-masq-merger*" --tags --always)) +AZURE_IPTABLES_MONITOR_VERSION ?= $(notdir $(shell git describe --match "azure-iptables-monitor*" --tags --always)) CNI_VERSION ?= $(ACN_VERSION) CNS_VERSION ?= $(ACN_VERSION) NPM_VERSION ?= $(ACN_VERSION) @@ -44,6 +45,7 @@ ZAPAI_VERSION ?= $(notdir $(shell git describe --match "zapai*" --tags --al # Build directories. AZURE_IPAM_DIR = $(REPO_ROOT)/azure-ipam AZURE_IP_MASQ_MERGER_DIR = $(REPO_ROOT)/azure-ip-masq-merger +AZURE_IPTABLES_MONITOR_DIR = $(REPO_ROOT)/azure-iptables-monitor IPV6_HP_BPF_DIR = $(REPO_ROOT)/bpf-prog/ipv6-hp-bpf CNI_NET_DIR = $(REPO_ROOT)/cni/network/plugin @@ -58,6 +60,7 @@ OUTPUT_DIR = $(REPO_ROOT)/output BUILD_DIR = $(OUTPUT_DIR)/$(GOOS)_$(GOARCH) AZURE_IPAM_BUILD_DIR = $(BUILD_DIR)/azure-ipam AZURE_IP_MASQ_MERGER_BUILD_DIR = $(BUILD_DIR)/azure-ip-masq-merger +AZURE_IPTABLES_MONITOR_BUILD_DIR = $(BUILD_DIR)/azure-iptables-monitor IPV6_HP_BPF_BUILD_DIR = $(BUILD_DIR)/bpf-prog/ipv6-hp-bpf IMAGE_DIR = $(OUTPUT_DIR)/images @@ -106,6 +109,7 @@ CNS_ARCHIVE_NAME = azure-cns-$(GOOS)-$(GOARCH)-$(CNS_VERSION).$(ARCHIVE_EXT) NPM_ARCHIVE_NAME = azure-npm-$(GOOS)-$(GOARCH)-$(NPM_VERSION).$(ARCHIVE_EXT) AZURE_IPAM_ARCHIVE_NAME = azure-ipam-$(GOOS)-$(GOARCH)-$(AZURE_IPAM_VERSION).$(ARCHIVE_EXT) AZURE_IP_MASQ_MERGER_ARCHIVE_NAME = azure-ip-masq-merger-$(GOOS)-$(GOARCH)-$(AZURE_IP_MASQ_MERGER_VERSION).$(ARCHIVE_EXT) +AZURE_IPTABLES_MONITOR_ARCHIVE_NAME = azure-iptables-monitor-$(GOOS)-$(GOARCH)-$(AZURE_IPTABLES_MONITOR_VERSION).$(ARCHIVE_EXT) IPV6_HP_BPF_ARCHIVE_NAME = ipv6-hp-bpf-$(GOOS)-$(GOARCH)-$(IPV6_HP_BPF_VERSION).$(ARCHIVE_EXT) # Image info file names. @@ -123,8 +127,8 @@ all-binaries-platforms: ## Make all platform binaries # OS specific binaries/images ifeq ($(GOOS),linux) -all-binaries: acncli azure-cni-plugin azure-cns azure-npm azure-ipam azure-ip-masq-merger ipv6-hp-bpf -all-images: npm-image cns-image cni-manager-image azure-ip-masq-merger-image ipv6-hp-bpf-image +all-binaries: acncli azure-cni-plugin azure-cns azure-npm azure-ipam azure-ip-masq-merger azure-iptables-monitor ipv6-hp-bpf +all-images: npm-image cns-image cni-manager-image azure-ip-masq-merger-image azure-iptables-monitor-image ipv6-hp-bpf-image else all-binaries: azure-cni-plugin azure-cns azure-npm all-images: @@ -139,6 +143,7 @@ azure-npm: azure-npm-binary npm-archive azure-ipam: azure-ipam-binary azure-ipam-archive ipv6-hp-bpf: ipv6-hp-bpf-binary ipv6-hp-bpf-archive azure-ip-masq-merger: azure-ip-masq-merger-binary azure-ip-masq-merger-archive +azure-iptables-monitor: azure-iptables-monitor-binary azure-iptables-monitor-archive ##@ Versioning @@ -157,6 +162,9 @@ azure-ipam-version: ## prints the azure-ipam version azure-ip-masq-merger-version: ## prints the azure-ip-masq-merger version @echo $(AZURE_IP_MASQ_MERGER_VERSION) +azure-iptables-monitor-version: ## prints the azure-iptables-monitor version + @echo $(AZURE_IPTABLES_MONITOR_VERSION) + ipv6-hp-bpf-version: ## prints the ipv6-hp-bpf version @echo $(IPV6_HP_BPF_VERSION) @@ -230,6 +238,10 @@ azure-npm-binary: azure-ip-masq-merger-binary: cd $(AZURE_IP_MASQ_MERGER_DIR) && CGO_ENABLED=0 go build -v -o $(AZURE_IP_MASQ_MERGER_BUILD_DIR)/azure-ip-masq-merger$(EXE_EXT) -ldflags "-X main.version=$(AZURE_IP_MASQ_MERGER_VERSION)" -gcflags="-dwarflocationlists=true" +# Build the azure-iptables-monitor binary. +azure-iptables-monitor-binary: + cd $(AZURE_IPTABLES_MONITOR_DIR) && CGO_ENABLED=0 go build -v -o $(AZURE_IPTABLES_MONITOR_BUILD_DIR)/azure-iptables-monitor$(EXE_EXT) -ldflags "-X main.version=$(AZURE_IPTABLES_MONITOR_VERSION)" -gcflags="-dwarflocationlists=true" + ##@ Containers ## Common variables for all containers. @@ -268,25 +280,27 @@ CONTAINER_TRANSPORT = docker endif ## Image name definitions. -ACNCLI_IMAGE = acncli -AZURE_IPAM_IMAGE = azure-ipam -IPV6_HP_BPF_IMAGE = ipv6-hp-bpf -CNI_IMAGE = azure-cni -CNS_IMAGE = azure-cns -NPM_IMAGE = azure-npm -AZURE_IP_MASQ_MERGER_IMAGE = azure-ip-masq-merger +ACNCLI_IMAGE = acncli +AZURE_IPAM_IMAGE = azure-ipam +IPV6_HP_BPF_IMAGE = ipv6-hp-bpf +CNI_IMAGE = azure-cni +CNS_IMAGE = azure-cns +NPM_IMAGE = azure-npm +AZURE_IP_MASQ_MERGER_IMAGE = azure-ip-masq-merger +AZURE_IPTABLES_MONITOR_IMAGE = azure-iptables-monitor ## Image platform tags. -ACNCLI_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(ACN_VERSION) -AZURE_IPAM_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IPAM_VERSION) -AZURE_IPAM_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IPAM_VERSION)-$(OS_SKU_WIN) -IPV6_HP_BPF_IMAGE_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(IPV6_HP_BPF_VERSION) -CNI_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNI_VERSION) -CNI_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNI_VERSION)-$(OS_SKU_WIN) -CNS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNS_VERSION) -CNS_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNS_VERSION)-$(OS_SKU_WIN) -NPM_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(NPM_VERSION) +ACNCLI_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(ACN_VERSION) +AZURE_IPAM_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IPAM_VERSION) +AZURE_IPAM_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IPAM_VERSION)-$(OS_SKU_WIN) +IPV6_HP_BPF_IMAGE_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(IPV6_HP_BPF_VERSION) +CNI_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNI_VERSION) +CNI_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNI_VERSION)-$(OS_SKU_WIN) +CNS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNS_VERSION) +CNS_WINDOWS_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(CNS_VERSION)-$(OS_SKU_WIN) +NPM_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(NPM_VERSION) AZURE_IP_MASQ_MERGER_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IP_MASQ_MERGER_VERSION) +AZURE_IPTABLES_MONITOR_PLATFORM_TAG ?= $(subst /,-,$(PLATFORM))-$(AZURE_IPTABLES_MONITOR_VERSION) qemu-user-static: ## Set up the host to run qemu multiplatform container builds. @@ -424,6 +438,32 @@ azure-ip-masq-merger-image-pull: ## pull azure-ip-masq-merger container image. IMAGE=$(AZURE_IP_MASQ_MERGER_IMAGE) \ TAG=$(AZURE_IP_MASQ_MERGER_PLATFORM_TAG) +# azure-iptables-monitor +azure-iptables-monitor-image-name: # util target to print the azure-iptables-monitor image name. + @echo $(AZURE_IPTABLES_MONITOR_IMAGE) + +azure-iptables-monitor-image-name-and-tag: # util target to print the azure-iptables-monitor image name and tag. + @echo $(IMAGE_REGISTRY)/$(AZURE_IPTABLES_MONITOR_IMAGE):$(AZURE_IPTABLES_MONITOR_PLATFORM_TAG) + +azure-iptables-monitor-image: ## build azure-iptables-monitor container image. + $(MAKE) container \ + DOCKERFILE=azure-iptables-monitor/Dockerfile \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + PLATFORM=$(PLATFORM) \ + TAG=$(AZURE_IPTABLES_MONITOR_PLATFORM_TAG) \ + TARGET=$(OS) \ + OS=$(OS) \ + ARCH=$(ARCH) + +azure-iptables-monitor-image-push: ## push azure-iptables-monitor container image. + $(MAKE) container-push \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + TAG=$(AZURE_IPTABLES_MONITOR_PLATFORM_TAG) + +azure-iptables-monitor-image-pull: ## pull azure-iptables-monitor container image. + $(MAKE) container-pull \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + TAG=$(AZURE_IPTABLES_MONITOR_PLATFORM_TAG) # ipv6-hp-bpf @@ -617,6 +657,22 @@ azure-ip-masq-merger-skopeo-archive: ## export tar archive of azure-ip-masq-merg IMAGE=$(AZURE_IP_MASQ_MERGER_IMAGE) \ TAG=$(AZURE_IP_MASQ_MERGER_VERSION) +azure-iptables-monitor-manifest-build: ## build azure-iptables-monitor multiplat container manifest. + $(MAKE) manifest-build \ + PLATFORMS="$(PLATFORMS)" \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + TAG=$(AZURE_IPTABLES_MONITOR_VERSION) + +azure-iptables-monitor-manifest-push: ## push azure-iptables-monitor multiplat container manifest + $(MAKE) manifest-push \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + TAG=$(AZURE_IPTABLES_MONITOR_VERSION) + +azure-iptables-monitor-skopeo-archive: ## export tar archive of azure-iptables-monitor multiplat container manifest. + $(MAKE) manifest-skopeo-archive \ + IMAGE=$(AZURE_IPTABLES_MONITOR_IMAGE) \ + TAG=$(AZURE_IPTABLES_MONITOR_VERSION) + ipv6-hp-bpf-manifest-build: ## build ipv6-hp-bpf multiplat container manifest. $(MAKE) manifest-build \ PLATFORMS="$(PLATFORMS)" \ @@ -775,6 +831,14 @@ ifeq ($(GOOS),linux) cd $(AZURE_IP_MASQ_MERGER_BUILD_DIR) && $(ARCHIVE_CMD) $(AZURE_IP_MASQ_MERGER_ARCHIVE_NAME) azure-ip-masq-merger$(EXE_EXT) endif +# Create a azure-iptables-monitor archive for the target platform. +.PHONY: azure-iptables-monitor-archive +azure-iptables-monitor-archive: azure-iptables-monitor-binary +ifeq ($(GOOS),linux) + $(MKDIR) $(AZURE_IPTABLES_MONITOR_BUILD_DIR) + cd $(AZURE_IPTABLES_MONITOR_BUILD_DIR) && $(ARCHIVE_CMD) $(AZURE_IPTABLES_MONITOR_ARCHIVE_NAME) azure-iptables-monitor$(EXE_EXT) +endif + # Create a ipv6-hp-bpf archive for the target platform. .PHONY: ipv6-hp-bpf-archive ipv6-hp-bpf-archive: ipv6-hp-bpf-binary @@ -811,6 +875,7 @@ workspace: ## Set up the Go workspace. go work use . go work use ./azure-ipam go work use ./azure-ip-masq-merger + go work use ./azure-iptables-monitor go work use ./build/tools go work use ./dropgz go work use ./zapai @@ -823,7 +888,7 @@ RESTART_CASE ?= false # CNI type is a key to direct the types of state validation done on a cluster. CNI_TYPE ?= cilium -test-all: test-azure-ipam test-azure-ip-masq-merger test-main ## run all unit tests. +test-all: test-azure-ipam test-azure-ip-masq-merger test-azure-iptables-monitor test-main ## run all unit tests. test-main: go test -mod=readonly -buildvcs=false -tags "unit" --skip 'TestE2E*' -race -covermode atomic -coverprofile=coverage-main.out $(COVER_PKG)/... @@ -863,6 +928,9 @@ test-azure-ipam: ## run the unit test for azure-ipam test-azure-ip-masq-merger: ## run the unit test for azure-ip-masq-merger cd $(AZURE_IP_MASQ_MERGER_DIR) && go test -race -covermode atomic -coverprofile=../coverage-azure-ip-masq-merger.out && go tool cover -func=../coverage-azure-ip-masq-merger.out +test-azure-iptables-monitor: ## run the unit test for azure-iptables-monitor + cd $(AZURE_IPTABLES_MONITOR_DIR) && go test -race -covermode atomic -coverprofile=../coverage-azure-iptables-monitor.out && go tool cover -func=../coverage-azure-iptables-monitor.out + kind: kind create cluster --config ./test/kind/kind.yaml diff --git a/azure-iptables-monitor/Dockerfile b/azure-iptables-monitor/Dockerfile new file mode 100644 index 0000000000..ae46e0988b --- /dev/null +++ b/azure-iptables-monitor/Dockerfile @@ -0,0 +1,29 @@ +ARG ARCH + +# mcr.microsoft.com/azurelinux/base/core:3.0 +FROM mcr.microsoft.com/azurelinux/base/core@sha256:9948138108a3d69f1dae62104599ac03132225c3b7a5ac57b85a214629c8567d AS mariner-core + +# mcr.microsoft.com/azurelinux/distroless/minimal:3.0 +FROM mcr.microsoft.com/azurelinux/distroless/minimal@sha256:0801b80a0927309572b9adc99bd1813bc680473175f6e8175cd4124d95dbd50c AS mariner-distroless + +# skopeo inspect docker://mcr.microsoft.com/oss/go/microsoft/golang:1.23.2-azurelinux3.0 --format "{{.Name}}@{{.Digest}}" +FROM --platform=linux/${ARCH} mcr.microsoft.com/oss/go/microsoft/golang@sha256:f1f0cbd464ae4cd9d41176d47f1f9fe16a6965425871f817587314e3a04576ec AS go + + +FROM go AS azure-iptables-monitor +ARG OS +ARG VERSION +WORKDIR /azure-iptables-monitor +COPY ./azure-iptables-monitor . +RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/iptables-monitor -trimpath -ldflags "-X main.version="$VERSION"" -gcflags="-dwarflocationlists=true" . + +FROM mariner-core AS iptables +RUN tdnf install -y iptables + +# TODO: change to mariner distroless +FROM mariner-distroless AS linux +COPY --from=iptables /usr/sbin/*tables* /usr/sbin/ +COPY --from=iptables /usr/lib /usr/lib +COPY --from=azure-iptables-monitor /go/bin/iptables-monitor azure-iptables-monitor + +ENTRYPOINT ["/azure-iptables-monitor"] diff --git a/azure-iptables-monitor/README.md b/azure-iptables-monitor/README.md new file mode 100644 index 0000000000..f37b00032c --- /dev/null +++ b/azure-iptables-monitor/README.md @@ -0,0 +1,63 @@ +# azure-iptables-monitor + +`azure-iptables-monitor` is a utility for monitoring iptables rules on Kubernetes nodes and labeling nodes based on whether they contain user-defined iptables rules. + +## Description + +The goal of this program is to periodically scan iptables rules across all tables (nat, mangle, filter, raw, security) and determine if any rules exist that don't match expected patterns. When unexpected rules are found, the node is labeled to indicate the presence of user-defined iptables rules. + +## Usage + +Follow the steps below to build and run the program: + +1. Build the binary using `make`: + ```bash + make azure-iptables-monitor + ``` + or make an image: + ```bash + make azure-iptables-monitor-image + ``` + +2. Deploy or copy the binary to your node(s). + +3. Prepare your allowed pattern files in the input directory. Each file should be named after an iptables table (`nat`, `mangle`, `filter`, `raw`, `security`) or `global` and contain regex patterns that match expected iptables rules. You may want to mount a configmap for this purpose. + +4. Start the program with: + ```bash + ./azure-iptables-monitor --input=/etc/config/ --interval=600 + ``` + - The `--input` flag specifies the directory containing allowed regex pattern files. Default: `/etc/config/` + - The `--interval` flag specifies how often to check iptables rules in seconds. Default: `600` + - The program must be in a k8 environment and `NODE_NAME` must be a set environment variable with the current node. + +5. The program will set the `user-iptables-rules` label on the current node to `true` if unexpected rules are found, or `false` if all rules match expected patterns. Proper RBAC is required for patching the node. + + +## Pattern File Format + +Each pattern file should contain one regex pattern per line: +``` +^-A INPUT -i lo -j ACCEPT$ +^-A FORWARD -j DOCKER.* +^-A POSTROUTING -s 10\.0\.0\.0/8 -j MASQUERADE$ +``` + +- `global`: Patterns that can match rules in any iptables table +- `nat`, `mangle`, `filter`, `raw`, `security`: Patterns specific to each iptables table +- Empty lines are ignored +- Each line should be a valid Go regex pattern + +## Debugging + +Logs are output to standard error. Increase verbosity with the `-v` flag: +```bash +./azure-iptables-monitor -v 3 +``` + +## Development + +To run tests at the repository level: +```bash +make test-azure-iptables-monitor +``` diff --git a/azure-iptables-monitor/go.mod b/azure-iptables-monitor/go.mod new file mode 100644 index 0000000000..9a99605b0b --- /dev/null +++ b/azure-iptables-monitor/go.mod @@ -0,0 +1,62 @@ +module github.com/Azure/azure-container-networking/azure-iptables-monitor + +go 1.23.0 + +require ( + github.com/coreos/go-iptables v0.8.0 + github.com/stretchr/testify v1.9.0 + k8s.io/apimachinery v0.31.3 + k8s.io/client-go v0.31.3 + k8s.io/component-base v0.31.3 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.3 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/azure-iptables-monitor/go.sum b/azure-iptables-monitor/go.sum new file mode 100644 index 0000000000..1de27f3efb --- /dev/null +++ b/azure-iptables-monitor/go.sum @@ -0,0 +1,182 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= +github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= +k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go new file mode 100644 index 0000000000..6df5e13fa2 --- /dev/null +++ b/azure-iptables-monitor/iptables_monitor.go @@ -0,0 +1,236 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "time" + + goiptables "github.com/coreos/go-iptables/iptables" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/component-base/logs" + "k8s.io/component-base/version/verflag" + "k8s.io/klog/v2" +) + +// Version is populated by make during build. +var version string + +var ( + configPath = flag.String("input", "/etc/config/", "Name of the directory with the allowed regex files") + checkInterval = flag.Int("interval", 600, "How often to check iptables rules (in seconds)") +) + +const nodeLabel = "user-iptables-rules" + +type FileLineReader interface { + Read(filename string) ([]string, error) +} + +type OSFileLineReader struct{} + +// Read opens the file and reads each line into a new string, returning the contents as a slice of strings +// Empty lines are skipped +func (OSFileLineReader) Read(filename string) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file %s: %w", filename, err) + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + // Skip empty lines + if line != "" { + lines = append(lines, line) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file %s: %w", filename, err) + } + + return lines, nil +} + +// patchNodeLabel sets a specified node label to a certain value by patching it +// Requires proper rbac (node patch) +func patchNodeLabel(clientset *kubernetes.Clientset, labelValue bool, nodeName string) error { + patch := []byte(fmt.Sprintf(`{ + "metadata": { + "labels": { + "%s": "%v" + } + } + }`, nodeLabel, labelValue)) + + _, err := clientset.CoreV1().Nodes().Patch( + context.TODO(), + nodeName, + types.StrategicMergePatchType, + patch, + metav1.PatchOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to patch node %s with label %s=%v: %w", nodeName, nodeLabel, labelValue, err) + } + return nil +} + +type IPTablesClient interface { + ListChains(table string) ([]string, error) + List(table, chain string) ([]string, error) +} + +// GetRules returns all rules as a slice of strings for the specified tableName +func GetRules(client IPTablesClient, tableName string) ([]string, error) { + var allRules []string + chains, err := client.ListChains(tableName) + if err != nil { + return nil, fmt.Errorf("failed to list chains for table %s: %w", tableName, err) + } + + for _, chain := range chains { + rules, err := client.List(tableName, chain) + if err != nil { + return nil, fmt.Errorf("failed to list rules for table %s chain %s: %w", tableName, chain, err) + } + allRules = append(allRules, rules...) + } + + return allRules, nil +} + +// hasUnexpectedRules checks if any rules in currentRules don't match any of the allowedPatterns +// Returns true if there are unexpected rules, false if all rules match expected patterns +func hasUnexpectedRules(currentRules, allowedPatterns []string) bool { + foundUnexpectedRules := false + + // compile regex patterns + compiledPatterns := make([]*regexp.Regexp, 0, len(allowedPatterns)) + for _, pattern := range allowedPatterns { + compiled, err := regexp.Compile(pattern) + if err != nil { + klog.Errorf("Error compiling regex pattern '%s': %v", pattern, err) + continue + } + compiledPatterns = append(compiledPatterns, compiled) + } + + // check each rule to see if it matches any allowed pattern + for _, rule := range currentRules { + ruleMatched := false + for _, pattern := range compiledPatterns { + if pattern.MatchString(rule) { + klog.V(3).Infof("MATCHED: '%s' -> pattern: '%s'", rule, pattern.String()) + ruleMatched = true + break + } + } + if !ruleMatched { + klog.Infof("Unexpected rule: %s", rule) + foundUnexpectedRules = true + } + } + + return foundUnexpectedRules +} + +// nodeHasUserIPTablesRules returns true if the node has iptables rules that do not match the regex +// specified in the rule's respective table: nat, mangle, filter, raw, or security +// The global file's regexes can match to a rule in any table +func nodeHasUserIPTablesRules(fileReader FileLineReader, iptablesClient IPTablesClient) bool { + tables := []string{"nat", "mangle", "filter", "raw", "security"} + + globalPatterns, err := fileReader.Read(filepath.Join(*configPath, "global")) + if err != nil { + globalPatterns = []string{} + klog.V(2).Infof("No global patterns file found, using empty patterns") + } + + userIPTablesRules := false + + for _, table := range tables { + rules, err := GetRules(iptablesClient, table) + if err != nil { + klog.Errorf("failed to get rules for table %s: %v", table, err) + continue + } + + var referencePatterns []string + referencePatterns, err = fileReader.Read(filepath.Join(*configPath, table)) + if err != nil { + referencePatterns = []string{} + klog.V(2).Infof("No reference patterns file found for table %s", table) + } + + referencePatterns = append(referencePatterns, globalPatterns...) + + klog.V(3).Infof("===== %s =====", table) + if hasUnexpectedRules(rules, referencePatterns) { + klog.Infof("Unexpected rules detected in table %s", table) + userIPTablesRules = true + } + } + + return userIPTablesRules +} + +func main() { + klog.InitFlags(nil) + flag.Parse() + + logs.InitLogs() + defer logs.FlushLogs() + + klog.Infof("Version: %s", version) + verflag.PrintAndExitIfRequested() + + config, err := rest.InClusterConfig() + if err != nil { + klog.Fatalf("failed to create in-cluster config: %v", err) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + klog.Fatalf("failed to create kubernetes clientset: %v", err) + } + + var iptablesClient IPTablesClient + iptablesClient, err = goiptables.New() + if err != nil { + klog.Fatalf("failed to create iptables client: %v", err) + } + + // get current node name from environment variable + currentNodeName := os.Getenv("NODE_NAME") + if currentNodeName == "" { + klog.Fatalf("NODE_NAME environment variable not set") + } + + klog.Infof("Starting iptables monitor for node: %s", currentNodeName) + + var fileReader FileLineReader = OSFileLineReader{} + + for { + nodeHasUserIPTablesRules := nodeHasUserIPTablesRules(fileReader, iptablesClient) + + // update node label based on whether user iptables rules were found + err = patchNodeLabel(clientset, nodeHasUserIPTablesRules, currentNodeName) + if err != nil { + klog.Errorf("failed to patch node label: %v", err) + } else { + klog.V(2).Infof("Successfully updated node label for %s: %s=%v", currentNodeName, nodeLabel, nodeHasUserIPTablesRules) + } + + time.Sleep(time.Duration(*checkInterval) * time.Second) + } +} diff --git a/azure-iptables-monitor/iptables_monitor_test.go b/azure-iptables-monitor/iptables_monitor_test.go new file mode 100644 index 0000000000..2ebbfa27ab --- /dev/null +++ b/azure-iptables-monitor/iptables_monitor_test.go @@ -0,0 +1,224 @@ +package main + +import ( + "errors" + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +type MockFileLineReader struct { + files map[string][]string +} + +func NewMockFileLineReader() *MockFileLineReader { + return &MockFileLineReader{ + files: make(map[string][]string), + } +} + +var ErrFileNotFound = errors.New("file not found") + +func (m *MockFileLineReader) Read(filename string) ([]string, error) { + if lines, exists := m.files[filename]; exists { + return lines, nil + } + return nil, fmt.Errorf("reading file %q: %w", filename, ErrFileNotFound) +} + +// MockIPTablesClient implements IPTablesClient for testing +type MockIPTablesClient struct { + // rules is organized as: table -> chain -> []rules + rules map[string]map[string][]string +} + +// NewMockIPTablesClient creates a new mock client with empty rules +func NewMockIPTablesClient() *MockIPTablesClient { + return &MockIPTablesClient{ + rules: make(map[string]map[string][]string), + } +} + +// ListChains returns all chain names for the given table +func (m *MockIPTablesClient) ListChains(table string) ([]string, error) { + chains, exists := m.rules[table] + if !exists { + return []string{}, nil + } + + chainNames := make([]string, 0, len(chains)) + for chainName := range chains { + chainNames = append(chainNames, chainName) + } + return chainNames, nil +} + +// List returns all rules for the given table and chain +func (m *MockIPTablesClient) List(table, chain string) ([]string, error) { + tableChains, exists := m.rules[table] + if !exists { + return []string{}, nil + } + + rules, exists := tableChains[chain] + if !exists { + return []string{}, nil + } + + return rules, nil +} + +func TestHasUnexpectedRules(t *testing.T) { + testCases := []struct { + name string + currentRules []string + allowedPatterns []string + expected bool // true if we expect one of our rules to not match our allowedPatterns + }{ + { + name: "no rules, no patterns", + currentRules: []string{}, + allowedPatterns: []string{}, + expected: false, + }, + { + name: "all rules match patterns", + currentRules: []string{"ACCEPT all -- anywhere anywhere", "DROP all -- 192.168.1.0/24 anywhere"}, + allowedPatterns: []string{ + "^ACCEPT.*anywhere.*anywhere$", + "^DROP.*192\\.168\\..*", + }, + expected: false, + }, + { + name: "some rules don't match patterns", + currentRules: []string{"ACCEPT all -- anywhere anywhere", "CUSTOM_RULE something unexpected"}, + allowedPatterns: []string{ + "^ACCEPT.*anywhere.*anywhere$", + }, + expected: true, + }, + { + name: "no patterns provided, rules exist", + currentRules: []string{"ACCEPT all -- anywhere anywhere"}, + allowedPatterns: []string{}, + expected: true, + }, + { + name: "invalid regex pattern", + currentRules: []string{"ACCEPT all -- anywhere anywhere"}, + allowedPatterns: []string{ + "^ACCEPT.*anywhere.*anywhere$", + "[invalid regex", // This will fail to compile + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := hasUnexpectedRules(tc.currentRules, tc.allowedPatterns) + require.Equal(t, tc.expected, result, "hasUnexpectedRules result mismatch") + }) + } +} + +func TestNodeHasUserIPTablesRules(t *testing.T) { + testCases := []struct { + name string + files map[string][]string + rules map[string]map[string][]string + expected bool + description string + }{ + { + name: "no unexpected rules", + files: map[string][]string{ + filepath.Join("/etc/config/", "global"): { // nolint + "^-A.*INPUT.*lo.*", + }, + filepath.Join("/etc/config/", "nat"): { // nolint + "^-A.*MASQUERADE.*", + }, + }, + rules: map[string]map[string][]string{ + "nat": { + "POSTROUTING": { + "-A POSTROUTING -s 10.0.0.0/8 -j MASQUERADE", + }, + "INPUT": { + "-A INPUT -i lo -j ACCEPT", + }, + }, + "filter": { + "INPUT": { + "-A INPUT -i lo -j ACCEPT", + }, + }, + }, + expected: false, + description: "all rules match expected patterns", + }, + { + name: "has unexpected rules", + files: map[string][]string{ + filepath.Join("/etc/config/", "nat"): { // nolint + "^-A.*CUSTOM_CHAIN.*", + }, + }, + rules: map[string]map[string][]string{ + "nat": { + "CUSTOM_CHAIN": { + "-A CUSTOM_CHAIN -j DROP", + }, + }, + "filter": { + "CUSTOM_CHAIN": { + "-A CUSTOM_CHAIN -j DROP", // This won't match any pattern + }, + }, + }, + expected: true, + description: "unexpected custom rule found", + }, + { + name: "no pattern files exist", + files: map[string][]string{}, + rules: map[string]map[string][]string{ + "nat": { + "POSTROUTING": { + "-A POSTROUTING -j MASQUERADE", + }, + }, + }, + expected: true, + description: "no patterns means all rules are unexpected", + }, + { + name: "empty iptables rules", + files: map[string][]string{ + filepath.Join("/etc/config/", "global"): { // nolint + "^-A.*ACCEPT.*", + }, + }, + rules: map[string]map[string][]string{}, + expected: false, + description: "no rules means no unexpected rules", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fileReader := NewMockFileLineReader() + iptablesClient := NewMockIPTablesClient() + + fileReader.files = tc.files + iptablesClient.rules = tc.rules + + result := nodeHasUserIPTablesRules(fileReader, iptablesClient) + require.Equal(t, tc.expected, result, tc.description) + }) + } +} From d4832b97b0692eb94913191f9663ba463d415c84 Mon Sep 17 00:00:00 2001 From: QxBytes Date: Tue, 8 Jul 2025 15:57:53 -0700 Subject: [PATCH 2/7] address feedback --- azure-iptables-monitor/Dockerfile | 1 - azure-iptables-monitor/iptables_monitor.go | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/azure-iptables-monitor/Dockerfile b/azure-iptables-monitor/Dockerfile index ae46e0988b..559f33a012 100644 --- a/azure-iptables-monitor/Dockerfile +++ b/azure-iptables-monitor/Dockerfile @@ -20,7 +20,6 @@ RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/iptables-monitor -trimpath -ld FROM mariner-core AS iptables RUN tdnf install -y iptables -# TODO: change to mariner distroless FROM mariner-distroless AS linux COPY --from=iptables /usr/sbin/*tables* /usr/sbin/ COPY --from=iptables /usr/lib /usr/lib diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 6df5e13fa2..9fc57cd4e5 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -221,14 +221,14 @@ func main() { var fileReader FileLineReader = OSFileLineReader{} for { - nodeHasUserIPTablesRules := nodeHasUserIPTablesRules(fileReader, iptablesClient) + userIPTablesRulesFound := nodeHasUserIPTablesRules(fileReader, iptablesClient) // update node label based on whether user iptables rules were found - err = patchNodeLabel(clientset, nodeHasUserIPTablesRules, currentNodeName) + err = patchNodeLabel(clientset, userIPTablesRulesFound, currentNodeName) if err != nil { klog.Errorf("failed to patch node label: %v", err) } else { - klog.V(2).Infof("Successfully updated node label for %s: %s=%v", currentNodeName, nodeLabel, nodeHasUserIPTablesRules) + klog.V(2).Infof("Successfully updated node label for %s: %s=%v", currentNodeName, nodeLabel, userIPTablesRulesFound) } time.Sleep(time.Duration(*checkInterval) * time.Second) From 1293839d4435f8f9cf0550ea3a82e69450c3ef74 Mon Sep 17 00:00:00 2001 From: QxBytes Date: Fri, 18 Jul 2025 16:54:31 -0700 Subject: [PATCH 3/7] add option to send node events if enabled --- azure-iptables-monitor/README.md | 1 + azure-iptables-monitor/iptables_monitor.go | 52 ++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/azure-iptables-monitor/README.md b/azure-iptables-monitor/README.md index f37b00032c..4c241a39f0 100644 --- a/azure-iptables-monitor/README.md +++ b/azure-iptables-monitor/README.md @@ -29,6 +29,7 @@ Follow the steps below to build and run the program: ``` - The `--input` flag specifies the directory containing allowed regex pattern files. Default: `/etc/config/` - The `--interval` flag specifies how often to check iptables rules in seconds. Default: `600` + - The `--events` flag enables Kubernetes event creation for rule violations. Default: `false` - The program must be in a k8 environment and `NODE_NAME` must be a set environment variable with the current node. 5. The program will set the `user-iptables-rules` label on the current node to `true` if unexpected rules are found, or `false` if all rules match expected patterns. Proper RBAC is required for patching the node. diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 9fc57cd4e5..912ee1c0e1 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -11,6 +11,7 @@ import ( "time" goiptables "github.com/coreos/go-iptables/iptables" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" @@ -26,6 +27,7 @@ var version string var ( configPath = flag.String("input", "/etc/config/", "Name of the directory with the allowed regex files") checkInterval = flag.Int("interval", 600, "How often to check iptables rules (in seconds)") + sendEvents = flag.Bool("events", false, "Whether to send node events if unexpected iptables rules are detected") ) const nodeLabel = "user-iptables-rules" @@ -86,6 +88,49 @@ func patchNodeLabel(clientset *kubernetes.Clientset, labelValue bool, nodeName s return nil } +// createNodeEvent creates a Kubernetes event for the specified node +func createNodeEvent(clientset *kubernetes.Clientset, nodeName, reason, message, eventType string) error { + node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get node %s: %w", nodeName, err) + } + + now := metav1.NewTime(time.Now()) + + event := &corev1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s.%d", nodeName, now.Unix()), + Namespace: "default", + }, + InvolvedObject: corev1.ObjectReference{ + Kind: "Node", + Name: nodeName, + UID: node.UID, // required for event to show up in node describe + APIVersion: "v1", + }, + Reason: reason, + Message: message, + Type: eventType, + FirstTimestamp: now, + LastTimestamp: now, + Count: 1, + Source: corev1.EventSource{ + Component: "azure-iptables-monitor", + }, + } + _, err = clientset.CoreV1().Events("default").Create( + context.TODO(), + event, + metav1.CreateOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to create event for node %s: %w", nodeName, err) + } + + klog.V(2).Infof("Created event for node %s: %s - %s", nodeName, reason, message) + return nil +} + type IPTablesClient interface { ListChains(table string) ([]string, error) List(table, chain string) ([]string, error) @@ -231,6 +276,13 @@ func main() { klog.V(2).Infof("Successfully updated node label for %s: %s=%v", currentNodeName, nodeLabel, userIPTablesRulesFound) } + if *sendEvents && userIPTablesRulesFound { + err = createNodeEvent(clientset, currentNodeName, "UnexpectedIPTablesRules", "Node has unexpected iptables rules", corev1.EventTypeWarning) + if err != nil { + klog.Errorf("failed to create event: %v", err) + } + } + time.Sleep(time.Duration(*checkInterval) * time.Second) } } From d08f6bd0a1129b11b5d0a6c3964b9ef8e447c37e Mon Sep 17 00:00:00 2001 From: QxBytes Date: Wed, 23 Jul 2025 11:59:38 -0700 Subject: [PATCH 4/7] remove dependency on node patching rbac now requires: - apiGroups: ["cilium.io"] resources: ["ciliumnodes"] verbs: ["patch"] we also must pass NODE_UID as an environment variable to send events --- azure-iptables-monitor/iptables_monitor.go | 47 +++++++++++++--------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 912ee1c0e1..3739d644e3 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -13,7 +13,9 @@ import ( goiptables "github.com/coreos/go-iptables/iptables" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/component-base/logs" @@ -66,7 +68,13 @@ func (OSFileLineReader) Read(filename string) ([]string, error) { // patchNodeLabel sets a specified node label to a certain value by patching it // Requires proper rbac (node patch) -func patchNodeLabel(clientset *kubernetes.Clientset, labelValue bool, nodeName string) error { +func patchNodeLabel(clientset dynamic.Interface, labelValue bool, nodeName string) error { + gvr := schema.GroupVersionResource{ + Group: "cilium.io", + Version: "v2", + Resource: "ciliumnodes", + } + patch := []byte(fmt.Sprintf(`{ "metadata": { "labels": { @@ -75,26 +83,16 @@ func patchNodeLabel(clientset *kubernetes.Clientset, labelValue bool, nodeName s } }`, nodeLabel, labelValue)) - _, err := clientset.CoreV1().Nodes().Patch( - context.TODO(), - nodeName, - types.StrategicMergePatchType, - patch, - metav1.PatchOptions{}, - ) + _, err := clientset.Resource(gvr). + Patch(context.TODO(), nodeName, types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { - return fmt.Errorf("failed to patch node %s with label %s=%v: %w", nodeName, nodeLabel, labelValue, err) + return fmt.Errorf("failed to patch %s with label %s=%v: %w", nodeName, nodeLabel, labelValue, err) } return nil } // createNodeEvent creates a Kubernetes event for the specified node -func createNodeEvent(clientset *kubernetes.Clientset, nodeName, reason, message, eventType string) error { - node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("failed to get node %s: %w", nodeName, err) - } - +func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, nodeUID types.UID, reason, message, eventType string) error { now := metav1.NewTime(time.Now()) event := &corev1.Event{ @@ -105,7 +103,7 @@ func createNodeEvent(clientset *kubernetes.Clientset, nodeName, reason, message, InvolvedObject: corev1.ObjectReference{ Kind: "Node", Name: nodeName, - UID: node.UID, // required for event to show up in node describe + UID: nodeUID, // required for event to show up in node describe APIVersion: "v1", }, Reason: reason, @@ -118,7 +116,7 @@ func createNodeEvent(clientset *kubernetes.Clientset, nodeName, reason, message, Component: "azure-iptables-monitor", }, } - _, err = clientset.CoreV1().Events("default").Create( + _, err := clientset.CoreV1().Events("default").Create( context.TODO(), event, metav1.CreateOptions{}, @@ -248,6 +246,10 @@ func main() { if err != nil { klog.Fatalf("failed to create kubernetes clientset: %v", err) } + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + klog.Fatalf("failed to create dynamic client: %v", err) + } var iptablesClient IPTablesClient iptablesClient, err = goiptables.New() @@ -261,6 +263,13 @@ func main() { klog.Fatalf("NODE_NAME environment variable not set") } + // get current node uid from environment variable + currentNodeUIDStr := os.Getenv("NODE_UID") + if currentNodeUIDStr == "" { + klog.Fatalf("NODE_UID environment variable not set") + } + currentNodeUID := types.UID(currentNodeUIDStr) + klog.Infof("Starting iptables monitor for node: %s", currentNodeName) var fileReader FileLineReader = OSFileLineReader{} @@ -269,7 +278,7 @@ func main() { userIPTablesRulesFound := nodeHasUserIPTablesRules(fileReader, iptablesClient) // update node label based on whether user iptables rules were found - err = patchNodeLabel(clientset, userIPTablesRulesFound, currentNodeName) + err = patchNodeLabel(dynamicClient, userIPTablesRulesFound, currentNodeName) if err != nil { klog.Errorf("failed to patch node label: %v", err) } else { @@ -277,7 +286,7 @@ func main() { } if *sendEvents && userIPTablesRulesFound { - err = createNodeEvent(clientset, currentNodeName, "UnexpectedIPTablesRules", "Node has unexpected iptables rules", corev1.EventTypeWarning) + err = createNodeEvent(clientset, currentNodeName, currentNodeUID, "UnexpectedIPTablesRules", "Node has unexpected iptables rules", corev1.EventTypeWarning) if err != nil { klog.Errorf("failed to create event: %v", err) } From 82ad20e60ee028e0aeffc1c54f7d1f0cdd151015 Mon Sep 17 00:00:00 2001 From: QxBytes Date: Wed, 23 Jul 2025 12:17:14 -0700 Subject: [PATCH 5/7] remove passing node uid in since not possible with downward api --- azure-iptables-monitor/iptables_monitor.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 3739d644e3..235c648730 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -92,7 +92,12 @@ func patchNodeLabel(clientset dynamic.Interface, labelValue bool, nodeName strin } // createNodeEvent creates a Kubernetes event for the specified node -func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, nodeUID types.UID, reason, message, eventType string) error { +func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, reason, message, eventType string) error { + node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get node UID for %s: %w", nodeName, err) + } + now := metav1.NewTime(time.Now()) event := &corev1.Event{ @@ -103,7 +108,7 @@ func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, nodeUID t InvolvedObject: corev1.ObjectReference{ Kind: "Node", Name: nodeName, - UID: nodeUID, // required for event to show up in node describe + UID: node.UID, // required for event to show up in node describe APIVersion: "v1", }, Reason: reason, @@ -116,7 +121,7 @@ func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, nodeUID t Component: "azure-iptables-monitor", }, } - _, err := clientset.CoreV1().Events("default").Create( + _, err = clientset.CoreV1().Events("default").Create( context.TODO(), event, metav1.CreateOptions{}, @@ -263,13 +268,6 @@ func main() { klog.Fatalf("NODE_NAME environment variable not set") } - // get current node uid from environment variable - currentNodeUIDStr := os.Getenv("NODE_UID") - if currentNodeUIDStr == "" { - klog.Fatalf("NODE_UID environment variable not set") - } - currentNodeUID := types.UID(currentNodeUIDStr) - klog.Infof("Starting iptables monitor for node: %s", currentNodeName) var fileReader FileLineReader = OSFileLineReader{} @@ -286,7 +284,7 @@ func main() { } if *sendEvents && userIPTablesRulesFound { - err = createNodeEvent(clientset, currentNodeName, currentNodeUID, "UnexpectedIPTablesRules", "Node has unexpected iptables rules", corev1.EventTypeWarning) + err = createNodeEvent(clientset, currentNodeName, "UnexpectedIPTablesRules", "Node has unexpected iptables rules", corev1.EventTypeWarning) if err != nil { klog.Errorf("failed to create event: %v", err) } From ea9952a27349ccb8f5aa9358f326c87d826003df Mon Sep 17 00:00:00 2001 From: QxBytes Date: Thu, 24 Jul 2025 11:42:36 -0700 Subject: [PATCH 6/7] update naming and readme for ciliumnodes --- azure-iptables-monitor/README.md | 12 +++++------ azure-iptables-monitor/iptables_monitor.go | 24 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/azure-iptables-monitor/README.md b/azure-iptables-monitor/README.md index 4c241a39f0..48aa3b060b 100644 --- a/azure-iptables-monitor/README.md +++ b/azure-iptables-monitor/README.md @@ -1,10 +1,10 @@ # azure-iptables-monitor -`azure-iptables-monitor` is a utility for monitoring iptables rules on Kubernetes nodes and labeling nodes based on whether they contain user-defined iptables rules. +`azure-iptables-monitor` is a utility for monitoring iptables rules on Kubernetes nodes and labeling a ciliumnode resource based on whether the corresponding node contains user-defined iptables rules. ## Description -The goal of this program is to periodically scan iptables rules across all tables (nat, mangle, filter, raw, security) and determine if any rules exist that don't match expected patterns. When unexpected rules are found, the node is labeled to indicate the presence of user-defined iptables rules. +The goal of this program is to periodically scan iptables rules across all tables (nat, mangle, filter, raw, security) and determine if any rules exist that don't match expected patterns. When unexpected rules are found, the ciliumnode resource is labeled to indicate the presence of user-defined iptables rules. ## Usage @@ -25,14 +25,14 @@ Follow the steps below to build and run the program: 4. Start the program with: ```bash - ./azure-iptables-monitor --input=/etc/config/ --interval=600 + ./azure-iptables-monitor --input=/etc/config/ --interval=300 ``` - The `--input` flag specifies the directory containing allowed regex pattern files. Default: `/etc/config/` - - The `--interval` flag specifies how often to check iptables rules in seconds. Default: `600` + - The `--interval` flag specifies how often to check iptables rules in seconds. Default: `300` - The `--events` flag enables Kubernetes event creation for rule violations. Default: `false` - - The program must be in a k8 environment and `NODE_NAME` must be a set environment variable with the current node. + - The program must be in a k8s environment and `NODE_NAME` must be a set environment variable with the current node. -5. The program will set the `user-iptables-rules` label on the current node to `true` if unexpected rules are found, or `false` if all rules match expected patterns. Proper RBAC is required for patching the node. +5. The program will set the `user-iptables-rules` label to `true` on the specified ciliumnode resource if unexpected rules are found, or `false` if all rules match expected patterns. Proper RBAC is required for patching (patch for ciliumnodes, create for events, get for nodes). ## Pattern File Format diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 235c648730..72c1c5e063 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -28,11 +28,11 @@ var version string var ( configPath = flag.String("input", "/etc/config/", "Name of the directory with the allowed regex files") - checkInterval = flag.Int("interval", 600, "How often to check iptables rules (in seconds)") + checkInterval = flag.Int("interval", 300, "How often to check iptables rules (in seconds)") sendEvents = flag.Bool("events", false, "Whether to send node events if unexpected iptables rules are detected") ) -const nodeLabel = "user-iptables-rules" +const label = "user-iptables-rules" type FileLineReader interface { Read(filename string) ([]string, error) @@ -66,9 +66,9 @@ func (OSFileLineReader) Read(filename string) ([]string, error) { return lines, nil } -// patchNodeLabel sets a specified node label to a certain value by patching it -// Requires proper rbac (node patch) -func patchNodeLabel(clientset dynamic.Interface, labelValue bool, nodeName string) error { +// patchLabel sets a specified label to a certain value on a ciliumnode resource by patching it +// Requires proper rbac +func patchLabel(clientset dynamic.Interface, labelValue bool, nodeName string) error { gvr := schema.GroupVersionResource{ Group: "cilium.io", Version: "v2", @@ -81,18 +81,18 @@ func patchNodeLabel(clientset dynamic.Interface, labelValue bool, nodeName strin "%s": "%v" } } - }`, nodeLabel, labelValue)) + }`, label, labelValue)) _, err := clientset.Resource(gvr). Patch(context.TODO(), nodeName, types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { - return fmt.Errorf("failed to patch %s with label %s=%v: %w", nodeName, nodeLabel, labelValue, err) + return fmt.Errorf("failed to patch %s with label %s=%v: %w", nodeName, label, labelValue, err) } return nil } // createNodeEvent creates a Kubernetes event for the specified node -func createNodeEvent(clientset *kubernetes.Clientset, nodeName string, reason, message, eventType string) error { +func createNodeEvent(clientset *kubernetes.Clientset, nodeName, reason, message, eventType string) error { node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get node UID for %s: %w", nodeName, err) @@ -275,12 +275,12 @@ func main() { for { userIPTablesRulesFound := nodeHasUserIPTablesRules(fileReader, iptablesClient) - // update node label based on whether user iptables rules were found - err = patchNodeLabel(dynamicClient, userIPTablesRulesFound, currentNodeName) + // update label based on whether user iptables rules were found + err = patchLabel(dynamicClient, userIPTablesRulesFound, currentNodeName) if err != nil { - klog.Errorf("failed to patch node label: %v", err) + klog.Errorf("failed to patch label: %v", err) } else { - klog.V(2).Infof("Successfully updated node label for %s: %s=%v", currentNodeName, nodeLabel, userIPTablesRulesFound) + klog.V(2).Infof("Successfully updated label for %s: %s=%v", currentNodeName, label, userIPTablesRulesFound) } if *sendEvents && userIPTablesRulesFound { From 6e810f860fb92586cecf855f536578e39b3c2640 Mon Sep 17 00:00:00 2001 From: QxBytes Date: Fri, 25 Jul 2025 15:30:45 -0700 Subject: [PATCH 7/7] address feedback (noop) --- azure-iptables-monitor/iptables_monitor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-iptables-monitor/iptables_monitor.go b/azure-iptables-monitor/iptables_monitor.go index 72c1c5e063..9ccdc07acf 100644 --- a/azure-iptables-monitor/iptables_monitor.go +++ b/azure-iptables-monitor/iptables_monitor.go @@ -187,6 +187,7 @@ func hasUnexpectedRules(currentRules, allowedPatterns []string) bool { if !ruleMatched { klog.Infof("Unexpected rule: %s", rule) foundUnexpectedRules = true + // continue to iterate over remaining rules to identify all unexpected rules } }