From 0fb291c925a4b2413ea28b3410da3ee8ddc90276 Mon Sep 17 00:00:00 2001 From: Anurag Rajawat Date: Tue, 17 Sep 2024 18:36:34 +0530 Subject: [PATCH] refactor(*)!: Refactor for better maintainability Signed-off-by: Anurag Rajawat --- .gitignore | 2 + deployments/sentryflow.yaml | 107 ++-- sentryflow/.dockerignore | 1 + sentryflow/Dockerfile | 40 +- sentryflow/Makefile | 108 ++-- sentryflow/cmd/root.go | 53 ++ sentryflow/cmd/version.go | 20 + sentryflow/collector/collectorHandler.go | 104 ---- sentryflow/collector/envoy.go | 226 -------- sentryflow/collector/interface.go | 16 - sentryflow/collector/opentelemetry.go | 143 ----- sentryflow/collector/wasm.go | 51 -- sentryflow/config/config.go | 149 ----- sentryflow/config/default.yaml | 22 + sentryflow/core/sentryflow.go | 196 ------- sentryflow/exporter/exportAPILogs.go | 132 ----- sentryflow/exporter/exportAPIMetrics.go | 177 ------ sentryflow/exporter/exportEnvoyMetrics.go | 69 --- sentryflow/exporter/exporterHandler.go | 259 --------- sentryflow/go.mod | 98 ++-- sentryflow/go.sum | 211 +++---- sentryflow/k8s/istioPatcher.go | 246 -------- sentryflow/k8s/k8sHandler.go | 437 --------------- sentryflow/main.go | 10 +- sentryflow/pkg/config/config.go | 77 +++ sentryflow/pkg/core/sentryflow.go | 100 ++++ sentryflow/pkg/core/server.go | 100 ++++ sentryflow/pkg/exporter/exporter.go | 50 ++ sentryflow/pkg/k8s/client.go | 39 ++ sentryflow/pkg/receiver/receiver.go | 45 ++ .../receiver/svcmesh/istio/sidecar/sidecar.go | 526 ++++++++++++++++++ sentryflow/pkg/util/util.go | 22 + sentryflow/processor/apiAnalyzer.go | 95 ---- sentryflow/processor/apiClassifier.go | 215 ------- sentryflow/processor/logProcessor.go | 131 ----- sentryflow/types/types.go | 36 -- 36 files changed, 1374 insertions(+), 2939 deletions(-) create mode 100644 sentryflow/.dockerignore create mode 100644 sentryflow/cmd/root.go create mode 100644 sentryflow/cmd/version.go delete mode 100644 sentryflow/collector/collectorHandler.go delete mode 100644 sentryflow/collector/envoy.go delete mode 100644 sentryflow/collector/interface.go delete mode 100644 sentryflow/collector/opentelemetry.go delete mode 100644 sentryflow/collector/wasm.go delete mode 100644 sentryflow/config/config.go create mode 100644 sentryflow/config/default.yaml delete mode 100644 sentryflow/core/sentryflow.go delete mode 100644 sentryflow/exporter/exportAPILogs.go delete mode 100644 sentryflow/exporter/exportAPIMetrics.go delete mode 100644 sentryflow/exporter/exportEnvoyMetrics.go delete mode 100644 sentryflow/exporter/exporterHandler.go delete mode 100644 sentryflow/k8s/istioPatcher.go delete mode 100644 sentryflow/k8s/k8sHandler.go create mode 100644 sentryflow/pkg/config/config.go create mode 100644 sentryflow/pkg/core/sentryflow.go create mode 100644 sentryflow/pkg/core/server.go create mode 100644 sentryflow/pkg/exporter/exporter.go create mode 100644 sentryflow/pkg/k8s/client.go create mode 100644 sentryflow/pkg/receiver/receiver.go create mode 100644 sentryflow/pkg/receiver/svcmesh/istio/sidecar/sidecar.go create mode 100644 sentryflow/pkg/util/util.go delete mode 100644 sentryflow/processor/apiAnalyzer.go delete mode 100644 sentryflow/processor/apiClassifier.go delete mode 100644 sentryflow/processor/logProcessor.go delete mode 100644 sentryflow/types/types.go diff --git a/.gitignore b/.gitignore index 9a410bc..c6d6b91 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ go.work.sum *.swp *.swo *~ + +venv/ \ No newline at end of file diff --git a/deployments/sentryflow.yaml b/deployments/sentryflow.yaml index d38da78..008cccf 100644 --- a/deployments/sentryflow.yaml +++ b/deployments/sentryflow.yaml @@ -2,45 +2,65 @@ apiVersion: v1 kind: Namespace metadata: name: sentryflow - labels: - istio-injection: disabled # avoid Istio sidecar-injection - pod-security.kubernetes.io/audit: privileged - pod-security.kubernetes.io/enforce: privileged - pod-security.kubernetes.io/warn: privileged --- apiVersion: v1 kind: ServiceAccount metadata: + name: sentryflow namespace: sentryflow - name: sentryflow-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: sentryflow-cr + name: sentryflow rules: -- apiGroups: ["*"] - verbs: ["*"] - resources: ["*"] + - apiGroups: + - networking.istio.io + verbs: + - get + - create + - delete + resources: + - envoyfilters --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: sentryflow-rb + name: sentryflow roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: sentryflow-cr + name: sentryflow subjects: -- kind: ServiceAccount + - kind: ServiceAccount + name: sentryflow + namespace: sentryflow +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config namespace: sentryflow - name: sentryflow-sa +data: + config.yaml: |2- + receivers: + serviceMeshes: + - name: istio-sidecar + enable: true + exporter: + grpc: + port: 8080 + + debug: + enable: false + pprof: + port: 6060 --- apiVersion: apps/v1 kind: Deployment metadata: - namespace: sentryflow name: sentryflow + namespace: sentryflow spec: replicas: 1 selector: @@ -51,17 +71,38 @@ spec: labels: app: sentryflow spec: - serviceAccountName: sentryflow-sa + serviceAccountName: sentryflow containers: - - name: sentryflow - image: 5gsec/sentryflow:v0.1 - ports: - - name: otel-grpc - protocol: TCP - containerPort: 4317 - - name: sentryflow-grpc - protocol: TCP - containerPort: 8080 + - name: sentryflow + image: ttl.sh/sentryflow:24h + imagePullPolicy: Always + args: + - --config + - /var/lib/sentryflow/config.yaml + volumeMounts: + - mountPath: /var/lib/sentryflow/ + name: config + ports: + - containerPort: 8080 + name: exporter + protocol: TCP + - containerPort: 8081 + name: filter-server + protocol: TCP + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1111 + allowPrivilegeEscalation: false + terminationGracePeriodSeconds: 30 + volumes: + - name: config + configMap: + name: config + defaultMode: 420 --- apiVersion: v1 kind: Service @@ -72,11 +113,11 @@ spec: selector: app: sentryflow ports: - - name: otel-grpc - protocol: TCP - port: 4317 - targetPort: 4317 - - name: sentryflow-grpc - protocol: TCP - port: 8080 - targetPort: 8080 + - name: exporter + port: 8080 + targetPort: 8080 + protocol: TCP + - name: filter-server + port: 8081 + targetPort: 8081 + protocol: TCP diff --git a/sentryflow/.dockerignore b/sentryflow/.dockerignore new file mode 100644 index 0000000..6dd29b7 --- /dev/null +++ b/sentryflow/.dockerignore @@ -0,0 +1 @@ +bin/ \ No newline at end of file diff --git a/sentryflow/Dockerfile b/sentryflow/Dockerfile index 4c1df63..6b89c84 100644 --- a/sentryflow/Dockerfile +++ b/sentryflow/Dockerfile @@ -1,36 +1,24 @@ -# SPDX-License-Identifier: Apache-2.0 +FROM golang:1.23 AS builder -### Builder +RUN mkdir -p /protobuf/golang -FROM golang:1.21-alpine3.17 as builder - -RUN apk --no-cache update -RUN apk add --no-cache git clang llvm make gcc protobuf musl-dev -RUN apk add --update alpine-sdk - -RUN go install github.com/golang/protobuf/protoc-gen-go@latest -RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest - -RUN mkdir /sentryflow -RUN mkdir /protobuf - -WORKDIR /protobuf -COPY /protobuf . +COPY protobuf/golang /protobuf/golang WORKDIR /sentryflow -COPY /sentryflow . -RUN export CGO_ENABLED=1; export CC=gcc; -RUN go build -o sentryflow +COPY sentryflow/go.mod . +COPY sentryflow/go.sum . +RUN go mod download -### Make executable image +COPY sentryflow/cmd cmd/ +COPY sentryflow/pkg pkg/ +COPY sentryflow/main.go main.go +COPY sentryflow/Makefile Makefile -FROM alpine:3.17 as sentryflow +RUN make build -# RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories -# RUN apk --no-cache update -# RUN apk add bash +FROM gcr.io/distroless/static-debian12 -COPY --from=builder /sentryflow/sentryflow / +COPY --from=builder /sentryflow/bin/sentryflow / -CMD ["/sentryflow"] +ENTRYPOINT ["/sentryflow"] diff --git a/sentryflow/Makefile b/sentryflow/Makefile index 91e228c..621abcb 100644 --- a/sentryflow/Makefile +++ b/sentryflow/Makefile @@ -1,60 +1,64 @@ -# SPDX-License-Identifier: Apache-2.0 +BINARY_NAME ?= sentryflow +REGISTRY ?= docker.io/5gsec +VERSION ?= $(shell git rev-parse HEAD) +BUILD_TS ?= $(shell date) +DOCKER_IMAGE ?= $(REGISTRY)/$(BINARY_NAME) +DOCKER_TAG ?= ${VERSION} +CONTAINER_TOOL ?= docker -PROG_NAME = sentryflow -IMAGE_NAME = 5gsec/$(PROG_NAME) -TAG = v0.1 +.PHONY: help +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) -.PHONY: build -build: gofmt golint gosec - go mod tidy - go build -o $(PROG_NAME) - -.PHONY: clean -clean: - rm -f $(PROG_NAME) - -.PHONY: gofmt -gofmt: - cd $(CURDIR); gofmt -w -s -d $(shell find . -type f -name '*.go' -print) - -.PHONY: golint -golint: -ifeq (, $(shell which golint)) - @{ \ - set -e ;\ - GOLINT_TEMP_DIR=$$(mktemp -d) ;\ - cd $$GOLINT_TEMP_DIR ;\ - go mod init tmp ;\ - go get golang.org/x/lint/golint ;\ - go install golang.org/x/lint/golint ;\ - rm -rf $$GOLINT_TEMP_DIR ;\ - } -endif - cd $(CURDIR); golint ./... +.DEFAULT_GOAL := help + +##@ Development +.PHONY: run +run: fmt vet ## Run SentryFlow on your host + @go mod tidy;go run main.go + +.PHONY: fmt +fmt: ## Run go fmt against code + @go fmt ./... -.PHONY: gosec -gosec: -ifeq (, $(shell which gosec)) - @{ \ +.PHONY: vet +vet: ## Run go vet against code + @go vet ./... + +GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint +GOLANGCI_LINT_VERSION ?= v1.60.3 +golangci-lint: + @[ -f $(GOLANGCI_LINT) ] || { \ set -e ;\ - GOSEC_TEMP_DIR=$$(mktemp -d) ;\ - cd $$GOSEC_TEMP_DIR ;\ - go mod init tmp ;\ - go get github.com/securego/gosec/v2/cmd/gosec ;\ - go install github.com/securego/gosec/v2/cmd/gosec ;\ - rm -rf $$GOSEC_TEMP_DIR ;\ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ } -endif - cd $(CURDIR); gosec -exclude=G402 ./... -.PHONY: build-image -build-image: - docker build -t $(IMAGE_NAME):$(TAG) -f ./Dockerfile ../ +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter + @$(GOLANGCI_LINT) run + +##@ Build + +.PHONY: build +build: fmt vet ## Build SentryFlow binary + @CGO_ENABLED=0 go build -ldflags="-s \ + -X 'main.Version=${VERSION}' \ + -X 'main.CommitHash=${VERSION}' \ + -X 'main.BuildTimestamp=${BUILD_TS}'" \ + -o bin/"${BINARY_NAME}" main.go -.PHONY: clean-image -clean-image: - docker rmi $(IMAGE_NAME):$(TAG) +.PHONY: image +image: ## Build and push SentryFlow's container image + $(CONTAINER_TOOL) build -t ${DOCKER_IMAGE}:${DOCKER_TAG} -f Dockerfile ../ + $(CONTAINER_TOOL) push ${DOCKER_IMAGE}:${DOCKER_TAG} -.PHONY: run-image -run-image: - docker run -it --rm $(IMAGE_NAME):$(TAG) +PLATFORMS ?= linux/arm64,linux/amd64 +.PHONY: imagex +imagex: ## Build and push SentryFlow's container image for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name project-v3-builder + $(CONTAINER_TOOL) buildx use project-v3-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${DOCKER_IMAGE}:${DOCKER_TAG} -f Dockerfile.cross ../ || { $(CONTAINER_TOOL) buildx rm project-v3-builder; rm Dockerfile.cross; exit 1; } + - $(CONTAINER_TOOL) buildx rm project-v3-builder + rm Dockerfile.cross diff --git a/sentryflow/cmd/root.go b/sentryflow/cmd/root.go new file mode 100644 index 0000000..f454ff2 --- /dev/null +++ b/sentryflow/cmd/root.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/5GSEC/SentryFlow/pkg/core" + "github.com/5GSEC/SentryFlow/pkg/util" +) + +var ( + configFilePath string + kubeConfig string + development bool + logger *zap.SugaredLogger +) + +func init() { + RootCmd.PersistentFlags().StringVar(&configFilePath, "config", "", "config file path") + RootCmd.PersistentFlags().StringVar(&kubeConfig, "kubeconfig", "", "kubeconfig file path") + RootCmd.PersistentFlags().BoolVar(&development, "development", false, "run in development mode") +} + +var RootCmd = &cobra.Command{ + Use: "sentryflow", + Run: func(cmd *cobra.Command, args []string) { + run() + }, +} + +func run() { + initLogger(development) + logBuildInfo() + ctx := context.WithValue(ctrl.SetupSignalHandler(), util.LoggerCtxKey, logger) + core.Run(ctx, configFilePath, kubeConfig) +} + +func initLogger(development bool) { + cfg := zap.NewProductionConfig() + cfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + if development { + cfg = zap.NewDevelopmentConfig() + cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + } + cfg.EncoderConfig.TimeKey = "timestamp" + cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + coreLogger, _ := cfg.Build() + logger = coreLogger.Sugar() +} diff --git a/sentryflow/cmd/version.go b/sentryflow/cmd/version.go new file mode 100644 index 0000000..5a78250 --- /dev/null +++ b/sentryflow/cmd/version.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "runtime" + "runtime/debug" +) + +func logBuildInfo() { + info, _ := debug.ReadBuildInfo() + vcsRev := "" + vcsTime := "" + for _, s := range info.Settings { + if s.Key == "vcs.revision" { + vcsRev = s.Value + } else if s.Key == "vcs.time" { + vcsTime = s.Value + } + } + logger.Infof("Git commit: %s, build time: %s, build version: %s, go os/arch: %s/%s\n", vcsRev, vcsTime, info.Main.Version, runtime.GOOS, runtime.GOARCH) +} diff --git a/sentryflow/collector/collectorHandler.go b/sentryflow/collector/collectorHandler.go deleted file mode 100644 index 043c569..0000000 --- a/sentryflow/collector/collectorHandler.go +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package collector - -import ( - "fmt" - "github.com/5gsec/SentryFlow/config" - "google.golang.org/grpc" - "log" - "net" - "net/http" -) - -// == // - -// ColH global reference for Collector Handler -var ColH *ColHandler - -// init Function -func init() { - ColH = NewCollectorHandler() -} - -// ColHandler Structure -type ColHandler struct { - colService net.Listener - grpcServer *grpc.Server - collectors []collectorInterface -} - -// NewCollectorHandler Function -func NewCollectorHandler() *ColHandler { - ch := &ColHandler{ - collectors: make([]collectorInterface, 0), - } - return ch -} - -// == // - -// StartCollector Function -func StartCollector() bool { - // Make a string with the given collector address and port - collectorService := fmt.Sprintf("%s:%s", config.GlobalConfig.CollectorAddr, config.GlobalConfig.CollectorPort) - - // Start listening gRPC port - colService, err := net.Listen("tcp", collectorService) - if err != nil { - log.Printf("[Collector] Failed to listen at %s: %v", collectorService, err) - return false - } - ColH.colService = colService - - log.Printf("[Collector] Listening Collector gRPC services (%s)", collectorService) - - // Create gRPC Service - gRPCServer := grpc.NewServer() - ColH.grpcServer = gRPCServer - - // initialize OpenTelemetry collector - ColH.collectors = append(ColH.collectors, newOpenTelemetryLogsServer()) - - // initialize Envoy collectors for AccessLogs and Metrics - ColH.collectors = append(ColH.collectors, newEnvoyAccessLogsServer()) - ColH.collectors = append(ColH.collectors, newEnvoyMetricsServer()) - - // register services - for _, col := range ColH.collectors { - col.registerService(ColH.grpcServer) - } - - log.Print("[Collector] Initialized Collector gRPC services") - - // Serve gRPC Service - go ColH.grpcServer.Serve(ColH.colService) - - log.Print("[Collector] Serving Collector gRPC services") - - // Start the http server - address := fmt.Sprintf("%s:%s", config.GlobalConfig.ApiLogCollectorAddr, config.GlobalConfig.ApiLogCollectorPort) - log.Print("[Collector] Serving Collector http service on ", address) - go func() { - // Create a new HTTP server - http.HandleFunc("/api/v1/events", DataHandler) - err = http.ListenAndServe(address, nil) - if err != nil { - log.Println("[Collector] Error serving Collector http service on ", err.Error()) - panic(err) - } - }() - - return true -} - -// StopCollector Function -func StopCollector() bool { - ColH.grpcServer.GracefulStop() - - log.Print("[Collector] Gracefully stopped Collector gRPC services") - - return true -} - -// == // diff --git a/sentryflow/collector/envoy.go b/sentryflow/collector/envoy.go deleted file mode 100644 index 7fbccee..0000000 --- a/sentryflow/collector/envoy.go +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package collector - -import ( - "io" - "log" - "strconv" - - "github.com/5gsec/SentryFlow/k8s" - "github.com/5gsec/SentryFlow/processor" - "github.com/5gsec/SentryFlow/protobuf" - "github.com/5gsec/SentryFlow/types" - - envoyAccLogsData "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3" - envoyAccLogs "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" - envoyMetrics "github.com/envoyproxy/go-control-plane/envoy/service/metrics/v3" - - "google.golang.org/grpc" -) - -// == // - -// EnvoyAccessLogsServer Structure -type EnvoyAccessLogsServer struct { - envoyAccLogs.UnimplementedAccessLogServiceServer - collectorInterface -} - -// newEnvoyAccessLogsServer Function -func newEnvoyAccessLogsServer() *EnvoyAccessLogsServer { - ret := &EnvoyAccessLogsServer{} - return ret -} - -// registerService Function -func (evyAccLogs *EnvoyAccessLogsServer) registerService(server *grpc.Server) { - envoyAccLogs.RegisterAccessLogServiceServer(server, evyAccLogs) -} - -// == // - -// EnvoyMetricsServer Structure -type EnvoyMetricsServer struct { - envoyMetrics.UnimplementedMetricsServiceServer - collectorInterface -} - -// newEnvoyMetricsServer Function -func newEnvoyMetricsServer() *EnvoyMetricsServer { - ret := &EnvoyMetricsServer{} - return ret -} - -// registerService Function -func (evyMetrics *EnvoyMetricsServer) registerService(server *grpc.Server) { - envoyMetrics.RegisterMetricsServiceServer(server, evyMetrics) -} - -// == // - -// generateAPILogsFromEnvoy Function -func generateAPILogsFromEnvoy(entry *envoyAccLogsData.HTTPAccessLogEntry) *protobuf.APILog { - comm := entry.GetCommonProperties() - timeStamp := comm.GetStartTime().Seconds - - srcInform := entry.GetCommonProperties().GetDownstreamRemoteAddress().GetSocketAddress() - srcIP := srcInform.GetAddress() - srcPort := strconv.Itoa(int(srcInform.GetPortValue())) - src := k8s.LookupK8sResource(srcIP) - - dstInform := entry.GetCommonProperties().GetUpstreamRemoteAddress().GetSocketAddress() - dstIP := dstInform.GetAddress() - dstPort := strconv.Itoa(int(dstInform.GetPortValue())) - dst := k8s.LookupK8sResource(dstIP) - - request := entry.GetRequest() - response := entry.GetResponse() - - protocol := entry.GetProtocolVersion().String() - method := request.GetRequestMethod().String() - path := request.GetPath() - resCode := response.GetResponseCode().GetValue() - - envoyAPILog := &protobuf.APILog{ - Id: 0, // @todo zero for now - TimeStamp: strconv.FormatInt(timeStamp, 10), - - SrcNamespace: src.Namespace, - SrcName: src.Name, - SrcLabel: src.Labels, - SrcIP: srcIP, - SrcPort: srcPort, - SrcType: types.K8sResourceTypeToString(src.Type), - - DstNamespace: dst.Namespace, - DstName: dst.Name, - DstLabel: dst.Labels, - DstIP: dstIP, - DstPort: dstPort, - DstType: types.K8sResourceTypeToString(dst.Type), - - Protocol: protocol, - Method: method, - Path: path, - ResponseCode: int32(resCode), - } - - return envoyAPILog -} - -// StreamAccessLogs Function -func (evyAccLogs *EnvoyAccessLogsServer) StreamAccessLogs(stream envoyAccLogs.AccessLogService_StreamAccessLogsServer) error { - for { - event, err := stream.Recv() - if err == io.EOF { - return nil - } else if err != nil { - log.Printf("[EnvoyAPILogs] Failed to receive an event: %v", err) - return err - } - - if event.GetHttpLogs() != nil { - for _, entry := range event.GetHttpLogs().LogEntry { - envoyAPILog := generateAPILogsFromEnvoy(entry) - processor.InsertAPILog(envoyAPILog) - } - } - } -} - -// == // - -// generateMetricsFromEnvoy Function -func generateMetricsFromEnvoy(event *envoyMetrics.StreamMetricsMessage, metaData map[string]interface{}) *protobuf.EnvoyMetrics { - envoyMetrics := &protobuf.EnvoyMetrics{ - TimeStamp: "", - - Namespace: metaData["NAMESPACE"].(string), - Name: metaData["NAME"].(string), - IPAddress: metaData["INSTANCE_IPS"].(string), - Labels: k8s.LookupK8sResource(metaData["INSTANCE_IPS"].(string)).Labels, - - Metrics: make(map[string]*protobuf.MetricValue), - } - - envoyMetrics.Metrics["GAUGE"] = &protobuf.MetricValue{ - Value: make(map[string]string), - } - - envoyMetrics.Metrics["COUNTER"] = &protobuf.MetricValue{ - Value: make(map[string]string), - } - - envoyMetrics.Metrics["HISTOGRAM"] = &protobuf.MetricValue{ - Value: make(map[string]string), - } - - envoyMetrics.Metrics["SUMMARY"] = &protobuf.MetricValue{ - Value: make(map[string]string), - } - - for _, metric := range event.GetEnvoyMetrics() { - metricType := metric.GetType().String() - metricName := metric.GetName() - - if envoyMetrics.Metrics[metricType].Value == nil { - continue - } - - for _, metricDetail := range metric.GetMetric() { - var metricValue string - - if envoyMetrics.TimeStamp == "" { - envoyMetrics.TimeStamp = strconv.FormatInt(metricDetail.GetTimestampMs(), 10) - } - - if metricType == "GAUGE" { - metricValue = strconv.FormatFloat(metricDetail.GetGauge().GetValue(), 'f', -1, 64) - } - - if metricType == "COUNTER" { - metricValue = strconv.FormatFloat(metricDetail.GetCounter().GetValue(), 'f', -1, 64) - } - - if metricType == "HISTOGRAM" { - metricValue = strconv.FormatUint(metricDetail.GetHistogram().GetSampleCount(), 10) - } - - if metricType == "SUMMARY" { - metricValue = strconv.FormatUint(metricDetail.GetHistogram().GetSampleCount(), 10) - } - - envoyMetrics.Metrics[metricType].Value[metricName] = metricValue - } - } - - return envoyMetrics -} - -// StreamMetrics Function -func (evyMetrics *EnvoyMetricsServer) StreamMetrics(stream envoyMetrics.MetricsService_StreamMetricsServer) error { - event, err := stream.Recv() - if err == io.EOF { - return nil - } else if err != nil { - log.Printf("[EnvoyMetrics] Failed to receive an event: %v", err) - return err - } - - err = event.ValidateAll() - if err != nil { - log.Printf("[EnvoyMetrics] Failed to validate an event: %v", err) - } - - identifier := event.GetIdentifier() - if identifier != nil { - metaData := identifier.GetNode().GetMetadata().AsMap() - envoyMetrics := generateMetricsFromEnvoy(event, metaData) - processor.InsertMetrics(envoyMetrics) - } - - return nil -} - -// == // diff --git a/sentryflow/collector/interface.go b/sentryflow/collector/interface.go deleted file mode 100644 index a610c4f..0000000 --- a/sentryflow/collector/interface.go +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package collector - -import ( - "google.golang.org/grpc" -) - -// == // - -// collectorInterface Interface -type collectorInterface interface { - registerService(server *grpc.Server) -} - -// == // diff --git a/sentryflow/collector/opentelemetry.go b/sentryflow/collector/opentelemetry.go deleted file mode 100644 index 05f9830..0000000 --- a/sentryflow/collector/opentelemetry.go +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package collector - -import ( - "context" - "strconv" - "strings" - - "github.com/5gsec/SentryFlow/k8s" - "github.com/5gsec/SentryFlow/processor" - "github.com/5gsec/SentryFlow/protobuf" - "github.com/5gsec/SentryFlow/types" - otelLogs "go.opentelemetry.io/proto/otlp/collector/logs/v1" - "google.golang.org/grpc" -) - -// == // - -// OpenTelemetryLogsServer structure -type OpenTelemetryLogsServer struct { - otelLogs.UnimplementedLogsServiceServer - collectorInterface -} - -// newOpenTelemetryLogsServer Function -func newOpenTelemetryLogsServer() *OpenTelemetryLogsServer { - ret := &OpenTelemetryLogsServer{} - return ret -} - -// registerService Function -func (otlLogs *OpenTelemetryLogsServer) registerService(server *grpc.Server) { - otelLogs.RegisterLogsServiceServer(server, otlLogs) -} - -// == // - -// generateAPILogsFromOtel Function -func generateAPILogsFromOtel(logText string) []*protobuf.APILog { - apiLogs := make([]*protobuf.APILog, 0) - - // Preprocess redundant chars - logText = strings.ReplaceAll(logText, `\"`, "") - logText = strings.ReplaceAll(logText, `}`, "") - - // Split logs by log_records, this is a single access log instance - parts := strings.Split(logText, "log_records") - if len(parts) == 0 { - return nil - } - - // Ignore the first entry (the metadata "resource_logs:{resource:{ scope_logs:{" part) - for _, accessLog := range parts[0:] { - var srcIP string - var srcPort string - var dstIP string - var dstPort string - - if len(accessLog) == 0 { - continue - } - - index := strings.Index(accessLog, "string_value:\"") - if index == -1 { - continue - } - - words := strings.Fields(accessLog[index+len("string_value:\""):]) - - timeStamp := words[0] - method := words[1] - path := words[2] - protocol := words[3] - resCode, _ := strconv.ParseInt(words[4], 10, 64) - - srcInform := words[21] - - // Extract the left and right words based on the colon delimiter (ADDR:PORT) - colonIndex := strings.LastIndex(srcInform, ":") - if colonIndex > 0 && colonIndex < len(srcInform)-1 { - srcIP = strings.TrimSpace(srcInform[:colonIndex]) - srcPort = strings.TrimSpace(srcInform[colonIndex+1:]) - } - src := k8s.LookupK8sResource(srcIP) - - dstInform := words[20] - - // Extract the left and right words based on the colon delimiter (ADDR:PORT) - colonIndex = strings.LastIndex(dstInform, ":") - if colonIndex > 0 && colonIndex < len(dstInform)-1 { - dstIP = strings.TrimSpace(dstInform[:colonIndex]) - dstPort = strings.TrimSpace(dstInform[colonIndex+1:]) - } - dst := k8s.LookupK8sResource(dstIP) - - // Create APILog - apiLog := protobuf.APILog{ - Id: 0, // @todo zero for now - TimeStamp: timeStamp, - - SrcNamespace: src.Namespace, - SrcName: src.Name, - SrcLabel: src.Labels, - SrcIP: srcIP, - SrcPort: srcPort, - SrcType: types.K8sResourceTypeToString(src.Type), - - DstNamespace: dst.Namespace, - DstName: dst.Name, - DstLabel: dst.Labels, - DstIP: dstIP, - DstPort: dstPort, - DstType: types.K8sResourceTypeToString(dst.Type), - - Protocol: protocol, - Method: method, - Path: path, - ResponseCode: int32(resCode), - } - - apiLogs = append(apiLogs, &apiLog) - } - - return apiLogs -} - -// Export Function for Log.Export in OpenTelemetry format -func (otlLogs *OpenTelemetryLogsServer) Export(_ context.Context, req *otelLogs.ExportLogsServiceRequest) (*otelLogs.ExportLogsServiceResponse, error) { - apiLogs := generateAPILogsFromOtel(req.String()) - for _, apiLog := range apiLogs { - processor.InsertAPILog(apiLog) - } - - // @todo not consider partial success - ret := otelLogs.ExportLogsServiceResponse{ - PartialSuccess: nil, - } - - return &ret, nil -} - -// == // diff --git a/sentryflow/collector/wasm.go b/sentryflow/collector/wasm.go deleted file mode 100644 index cd1379c..0000000 --- a/sentryflow/collector/wasm.go +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package collector - -import ( - "encoding/json" - "github.com/5gsec/SentryFlow/processor" - "github.com/5gsec/SentryFlow/protobuf" - "io/ioutil" - "log" - "net/http" -) - -// Handler for the HTTP endpoint to receive api events from WASM filter -func DataHandler(w http.ResponseWriter, r *http.Request) { - // Check if the request is POST - if r.Method != http.MethodPost { - http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) - return - } - - // Read the request body - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read request body", http.StatusInternalServerError) - return - } - // Parse the JSON data into the TelemetryData struct - var apiLog *protobuf.APILogV2 - err = json.Unmarshal(body, &apiLog) - if err != nil { - log.Print("failed to parse json") - log.Print(err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - // Check protocol version - if r.ProtoMajor == 2 { - apiLog.Protocol = "HTTP/2.0" - } else if r.ProtoMajor == 1 && r.ProtoMinor == 1 { - apiLog.Protocol = "HTTP/1.1" - } else if r.ProtoMajor == 1 && r.ProtoMinor == 0 { - apiLog.Protocol = "HTTP/1.0" - } else { - apiLog.Protocol = "Unknown" - } - processor.InsertAPILog(apiLog) - - // Log the received telemetry data - log.Printf("Received data: %+v\n", apiLog) -} diff --git a/sentryflow/config/config.go b/sentryflow/config/config.go deleted file mode 100644 index 15e2c7e..0000000 --- a/sentryflow/config/config.go +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package config - -import ( - "flag" - "fmt" - "log" - "strings" - - "github.com/spf13/viper" -) - -// SentryFlowConfig structure -type SentryFlowConfig struct { - CollectorAddr string // Address for Collector gRPC - CollectorPort string // Port for Collector gRPC - ApiLogCollectorAddr string // Address for API Log HTTP Collector - ApiLogCollectorPort string // Port for API Log HTTP Collector - - ExporterAddr string // IP address to use for exporter gRPC - ExporterPort string // Port to use for exporter gRPC - - PatchingNamespaces bool // Enable/Disable patching namespaces with 'istio-injection' - RestartingPatchedDeployments bool // Enable/Disable restarting deployments after patching - - AggregationPeriod int // Period for aggregating metrics - CleanUpPeriod int // Period for cleaning up outdated metrics - - AIEngineService string // Address for AI Engine - AIEngineServicePort string // Port for AI Engine - AIEngineBatchSize int // Batch Size to send APIs to AI Engine - - Debug bool // Enable/Disable SentryFlow debug mode -} - -// GlobalConfig Global configuration for SentryFlow -var GlobalConfig SentryFlowConfig - -// init Function -func init() { - _ = LoadConfig() -} - -// Config const -const ( - CollectorAddr string = "collectorAddr" - CollectorPort string = "collectorPort" - ApiLogCollectorAddr string = "apiLogCollectorAddr" - ApiLogCollectorPort string = "apiLogCollectorPort" - - ExporterAddr string = "exporterAddr" - ExporterPort string = "exporterPort" - - PatchingNamespaces string = "patchingNamespaces" - RestartingPatchedDeployments string = "restartingPatchedDeployments" - - AggregationPeriod string = "aggregationPeriod" - CleanUpPeriod string = "cleanUpPeriod" - - AIEngineService string = "aiEngineService" - AIEngineServicePort string = "aiEngineServicePort" - AIEngineBatchSize string = "aiEngineBatchSize" - - Debug string = "debug" -) - -func readCmdLineParams() { - collectorAddrStr := flag.String(CollectorAddr, "0.0.0.0", "Address for Collector gRPC") - collectorPortStr := flag.String(CollectorPort, "4317", "Port for Collector gRPC") - apiLogCollectorAddrStr := flag.String(ApiLogCollectorAddr, "0.0.0.0", "Address for API log HTTP Colletor") - apiLogCollectorPortStr := flag.String(ApiLogCollectorPort, "8081", "Port for API log HTTP Colletor") - - exporterAddrStr := flag.String(ExporterAddr, "0.0.0.0", "Address for Exporter gRPC") - exporterPortStr := flag.String(ExporterPort, "8080", "Port for Exporter gRPC") - - patchingNamespacesB := flag.Bool(PatchingNamespaces, false, "Enable patching 'istio-injection' to all namespaces") - restartingPatchedDeploymentsB := flag.Bool(RestartingPatchedDeployments, false, "Enable restarting the deployments in all patched namespaces") - - aggregationPeriodInt := flag.Int(AggregationPeriod, 1, "Period for aggregating metrics") - cleanUpPeriodInt := flag.Int(CleanUpPeriod, 5, "Period for cleanning up outdated metrics") - - aiEngineServiceStr := flag.String(AIEngineService, "ai-engine.sentryflow.svc.cluster.local", "Address for SentryFlow AI Engine") - aiEngineServicePortStr := flag.String(AIEngineServicePort, "5000", "Port for SentryFlow AI Engine") - aiEngineBatchSizeInt := flag.Int(AIEngineBatchSize, 5, "Batch size to send APIs to SentryFlow AI Engine") - - configDebugB := flag.Bool(Debug, false, "Enable debugging mode") - - var flags []string - flag.VisitAll(func(f *flag.Flag) { - kv := fmt.Sprintf("%s:%v", f.Name, f.Value) - flags = append(flags, kv) - }) - log.Printf("Arguments [%s]", strings.Join(flags, " ")) - - flag.Parse() - - viper.SetDefault(CollectorAddr, *collectorAddrStr) - viper.SetDefault(CollectorPort, *collectorPortStr) - viper.SetDefault(ApiLogCollectorAddr, *apiLogCollectorAddrStr) - viper.SetDefault(ApiLogCollectorPort, *apiLogCollectorPortStr) - - viper.SetDefault(ExporterAddr, *exporterAddrStr) - viper.SetDefault(ExporterPort, *exporterPortStr) - - viper.SetDefault(PatchingNamespaces, *patchingNamespacesB) - viper.SetDefault(RestartingPatchedDeployments, *restartingPatchedDeploymentsB) - - viper.SetDefault(AggregationPeriod, *aggregationPeriodInt) - viper.SetDefault(CleanUpPeriod, *cleanUpPeriodInt) - - viper.SetDefault(AIEngineService, *aiEngineServiceStr) - viper.SetDefault(AIEngineServicePort, *aiEngineServicePortStr) - viper.SetDefault(AIEngineBatchSize, *aiEngineBatchSizeInt) - - viper.SetDefault(Debug, *configDebugB) -} - -// LoadConfig Load configuration -func LoadConfig() error { - // Read configuration from command line - readCmdLineParams() - - // Read environment variable, those are upper-cased - viper.AutomaticEnv() - - GlobalConfig.CollectorAddr = viper.GetString(CollectorAddr) - GlobalConfig.CollectorPort = viper.GetString(CollectorPort) - GlobalConfig.ApiLogCollectorAddr = viper.GetString(ApiLogCollectorAddr) - GlobalConfig.ApiLogCollectorPort = viper.GetString(ApiLogCollectorPort) - GlobalConfig.ExporterAddr = viper.GetString(ExporterAddr) - GlobalConfig.ExporterPort = viper.GetString(ExporterPort) - - GlobalConfig.PatchingNamespaces = viper.GetBool(PatchingNamespaces) - GlobalConfig.RestartingPatchedDeployments = viper.GetBool(RestartingPatchedDeployments) - - GlobalConfig.AggregationPeriod = viper.GetInt(AggregationPeriod) - GlobalConfig.CleanUpPeriod = viper.GetInt(CleanUpPeriod) - - GlobalConfig.AIEngineService = viper.GetString(AIEngineService) - GlobalConfig.AIEngineServicePort = viper.GetString(AIEngineServicePort) - GlobalConfig.AIEngineBatchSize = viper.GetInt(AIEngineBatchSize) - - GlobalConfig.Debug = viper.GetBool(Debug) - - log.Printf("Configuration [%+v]", GlobalConfig) - - return nil -} diff --git a/sentryflow/config/default.yaml b/sentryflow/config/default.yaml new file mode 100644 index 0000000..6179851 --- /dev/null +++ b/sentryflow/config/default.yaml @@ -0,0 +1,22 @@ +receivers: # aka sources + serviceMeshes: + - name: istio-sidecar + enable: true + others: # TBD + - name: "coroot" + # Either gRPC or HTTP not both + grpc: + url: localhost + port: 1234 + http: + url: localhost + port: 4321 + +exporter: + grpc: + port: 8080 + +debug: + enable: false + pprof: + port: 6060 diff --git a/sentryflow/core/sentryflow.go b/sentryflow/core/sentryflow.go deleted file mode 100644 index 886a2ae..0000000 --- a/sentryflow/core/sentryflow.go +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package core - -import ( - "log" - "os" - "os/signal" - "sync" - "syscall" - - "github.com/5gsec/SentryFlow/collector" - "github.com/5gsec/SentryFlow/config" - "github.com/5gsec/SentryFlow/exporter" - "github.com/5gsec/SentryFlow/k8s" - "github.com/5gsec/SentryFlow/processor" -) - -// == // - -// StopChan Channel -var StopChan chan struct{} - -// init Function -func init() { - StopChan = make(chan struct{}) -} - -// SentryFlowService Structure -type SentryFlowService struct { - waitGroup *sync.WaitGroup -} - -// NewSentryFlow Function -func NewSentryFlow() *SentryFlowService { - sf := new(SentryFlowService) - sf.waitGroup = new(sync.WaitGroup) - return sf -} - -// DestroySentryFlow Function -func (sf *SentryFlowService) DestroySentryFlow() { - close(StopChan) - - // Remove SentryFlow collector config from Kubernetes - if k8s.UnpatchIstioConfigMap() { - log.Print("[SentryFlow] Unpatched Istio ConfigMap") - } else { - log.Print("[SentryFlow] Failed to unpatch Istio ConfigMap") - } - - // Stop collector - if collector.StopCollector() { - log.Print("[SentryFlow] Stopped Collectors") - } else { - log.Print("[SentryFlow] Failed to stop Collectors") - } - - // Stop Log Processor - if processor.StopLogProcessor() { - log.Print("[SentryFlow] Stopped Log Processors") - } else { - log.Print("[SentryFlow] Failed to stop Log Processors") - } - - // Stop API Aanalyzer - if processor.StopAPIAnalyzer() { - log.Print("[SentryFlow] Stopped API Analyzer") - } else { - log.Print("[SentryFlow] Failed to stop API Analyzer") - } - - // Stop API classifier - // if processor.StopAPIClassifier() { - // log.Print("[SentryFlow] Stopped API Classifier") - // } else { - // log.Print("[SentryFlow] Failed to stop API Classifier") - // } - - // Stop exporter - if exporter.StopExporter() { - log.Print("[SentryFlow] Stopped Exporters") - } else { - log.Print("[SentryFlow] Failed to stop Exporters") - } - - log.Print("[SentryFlow] Waiting for routine terminations") - - sf.waitGroup.Wait() - - log.Print("[SentryFlow] Terminated SentryFlow") -} - -// == // - -// GetOSSigChannel Function -func GetOSSigChannel() chan os.Signal { - c := make(chan os.Signal, 1) - - signal.Notify(c, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT, - os.Interrupt) - - return c -} - -// == // - -// SentryFlow Function -func SentryFlow() { - sf := NewSentryFlow() - - log.Print("[SentryFlow] Initializing SentryFlow") - - // == // - - // Initialize Kubernetes client - if !k8s.InitK8sClient() { - sf.DestroySentryFlow() - return - } - - // Start Kubernetes informers - k8s.RunInformers(StopChan, sf.waitGroup) - - // Patch Istio ConfigMap - if !k8s.PatchIstioConfigMap() { - sf.DestroySentryFlow() - return - } - - // Patch Namespaces - if config.GlobalConfig.PatchingNamespaces { - if !k8s.PatchNamespaces() { - sf.DestroySentryFlow() - return - } - } - - // Patch Deployments - if config.GlobalConfig.RestartingPatchedDeployments { - if !k8s.RestartDeployments() { - sf.DestroySentryFlow() - return - } - } - - // == // - - // Start collector - if !collector.StartCollector() { - sf.DestroySentryFlow() - return - } - - // Start log processor - if !processor.StartLogProcessor(sf.waitGroup) { - sf.DestroySentryFlow() - return - } - - // Start API analyzer - if !processor.StartAPIAnalyzer(sf.waitGroup) { - sf.DestroySentryFlow() - return - } - - // Start API classifier - // if !processor.StartAPIClassifier(sf.waitGroup) { - // sf.DestroySentryFlow() - // return - // } - - // Start exporter - if !exporter.StartExporter(sf.waitGroup) { - sf.DestroySentryFlow() - return - } - - log.Print("[SentryFlow] Initialization is completed") - - // == // - - // listen for interrupt signals - sigChan := GetOSSigChannel() - <-sigChan - log.Print("Got a signal to terminate SentryFlow") - - // == // - - // Destroy SentryFlow - sf.DestroySentryFlow() -} diff --git a/sentryflow/exporter/exportAPILogs.go b/sentryflow/exporter/exportAPILogs.go deleted file mode 100644 index 5fe1f27..0000000 --- a/sentryflow/exporter/exportAPILogs.go +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package exporter - -import ( - "errors" - "fmt" - "log" - "sort" - "strings" - - "github.com/5gsec/SentryFlow/protobuf" -) - -// == // - -// apiLogStreamInform structure -type apiLogStreamInform struct { - Hostname string - IPAddress string - - stream protobuf.SentryFlow_GetAPILogServer - error chan error -} - -// apiLogStreamInformV2 structure -type apiLogStreamInformV2 struct { - Hostname string - IPAddress string - - stream protobuf.SentryFlow_GetAPILogV2Server - error chan error -} - -// GetAPILog Function (for gRPC) -func (exs *ExpService) GetAPILog(info *protobuf.ClientInfo, stream protobuf.SentryFlow_GetAPILogServer) error { - log.Printf("[Exporter] Client %s (%s) connected (GetAPILog)", info.HostName, info.IPAddress) - - currExporter := &apiLogStreamInform{ - Hostname: info.HostName, - IPAddress: info.IPAddress, - stream: stream, - } - - ExpH.exporterLock.Lock() - ExpH.apiLogExporters = append(ExpH.apiLogExporters, currExporter) - ExpH.exporterLock.Unlock() - - return <-currExporter.error -} - -// GetAPILogV2 Function (for gRPC) -func (exs *ExpService) GetAPILogV2(info *protobuf.ClientInfo, stream protobuf.SentryFlow_GetAPILogV2Server) error { - log.Printf("[Exporter] Client %s (%s) connected (GetAPILogV2)", info.HostName, info.IPAddress) - - currExporter := &apiLogStreamInformV2{ - Hostname: info.HostName, - IPAddress: info.IPAddress, - stream: stream, - } - - ExpH.exporterLock.Lock() - ExpH.apiLogExportersV2 = append(ExpH.apiLogExportersV2, currExporter) - ExpH.exporterLock.Unlock() - - return <-currExporter.error -} - -// SendAPILogs Function -func (exp *ExpHandler) SendAPILogs(apiLog *protobuf.APILog) error { - failed := 0 - total := len(exp.apiLogExporters) - - for _, exporter := range exp.apiLogExporters { - log.Print("Sending api log!!!!") - log.Printf("Sending api log right here!!!!! %+v\n", apiLog) - if err := exporter.stream.Send(apiLog); err != nil { - log.Printf("[Exporter] Failed to export an API log to %s (%s): %v", exporter.Hostname, exporter.IPAddress, err) - failed++ - } - } - - if failed != 0 { - msg := fmt.Sprintf("[Exporter] Failed to export API logs properly (%d/%d failed)", failed, total) - return errors.New(msg) - } - - return nil -} - -// SendAPILogsV2 Function -func (exp *ExpHandler) SendAPILogsV2(apiLog *protobuf.APILogV2) error { - failed := 0 - total := len(exp.apiLogExportersV2) - - for _, exporter := range exp.apiLogExportersV2 { - if err := exporter.stream.Send(apiLog); err != nil { - log.Printf("[Exporter] Failed to export an API log(V2) to %s (%s): %v", exporter.Hostname, exporter.IPAddress, err) - failed++ - } - } - - if failed != 0 { - msg := fmt.Sprintf("[Exporter] Failed to export API logs(V2) properly (%d/%d failed)", failed, total) - return errors.New(msg) - } - - return nil -} - -// == // - -// InsertAPILog Function -func InsertAPILog(apiLog interface{}) { - switch data := apiLog.(type) { - case *protobuf.APILog: - ExpH.exporterAPILogs <- data - // Make a string with labels - var labelString []string - for k, v := range data.SrcLabel { - labelString = append(labelString, fmt.Sprintf("%s:%s", k, v)) - } - sort.Strings(labelString) - - // Update Stats per namespace and per labels - UpdateStats(data.SrcNamespace, strings.Join(labelString, ","), data.GetPath()) - case *protobuf.APILogV2: - ExpH.exporterAPILogsV2 <- data - } -} - -// == // diff --git a/sentryflow/exporter/exportAPIMetrics.go b/sentryflow/exporter/exportAPIMetrics.go deleted file mode 100644 index 65502fb..0000000 --- a/sentryflow/exporter/exportAPIMetrics.go +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package exporter - -import ( - "errors" - "fmt" - "log" - "time" - - "github.com/5gsec/SentryFlow/config" - "github.com/5gsec/SentryFlow/protobuf" -) - -// == // - -// Stats Structure -type Stats struct { - Count int -} - -// StatsPerLabel structure -type StatsPerLabel struct { - APIs map[string]Stats - LastUpdated uint64 -} - -// == // - -// apiMetricStreamInform structure -type apiMetricStreamInform struct { - Hostname string - IPAddress string - - apiMetricsStream protobuf.SentryFlow_GetAPIMetricsServer - - error chan error -} - -// GetAPIMetrics Function (for gRPC) -func (exs *ExpService) GetAPIMetrics(info *protobuf.ClientInfo, stream protobuf.SentryFlow_GetAPIMetricsServer) error { - log.Printf("[Exporter] Client %s (%s) connected (GetAPIMetrics)", info.HostName, info.IPAddress) - - currExporter := &apiMetricStreamInform{ - Hostname: info.HostName, - IPAddress: info.IPAddress, - apiMetricsStream: stream, - } - - ExpH.exporterLock.Lock() - ExpH.apiMetricsExporters = append(ExpH.apiMetricsExporters, currExporter) - ExpH.exporterLock.Unlock() - - return <-currExporter.error -} - -// SendAPIMetrics Function -func (exp *ExpHandler) SendAPIMetrics(apiMetrics *protobuf.APIMetrics) error { - failed := 0 - total := len(exp.apiMetricsExporters) - - for _, exporter := range exp.apiMetricsExporters { - if err := exporter.apiMetricsStream.Send(apiMetrics); err != nil { - log.Printf("[Exporter] Failed to export API metrics to %s (%s): %v", exporter.Hostname, exporter.IPAddress, err) - failed++ - } - } - - if failed != 0 { - msg := fmt.Sprintf("[Exporter] Failed to export API metrics properly (%d/%d failed)", failed, total) - return errors.New(msg) - } - - return nil -} - -// == // - -// UpdateStats Function -func UpdateStats(namespace string, label string, api string) { - ExpH.statsPerLabelLock.RLock() - defer ExpH.statsPerLabelLock.RUnlock() - - // Check if namespace+label exists - if _, ok := ExpH.statsPerLabel[namespace+label]; !ok { - ExpH.statsPerLabel[namespace+label] = StatsPerLabel{ - APIs: make(map[string]Stats), - LastUpdated: uint64(time.Now().Unix()), - } - } - - statsPerLabel := ExpH.statsPerLabel[namespace+label] - statsPerLabel.LastUpdated = uint64(time.Now().Unix()) - - // Check if API exists - if _, ok := statsPerLabel.APIs[api]; !ok { - init := Stats{ - Count: 1, - } - statsPerLabel.APIs[api] = init - } else { - stats := statsPerLabel.APIs[api] - stats.Count++ - statsPerLabel.APIs[api] = stats - } - - ExpH.statsPerLabel[namespace+label] = statsPerLabel -} - -// AggregateAPIMetrics Function -func AggregateAPIMetrics() { - ticker := time.NewTicker(time.Duration(config.GlobalConfig.AggregationPeriod) * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - ExpH.statsPerLabelLock.RLock() - - APIMetrics := make(map[string]uint64) - - for _, statsPerLabel := range ExpH.statsPerLabel { - for api, stats := range statsPerLabel.APIs { - APIMetrics[api] = uint64(stats.Count) - } - } - - if len(APIMetrics) > 0 { - err := ExpH.SendAPIMetrics(&protobuf.APIMetrics{PerAPICounts: APIMetrics}) - if err != nil { - log.Printf("[Envoy] Failed to export API metrics: %v", err) - return - } - } - - ExpH.statsPerLabelLock.RUnlock() - case <-ExpH.stopChan: - return - } - } -} - -// CleanUpOutdatedStats Function -func CleanUpOutdatedStats() { - ticker := time.NewTicker(time.Duration(config.GlobalConfig.CleanUpPeriod) * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - ExpH.statsPerLabelLock.Lock() - - cleanUpTime := uint64((time.Now().Add(-time.Duration(config.GlobalConfig.CleanUpPeriod) * time.Second)).Unix()) - labelToDelete := []string{} - - for label, statsPerLabel := range ExpH.statsPerLabel { - if statsPerLabel.LastUpdated < cleanUpTime { - labelToDelete = append(labelToDelete, label) - } - } - - for _, label := range labelToDelete { - delete(ExpH.statsPerLabel, label) - } - - ExpH.statsPerLabelLock.Unlock() - case <-ExpH.stopChan: - return - } - } -} - -// == // - -// Exporting API metrics is handled by API Classifier - -// == // diff --git a/sentryflow/exporter/exportEnvoyMetrics.go b/sentryflow/exporter/exportEnvoyMetrics.go deleted file mode 100644 index 7caf94a..0000000 --- a/sentryflow/exporter/exportEnvoyMetrics.go +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package exporter - -import ( - "errors" - "fmt" - "log" - - "github.com/5gsec/SentryFlow/protobuf" -) - -// == // - -// envoyMetricsStreamInform structure -type envoyMetricsStreamInform struct { - Hostname string - IPAddress string - - metricsStream protobuf.SentryFlow_GetEnvoyMetricsServer - - error chan error -} - -// GetEnvoyMetrics Function (for gRPC) -func (exs *ExpService) GetEnvoyMetrics(info *protobuf.ClientInfo, stream protobuf.SentryFlow_GetEnvoyMetricsServer) error { - log.Printf("[Exporter] Client %s (%s) connected (GetEnvoyMetrics)", info.HostName, info.IPAddress) - - currExporter := &envoyMetricsStreamInform{ - Hostname: info.HostName, - IPAddress: info.IPAddress, - metricsStream: stream, - } - - ExpH.exporterLock.Lock() - ExpH.envoyMetricsExporters = append(ExpH.envoyMetricsExporters, currExporter) - ExpH.exporterLock.Unlock() - - return <-currExporter.error -} - -// SendEnvoyMetrics Function -func (exp *ExpHandler) SendEnvoyMetrics(evyMetrics *protobuf.EnvoyMetrics) error { - failed := 0 - total := len(exp.envoyMetricsExporters) - - for _, exporter := range exp.envoyMetricsExporters { - if err := exporter.metricsStream.Send(evyMetrics); err != nil { - log.Printf("[Exporter] Failed to export Envoy metrics to %s(%s): %v", exporter.Hostname, exporter.IPAddress, err) - failed++ - } - } - - if failed != 0 { - msg := fmt.Sprintf("[Exporter] Failed to export Envoy metrics properly (%d/%d failed)", failed, total) - return errors.New(msg) - } - - return nil -} - -// == // - -// InsertEnvoyMetrics Function -func InsertEnvoyMetrics(evyMetrics *protobuf.EnvoyMetrics) { - ExpH.exporterMetrics <- evyMetrics -} - -// == // diff --git a/sentryflow/exporter/exporterHandler.go b/sentryflow/exporter/exporterHandler.go deleted file mode 100644 index 05baed9..0000000 --- a/sentryflow/exporter/exporterHandler.go +++ /dev/null @@ -1,259 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package exporter - -import ( - "fmt" - "net" - "sync" - - "github.com/5gsec/SentryFlow/config" - "github.com/5gsec/SentryFlow/protobuf" - - "log" - - "google.golang.org/grpc" -) - -// == // - -// ExpH global reference for Exporter Handler -var ExpH *ExpHandler - -// init Function -func init() { - ExpH = NewExporterHandler() -} - -// ExpHandler structure -type ExpHandler struct { - exporterService net.Listener - grpcServer *grpc.Server - grpcService *ExpService - - apiLogExporters []*apiLogStreamInform - apiLogExportersV2 []*apiLogStreamInformV2 - apiMetricsExporters []*apiMetricStreamInform - envoyMetricsExporters []*envoyMetricsStreamInform - - exporterLock sync.Mutex - - exporterAPILogs chan *protobuf.APILog - exporterAPILogsV2 chan *protobuf.APILogV2 - exporterAPIMetrics chan *protobuf.APIMetrics - exporterMetrics chan *protobuf.EnvoyMetrics - - statsPerLabel map[string]StatsPerLabel - statsPerLabelLock sync.RWMutex - - stopChan chan struct{} -} - -// ExpService Structure -type ExpService struct { - protobuf.UnimplementedSentryFlowServer -} - -// == // - -// NewExporterHandler Function -func NewExporterHandler() *ExpHandler { - exp := &ExpHandler{ - grpcService: new(ExpService), - - apiLogExporters: make([]*apiLogStreamInform, 0), - apiLogExportersV2: make([]*apiLogStreamInformV2, 0), - apiMetricsExporters: make([]*apiMetricStreamInform, 0), - envoyMetricsExporters: make([]*envoyMetricsStreamInform, 0), - - exporterLock: sync.Mutex{}, - - exporterAPILogs: make(chan *protobuf.APILog), - exporterAPILogsV2: make(chan *protobuf.APILogV2), - exporterAPIMetrics: make(chan *protobuf.APIMetrics), - exporterMetrics: make(chan *protobuf.EnvoyMetrics), - - statsPerLabel: make(map[string]StatsPerLabel), - statsPerLabelLock: sync.RWMutex{}, - - stopChan: make(chan struct{}), - } - - return exp -} - -// == // - -// StartExporter Function -func StartExporter(wg *sync.WaitGroup) bool { - // Make a string with the given exporter address and port - exporterService := fmt.Sprintf("%s:%s", config.GlobalConfig.ExporterAddr, config.GlobalConfig.ExporterPort) - - // Start listening gRPC port - expService, err := net.Listen("tcp", exporterService) - if err != nil { - log.Printf("[Exporter] Failed to listen at %s: %v", exporterService, err) - return false - } - ExpH.exporterService = expService - - log.Printf("[Exporter] Listening Exporter gRPC services (%s)", exporterService) - - // Create gRPC server - gRPCServer := grpc.NewServer() - ExpH.grpcServer = gRPCServer - - protobuf.RegisterSentryFlowServer(gRPCServer, ExpH.grpcService) - - log.Printf("[Exporter] Initialized Exporter gRPC services") - - // Serve gRPC Service - go ExpH.grpcServer.Serve(ExpH.exporterService) - - log.Printf("[Exporter] Serving Exporter gRPC services (%s)", exporterService) - - // Export APILogs - go ExpH.exportAPILogs(wg) - - // Export APILogsV2 - go ExpH.exportAPILogsV2(wg) - - log.Printf("[Exporter] Exporting API logs through gRPC services") - - // Export APIMetrics - go ExpH.exportAPIMetrics(wg) - - log.Printf("[Exporter] Exporting API metrics through gRPC services") - - // Export EnvoyMetrics - go ExpH.exportEnvoyMetrics(wg) - - log.Printf("[Exporter] Exporting Envoy metrics through gRPC services") - - // Start Export Time Ticker Routine - go AggregateAPIMetrics() - go CleanUpOutdatedStats() - - return true -} - -// StopExporter Function -func StopExporter() bool { - // One for exportAPILogs - ExpH.stopChan <- struct{}{} - - // One for exportAPILogsV2 - ExpH.stopChan <- struct{}{} - - // One for exportAPIMetrics - ExpH.stopChan <- struct{}{} - - // One for exportEnvoyMetrics - ExpH.stopChan <- struct{}{} - - // Stop gRPC server - ExpH.grpcServer.GracefulStop() - - log.Printf("[Exporter] Gracefully stopped Exporter gRPC services") - - return true -} - -// == // - -// exportAPILogs Function -func (exp *ExpHandler) exportAPILogs(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case apiLog, ok := <-exp.exporterAPILogs: - if !ok { - log.Printf("[Exporter] Failed to fetch APIs from APIs channel") - wg.Done() - return - } - - if err := exp.SendAPILogs(apiLog); err != nil { - log.Printf("[Exporter] Failed to export API Logs: %v", err) - } - - case <-exp.stopChan: - wg.Done() - return - } - } -} - -// exportAPILogs Function -func (exp *ExpHandler) exportAPILogsV2(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case apiLog, ok := <-exp.exporterAPILogsV2: - if !ok { - log.Printf("[Exporter] Failed to fetch APILogs(V2) from APIs channel") - wg.Done() - return - } - - if err := exp.SendAPILogsV2(apiLog); err != nil { - log.Printf("[Exporter] Failed to export API Logs(V2): %v", err) - } - - case <-exp.stopChan: - wg.Done() - return - } - } -} - -// exportAPIMetrics Function -func (exp *ExpHandler) exportAPIMetrics(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case apiMetrics, ok := <-exp.exporterAPIMetrics: - if !ok { - log.Printf("[Exporter] Failed to fetch metrics from API Metrics channel") - wg.Done() - return - } - if err := exp.SendAPIMetrics(apiMetrics); err != nil { - log.Printf("[Exporter] Failed to export API metrics: %v", err) - } - - case <-exp.stopChan: - wg.Done() - return - } - } -} - -// exportEnvoyMetrics Function -func (exp *ExpHandler) exportEnvoyMetrics(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case evyMetrics, ok := <-exp.exporterMetrics: - if !ok { - log.Printf("[Exporter] Failed to fetch metrics from Envoy Metrics channel") - wg.Done() - return - } - - if err := exp.SendEnvoyMetrics(evyMetrics); err != nil { - log.Printf("[Exporter] Failed to export Envoy metrics: %v", err) - } - - case <-exp.stopChan: - wg.Done() - return - } - } -} - -// == // diff --git a/sentryflow/go.mod b/sentryflow/go.mod index 3936109..8692545 100644 --- a/sentryflow/go.mod +++ b/sentryflow/go.mod @@ -1,39 +1,43 @@ -module github.com/5gsec/SentryFlow +module github.com/5GSEC/SentryFlow -go 1.21 - -replace github.com/5gsec/SentryFlow/protobuf => ../protobuf +go 1.23 require ( - github.com/5gsec/SentryFlow/protobuf v0.0.0-00010101000000-000000000000 - github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 - github.com/spf13/viper v1.18.2 - go.opentelemetry.io/proto/otlp v1.0.0 - google.golang.org/grpc v1.66.0 - gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 - k8s.io/client-go v0.29.0 + github.com/5GSEC/SentryFlow/protobuf v0.0.0-00010101000000-000000000000 + github.com/golang/protobuf v1.5.4 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + go.uber.org/zap v1.26.0 + google.golang.org/grpc v1.66.2 + google.golang.org/protobuf v1.34.2 + istio.io/api v1.23.1 + istio.io/client-go v1.23.1 + k8s.io/apimachinery v0.31.1 + k8s.io/client-go v0.31.1 + sigs.k8s.io/controller-runtime v0.19.0 ) require ( - github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect + github.com/beorn7/perks v1.0.1 // 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/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.3.0 // 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.3 // 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.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.6 // 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/magiconair/properties v1.8.7 // indirect @@ -42,35 +46,43 @@ require ( 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/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pkg/errors v0.9.1 // 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/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/protobuf v1.34.2 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + k8s.io/api v0.31.1 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect + k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 // 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.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) + +replace github.com/5GSEC/SentryFlow/protobuf => ../protobuf/golang diff --git a/sentryflow/go.sum b/sentryflow/go.sum index c04d903..866e9aa 100644 --- a/sentryflow/go.sum +++ b/sentryflow/go.sum @@ -1,34 +1,40 @@ -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -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 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= -github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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= @@ -39,25 +45,24 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/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/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -73,107 +78,116 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G 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.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +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/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +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/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +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.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +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/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -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/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -181,24 +195,31 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +istio.io/api v1.23.1 h1:bm2XF0j058FfzWVHUfpmMj4sFDkcD1X609qs5AU97Pc= +istio.io/api v1.23.1/go.mod h1:QPSTGXuIQdnZFEm3myf9NZ5uBMwCdJWUvfj9ZZ+2oBM= +istio.io/client-go v1.23.1 h1:IX2cgUUXnVYo+9H6bFGSp/vuKVLPUkmiN8qk1/mvsYs= +istio.io/client-go v1.23.1/go.mod h1:+fxu+O2GkITM3HEREUWdobvRXqI/UhAAI7hfxqqpRh0= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +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-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 h1:b2FmK8YH+QEwq/Sy2uAEhmqL5nPfGYbJOcaqjeYYZoA= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= 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.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +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/sentryflow/k8s/istioPatcher.go b/sentryflow/k8s/istioPatcher.go deleted file mode 100644 index 6ec4d35..0000000 --- a/sentryflow/k8s/istioPatcher.go +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package k8s - -import ( - "errors" - "log" - - "gopkg.in/yaml.v2" - "k8s.io/apimachinery/pkg/util/json" -) - -// meshConfig structure -type meshConfig struct { - DefaultConfig struct { - DiscoveryAddress string `yaml:"discoveryAddress"` - EnvoyAccessLogService struct { - Address string `yaml:"address"` - } `yaml:"envoyAccessLogService"` - EnvoyMetricsService struct { - Address string `yaml:"address"` - } `yaml:"envoyMetricsService"` - } `yaml:"defaultConfig"` - - DefaultProviders struct { - AccessLogs []string `yaml:"accessLogs"` - Metrics []string `yaml:"metrics"` - } `yaml:"defaultProviders"` - - EnableEnvoyAccessLogService bool `yaml:"enableEnvoyAccessLogService"` - - ExtensionProviders []struct { - EnvoyOtelAls struct { - Port string `yaml:"port"` - Service string `yaml:"service"` - } `yaml:"envoyOtelAls"` - Name string `yaml:"name"` - } `yaml:"extensionProviders"` - - ExtraFields map[string]interface{} `yaml:",inline"` // all extra fields that SentryFlow will not touch -} - -// PatchIstioConfigMap Function -func PatchIstioConfigMap() bool { - log.Print("[PatchIstioConfigMap] Patching Istio ConfigMap") - - meshCfg, err := parseIstioConfigMap() - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to parse Istio ConfigMap: %v", err) - return false - } - - if isIstioAlreadyPatched(meshCfg) { - log.Print("[PatchIstioConfigMap] Istio ConfigMap was already patched before, skipping...") - return true - } - - // set metrics and envoy access logging to Sentryflow - meshCfg.DefaultConfig.EnvoyAccessLogService.Address = "sentryflow.sentryflow.svc.cluster.local:4317" - meshCfg.DefaultConfig.EnvoyMetricsService.Address = "sentryflow.sentryflow.svc.cluster.local:4317" - - // add Sentryflow as Otel AL collector - if patched, _ := isEnvoyOtelAlPatched(meshCfg); !patched { - sfOtelAl := struct { - EnvoyOtelAls struct { - Port string `yaml:"port"` - Service string `yaml:"service"` - } `yaml:"envoyOtelAls"` - Name string `yaml:"name"` - }{ - EnvoyOtelAls: struct { - Port string `yaml:"port"` - Service string `yaml:"service"` - }{ - Port: "4317", - Service: "sentryflow.sentryflow.svc.cluster.local", - }, - Name: "sentryflow", - } - meshCfg.ExtensionProviders = append(meshCfg.ExtensionProviders, sfOtelAl) - } - - // add default access log provider - if patched, _ := isEnvoyALProviderPatched(meshCfg); !patched { - meshCfg.DefaultProviders.AccessLogs = append(meshCfg.DefaultProviders.AccessLogs, "sentryflow") - } - - meshCfg.EnableEnvoyAccessLogService = true - - yamlMeshCfg, err := yaml.Marshal(meshCfg) - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to unmarshall Istio ConfigMap: %v", err) - return false - } - - strMeshCfg := string(yamlMeshCfg[:]) - err = K8sH.updateConfigMap("istio-system", "istio", strMeshCfg) - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to update Istio ConfigMap: %v", err) - return false - } - - log.Print("[PatchIstioConfigMap] Successfully patched Istio ConfigMap") - - return true -} - -// UnpatchIstioConfigMap Function -func UnpatchIstioConfigMap() bool { - log.Print("[PatchIstioConfigMap] Unpatching Istio ConfigMap") - - meshCfg, err := parseIstioConfigMap() - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to parse Istio ConfigMap: %v", err) - return false - } - - // set metrics and envoy access logging back to empty value - meshCfg.DefaultConfig.EnvoyAccessLogService.Address = "" - meshCfg.DefaultConfig.EnvoyMetricsService.Address = "" - - // remove EnvoyOtelAl - if patched, targetIdx := isEnvoyOtelAlPatched(meshCfg); patched { - tmp := make([]struct { - EnvoyOtelAls struct { - Port string `yaml:"port"` - Service string `yaml:"service"` - } `yaml:"envoyOtelAls"` - Name string `yaml:"name"` - }, 0) - for idx, envoyOtelAl := range meshCfg.ExtensionProviders { - if idx != targetIdx { - tmp = append(tmp, envoyOtelAl) - } - } - meshCfg.ExtensionProviders = tmp - } - - // remove default access log provider - if patched, targetIdx := isEnvoyALProviderPatched(meshCfg); patched { - tmp := make([]string, 0) - for idx, provider := range meshCfg.DefaultProviders.AccessLogs { - if idx != targetIdx { - tmp = append(tmp, provider) - } - } - meshCfg.DefaultProviders.AccessLogs = tmp - } - - // @todo this might be incorrect, the user might have just set up envoy access log service manually before. - // @todo check if this shall actually be overwritten by SentryFlow - // meshCfg.EnableEnvoyAccessLogService = false - - yamlMeshCfg, err := yaml.Marshal(meshCfg) - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to unmarshall Istio ConfigMap: %v", err) - return false - } - - strMeshCfg := string(yamlMeshCfg[:]) - err = K8sH.updateConfigMap("istio-system", "istio", strMeshCfg) - if err != nil { - log.Printf("[PatchIstioConfigMap] Unable to update Istio ConfigMap: %v", err) - return false - } - - log.Print("[PatchIstioConfigMap] Successfully unpatched Istio ConfigMap") - - return true -} - -// parseIstioConfigMap Function -func parseIstioConfigMap() (meshConfig, error) { - var meshCfg meshConfig - - configMapData, err := K8sH.getConfigMap("istio-system", "istio") - if err != nil { - return meshCfg, err - } - - // unmarshall JSON format of Istio config - var rawIstioCfg map[string]interface{} - err = json.Unmarshal([]byte(configMapData), &rawIstioCfg) - if err != nil { - return meshCfg, err - } - - // extract mesh field from configmap - meshData, ok := rawIstioCfg["mesh"].(string) - if !ok { - return meshCfg, errors.New("[PatchIstioConfigMap] Unable to find field \"mesh\" from Istio config") - } - - // unmarshall YAML format of Istio config - err = yaml.Unmarshal([]byte(meshData), &meshCfg) - if err != nil { - return meshCfg, err - } - - return meshCfg, nil -} - -// isEnvoyOtelAlPatched Function -func isEnvoyOtelAlPatched(meshCfg meshConfig) (bool, int) { - for idx, envoyOtelAl := range meshCfg.ExtensionProviders { - if envoyOtelAl.Name == "sentryflow" && - envoyOtelAl.EnvoyOtelAls.Port == "4317" && - envoyOtelAl.EnvoyOtelAls.Service == "sentryflow.sentryflow.svc.cluster.local" { - return true, idx - } - } - - return false, -1 -} - -// isEnvoyALProviderPatched Function -func isEnvoyALProviderPatched(meshCfg meshConfig) (bool, int) { - for idx, accessLogProvider := range meshCfg.DefaultProviders.AccessLogs { - if accessLogProvider == "sentryflow" { - return true, idx - } - } - return false, -1 -} - -// isIstioAlreadyPatched Function -func isIstioAlreadyPatched(meshCfg meshConfig) bool { - if meshCfg.DefaultConfig.EnvoyAccessLogService.Address != "sentryflow.sentryflow.svc.cluster.local:4317" || - meshCfg.DefaultConfig.EnvoyMetricsService.Address != "sentryflow.sentryflow.svc.cluster.local:4317" { - return false - } - - if patched, _ := isEnvoyOtelAlPatched(meshCfg); !patched { - return false - } - - if patched, _ := isEnvoyALProviderPatched(meshCfg); !patched { - return false - } - - if !meshCfg.EnableEnvoyAccessLogService { - return false - } - - return true -} diff --git a/sentryflow/k8s/k8sHandler.go b/sentryflow/k8s/k8sHandler.go deleted file mode 100644 index a13a07f..0000000 --- a/sentryflow/k8s/k8sHandler.go +++ /dev/null @@ -1,437 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package k8s - -import ( - "context" - "errors" - "log" - "sync" - "time" - - "k8s.io/apimachinery/pkg/util/json" - - "github.com/5gsec/SentryFlow/types" - - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" -) - -// == // - -// K8sH global reference for Kubernetes Handler -var K8sH *KubernetesHandler - -// init Function -func init() { - K8sH = NewK8sHandler() -} - -// KubernetesHandler Structure -type KubernetesHandler struct { - config *rest.Config - clientSet *kubernetes.Clientset - - watchers map[string]*cache.ListWatch - informers map[string]cache.Controller - - podMap map[string]*corev1.Pod // NOT thread safe - serviceMap map[string]*corev1.Service // NOT thread safe -} - -// NewK8sHandler Function -func NewK8sHandler() *KubernetesHandler { - kh := &KubernetesHandler{ - watchers: make(map[string]*cache.ListWatch), - informers: make(map[string]cache.Controller), - - podMap: make(map[string]*corev1.Pod), - serviceMap: make(map[string]*corev1.Service), - } - - return kh -} - -// == // - -// InitK8sClient Function -func InitK8sClient() bool { - var err error - - // Initialize in cluster config - K8sH.config, err = rest.InClusterConfig() - if err != nil { - log.Print("[InitK8sClient] Failed to initialize Kubernetes client") - return false - } - - // Initialize Kubernetes clientSet - K8sH.clientSet, err = kubernetes.NewForConfig(K8sH.config) - if err != nil { - log.Print("[InitK8sClient] Failed to initialize Kubernetes client") - return false - } - - // Create a mapping table for existing pods and services to IPs - K8sH.initExistingResources() - - watchTargets := []string{"pods", "services"} - - // Initialize watchers for pods and services - for _, target := range watchTargets { - watcher := cache.NewListWatchFromClient( - K8sH.clientSet.CoreV1().RESTClient(), - target, - corev1.NamespaceAll, - fields.Everything(), - ) - K8sH.watchers[target] = watcher - } - - // Initialize informers - K8sH.initInformers() - - log.Print("[InitK8sClient] Initialized Kubernetes client") - - return true -} - -// initExistingResources Function that creates a mapping table for existing pods and services to IPs -// This is required since informers are NOT going to see existing resources until they are updated, created or deleted -// @todo: Refactor this function, this is kind of messy -func (k8s *KubernetesHandler) initExistingResources() { - // List existing Pods - podList, err := k8s.clientSet.CoreV1().Pods(corev1.NamespaceAll).List(context.TODO(), v1.ListOptions{}) - if err != nil { - log.Printf("[K8s] Failed to get Pods: %v", err.Error()) - } - - // Add existing Pods to the podMap - for _, pod := range podList.Items { - currentPod := pod - k8s.podMap[pod.Status.PodIP] = ¤tPod - log.Printf("[K8s] Add existing pod %s: %s/%s", pod.Status.PodIP, pod.Namespace, pod.Name) - } - - // List existing Services - serviceList, err := k8s.clientSet.CoreV1().Services(corev1.NamespaceAll).List(context.TODO(), v1.ListOptions{}) - if err != nil { - log.Printf("[K8s] Failed to get Services: %v", err.Error()) - } - - // Add existing Services to the serviceMap - for _, service := range serviceList.Items { - currentService := service - - // Check if the service has a LoadBalancer type - if service.Spec.Type == "LoadBalancer" { - for _, lbIngress := range service.Status.LoadBalancer.Ingress { - lbIP := lbIngress.IP - if lbIP != "" { - k8s.serviceMap[lbIP] = ¤tService - log.Printf("[K8s] Add existing service (LoadBalancer) %s: %s/%s", lbIP, service.Namespace, service.Name) - } - } - } else { - k8s.serviceMap[service.Spec.ClusterIP] = ¤tService - if len(service.Spec.ExternalIPs) != 0 { - for _, eIP := range service.Spec.ExternalIPs { - k8s.serviceMap[eIP] = ¤tService - log.Printf("[K8s] Add existing service %s: %s/%s", eIP, service.Namespace, service.Name) - } - } - } - } -} - -// initInformers Function that initializes informers for services and pods in a cluster -func (k8s *KubernetesHandler) initInformers() { - // Create Pod controller informer - _, pc := cache.NewInformer( - k8s.watchers["pods"], - &corev1.Pod{}, - time.Second*0, - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { // Add pod - pod := obj.(*corev1.Pod) - k8s.podMap[pod.Status.PodIP] = pod - }, - UpdateFunc: func(oldObj, newObj interface{}) { // Update pod - newPod := newObj.(*corev1.Pod) - k8s.podMap[newPod.Status.PodIP] = newPod - }, - DeleteFunc: func(obj interface{}) { // Remove deleted pod - pod := obj.(*corev1.Pod) - delete(k8s.podMap, pod.Status.PodIP) - }, - }, - ) - k8s.informers["pods"] = pc - - // Create Service controller informer - _, sc := cache.NewInformer( - k8s.watchers["services"], - &corev1.Service{}, - time.Second*0, - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { // Add service - service := obj.(*corev1.Service) - - if service.Spec.Type == "LoadBalancer" { - for _, lbIngress := range service.Status.LoadBalancer.Ingress { - lbIP := lbIngress.IP - if lbIP != "" { - k8s.serviceMap[lbIP] = service - } - } - } else { - k8s.serviceMap[service.Spec.ClusterIP] = service - if len(service.Spec.ExternalIPs) != 0 { - for _, eIP := range service.Spec.ExternalIPs { - k8s.serviceMap[eIP] = service - } - } - } - }, - UpdateFunc: func(oldObj, newObj interface{}) { // Update service - newService := newObj.(*corev1.Service) - if newService.Spec.Type == "LoadBalancer" { - for _, lbIngress := range newService.Status.LoadBalancer.Ingress { - lbIP := lbIngress.IP - if lbIP != "" { - k8s.serviceMap[lbIP] = newService - } - } - } else { - k8s.serviceMap[newService.Spec.ClusterIP] = newService - if len(newService.Spec.ExternalIPs) != 0 { - for _, eIP := range newService.Spec.ExternalIPs { - k8s.serviceMap[eIP] = newService - } - } - } - }, - DeleteFunc: func(obj interface{}) { - service := obj.(*corev1.Service) - if service.Spec.Type == "LoadBalancer" { - for _, lbIngress := range service.Status.LoadBalancer.Ingress { - lbIP := lbIngress.IP - if lbIP != "" { - delete(k8s.serviceMap, lbIP) - } - } - } else { - delete(k8s.serviceMap, service.Spec.ClusterIP) // Remove deleted service - if len(service.Spec.ExternalIPs) != 0 { - for _, eIP := range service.Spec.ExternalIPs { - delete(k8s.serviceMap, eIP) - } - } - } - }, - }, - ) - k8s.informers["services"] = sc -} - -// == // - -// RunInformers Function that starts running informers -func RunInformers(stopChan chan struct{}, wg *sync.WaitGroup) { - wg.Add(1) - - for name, informer := range K8sH.informers { - name := name - informer := informer - go func() { - log.Printf("[RunInformers] Starting an informer for %s", name) - informer.Run(stopChan) - defer wg.Done() - }() - } - - log.Print("[RunInformers] Started all Kubernetes informers") -} - -// getConfigMap Function -func (k8s *KubernetesHandler) getConfigMap(namespace, name string) (string, error) { - cm, err := k8s.clientSet.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, v1.GetOptions{}) - if err != nil { - log.Printf("[K8s] Failed to get ConfigMaps: %v", err) - return "", err - } - - // convert data to string - data, err := json.Marshal(cm.Data) - if err != nil { - log.Printf("[K8s] Failed to marshal ConfigMap: %v", err) - return "", err - } - - return string(data), nil -} - -// updateConfigMap Function -func (k8s *KubernetesHandler) updateConfigMap(namespace, name, data string) error { - cm, err := k8s.clientSet.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, v1.GetOptions{}) - if err != nil { - log.Printf("[K8s] Failed to get ConfigMap: %v", err) - return err - } - - if _, ok := cm.Data["mesh"]; !ok { - return errors.New("[K8s] Unable to find field \"mesh\" from Istio config") - } - - cm.Data["mesh"] = data - if _, err := k8s.clientSet.CoreV1().ConfigMaps(namespace).Update(context.Background(), cm, v1.UpdateOptions{}); err != nil { - return err - } - - return nil -} - -// PatchNamespaces Function that patches namespaces for adding 'istio-injection' -func PatchNamespaces() bool { - namespaces, err := K8sH.clientSet.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{}) - if err != nil { - log.Printf("[PatchNamespaces] Failed to get Namespaces: %v", err) - return false - } - - for _, ns := range namespaces.Items { - namespace := ns.DeepCopy() - - // Skip the following namespaces - if namespace.Name == "sentryflow" { - continue - } - - namespace.Labels["istio-injection"] = "enabled" - - // Patch the namespace - if _, err := K8sH.clientSet.CoreV1().Namespaces().Update(context.TODO(), namespace, v1.UpdateOptions{FieldManager: "patcher"}); err != nil { - log.Printf("[PatchNamespaces] Failed to update Namespace %s: %v", namespace.Name, err) - return false - } - - log.Printf("[PatchNamespaces] Updated Namespace %s", namespace.Name) - } - - log.Print("[PatchNamespaces] Updated all Namespaces") - - return true -} - -// restartDeployment Function that performs a rolling restart for a deployment in the specified namespace -// @todo: fix this, this DOES NOT restart deployments -func (k8s *KubernetesHandler) restartDeployment(namespace string, deploymentName string) error { - deploymentClient := k8s.clientSet.AppsV1().Deployments(namespace) - - // Get the deployment to retrieve the current spec - deployment, err := deploymentClient.Get(context.Background(), deploymentName, v1.GetOptions{}) - if err != nil { - return err - } - - // Trigger a rolling restart by updating the deployment's labels or annotations - deployment.Spec.Template.ObjectMeta.Labels["restartedAt"] = v1.Now().String() - - // Update the deployment to trigger the rolling restart - _, err = deploymentClient.Update(context.TODO(), deployment, v1.UpdateOptions{}) - if err != nil { - return err - } - - return nil -} - -// RestartDeployments Function that restarts the deployments in the namespaces with "istio-injection=enabled" -func RestartDeployments() bool { - deployments, err := K8sH.clientSet.AppsV1().Deployments("").List(context.Background(), v1.ListOptions{}) - if err != nil { - log.Printf("[PatchDeployments] Failed to get Deployments: %v", err) - return false - } - - for _, deployment := range deployments.Items { - // Skip the following namespaces - if deployment.Namespace == "sentryflow" { - continue - } - - // Restart the deployment - if err := K8sH.restartDeployment(deployment.Namespace, deployment.Name); err != nil { - log.Printf("[PatchDeployments] Failed to restart Deployment %s/%s: %v", deployment.Namespace, deployment.Name, err) - return false - } - - log.Printf("[PatchDeployments] Deployment %s/%s restarted", deployment.Namespace, deployment.Name) - } - - log.Print("[PatchDeployments] Restarted all patched deployments") - - return true -} - -// == // - -// lookupIPAddress Function -func lookupIPAddress(ipAddr string) interface{} { - // Look for pod map - pod, ok := K8sH.podMap[ipAddr] - if ok { - return pod - } - - // Look for service map - service, ok := K8sH.serviceMap[ipAddr] - if ok { - return service - } - - return nil -} - -// LookupK8sResource Function -func LookupK8sResource(srcIP string) types.K8sResource { - ret := types.K8sResource{ - Namespace: "Unknown", - Name: "Unknown", - Labels: make(map[string]string), - Type: types.K8sResourceTypeUnknown, - } - - // Find Kubernetes resource from source IP (service or a pod) - raw := lookupIPAddress(srcIP) - - // Currently supports Service or Pod - switch raw.(type) { - case *corev1.Pod: - pod, ok := raw.(*corev1.Pod) - if ok { - ret.Namespace = pod.Namespace - ret.Name = pod.Name - ret.Labels = pod.Labels - ret.Type = types.K8sResourceTypePod - } - case *corev1.Service: - svc, ok := raw.(*corev1.Service) - if ok { - ret.Namespace = svc.Namespace - ret.Name = svc.Name - ret.Labels = svc.Labels - ret.Type = types.K8sResourceTypeService - } - default: - ret.Type = types.K8sResourceTypeUnknown - } - - return ret -} - -// == // diff --git a/sentryflow/main.go b/sentryflow/main.go index d96e538..6163445 100644 --- a/sentryflow/main.go +++ b/sentryflow/main.go @@ -1,15 +1,9 @@ -// SPDX-License-Identifier: Apache-2.0 - package main import ( - "github.com/5gsec/SentryFlow/core" + "github.com/5GSEC/SentryFlow/cmd" ) -// ========== // -// == Main == // -// ========== // - func main() { - core.SentryFlow() + _ = cmd.RootCmd.Execute() } diff --git a/sentryflow/pkg/config/config.go b/sentryflow/pkg/config/config.go new file mode 100644 index 0000000..0cf5ab0 --- /dev/null +++ b/sentryflow/pkg/config/config.go @@ -0,0 +1,77 @@ +package config + +import ( + "encoding/json" + + "github.com/spf13/viper" + "go.uber.org/zap" +) + +const ( + DefaultConfigFilePath = "config/default.yaml" +) + +type Endpoint struct { + Url string `json:"url"` + Port uint16 `json:"port"` +} + +type Base struct { + Name string `json:"name,omitempty"` + // Todo: Do we really need both gRPC and http variants? + Grpc *Endpoint `json:"grpc,omitempty"` + Http *Endpoint `json:"http,omitempty"` +} + +type ServiceMesh struct { + Name string `json:"name"` + Enable bool `json:"enable"` +} + +type Receivers struct { + ServiceMeshes []*ServiceMesh `json:"serviceMeshes,omitempty"` + Others []*Base `json:"others,omitempty"` + Port uint16 `json:"port"` +} + +type PProf struct { + Port uint16 `json:"port"` +} + +type DebugCfg struct { + Enable bool `json:"enable"` + PProf *PProf `json:"pprof"` +} + +type Config struct { + Receivers *Receivers `json:"receivers"` + Exporter *Base `json:"exporter"` + Debug *DebugCfg `json:"debug,omitempty"` +} + +func New(configFilePath string, logger *zap.SugaredLogger) (*Config, error) { + if configFilePath == "" { + configFilePath = DefaultConfigFilePath + logger.Warnf("Using default config file path: %s", configFilePath) + } + + viper.SetConfigFile(configFilePath) + if err := viper.ReadInConfig(); err != nil { + logger.Errorf("Failed to read config file: %v", err) + return nil, err + } + + config := &Config{} + if err := viper.Unmarshal(config); err != nil { + logger.Errorf("Failed to unmarshal config file: %v", err) + return nil, err + } + + bytes, err := json.Marshal(config) + if err != nil { + logger.Errorf("Failed to marshal config file: %v", err) + } + logger.Debugf("Config: %s", string(bytes)) + + return config, nil +} diff --git a/sentryflow/pkg/core/sentryflow.go b/sentryflow/pkg/core/sentryflow.go new file mode 100644 index 0000000..526e753 --- /dev/null +++ b/sentryflow/pkg/core/sentryflow.go @@ -0,0 +1,100 @@ +package core + +import ( + "context" + "net/http" + "sync" + + "go.uber.org/zap" + "google.golang.org/grpc" + istionet "istio.io/client-go/pkg/apis/networking/v1alpha3" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/5GSEC/SentryFlow/pkg/config" + "github.com/5GSEC/SentryFlow/pkg/exporter" + "github.com/5GSEC/SentryFlow/pkg/k8s" + "github.com/5GSEC/SentryFlow/pkg/receiver" + "github.com/5GSEC/SentryFlow/pkg/util" + "github.com/5GSEC/SentryFlow/protobuf" +) + +type Manager struct { + Ctx context.Context + Logger *zap.SugaredLogger + GrpcServer *grpc.Server + HttpServer *http.Server + K8sClient client.Client + Wg *sync.WaitGroup + ApiEvents chan *protobuf.APIEvent +} + +func Run(ctx context.Context, configFilePath string, kubeConfig string) { + mgr := &Manager{ + Ctx: ctx, + Logger: util.LoggerFromCtx(ctx), + GrpcServer: grpc.NewServer(), + Wg: &sync.WaitGroup{}, + ApiEvents: make(chan *protobuf.APIEvent, 1024), + } + mgr.Logger.Info("Starting SentryFlow") + + cfg, err := config.New(configFilePath, mgr.Logger) + if err != nil { + return + } + + // Todo: Enable pprof and debug logging + if cfg.Debug.Enable { + + } + + k8sClient, err := k8s.NewClient(registerAndGetScheme(), kubeConfig) + if err != nil { + mgr.Logger.Fatalf("Failed to create k8s client: %v", err) + return + } + mgr.K8sClient = k8sClient + + mgr.Wg.Add(1) + go func() { + defer mgr.Wg.Done() + mgr.startHttpServer(cfg.Receivers.Port) + }() + + if err := receiver.Init(mgr.Ctx, mgr.Logger.Named("receiver"), mgr.K8sClient, cfg.Receivers, + mgr.ApiEvents, mgr.GrpcServer, mgr.Wg); err != nil { + mgr.Logger.Fatalf("failed to initialize receiver: %v", err) + return + } + + if err := exporter.Init(mgr.Logger.Named("exporter"), mgr.GrpcServer, cfg.Exporter, mgr.ApiEvents); err != nil { + mgr.Logger.Fatalf("Failed to initialize exporter: %v", err) + } + + mgr.Wg.Add(1) + go func() { + defer mgr.Wg.Done() + mgr.startGrpcServer(cfg.Exporter.Grpc.Port) + }() + + mgr.Logger.Info("Started SentryFlow") + + <-ctx.Done() + mgr.Logger.Info("Shutdown Signal Received. Waiting for all workers to finish.") + mgr.Logger.Info("Shutting down SentryFlow") + + mgr.stopServers() + mgr.Wg.Wait() + close(mgr.ApiEvents) + + mgr.Logger.Info("All workers finished. Stopped SentryFlow") +} + +func registerAndGetScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + utilruntime.Must(istionet.AddToScheme(scheme)) + return scheme +} diff --git a/sentryflow/pkg/core/server.go b/sentryflow/pkg/core/server.go new file mode 100644 index 0000000..596eaf7 --- /dev/null +++ b/sentryflow/pkg/core/server.go @@ -0,0 +1,100 @@ +package core + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "strings" + + "google.golang.org/protobuf/encoding/protojson" + + "github.com/5GSEC/SentryFlow/protobuf" +) + +func (m *Manager) startGrpcServer(port uint16) { + m.Logger.Info("Starting gRPC server") + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + m.Logger.Fatalf("Failed to listen on %v port, error: %v", port, err) + } + + m.Logger.Infof("gRPC server listening on port %d", port) + if err := m.GrpcServer.Serve(listener); err != nil { + m.Logger.Fatalf("Failed to serve gRPC server on port %d, error: %v", port, err) + } +} + +func (m *Manager) startHttpServer(port uint16) { + m.Logger.Info("Starting HTTP server") + m.HttpServer = &http.Server{ + Addr: fmt.Sprintf(":%d", port), + } + m.registerRoutes() + + m.Logger.Infof("HTTP server listening on port %d", port) + if err := m.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + m.Logger.Fatalf("Failed to serve http server, error: %v", err) + } +} + +func (m *Manager) stopServers() { + m.Logger.Info("Stopping servers") + if err := m.HttpServer.Shutdown(context.Background()); err != nil { + m.Logger.Errorf("Failed to shutdown http server, error: %v", err) + } + m.GrpcServer.GracefulStop() + m.Logger.Info("Stopped servers") +} + +func (m *Manager) registerRoutes() { + http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + w.WriteHeader(http.StatusOK) + }) + + http.HandleFunc("/api/v1/events", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + if r.Body == nil { + m.Logger.Info("Body is nil") + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + m.Logger.Errorf("failed to read request body, error: %v", err) + http.Error(w, "failed to read request body", http.StatusInternalServerError) + return + } + + apiEvent := &protobuf.APIEvent{} + if err := protojson.Unmarshal(body, apiEvent); err != nil { + m.Logger.Info("failed to unmarshal api event, error:", err) + http.Error(w, "failed to parse request body", http.StatusBadRequest) + return + } + + if r.ProtoMajor == 2 { + if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + apiEvent.Protocol = "grpc" + } else { + apiEvent.Protocol = "HTTP/2.0" + } + } else if r.ProtoMajor == 1 && r.ProtoMinor == 1 { + apiEvent.Protocol = "HTTP/1.1" + } else if r.ProtoMajor == 1 && r.ProtoMinor == 0 { + apiEvent.Protocol = "HTTP/1.0" + } + m.ApiEvents <- apiEvent + }) +} diff --git a/sentryflow/pkg/exporter/exporter.go b/sentryflow/pkg/exporter/exporter.go new file mode 100644 index 0000000..5cd3cb7 --- /dev/null +++ b/sentryflow/pkg/exporter/exporter.go @@ -0,0 +1,50 @@ +package exporter + +import ( + "fmt" + + "go.uber.org/zap" + "google.golang.org/grpc" + + "github.com/5GSEC/SentryFlow/pkg/config" + "github.com/5GSEC/SentryFlow/protobuf" +) + +type exporter struct { + protobuf.UnimplementedSentryFlowServer + apiEvents chan *protobuf.APIEvent + logger *zap.SugaredLogger +} + +func (e *exporter) GetAPIEvent(clientInfo *protobuf.ClientInfo, stream grpc.ServerStreamingServer[protobuf.APIEvent]) error { + e.logger.Infof("Client %s (%s) connected", clientInfo.HostName, clientInfo.IPAddress) + + for apiEvent := range e.apiEvents { + if err := stream.Send(apiEvent); err != nil { + e.logger.Errorf("failed to send APIEvent: %v", err) + return err + } + } + + return nil +} + +func Init(logger *zap.SugaredLogger, server *grpc.Server, cfg *config.Base, events chan *protobuf.APIEvent) error { + logger.Info("Starting exporter") + + if cfg == nil { + return fmt.Errorf("failed to start exporter as no config was found") + } + + grpcCfg := cfg.Grpc + if grpcCfg == nil { + return fmt.Errorf("failed to start exporter as no grpc config found") + } + + e := &exporter{ + apiEvents: events, + logger: logger, + } + protobuf.RegisterSentryFlowServer(server, e) + return nil +} diff --git a/sentryflow/pkg/k8s/client.go b/sentryflow/pkg/k8s/client.go new file mode 100644 index 0000000..b6b0e8d --- /dev/null +++ b/sentryflow/pkg/k8s/client.go @@ -0,0 +1,39 @@ +package k8s + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// NewClient returns a new Client using the provided scheme to map go structs to +// GroupVersionKinds. +func NewClient(scheme *runtime.Scheme, kubeConfig string) (client.Client, error) { + config, err := getConfig(kubeConfig) + if err != nil { + return nil, fmt.Errorf("failed to get config: %v", err) + } + return client.New(config, client.Options{ + Scheme: scheme, + }) +} + +func getConfig(kubeConfig string) (*rest.Config, error) { + config, err := rest.InClusterConfig() + if err != nil && errors.Is(err, rest.ErrNotInCluster) { + if kubeConfig == "" { + kubeConfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") + } + config, err = clientcmd.BuildConfigFromFlags("", kubeConfig) + if err != nil { + return nil, err + } + } + return config, nil +} diff --git a/sentryflow/pkg/receiver/receiver.go b/sentryflow/pkg/receiver/receiver.go new file mode 100644 index 0000000..396629d --- /dev/null +++ b/sentryflow/pkg/receiver/receiver.go @@ -0,0 +1,45 @@ +package receiver + +import ( + "context" + "fmt" + "sync" + + "go.uber.org/zap" + "google.golang.org/grpc" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/5GSEC/SentryFlow/pkg/config" + istiosidecar "github.com/5GSEC/SentryFlow/pkg/receiver/svcmesh/istio/sidecar" + "github.com/5GSEC/SentryFlow/pkg/util" + "github.com/5GSEC/SentryFlow/protobuf" +) + +func Init(ctx context.Context, logger *zap.SugaredLogger, k8sClient client.Client, cfg *config.Receivers, + apiEvents chan *protobuf.APIEvent, server *grpc.Server, wg *sync.WaitGroup) error { + for _, serviceMesh := range cfg.ServiceMeshes { + if serviceMesh.Name != "" && serviceMesh.Enable { + switch serviceMesh.Name { + case util.ServiceMeshIstioSidecar: + wg.Add(1) + go func() { + defer wg.Done() + istiosidecar.StartMonitoring(ctx, logger.Named("istio-sidecar"), k8sClient) + }() + default: + return fmt.Errorf("unsupported Service Mesh, %v", serviceMesh.Name) + } + } + } + + // Todo: Will be configured as per requirements + // Add the initialization/connecting/glue code in + // `pkg/receiver/other/{other-source-name}` directory + for _, other := range cfg.Others { + if other.Name != "" { + // Handle gRPC or HTTP config accordingly + } + } + + return nil +} diff --git a/sentryflow/pkg/receiver/svcmesh/istio/sidecar/sidecar.go b/sentryflow/pkg/receiver/svcmesh/istio/sidecar/sidecar.go new file mode 100644 index 0000000..945aafc --- /dev/null +++ b/sentryflow/pkg/receiver/svcmesh/istio/sidecar/sidecar.go @@ -0,0 +1,526 @@ +package sidecar + +import ( + "context" + "fmt" + + _struct "github.com/golang/protobuf/ptypes/struct" + "go.uber.org/zap" + networkingv1alpha3 "istio.io/api/networking/v1alpha3" + istionet "istio.io/client-go/pkg/apis/networking/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + FilterName = "http-filter" + UpstreamAndClusterName = "sentryflow" + ApiPath = "/api/v1/events" + FilterURI = "https://raw.githubusercontent.com/anurag-rajawat/envoy-wasm-filters/main/httpfilters.wasm" + RemoteWasmFilterClusterName = "remote_wasm" + FilterSha256 = "714be6a76e8853fa331a285c8d420a740675708f1503df88370a30197f8b6e37" + Timeout = "5s" + SentryFlowDefaultFilterServerPort = 8081 +) + +func StartMonitoring(ctx context.Context, logger *zap.SugaredLogger, k8sClient client.Client) { + logger.Info("Starting istio sidecar mesh monitoring") + + if err := createEnvoyFilter(ctx, logger, k8sClient); err != nil { + logger.Errorf("Failed to create EnvoyFilter. Stopping istio sidecar mesh monitoring, error: %v", err) + return + } + logger.Info("Started istio sidecar mesh monitoring") + + <-ctx.Done() + logger.Info("Shutting down istio sidecar mesh monitoring") + if err := deleteEnvoyFilter(logger, k8sClient); err != nil { + logger.Errorf("Failed to delete EnvoyFilter, error: %v", err) + } + + logger.Info("Stopped istio sidecar mesh monitoring") +} + +func deleteEnvoyFilter(logger *zap.SugaredLogger, k8sClient client.Client) error { + existingFilter := &istionet.EnvoyFilter{} + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: FilterName, Namespace: "istio-system"}, existingFilter); err != nil { + return err + } + + if err := k8sClient.Delete(context.Background(), existingFilter); err != nil { + return err + } + logger.Infow("Deleted EnvoyFilter", "name", FilterName, "namespace", "istio-system") + + return nil +} + +func createEnvoyFilter(ctx context.Context, logger *zap.SugaredLogger, k8sClient client.Client) error { + var configVal = fmt.Sprintf(`{"upstream_name": "%v", "authority": "%v", "api_path": "%v"} +`, UpstreamAndClusterName, UpstreamAndClusterName, ApiPath) + + filter := &istionet.EnvoyFilter{ + TypeMeta: v1.TypeMeta{ + Kind: "EnvoyFilter", + APIVersion: "networking.istio.io/v1alpha3", + }, + ObjectMeta: v1.ObjectMeta{ + Name: FilterName, + // Deploy the filter to whatever istio considers its "root" namespace so that we + // don't have to create the ConfigMap(s) containing the WASM filter binary, and + // the associated annotations/configuration for the Istio sidecar(s). + // https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig:~:text=No-,rootNamespace,-string + Namespace: "istio-system", + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "sentryflow", + }, + }, + Spec: networkingv1alpha3.EnvoyFilter{ + ConfigPatches: []*networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networkingv1alpha3.EnvoyFilter_HTTP_FILTER, + Match: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networkingv1alpha3.EnvoyFilter_ANY, + ObjectTypes: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networkingv1alpha3.EnvoyFilter_ListenerMatch{ + FilterChain: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: "envoy.filters.network.http_connection_manager", + SubFilter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_SubFilterMatch{ + Name: "envoy.filters.http.router", + }, + }, + }, + }, + }, + }, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_INSERT_BEFORE, + Value: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "name": { + Kind: &_struct.Value_StringValue{ + StringValue: "envoy.filters.http.wasm", + }, + }, + "typedConfig": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "@type": { + Kind: &_struct.Value_StringValue{ + StringValue: "type.googleapis.com/udpa.type.v1.TypedStruct", + }, + }, + "typeUrl": { + Kind: &_struct.Value_StringValue{ + StringValue: "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm", + }, + }, + "value": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "config": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "name": { + Kind: &_struct.Value_StringValue{ + StringValue: FilterName, + }, + }, + "rootId": { + Kind: &_struct.Value_StringValue{ + StringValue: FilterName, + }, + }, + "configuration": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "@type": { + Kind: &_struct.Value_StringValue{ + StringValue: "type.googleapis.com/google.protobuf.StringValue", + }, + }, + "value": { + Kind: &_struct.Value_StringValue{ + StringValue: configVal, + }, + }, + }, + }, + }, + }, + "vmConfig": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "code": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "remote": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "http_uri": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "uri": { + Kind: &_struct.Value_StringValue{ + StringValue: FilterURI, + }, + }, + "timeout": { + Kind: &_struct.Value_StringValue{ + StringValue: Timeout, + }, + }, + "cluster": { + Kind: &_struct.Value_StringValue{ + StringValue: RemoteWasmFilterClusterName, + }, + }, + }, + }, + }, + }, + "sha256": { + Kind: &_struct.Value_StringValue{ + StringValue: FilterSha256, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "runtime": { + Kind: &_struct.Value_StringValue{ + StringValue: "envoy.wasm.runtime.v8", + }, + }, + "vmId": { + Kind: &_struct.Value_StringValue{ + StringValue: FilterName, + }, + }, + "allow_precompiled": { + Kind: &_struct.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ApplyTo: networkingv1alpha3.EnvoyFilter_CLUSTER, + Match: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networkingv1alpha3.EnvoyFilter_SIDECAR_OUTBOUND, + }, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_ADD, + Value: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "name": { + Kind: &_struct.Value_StringValue{ + StringValue: UpstreamAndClusterName, + }, + }, + "type": { + Kind: &_struct.Value_StringValue{ + StringValue: "LOGICAL_DNS", + }, + }, + "connect_timeout": { + Kind: &_struct.Value_StringValue{ + StringValue: "1s", + }, + }, + "lb_policy": { + Kind: &_struct.Value_StringValue{ + StringValue: "ROUND_ROBIN", + }, + }, + "load_assignment": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "cluster_name": { + Kind: &_struct.Value_StringValue{ + StringValue: UpstreamAndClusterName, + }, + }, + "endpoints": { + Kind: &_struct.Value_ListValue{ + ListValue: &_struct.ListValue{ + Values: []*_struct.Value{ + { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "lb_endpoints": { + Kind: &_struct.Value_ListValue{ + ListValue: &_struct.ListValue{ + Values: []*_struct.Value{{ + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "endpoint": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "address": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "socket_address": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "protocol": { + Kind: &_struct.Value_StringValue{ + StringValue: "TCP", + }, + }, + "address": { + Kind: &_struct.Value_StringValue{ + StringValue: UpstreamAndClusterName + "." + UpstreamAndClusterName, + }, + }, + "port_value": { + Kind: &_struct.Value_NumberValue{ + NumberValue: SentryFlowDefaultFilterServerPort, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ApplyTo: networkingv1alpha3.EnvoyFilter_CLUSTER, + Match: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networkingv1alpha3.EnvoyFilter_SIDECAR_OUTBOUND, + }, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_ADD, + Value: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "name": { + Kind: &_struct.Value_StringValue{ + StringValue: RemoteWasmFilterClusterName, + }, + }, + "type": { + Kind: &_struct.Value_StringValue{ + StringValue: "STRICT_DNS", + }, + }, + "connect_timeout": { + Kind: &_struct.Value_StringValue{ + StringValue: "1s", + }, + }, + "dns_refresh_rate": { + Kind: &_struct.Value_StringValue{ + StringValue: Timeout, + }, + }, + "dns_lookup_family": { + Kind: &_struct.Value_StringValue{ + StringValue: "V4_ONLY", + }, + }, + "lb_policy": { + Kind: &_struct.Value_StringValue{ + StringValue: "ROUND_ROBIN", + }, + }, + "load_assignment": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "cluster_name": { + Kind: &_struct.Value_StringValue{ + StringValue: RemoteWasmFilterClusterName, + }, + }, + "endpoints": { + Kind: &_struct.Value_ListValue{ + ListValue: &_struct.ListValue{ + Values: []*_struct.Value{ + { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "lb_endpoints": { + Kind: &_struct.Value_ListValue{ + ListValue: &_struct.ListValue{ + Values: []*_struct.Value{ + { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "endpoint": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "address": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "socket_address": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "address": { + Kind: &_struct.Value_StringValue{ + StringValue: "raw.githubusercontent.com", + }, + }, + "port_value": { + Kind: &_struct.Value_NumberValue{ + NumberValue: 443, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "transport_socket": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "name": { + Kind: &_struct.Value_StringValue{ + StringValue: "envoy.transport_sockets.tls", + }, + }, + "typed_config": { + Kind: &_struct.Value_StructValue{ + StructValue: &_struct.Struct{ + Fields: map[string]*_struct.Value{ + "@type": { + Kind: &_struct.Value_StringValue{ + StringValue: "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + }, + }, + "sni": { + Kind: &_struct.Value_StringValue{ + StringValue: "raw.githubusercontent.com", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + existingFilter := &istionet.EnvoyFilter{} + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(filter), existingFilter); err != nil { + if errors.IsNotFound(err) { + if err := k8sClient.Create(ctx, filter); err != nil { + return err + } + logger.Infow("Created EnvoyFilter", "name", filter.Name, "namespace", filter.Namespace) + return nil + } + return err + } + logger.Infow("Found EnvoyFilter", "name", filter.Name, "namespace", filter.Namespace) + return nil +} diff --git a/sentryflow/pkg/util/util.go b/sentryflow/pkg/util/util.go new file mode 100644 index 0000000..eeba8bb --- /dev/null +++ b/sentryflow/pkg/util/util.go @@ -0,0 +1,22 @@ +package util + +import ( + "context" + + "go.uber.org/zap" +) + +const ( + LoggerCtxKey = "logger" + ServiceMeshIstioSidecar = "istio-sidecar" + ServiceMeshIstioAmbient = "istio-ambient" + ServiceMeshKong = "kong" + ServiceMeshConsul = "consul" + ServiceMeshLinkerd = "linkerd" + ReceiverOtherOtel = "otel" +) + +func LoggerFromCtx(ctx context.Context) *zap.SugaredLogger { + logger, _ := ctx.Value(LoggerCtxKey).(*zap.SugaredLogger) + return logger +} diff --git a/sentryflow/processor/apiAnalyzer.go b/sentryflow/processor/apiAnalyzer.go deleted file mode 100644 index b7d7474..0000000 --- a/sentryflow/processor/apiAnalyzer.go +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package processor - -import ( - "log" - "sync" - - "github.com/5gsec/SentryFlow/config" -) - -// == // - -// APIA Local reference for API Analyzer -var APIA *Analyzer - -// init function -func init() { - APIA = NewAPIAnalyzer() -} - -// Analyzer Structure -type Analyzer struct { - stopChan chan struct{} - - apiLog chan string - apiLogs []string - apiLogsLock sync.Mutex -} - -// NewAPIAnalyzer Function -func NewAPIAnalyzer() *Analyzer { - ret := &Analyzer{ - apiLog: make(chan string), - apiLogs: []string{}, - apiLogsLock: sync.Mutex{}, - } - return ret -} - -// StartAPIAnalyzer Function -func StartAPIAnalyzer(wg *sync.WaitGroup) bool { - // keep analyzing given APIs - go analyzeAPIs(wg) - - log.Print("[APIAnalyzer] Started API Analyzer") - - return true -} - -// AnalyzeAPI Function -func AnalyzeAPI(api string) { - APIA.apiLog <- api -} - -// StopAPIAnalyzer Function -func StopAPIAnalyzer() bool { - APIA.stopChan <- struct{}{} - - log.Print("[APIAnalyzer] Stopped API Analyzer") - - return true -} - -// == // - -// analyzeAPIs Function -func analyzeAPIs(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case api, ok := <-APIA.apiLog: - if !ok { - continue - } - - APIA.apiLogsLock.Lock() - - APIA.apiLogs = append(APIA.apiLogs, api) - - if len(APIA.apiLogs) > config.GlobalConfig.AIEngineBatchSize { - ClassifyAPIs(APIA.apiLogs) - APIA.apiLogs = []string{} - } - - APIA.apiLogsLock.Unlock() - case <-APIA.stopChan: - wg.Done() - return - } - } -} - -// == // diff --git a/sentryflow/processor/apiClassifier.go b/sentryflow/processor/apiClassifier.go deleted file mode 100644 index 79f86aa..0000000 --- a/sentryflow/processor/apiClassifier.go +++ /dev/null @@ -1,215 +0,0 @@ -// // SPDX-License-Identifier: Apache-2.0 - -package processor - -import ( - "context" - "fmt" - "io" - "log" - "sync" - "time" - - "github.com/5gsec/SentryFlow/config" - "github.com/5gsec/SentryFlow/exporter" - "github.com/5gsec/SentryFlow/protobuf" - "google.golang.org/grpc" -) - -// APIC Local reference for AI-driven API Classifier -var APIC *APIClassifier - -// APIClassifier Structure -type APIClassifier struct { - stopChan chan struct{} - - APIs chan []string - - connected bool - reConnTrial time.Duration - - AIStream *streamInform -} - -// streamInform Structure -type streamInform struct { - AIStream protobuf.APIClassifier_ClassifyAPIsClient -} - -// init Function -func init() { - APIC = NewAPIClassifier() -} - -// NewAPIClassifier Function -func NewAPIClassifier() *APIClassifier { - ah := &APIClassifier{ - stopChan: make(chan struct{}), - - APIs: make(chan []string), - - connected: false, - reConnTrial: (1 * time.Minute), - } - - return ah -} - -// initAPIClassifier Function -func initAPIClassifier() bool { - AIEngineService := fmt.Sprintf("%s:%s", config.GlobalConfig.AIEngineService, config.GlobalConfig.AIEngineServicePort) - - // Set up a connection to the server - conn, err := grpc.Dial(AIEngineService, grpc.WithInsecure()) - if err != nil { - log.Printf("[APIClassifier] Failed to connect to %s: %v", AIEngineService, err) - return false - } - - log.Printf("[APIClassifier] Connecting to %s", AIEngineService) - - client := protobuf.NewAPIClassifierClient(conn) - - // Start serving gRPC server - stream, err := client.ClassifyAPIs(context.Background()) - if err != nil { - log.Printf("[APIClassifier] Failed to make a stream: %v", err) - return false - } - - log.Printf("[APIClassifier] Successfully connected to %s", AIEngineService) - - APIC.AIStream = &streamInform{ - AIStream: stream, - } - - log.Print("[APIClassifier] Started API Classifier") - - return true -} - -// StartAPIClassifier Function -func StartAPIClassifier(wg *sync.WaitGroup) bool { - go connRoutine(wg) - go sendAPIRoutine(wg) - go recvAPIRoutine(wg) - - return true -} - -// ClassifyAPIs function -func ClassifyAPIs(APIs []string) { - if APIC.connected { - APIC.APIs <- APIs - } -} - -// StopAPIClassifier Function -func StopAPIClassifier() bool { - // one for connRoutine - APIC.stopChan <- struct{}{} - - // one for sendAPIRoutine - APIC.stopChan <- struct{}{} - - // one for recvAPIRoutine - APIC.stopChan <- struct{}{} - - log.Print("[APIClassifier] Stopped API Classifier") - - return true -} - -// connRoutine Function -func connRoutine(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case <-APIC.stopChan: - wg.Done() - return - default: - if !APIC.connected { - if initAPIClassifier() { - APIC.connected = true - } else { - time.Sleep(APIC.reConnTrial) - } - } - } - } -} - -// sendAPIRoutine Function -func sendAPIRoutine(wg *sync.WaitGroup) { - wg.Add(1) - - for { - if !APIC.connected { - time.Sleep(APIC.reConnTrial) - continue - } - - select { - case api, ok := <-APIC.APIs: - if !ok { - log.Print("[APIClassifier] Failed to fetch APIs from APIs channel") - continue - } - - curAPIRequest := &protobuf.APIClassifierRequest{ - API: api, - } - - err := APIC.AIStream.AIStream.Send(curAPIRequest) - if err != nil { - log.Printf("[APIClassifier] Failed to send an API to AI Engine: %v", err) - APIC.connected = false - continue - } - case <-APIC.stopChan: - wg.Done() - return - } - } -} - -// recvAPIRoutine Function -func recvAPIRoutine(wg *sync.WaitGroup) { - wg.Add(1) - - for { - if !APIC.connected { - time.Sleep(APIC.reConnTrial) - continue - } - - select { - default: - APIMetrics := make(map[string]uint64) - - event, err := APIC.AIStream.AIStream.Recv() - if err == io.EOF { - continue - } else if err != nil { - log.Printf("[APIClassifier] Failed to receive an event from AI Engine: %v", err) - APIC.connected = false - continue - } - - for api, count := range event.APIs { - APIMetrics[api] = count - } - - err = exporter.ExpH.SendAPIMetrics(&protobuf.APIMetrics{PerAPICounts: APIMetrics}) - if err != nil { - log.Printf("[APIClassifier] Failed to export API metrics: %v", err) - continue - } - case <-APIC.stopChan: - wg.Done() - return - } - } -} diff --git a/sentryflow/processor/logProcessor.go b/sentryflow/processor/logProcessor.go deleted file mode 100644 index f097b80..0000000 --- a/sentryflow/processor/logProcessor.go +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package processor - -import ( - "log" - "sync" - - "github.com/5gsec/SentryFlow/exporter" - "github.com/5gsec/SentryFlow/protobuf" -) - -// == // - -// LogH global reference for Log Handler -var LogH *LogHandler - -// init Function -func init() { - LogH = NewLogHandler() -} - -// LogHandler Structure -type LogHandler struct { - stopChan chan struct{} - - apiLogChan chan interface{} - metricsChan chan interface{} -} - -// NewLogHandler Structure -func NewLogHandler() *LogHandler { - lh := &LogHandler{ - stopChan: make(chan struct{}), - - apiLogChan: make(chan interface{}), - metricsChan: make(chan interface{}), - } - - return lh -} - -// == // - -// StartLogProcessor Function -func StartLogProcessor(wg *sync.WaitGroup) bool { - // handle API logs - go ProcessAPILogs(wg) - - // handle Envoy metrics - go ProcessEnvoyMetrics(wg) - - log.Print("[LogProcessor] Started Log Processors") - - return true -} - -// StopLogProcessor Function -func StopLogProcessor() bool { - // One for ProcessAPILogs - LogH.stopChan <- struct{}{} - - // One for ProcessMetrics - LogH.stopChan <- struct{}{} - - log.Print("[LogProcessor] Stopped Log Processors") - - return true -} - -// == // - -// ProcessAPILogs Function -func ProcessAPILogs(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case data, ok := <-LogH.apiLogChan: - if !ok { - log.Print("[LogProcessor] Failed to process an API log") - } - switch logType := data.(type) { - case *protobuf.APILog: - go AnalyzeAPI(logType.Path) - go exporter.InsertAPILog(logType) - case *protobuf.APILogV2: - go exporter.InsertAPILog(logType) - default: - log.Print("Unsupported API log Version") - continue - } - - case <-LogH.stopChan: - wg.Done() - return - } - } -} - -// InsertAPILog Function -func InsertAPILog(data interface{}) { - LogH.apiLogChan <- data -} - -// ProcessEnvoyMetrics Function -func ProcessEnvoyMetrics(wg *sync.WaitGroup) { - wg.Add(1) - - for { - select { - case logType, ok := <-LogH.metricsChan: - if !ok { - log.Print("[LogProcessor] Failed to process Envoy metrics") - } - - go exporter.InsertEnvoyMetrics(logType.(*protobuf.EnvoyMetrics)) - - case <-LogH.stopChan: - wg.Done() - return - } - } -} - -// InsertMetrics Function -func InsertMetrics(data interface{}) { - LogH.metricsChan <- data -} - -// == // diff --git a/sentryflow/types/types.go b/sentryflow/types/types.go deleted file mode 100644 index 4ce59ab..0000000 --- a/sentryflow/types/types.go +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package types - -// == // - -// K8sResourceTypes -const ( - K8sResourceTypeUnknown = 0 - K8sResourceTypePod = 1 - K8sResourceTypeService = 2 -) - -// K8sResource Structure -type K8sResource struct { - Type uint8 - Namespace string - Name string - Labels map[string]string - Containers []string -} - -// K8sResourceTypeToString Function -func K8sResourceTypeToString(resourceType uint8) string { - switch resourceType { - case K8sResourceTypePod: - return "Pod" - case K8sResourceTypeService: - return "Service" - case K8sResourceTypeUnknown: - return "Unknown" - } - return "Unknown" -} - -// == //