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..559f33a012 --- /dev/null +++ b/azure-iptables-monitor/Dockerfile @@ -0,0 +1,28 @@ +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 + +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..48aa3b060b --- /dev/null +++ b/azure-iptables-monitor/README.md @@ -0,0 +1,64 @@ +# azure-iptables-monitor + +`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 ciliumnode resource 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=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: `300` + - The `--events` flag enables Kubernetes event creation for rule violations. Default: `false` + - 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 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 + +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..9ccdc07acf --- /dev/null +++ b/azure-iptables-monitor/iptables_monitor.go @@ -0,0 +1,296 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "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/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" + "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", 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 label = "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 +} + +// 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", + Resource: "ciliumnodes", + } + + patch := []byte(fmt.Sprintf(`{ + "metadata": { + "labels": { + "%s": "%v" + } + } + }`, 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, label, 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 UID for %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) +} + +// 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 + // continue to iterate over remaining rules to identify all unexpected rules + } + } + + 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) + } + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + klog.Fatalf("failed to create dynamic client: %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 { + userIPTablesRulesFound := nodeHasUserIPTablesRules(fileReader, iptablesClient) + + // update label based on whether user iptables rules were found + err = patchLabel(dynamicClient, userIPTablesRulesFound, currentNodeName) + if err != nil { + klog.Errorf("failed to patch label: %v", err) + } else { + klog.V(2).Infof("Successfully updated label for %s: %s=%v", currentNodeName, label, 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) + } +} 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) + }) + } +}