diff --git a/go.mod b/go.mod index e31e69a3..f84e3124 100644 --- a/go.mod +++ b/go.mod @@ -7,22 +7,25 @@ require ( github.com/cilium/ebpf v0.11.0 github.com/deckarep/golang-set/v2 v2.3.1 github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb + github.com/google/uuid v1.3.1 github.com/goradd/maps v0.1.5 - github.com/inspektor-gadget/inspektor-gadget v0.20.1-0.20230919195508-052fbf78b54c + github.com/inspektor-gadget/inspektor-gadget v0.21.0 github.com/kinbiko/jsonassert v1.1.1 github.com/kubescape/backend v0.0.13 github.com/kubescape/go-logger v0.0.21 - github.com/kubescape/k8s-interface v0.0.145 - github.com/kubescape/storage v0.0.22 + github.com/kubescape/k8s-interface v0.0.148 + github.com/kubescape/storage v0.0.33 github.com/panjf2000/ants/v2 v2.8.1 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 go.etcd.io/bbolt v1.3.7 - go.opentelemetry.io/otel v1.18.0 - go.opentelemetry.io/otel/trace v1.18.0 - golang.org/x/sys v0.12.0 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 + golang.org/x/sys v0.13.0 + k8s.io/api v0.28.2 k8s.io/apimachinery v0.28.2 k8s.io/client-go v0.28.2 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) require ( @@ -54,6 +57,7 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect @@ -63,10 +67,12 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect @@ -74,6 +80,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -86,6 +93,7 @@ require ( github.com/moby/sys/signal v0.7.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect @@ -93,6 +101,7 @@ require ( github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.2.0 // indirect @@ -109,6 +118,8 @@ require ( github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.2 // indirect github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.2 // indirect github.com/uptrace/uptrace-go v1.18.0 // indirect + github.com/vishvananda/netns v0.0.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect @@ -116,40 +127,44 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 // indirect - go.opentelemetry.io/otel/metric v1.18.0 // indirect - go.opentelemetry.io/otel/sdk v1.18.0 // indirect - go.opentelemetry.io/otel/sdk/metric v0.41.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.starlark.net v0.0.0-20230814145427-12f4cb8177e4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/term v0.12.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/tools v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/grpc v1.58.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.28.2 // indirect + k8s.io/cli-runtime v0.28.2 // indirect k8s.io/cri-api v0.28.2 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230811205723-7ac0aad8c58d // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/controller-runtime v0.15.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.14.0 // indirect + sigs.k8s.io/kustomize/kyaml v0.14.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5-0.20230524185006-155d84c555d6 diff --git a/go.sum b/go.sum index 346ac351..ab54715d 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -198,6 +200,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -211,8 +215,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -233,6 +238,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -245,6 +252,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/goradd/maps v0.1.5 h1:Ut7BPJgNy5BYbleI3LswVJJquiM8X5uN0ZuZBHSdRUI= github.com/goradd/maps v0.1.5/go.mod h1:E5X1CHMgfVm1qFTHgXpgVLVylO5wtlhZdB93dRGjnc0= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= @@ -258,8 +267,10 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inspektor-gadget/inspektor-gadget v0.20.1-0.20230919195508-052fbf78b54c h1:Ky1h6UPcTxUDO7sGFLpT1SO+sa4GaArkHiVVWcd1IqU= -github.com/inspektor-gadget/inspektor-gadget v0.20.1-0.20230919195508-052fbf78b54c/go.mod h1:1T2qoaBdFwsbngOJfodchstayKT3Hxxr5ygD2gKWtJo= +github.com/inspektor-gadget/inspektor-gadget v0.21.0 h1:0S+Skx4uVCnTs2ajEshX/Hx92t/fUWYV262z//lfW1U= +github.com/inspektor-gadget/inspektor-gadget v0.21.0/go.mod h1:SJGA8Fox1dtbgWNGbW/IUtoP3HLsSlIVsDiMqTVbqXI= +github.com/inspektor-gadget/netns v0.0.5-0.20230524185006-155d84c555d6 h1:fQqkJ+WkYfzy6BoUh32fr9uYrXfOGtsfw0skMQkfOic= +github.com/inspektor-gadget/netns v0.0.5-0.20230524185006-155d84c555d6/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 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= @@ -287,10 +298,12 @@ github.com/kubescape/backend v0.0.13 h1:N+fH8giGGqvy3ff2li2AwG5guVduhdiPWyvZaZxr github.com/kubescape/backend v0.0.13/go.mod h1:ug9NFmmxT4DcQx3sgdLRzlLPWMKGHE/fpbcYUm5G5Qo= github.com/kubescape/go-logger v0.0.21 h1:4ZRIEw3UGUH6BG/cH3yiqFipzQSfGAoCrxlsZuk37ys= github.com/kubescape/go-logger v0.0.21/go.mod h1:x3HBpZo3cMT/WIdy18BxvVVd5D0e/PWFVk/HiwBNu3g= -github.com/kubescape/k8s-interface v0.0.145 h1:Mpxsq7PYozhOoPIy2lyJV1BckSU9zMRiJN9O2hrmgZc= -github.com/kubescape/k8s-interface v0.0.145/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8= -github.com/kubescape/storage v0.0.22 h1:+VPpUFcC0J7M/r3OxXAmuxb7mLyNI7+vKFIee9vR0+A= -github.com/kubescape/storage v0.0.22/go.mod h1:rP6g1ukp4zlytnBcZw+fJHu0j6woOiQ/KfTZfrdM8kw= +github.com/kubescape/k8s-interface v0.0.148 h1:vtXDUjvCow5wMdDvb5c/Td9SpafeRqsV/LnOd0NVVsE= +github.com/kubescape/k8s-interface v0.0.148/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8= +github.com/kubescape/storage v0.0.33 h1:CAD2A6cO1mekX6ecOGYrHfY5HMOZWm3uzoxSTafqxgs= +github.com/kubescape/storage v0.0.33/go.mod h1:ObCIVOnVyWwRwU0iuKTzOnrJQScqPgkw0FgvSINwosY= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -323,6 +336,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 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= @@ -330,7 +345,7 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -346,6 +361,8 @@ github.com/panjf2000/ants/v2 v2.8.1 h1:C+n/f++aiW8kHCExKlpX6X+okmxKXP7DWLutxuAPu github.com/panjf2000/ants/v2 v2.8.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -368,6 +385,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20210917134616-9c00a300bb7a h1:np2nR32/A/VcOG9Hn+IOPA8kMk1gbBzK5LpSsgq5pJI= github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20210917134616-9c00a300bb7a/go.mod h1:wiP6GQ2T378F+YIyuNw7yXtBxJZR+fqrrn1Z6UHZi0Q= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -409,6 +427,7 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -438,7 +457,8 @@ github.com/uptrace/uptrace-go v1.18.0/go.mod h1:BUW3sFgEyRmZIxts4cv6TGaJnWAW95uW github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -456,8 +476,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 h1:TXu20nL4yYfJlQeqG/D3Ia6b0p2HZmLfJto9hqJTQ/c= go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0/go.mod h1:tQ5gBnfjndV1su3+DiLuu6rnd9hBBzg4rkRILnjSNFg= -go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= -go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj7s8sHaC4mhTlAeppRmZXLgZ6k= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 h1:HgbDTD8pioFdY3NRc/YCvsWjqQPtweGyXxa32LgnTOw= @@ -468,16 +488,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32a go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 h1:hSWWvDjXHVLq9DkmB+77fl8v7+t+yYiS+eNkiplDK54= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0/go.mod h1:zG7KQql1WjZCaUJd+L/ReSYx4bjbYJxg5ws9ws+mYes= -go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= -go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= -go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= -go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= -go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk= -go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w= -go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= -go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= 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.starlark.net v0.0.0-20230814145427-12f4cb8177e4 h1:Ydko8M6UfXgvSpGOnbAjRMQDIvBheUsjBjkm6Azcpf4= +go.starlark.net v0.0.0-20230814145427-12f4cb8177e4/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -494,8 +516,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -506,8 +528,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -532,8 +554,8 @@ 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/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -572,8 +594,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -599,8 +621,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -649,11 +671,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -721,8 +744,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= @@ -824,8 +847,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= -google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -874,6 +897,8 @@ k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= +k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk= +k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA= k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= k8s.io/cri-api v0.28.2 h1:RzDo9YY9tkWhAx9/UZEcn6ug1WcvDhU3eA1YLevFreI= @@ -891,6 +916,10 @@ sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUT sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= 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/kustomize/api v0.14.0 h1:6+QLmXXA8X4eDM7ejeaNUyruA1DDB3PVIjbpVhDOJRA= +sigs.k8s.io/kustomize/api v0.14.0/go.mod h1:vmOXlC8BcmcUJQjiceUbcyQ75JBP6eg8sgoyzc+eLpQ= +sigs.k8s.io/kustomize/kyaml v0.14.3 h1:WpabVAKZe2YEp/irTSHwD6bfjwZnTtSDewd2BVJGMZs= +sigs.k8s.io/kustomize/kyaml v0.14.3/go.mod h1:npvh9epWysfQ689Rtt/U+dpOJDTBn8kUnF1O6VzvmZA= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/main.go b/main.go index 87c84f71..b8f2c6b8 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "node-agent/pkg/config" "node-agent/pkg/containerwatcher/v1" "node-agent/pkg/filehandler/v1" + "node-agent/pkg/networkmanager" "node-agent/pkg/relevancymanager" relevancymanagerv1 "node-agent/pkg/relevancymanager/v1" "node-agent/pkg/sbomhandler/v1" @@ -103,8 +104,15 @@ func main() { relevancymanager.CreateRelevancyManagerMock() } + var networkManagerClient networkmanager.NetworkManagerClient + if cfg.EnableNetworkTracing { + networkManagerClient = networkmanager.CreateNetworkManager(ctx, cfg, k8sClient, storageClient, clusterData.ClusterName) + } else { + networkManagerClient = networkmanager.CreateNetworkManagerMock() + } + // Create the container handler - mainHandler, err := containerwatcher.CreateIGContainerWatcher(cfg, applicationProfileManager, k8sClient, relevancyManager) + mainHandler, err := containerwatcher.CreateIGContainerWatcher(cfg, applicationProfileManager, k8sClient, relevancyManager, networkManagerClient) if err != nil { logger.L().Ctx(ctx).Fatal("error creating the container watcher", helpers.Error(err)) } diff --git a/pkg/config/config.go b/pkg/config/config.go index d9891b48..c0ffe889 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,6 +11,7 @@ const NodeNameEnvVar = "NODE_NAME" type Config struct { EnableFullPathTracing bool `mapstructure:"fullPathTracingEnabled"` EnableApplicationProfile bool `mapstructure:"applicationProfileServiceEnabled"` + EnableNetworkTracing bool `mapstructure:"networkServiceEnabled"` EnableRelevancy bool `mapstructure:"relevantCVEServiceEnabled"` InitialDelay time.Duration `mapstructure:"initialDelay"` MaxSniffingTime time.Duration `mapstructure:"maxSniffingTimePerContainer"` diff --git a/pkg/containerwatcher/v1/container_watcher.go b/pkg/containerwatcher/v1/container_watcher.go index 1da5cd20..340c2eb0 100644 --- a/pkg/containerwatcher/v1/container_watcher.go +++ b/pkg/containerwatcher/v1/container_watcher.go @@ -6,6 +6,7 @@ import ( "node-agent/pkg/applicationprofilemanager" "node-agent/pkg/config" "node-agent/pkg/containerwatcher" + "node-agent/pkg/networkmanager" "node-agent/pkg/relevancymanager" "node-agent/pkg/utils" @@ -15,8 +16,11 @@ import ( tracercapabilitiestype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/capabilities/types" tracerexec "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/tracer" tracerexectype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types" + tracernetwork "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/tracer" + tracernetworktype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" traceropen "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/tracer" traceropentype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/types" + "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" tracercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/tracer-collection" "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" @@ -27,10 +31,12 @@ import ( const ( capabilitiesTraceName = "trace_capabilities" execTraceName = "trace_exec" + networkTraceName = "trace_network" openTraceName = "trace_open" capabilitiesWorkerPoolSize = 1 execWorkerPoolSize = 2 openWorkerPoolSize = 8 + networkWorkerPoolSize = 1 ) type IGContainerWatcher struct { @@ -43,6 +49,7 @@ type IGContainerWatcher struct { applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient k8sClient *k8sinterface.KubernetesApi relevancyManager relevancymanager.RelevancyManagerClient + networkManager networkmanager.NetworkManagerClient // IG Collections containerCollection *containercollection.ContainerCollection tracerCollection *tracercollection.TracerCollection @@ -51,15 +58,19 @@ type IGContainerWatcher struct { execTracer *tracerexec.Tracer openTracer *traceropen.Tracer syscallTracer *tracerseccomp.Tracer + networkTracer *tracernetwork.Tracer + kubeIPInstance operators.OperatorInstance + kubeNameInstance operators.OperatorInstance // Worker pools capabilitiesWorkerPool *ants.PoolWithFunc execWorkerPool *ants.PoolWithFunc openWorkerPool *ants.PoolWithFunc + networkWorkerPool *ants.PoolWithFunc } var _ containerwatcher.ContainerWatcher = (*IGContainerWatcher)(nil) -func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient, k8sClient *k8sinterface.KubernetesApi, relevancyManager relevancymanager.RelevancyManagerClient) (*IGContainerWatcher, error) { +func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient, k8sClient *k8sinterface.KubernetesApi, relevancyManager relevancymanager.RelevancyManagerClient, networkManagerClient networkmanager.NetworkManagerClient) (*IGContainerWatcher, error) { // Use container collection to get notified for new containers containerCollection := &containercollection.ContainerCollection{} // Create a tracer collection instance @@ -90,6 +101,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli if err != nil { return nil, fmt.Errorf("creating exec worker pool: %w", err) } + // Create an open worker pool openWorkerPool, err := ants.NewPoolWithFunc(openWorkerPoolSize, func(i interface{}) { event := i.(traceropentype.Event) @@ -105,6 +117,23 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli return nil, fmt.Errorf("creating open worker pool: %w", err) } + // Create a network worker pool + networkWorkerPool, err := ants.NewPoolWithFunc(networkWorkerPoolSize, func(i interface{}) { + event := i.(tracernetworktype.Event) + + k8sContainerID := utils.CreateK8sContainerID(event.K8s.Namespace, event.K8s.PodName, event.K8s.ContainerName) + + if k8sContainerID == "//" { + return + } + + networkManagerClient.SaveNetworkEvent(event.Runtime.ContainerID, event.K8s.PodName, event) + }) + + if err != nil { + return nil, fmt.Errorf("creating open network pool: %w", err) + } + return &IGContainerWatcher{ // Configuration cfg: cfg, @@ -113,6 +142,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli applicationProfileManager: applicationProfileManager, k8sClient: k8sClient, relevancyManager: relevancyManager, + networkManager: networkManagerClient, // IG Collections containerCollection: containerCollection, tracerCollection: tracerCollection, @@ -120,6 +150,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli capabilitiesWorkerPool: capabilitiesWorkerPool, execWorkerPool: execWorkerPool, openWorkerPool: openWorkerPool, + networkWorkerPool: networkWorkerPool, }, nil } diff --git a/pkg/containerwatcher/v1/container_watcher_private.go b/pkg/containerwatcher/v1/container_watcher_private.go index bc47193c..f9d4fc10 100644 --- a/pkg/containerwatcher/v1/container_watcher_private.go +++ b/pkg/containerwatcher/v1/container_watcher_private.go @@ -36,6 +36,7 @@ func (ch *IGContainerWatcher) startContainerCollection(ctx context.Context) erro ch.containerCallback, ch.applicationProfileManager.ContainerCallback, ch.relevancyManager.ContainerCallback, + ch.networkManager.ContainerCallback, } // Define the different options for the container collection instance @@ -98,6 +99,15 @@ func (ch *IGContainerWatcher) startTracers() error { return err } } + + if ch.cfg.EnableNetworkTracing { + // Start network tracer + if err := ch.startNetworkTracing(); err != nil { + logger.L().Error("error starting network tracing", helpers.Error(err)) + return err + } + } + return nil } @@ -127,6 +137,15 @@ func (ch *IGContainerWatcher) stopTracers() error { errs = errors.Join(err, ch.stopCapabilitiesTracing()) } } + + if ch.cfg.EnableNetworkTracing { + // Stop network tracer + if err := ch.stopNetworkTracing(); err != nil { + logger.L().Error("error stopping network tracing", helpers.Error(err)) + errs = errors.Join(err, ch.stopNetworkTracing()) + } + } + return errs } @@ -152,4 +171,5 @@ func (ch *IGContainerWatcher) unregisterContainer(container *containercollection ch.tracerCollection.TracerMapsUpdater()(event) ch.applicationProfileManager.ContainerCallback(event) ch.relevancyManager.ContainerCallback(event) + ch.networkManager.ContainerCallback(event) } diff --git a/pkg/containerwatcher/v1/network.go b/pkg/containerwatcher/v1/network.go new file mode 100644 index 00000000..60b5a449 --- /dev/null +++ b/pkg/containerwatcher/v1/network.go @@ -0,0 +1,94 @@ +package containerwatcher + +import ( + "fmt" + + "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection/networktracer" + tracernetwork "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/tracer" + tracernetworktypes "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" + "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" + "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/kubeipresolver" + "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/kubenameresolver" + "github.com/inspektor-gadget/inspektor-gadget/pkg/types" + "github.com/inspektor-gadget/inspektor-gadget/pkg/utils/host" + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" +) + +func (ch *IGContainerWatcher) networkEventCallback(event *tracernetworktypes.Event) { + if event.Type == types.DEBUG { + return + } + + if event.Type != types.NORMAL { + // dropped event + logger.L().Ctx(ch.ctx).Warning("network tracer got drop events - we may miss some realtime data", helpers.Interface("event", event), helpers.String("error", event.Message)) + return + } + ch.containerCollection.EnrichByMntNs(&event.CommonData, event.MountNsID) + + ch.kubeIPInstance.EnrichEvent(event) + ch.kubeNameInstance.EnrichEvent(event) + + _ = ch.networkWorkerPool.Invoke(*event) +} + +func (ch *IGContainerWatcher) startNetworkTracing() error { + host.Init(host.Config{AutoMountFilesystems: true}) + + if err := ch.tracerCollection.AddTracer(networkTraceName, ch.containerSelector); err != nil { + return fmt.Errorf("adding tracer: %w", err) + } + + tracerNetwork, err := tracernetwork.NewTracer() + if err != nil { + return fmt.Errorf("creating tracer: %w", err) + } + + kubeIPOp := operators.GetRaw(kubeipresolver.OperatorName).(*kubeipresolver.KubeIPResolver) + kubeIPOp.Init(nil) + ch.kubeIPInstance, err = kubeIPOp.Instantiate(nil, nil, nil) + if err == nil { + ch.kubeIPInstance.PreGadgetRun() + } else { + return fmt.Errorf("creating kube ip resolver: %w", err) + } + + kubeNameOp := operators.GetRaw(kubenameresolver.OperatorName).(*kubenameresolver.KubeNameResolver) + kubeNameOp.Init(nil) + ch.kubeNameInstance, err = kubeNameOp.Instantiate(nil, nil, nil) + if err == nil { + ch.kubeNameInstance.PreGadgetRun() + } else { + return fmt.Errorf("creating kube name resolver: %w", err) + } + + tracerNetwork.SetEventHandler(ch.networkEventCallback) + + ch.networkTracer = tracerNetwork + + config := &networktracer.ConnectToContainerCollectionConfig[tracernetworktypes.Event]{ + Tracer: ch.networkTracer, + Resolver: ch.containerCollection, + Selector: ch.containerSelector, + Base: tracernetworktypes.Base, + } + + _, err = networktracer.ConnectToContainerCollection(config) + if err != nil { + return fmt.Errorf("creating tracer: %w", err) + } + + return nil +} + +func (ch *IGContainerWatcher) stopNetworkTracing() error { + // Stop network tracer + if err := ch.tracerCollection.RemoveTracer(networkTraceName); err != nil { + return fmt.Errorf("removing tracer: %w", err) + } + + ch.networkTracer.Close() + + return nil +} diff --git a/pkg/containerwatcher/v1/open_test.go b/pkg/containerwatcher/v1/open_test.go index c793e3f5..0ab7197a 100644 --- a/pkg/containerwatcher/v1/open_test.go +++ b/pkg/containerwatcher/v1/open_test.go @@ -19,7 +19,7 @@ func BenchmarkIGContainerWatcher_openEventCallback(b *testing.B) { assert.NoError(b, err) relevancyManager, err := relevancymanager.CreateRelevancyManager(ctx, cfg, "cluster", fileHandler, nil, nil) assert.NoError(b, err) - mainHandler, err := CreateIGContainerWatcher(cfg, nil, nil, relevancyManager) + mainHandler, err := CreateIGContainerWatcher(cfg, nil, nil, relevancyManager, nil) assert.NoError(b, err) event := &traceropentype.Event{ Event: types.Event{ diff --git a/pkg/networkmanager/network_event.go b/pkg/networkmanager/network_event.go new file mode 100644 index 00000000..57bce6e4 --- /dev/null +++ b/pkg/networkmanager/network_event.go @@ -0,0 +1,89 @@ +package networkmanager + +import ( + "fmt" + "sort" + "strings" +) + +type NetworkEvent struct { + Port uint16 + PktType string + Protocol string + PodLabels string + Destination Destination +} + +type Destination struct { + Namespace string + Name string + Kind EndpointKind + PodLabels string + IPAddress string +} + +type EndpointKind string + +var defaultLabelsToIgnore = map[string]struct{}{ + "controller-revision-hash": {}, + "pod-template-generation": {}, + "pod-template-hash": {}, +} + +const ( + EndpointKindPod EndpointKind = "pod" + EndpointKindService EndpointKind = "svc" + EndpointKindRaw EndpointKind = "raw" +) + +func (ne *NetworkEvent) String() string { + return fmt.Sprintf("Port: %d, PktType: %s, Protocol: %s, PodLabels: %s, Destination: %s", ne.Port, ne.PktType, ne.Protocol, ne.PodLabels, ne.Destination) +} + +// GetDestinationPodLabels returns a map of pod labels from the string in the network event. The labels are saved separated by commas, so we need to split them +func (ne *NetworkEvent) GetDestinationPodLabels() map[string]string { + podLabels := make(map[string]string, 0) + + if ne.Destination.PodLabels == "" { + return podLabels + } + + podLabelsSlice := strings.Split(ne.Destination.PodLabels, ",") + for _, podLabel := range podLabelsSlice { + podLabelSlice := strings.Split(podLabel, "=") + if len(podLabelSlice) == 2 { + podLabels[podLabelSlice[0]] = podLabelSlice[1] + } + } + + return podLabels +} + +func (ne *NetworkEvent) SetPodLabels(podLabels map[string]string) { + ne.PodLabels = generatePodLabels(podLabels) +} + +func (ne *NetworkEvent) SetDestinationPodLabels(podLabels map[string]string) { + ne.Destination.PodLabels = generatePodLabels(podLabels) +} + +// generatePodLabels generates a single string from a map of pod labels. This string is separated with commas and is needed so the set in network manager will work +func generatePodLabels(podLabels map[string]string) string { + var keys []string + for key := range podLabels { + keys = append(keys, key) + } + + sort.Strings(keys) + + var podLabelsString string + for _, key := range keys { + podLabelsString = podLabelsString + key + "=" + podLabels[key] + "," + } + + if len(podLabelsString) > 0 { + podLabelsString = podLabelsString[:len(podLabelsString)-1] + } + + return podLabelsString +} diff --git a/pkg/networkmanager/network_event_test.go b/pkg/networkmanager/network_event_test.go new file mode 100644 index 00000000..a082b42f --- /dev/null +++ b/pkg/networkmanager/network_event_test.go @@ -0,0 +1,136 @@ +package networkmanager + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGeneratePodLabels(t *testing.T) { + tests := []struct { + name string + podLabels map[string]string + expectedResult string + }{ + { + name: "Empty Map", + podLabels: map[string]string{}, + expectedResult: "", + }, + { + name: "Single Label", + podLabels: map[string]string{ + "key1": "value1", + }, + expectedResult: "key1=value1", + }, + { + name: "Multiple Labels", + podLabels: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + expectedResult: "key1=value1,key2=value2,key3=value3", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actualResult := generatePodLabels(test.podLabels) + if actualResult != test.expectedResult { + t.Errorf("Expected: %s, but got: %s", test.expectedResult, actualResult) + } + }) + } + + // test that pod labels are sorted + podLabels1 := map[string]string{ + "key1": "value1", + "key2": "value2", + } + + podLabels2 := map[string]string{ + "key2": "value2", + "key1": "value1", + } + + actualResult1 := generatePodLabels(podLabels1) + actualResult2 := generatePodLabels(podLabels2) + assert.Equal(t, actualResult1, actualResult2) + +} + +func TestSetPodLabels(t *testing.T) { + ne := &NetworkEvent{} + + tests := []struct { + name string + podLabels map[string]string + expectedResult string + }{ + { + name: "regular order", + podLabels: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + expectedResult: "key1=value1,key2=value2", + }, + { + name: "change order", + podLabels: map[string]string{ + "key2": "value2", + "key1": "value1", + }, + expectedResult: "key1=value1,key2=value2", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ne.SetPodLabels(test.podLabels) + actualResult := ne.PodLabels + if actualResult != test.expectedResult { + t.Errorf("Expected: %s, but got: %s", test.expectedResult, actualResult) + } + }) + } +} + +func TestSetDestinationPodLabels(t *testing.T) { + ne := &NetworkEvent{} + + tests := []struct { + name string + podLabels map[string]string + expectedResult string + }{ + { + name: "regular order", + podLabels: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + expectedResult: "key1=value1,key2=value2", + }, + { + name: "change order", + podLabels: map[string]string{ + "key2": "value2", + "key1": "value1", + }, + expectedResult: "key1=value1,key2=value2", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ne.SetDestinationPodLabels(test.podLabels) + actualResult := ne.Destination.PodLabels + if actualResult != test.expectedResult { + t.Errorf("Expected: %s, but got: %s", test.expectedResult, actualResult) + } + }) + } +} diff --git a/pkg/networkmanager/network_manager.go b/pkg/networkmanager/network_manager.go new file mode 100644 index 00000000..28f11ef4 --- /dev/null +++ b/pkg/networkmanager/network_manager.go @@ -0,0 +1,543 @@ +package networkmanager + +import ( + "context" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "node-agent/pkg/config" + "node-agent/pkg/k8sclient" + "node-agent/pkg/storage" + "node-agent/pkg/utils" + "strings" + "time" + + "github.com/armosec/utils-k8s-go/wlid" + mapset "github.com/deckarep/golang-set/v2" + "github.com/google/uuid" + "github.com/goradd/maps" + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + tracernetworktype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" + instanceidhandlerV1 "github.com/kubescape/k8s-interface/instanceidhandler/v1" + "github.com/kubescape/k8s-interface/k8sinterface" + "github.com/kubescape/k8s-interface/workloadinterface" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + "github.com/kubescape/storage/pkg/generated/clientset/versioned/scheme" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/utils/ptr" +) + +const ( + internalTrafficType = "internal" + externalTrafficType = "external" + hostPktType = "HOST" + outgoingPktType = "OUTGOING" +) + +type NetworkManager struct { + cfg config.Config + ctx context.Context + k8sClient k8sclient.K8sClientInterface + storageClient storage.StorageClient + containerAndPodToWLIDMap maps.SafeMap[string, string] + containerAndPodToEventsMap maps.SafeMap[string, mapset.Set[NetworkEvent]] // TODO: change it to set + clusterName string + watchedContainerChannels maps.SafeMap[string, chan error] // key is ContainerID +} + +var _ NetworkManagerClient = (*NetworkManager)(nil) + +func CreateNetworkManager(ctx context.Context, cfg config.Config, k8sClient k8sclient.K8sClientInterface, storageClient storage.StorageClient, clusterName string) *NetworkManager { + return &NetworkManager{ + cfg: cfg, + ctx: ctx, + k8sClient: k8sClient, + storageClient: storageClient, + clusterName: clusterName, + } +} + +func (am *NetworkManager) ContainerCallback(notif containercollection.PubSubEvent) { + k8sContainerID := utils.CreateK8sContainerID(notif.Container.K8s.Namespace, notif.Container.K8s.PodName, notif.Container.K8s.ContainerName) + ctx, span := otel.Tracer("").Start(am.ctx, "NetworkManager.ContainerCallback", trace.WithAttributes(attribute.String("containerID", notif.Container.Runtime.ContainerID), attribute.String("k8s workload", k8sContainerID))) + defer span.End() + + switch notif.Type { + case containercollection.EventTypeAddContainer: + if am.watchedContainerChannels.Has(notif.Container.Runtime.ContainerID) { + logger.L().Debug("container already exist in memory", helpers.String("container ID", notif.Container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + return + } + go am.handleContainerStarted(ctx, notif.Container, k8sContainerID) + + case containercollection.EventTypeRemoveContainer: + channel := am.watchedContainerChannels.Get(notif.Container.Runtime.ContainerID) + if channel != nil { + channel <- utils.ContainerHasTerminatedError + } + am.watchedContainerChannels.Delete(notif.Container.Runtime.ContainerID) + } +} + +func (am *NetworkManager) SaveNetworkEvent(containerID, podName string, event tracernetworktype.Event) { + + if !am.isValidEvent(event) { + return + } + + networkEvent := &NetworkEvent{ + Port: event.Port, + Protocol: event.Proto, + PktType: event.PktType, + Destination: Destination{ + Namespace: event.DstEndpoint.Namespace, + Name: event.DstEndpoint.Name, + Kind: EndpointKind(event.DstEndpoint.Kind), + IPAddress: event.DstEndpoint.Addr, + }, + } + networkEvent.SetPodLabels(event.PodLabels) + networkEvent.SetDestinationPodLabels(event.DstEndpoint.PodLabels) + + networkEventsSet := am.containerAndPodToEventsMap.Get(containerID + podName) + if am.containerAndPodToEventsMap.Get(containerID+podName) == nil { + networkEventsSet = mapset.NewSet[NetworkEvent]() + } + networkEventsSet.Add(*networkEvent) + am.containerAndPodToEventsMap.Set(containerID+podName, networkEventsSet) +} + +// isValidEvent checks if the event is a valid event that should be saved +func (am *NetworkManager) isValidEvent(event tracernetworktype.Event) bool { + // unknown type, shouldn't happen + if event.PktType != hostPktType && event.PktType != outgoingPktType { + logger.L().Debug("NetworkManager - pktType is not HOST or OUTGOING", helpers.Interface("event", event)) + return false + } + + // ignore localhost + if event.PktType == hostPktType && event.PodHostIP == event.DstEndpoint.Addr { + return false + } + + // ignore host netns + if event.K8s.HostNetwork { + return false + } + + return true +} + +func (am *NetworkManager) handleContainerStarted(ctx context.Context, container *containercollection.Container, k8sContainerID string) { + ctx, span := otel.Tracer("").Start(ctx, "NetworkManager.handleContainerStarted") + defer span.End() + + watchedContainer := &utils.WatchedContainerData{ + ContainerID: container.Runtime.ContainerID, + UpdateDataTicker: time.NewTicker(am.cfg.InitialDelay), + SyncChannel: make(chan error, 10), + K8sContainerID: k8sContainerID, + RelevantRealtimeFilesByPackageSourceInfo: map[string]*utils.PackageSourceInfoData{}, + RelevantRealtimeFilesBySPDXIdentifier: map[v1beta1.ElementID]bool{}, + } + am.watchedContainerChannels.Set(watchedContainer.ContainerID, watchedContainer.SyncChannel) + + // retrieve parent WL + parentWL, err := am.getParentWorkloadFromContainer(container) + if err != nil { + logger.L().Warning("NetworkManager - failed to get parent workload", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + return + } + + selector, err := parentWL.GetSelector() + if err != nil { + // if we get not selector, we can't create/update network neighbor + logger.L().Warning("NetworkManager - failed to get selector", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + return + } + + if selector == nil { + // if we get not selector, we can't create/update network neighbor + logger.L().Warning("NetworkManager - selector is nil", helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + return + } + + // check if network neighbor CRD exists + networkNeighbors, err := am.storageClient.GetNetworkNeighbors(parentWL.GetNamespace(), generateNetworkNeighborsNameFromWorkload(parentWL)) + if err != nil { + if !strings.Contains(err.Error(), "not found") { + logger.L().Warning("NetworkManager - failed to get network neighbor", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } else { + // network neighbor not found, create new one + newNetworkNeighbors := generateNetworkNeighborsCRD(parentWL, selector) + + if err = am.storageClient.CreateNetworkNeighbors(newNetworkNeighbors, parentWL.GetNamespace()); err != nil { + logger.L().Warning("NetworkManager - failed to create network neighbor", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } + logger.L().Debug("NetworkManager - created network neighbor", helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } + } else { + // CRD found, update labels + networkNeighbors.Spec.LabelSelector = *selector + if err = am.storageClient.PatchNetworkNeighborsMatchLabels(networkNeighbors.GetName(), networkNeighbors.GetNamespace(), networkNeighbors); err != nil { + logger.L().Warning("NetworkManager - failed to update network neighbor", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } + logger.L().Debug("NetworkManager - updated network neighbor labels", helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } + + // save container + pod to wlid map + am.containerAndPodToWLIDMap.Set(container.Runtime.ContainerID+container.K8s.PodName, parentWL.GenerateWlid(am.clusterName)) + + if err := am.monitorContainer(ctx, container, watchedContainer); err != nil { + logger.L().Info("NetworkManager - stop monitor on container", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + } + + am.deleteResources(container) +} + +// TODO: use same function in relevancy +func (am *NetworkManager) getParentWorkloadFromContainer(container *containercollection.Container) (k8sinterface.IWorkload, error) { + wl, err := am.k8sClient.GetWorkload(container.K8s.Namespace, "Pod", container.K8s.PodName) + if err != nil { + return nil, err + } + pod := wl.(*workloadinterface.Workload) + + // find parentWlid + kind, name, err := am.k8sClient.CalculateWorkloadParentRecursive(pod) + if err != nil { + return nil, err + } + + parentWorkload, err := am.k8sClient.GetWorkload(pod.GetNamespace(), kind, name) + if err != nil { + return nil, err + } + + w := parentWorkload.(*workloadinterface.Workload) + parentWlid := w.GenerateWlid(am.clusterName) + + err = wlid.IsWlidValid(parentWlid) + if err != nil { + return nil, err + } + + return parentWorkload, nil +} + +func (am *NetworkManager) deleteResources(container *containercollection.Container) { + // clean up + am.containerAndPodToWLIDMap.Delete(container.Runtime.ContainerID + container.K8s.PodName) + am.containerAndPodToEventsMap.Delete(container.Runtime.ContainerID + container.K8s.PodName) + am.watchedContainerChannels.Delete(container.Runtime.ContainerID) +} + +func (am *NetworkManager) monitorContainer(ctx context.Context, container *containercollection.Container, watchedContainer *utils.WatchedContainerData) error { + for { + select { + case <-watchedContainer.UpdateDataTicker.C: + // adjust ticker after first tick + if !watchedContainer.InitialDelayExpired { + watchedContainer.InitialDelayExpired = true + watchedContainer.UpdateDataTicker.Reset(am.cfg.UpdateDataPeriod) + } + + am.handleNetworkEvents(ctx, container, watchedContainer) + case err := <-watchedContainer.SyncChannel: + switch { + case errors.Is(err, utils.ContainerHasTerminatedError): + am.handleNetworkEvents(ctx, container, watchedContainer) + return nil + } + } + } +} + +// handleNetworkEvents retrieves network events from map, generate entries for CRD and sends a PATCH command to update them +func (am *NetworkManager) handleNetworkEvents(ctx context.Context, container *containercollection.Container, watchedContainer *utils.WatchedContainerData) { + + networkEvents := am.containerAndPodToEventsMap.Get(container.Runtime.ContainerID + container.K8s.PodName) + if networkEvents == nil { + // no events to handle + return + } + // TODO: dns enrichment + + // update CRD based on events + + // retrieve parent WL from internal map + parentWlid := am.containerAndPodToWLIDMap.Get(container.Runtime.ContainerID + container.K8s.PodName) + if parentWlid == "" { + logger.L().Warning("failed to get parent wlid from map", helpers.String("container ID", container.Runtime.ContainerID), helpers.String("pod name", container.K8s.PodName)) + return + } + + networkNeighborsSpec := am.generateNetworkNeighborsEntries(container.K8s.Namespace, networkEvents) + // send PATCH command using entries generated from events + if err := am.storageClient.PatchNetworkNeighborsIngressAndEgress(generateNetworkNeighborsNameFromWlid(parentWlid), wlid.GetNamespaceFromWlid(parentWlid), &v1beta1.NetworkNeighbors{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + instanceidhandlerV1.StatusMetadataKey: completeStatus, + }, + }, + Spec: networkNeighborsSpec, + }); err != nil { + // check if error is because crd wasn't created + if !strings.Contains(err.Error(), "not found") { + logger.L().Warning("failed to update network neighbor", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", watchedContainer.K8sContainerID)) + return + } + + // if the error is not found, we need to create it + // this can happen if the storage wasn't available when the container started + parentWL, err := am.getParentWorkloadFromContainer(container) + if err != nil { + logger.L().Warning("NetworkManager - failed to get parent workload", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("parent wlid", parentWlid)) + return + } + + selector, err := parentWL.GetSelector() + if err != nil { + logger.L().Warning("NetworkManager - failed to get selector", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("parent wlid", parentWlid)) + return + } + newNetworkNeighbors := generateNetworkNeighborsCRD(parentWL, selector) + + // update spec and annotations + networkNeighborsSpec.LabelSelector = *selector + newNetworkNeighbors.Spec = networkNeighborsSpec + newNetworkNeighbors.Annotations = map[string]string{ + instanceidhandlerV1.StatusMetadataKey: completeStatus, + } + + logger.L().Debug("NetworkManager - creating network neighbor", helpers.Interface("labels", selector), helpers.String("container ID", container.Runtime.ContainerID), helpers.Interface("labels", newNetworkNeighbors.Spec.LabelSelector)) + + if err = am.storageClient.CreateNetworkNeighbors(newNetworkNeighbors, parentWL.GetNamespace()); err != nil { + logger.L().Warning("NetworkManager - failed to create network neighbor", helpers.String("reason", err.Error()), helpers.String("container ID", container.Runtime.ContainerID), helpers.String("parent wlid", parentWlid)) + return + } + } + logger.L().Debug("NetworkManager - updated network neighbor", helpers.String("container ID", container.Runtime.ContainerID), helpers.String("k8s workload", watchedContainer.K8sContainerID)) + + // remove events from map + am.containerAndPodToEventsMap.Delete(container.Runtime.ContainerID + container.K8s.PodName) +} + +func (am *NetworkManager) generateNetworkNeighborsEntries(namespace string, networkEvents mapset.Set[NetworkEvent]) v1beta1.NetworkNeighborsSpec { + var networkNeighborsSpec v1beta1.NetworkNeighborsSpec + + // auxiliary maps to avoid duplicates + ingressIdentifiersMap := make(map[string]v1beta1.NetworkNeighbor) + egressIdentifiersMap := make(map[string]v1beta1.NetworkNeighbor) + + networkEventsIterator := networkEvents.Iterator() + if networkEventsIterator == nil { + return networkNeighborsSpec + } + + for networkEvent := range networkEventsIterator.C { + var neighborEntry v1beta1.NetworkNeighbor + + if networkEvent.Destination.Kind == EndpointKindPod { + // for Pods we need to remove the default labels + neighborEntry.PodSelector = &metav1.LabelSelector{ + MatchLabels: filterLabels(networkEvent.GetDestinationPodLabels()), + } + + if namespaceLabels := getNamespaceMatchLabels(networkEvent.Destination.Namespace, namespace); namespaceLabels != nil { + neighborEntry.NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: namespaceLabels, + } + } + + } else if networkEvent.Destination.Kind == EndpointKindService { + // for service, we need to retrieve it and use its selector + svc, err := am.k8sClient.GetWorkload(networkEvent.Destination.Namespace, "Service", networkEvent.Destination.Name) + if err != nil { + logger.L().Warning("failed to get service", helpers.String("reason", err.Error()), helpers.String("service name", networkEvent.Destination.Name)) + continue + } + + selector := svc.GetServiceSelector() + if len(selector) == 0 { + if err = am.handleServiceWithNoSelectors(svc, networkEvent, egressIdentifiersMap, ingressIdentifiersMap); err != nil { + logger.L().Warning("failed to handle service with no selectors", helpers.String("reason", err.Error()), helpers.String("service name", networkEvent.Destination.Name)) + } + continue + } else { + neighborEntry.PodSelector = &metav1.LabelSelector{ + MatchLabels: selector, + } + if namespaceLabels := getNamespaceMatchLabels(networkEvent.Destination.Namespace, namespace); namespaceLabels != nil { + neighborEntry.NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: namespaceLabels, + } + } + } + + } else { + if networkEvent.Destination.IPAddress == "127.0.0.1" { + // No need to generate for localhost + continue + } + neighborEntry.IPAddress = networkEvent.Destination.IPAddress + } + + saveNeighborEntry(networkEvent, neighborEntry, egressIdentifiersMap, ingressIdentifiersMap) + + } + + networkNeighborsSpec.Egress = make([]v1beta1.NetworkNeighbor, 0, len(egressIdentifiersMap)) + for _, neighborEntry := range egressIdentifiersMap { + networkNeighborsSpec.Egress = append(networkNeighborsSpec.Egress, neighborEntry) + } + + networkNeighborsSpec.Ingress = make([]v1beta1.NetworkNeighbor, 0, len(ingressIdentifiersMap)) + for _, neighborEntry := range ingressIdentifiersMap { + networkNeighborsSpec.Ingress = append(networkNeighborsSpec.Ingress, neighborEntry) + } + + return networkNeighborsSpec +} + +// saveNeighborEntry encapsulates the logic of generating identifiers and adding the neighborEntry to the map +func saveNeighborEntry(networkEvent NetworkEvent, neighborEntry v1beta1.NetworkNeighbor, egressIdentifiersMap map[string]v1beta1.NetworkNeighbor, ingressIdentifiersMap map[string]v1beta1.NetworkNeighbor) { + + portIdentifier := generatePortIdentifierFromEvent(networkEvent) + + neighborEntry.Ports = []v1beta1.NetworkPort{ + { + Protocol: v1beta1.Protocol(networkEvent.Protocol), + Port: ptr.To(int32(networkEvent.Port)), + Name: portIdentifier, + }} + + neighborEntry.Type = internalTrafficType + if neighborEntry.NamespaceSelector == nil && neighborEntry.PodSelector == nil { + neighborEntry.Type = externalTrafficType + } + + // generate identifier for this neighborEntry + identifier, err := generateNeighborsIdentifier(neighborEntry) + if err != nil { + // if we fail to hash, use a random identifier so at least we have the data on the crd + logger.L().Debug("failed to hash identifier", helpers.String("identifier", identifier), helpers.String("error", err.Error())) + identifier = uuid.New().String() + } + + if networkEvent.PktType == outgoingPktType { + addToMap(egressIdentifiersMap, identifier, portIdentifier, neighborEntry) + } else { + addToMap(ingressIdentifiersMap, identifier, portIdentifier, neighborEntry) + } +} + +// addToMap adds neighborEntry to identifiersMap, if identifier already exists, it will add the ports to the existing entry +func addToMap(identifiersMap map[string]v1beta1.NetworkNeighbor, identifier string, portIdentifier string, neighborEntry v1beta1.NetworkNeighbor) { + if existingNeighborEntry, ok := identifiersMap[identifier]; ok { + found := false + for _, port := range existingNeighborEntry.Ports { + if port.Name == portIdentifier { + found = true + break + } + } + if !found { + neighborEntry.Ports = append(existingNeighborEntry.Ports, neighborEntry.Ports...) + } + } + neighborEntry.Identifier = identifier + identifiersMap[identifier] = neighborEntry +} + +func (am *NetworkManager) handleServiceWithNoSelectors(svc workloadinterface.IWorkload, networkEvent NetworkEvent, egressIdentifiersMap map[string]v1beta1.NetworkNeighbor, ingressIdentifiersMap map[string]v1beta1.NetworkNeighbor) error { + + // retrieve endpoint + endpoints, err := am.k8sClient.GetWorkload(networkEvent.Destination.Namespace, "Endpoint", networkEvent.Destination.Name) + if err != nil { + return err + } + + endpointsMap := endpoints.GetObject() + + endpointsBytes, err := json.Marshal(endpointsMap) + if err != nil { + return err + } + + decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() + obj := &v1.Endpoints{} + + if err = runtime.DecodeInto(decoder, endpointsBytes, obj); err != nil { + return err + } + + // for each IP in the endpoint, generate a neighborEntry with its ports + for _, subset := range obj.Subsets { + for _, address := range subset.Addresses { + neighborEntry := v1beta1.NetworkNeighbor{ + IPAddress: address.IP, + Type: internalTrafficType, + } + for _, ports := range subset.Ports { + neighborEntry.Ports = append(neighborEntry.Ports, v1beta1.NetworkPort{ + Protocol: v1beta1.Protocol(ports.Protocol), + Port: ptr.To(int32(ports.Port)), + Name: generatePortIdentifier(string(ports.Protocol), ports.Port), + }) + } + + identifier, err := generateNeighborsIdentifier(neighborEntry) + if err != nil { + identifier = uuid.New().String() + } + neighborEntry.Identifier = identifier + + if networkEvent.PktType == outgoingPktType { + egressIdentifiersMap[identifier] = neighborEntry + } else { + ingressIdentifiersMap[identifier] = neighborEntry + } + } + } + + return nil +} + +func getNamespaceMatchLabels(destinationNamespace, sourceNamespace string) map[string]string { + if destinationNamespace != sourceNamespace { + // from version 1.22, all namespace have the kubernetes.io/metadata.name label + return map[string]string{ + "kubernetes.io/metadata.name": destinationNamespace, + } + } + return nil +} + +func generateNeighborsIdentifier(neighborEntry v1beta1.NetworkNeighbor) (string, error) { + // identifier is hash of everything in egress except ports + identifier := fmt.Sprintf("%s-%s-%s-%s-%s", neighborEntry.Type, neighborEntry.IPAddress, neighborEntry.DNS, neighborEntry.NamespaceSelector, neighborEntry.PodSelector) + hash := sha256.New() + _, err := hash.Write([]byte(identifier)) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} + +func generatePortIdentifierFromEvent(networkEvent NetworkEvent) string { + return generatePortIdentifier(networkEvent.Protocol, int32(networkEvent.Port)) +} + +func generatePortIdentifier(protocol string, port int32) string { + return fmt.Sprintf("%s-%d", protocol, port) +} diff --git a/pkg/networkmanager/network_manager_interface.go b/pkg/networkmanager/network_manager_interface.go new file mode 100644 index 00000000..c28cc176 --- /dev/null +++ b/pkg/networkmanager/network_manager_interface.go @@ -0,0 +1,11 @@ +package networkmanager + +import ( + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + tracernetworktype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" +) + +type NetworkManagerClient interface { + ContainerCallback(notif containercollection.PubSubEvent) + SaveNetworkEvent(containerName, podName string, networkEvent tracernetworktype.Event) +} diff --git a/pkg/networkmanager/network_manager_mock.go b/pkg/networkmanager/network_manager_mock.go new file mode 100644 index 00000000..58e1ff61 --- /dev/null +++ b/pkg/networkmanager/network_manager_mock.go @@ -0,0 +1,22 @@ +package networkmanager + +import ( + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + tracernetworktype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" +) + +type NetworkManagerMock struct { +} + +var _ NetworkManagerClient = (*NetworkManagerMock)(nil) + +func CreateNetworkManagerMock() *NetworkManagerMock { + return &NetworkManagerMock{} +} + +func (am *NetworkManagerMock) ContainerCallback(notif containercollection.PubSubEvent) { + +} + +func (am *NetworkManagerMock) SaveNetworkEvent(containerID, podName string, event tracernetworktype.Event) { +} diff --git a/pkg/networkmanager/network_manager_test.go b/pkg/networkmanager/network_manager_test.go new file mode 100644 index 00000000..fdaf0e6d --- /dev/null +++ b/pkg/networkmanager/network_manager_test.go @@ -0,0 +1,1270 @@ +package networkmanager + +import ( + "context" + "fmt" + "node-agent/pkg/config" + "testing" + + _ "embed" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + mapset "github.com/deckarep/golang-set/v2" + tracernetworktype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types" + "github.com/inspektor-gadget/inspektor-gadget/pkg/types" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + "github.com/stretchr/testify/assert" +) + +// this test is for development purposes +// func TestNetworkManager(t *testing.T) { +// cfg := config.Config{ +// InitialDelay: 1 * time.Second, +// MaxSniffingTime: 5 * time.Minute, +// UpdateDataPeriod: 10 * time.Second, +// } +// ctx := context.TODO() +// k8sClient := k8sinterface.NewKubernetesApi() +// storageClient, err := storagev1.CreateStorageNoCache() +// assert.NoError(t, err) +// am := CreateNetworkManager(ctx, cfg, k8sClient, storageClient, "test-cluster") +// containers := []containercollection.Container{ +// { +// K8s: containercollection.K8sMetadata{ +// BasicK8sMetadata: types.BasicK8sMetadata{ +// Namespace: "default", +// PodName: "nginx-deployment-fcc867f7-dgjrg", +// ContainerName: "nginx", +// }, +// }, +// Runtime: containercollection.RuntimeMetadata{ +// BasicRuntimeMetadata: types.BasicRuntimeMetadata{ +// ContainerID: "docker://802c6c322d264557779fe785013a0dfa84eb658e7791aa36396da809fcb3329c", +// }, +// }, +// }, +// { +// K8s: containercollection.K8sMetadata{ +// BasicK8sMetadata: types.BasicK8sMetadata{ +// Namespace: "kube-system", +// PodName: "fluentd-elasticsearch-hlsbx", +// ContainerName: "fluentd-elasticsearch", +// }, +// }, +// Runtime: containercollection.RuntimeMetadata{ +// BasicRuntimeMetadata: types.BasicRuntimeMetadata{ +// ContainerID: "docker://50b40cad5db4165b712909453e1927d8baada94cdefa7c11b90cb775024d041d", +// }, +// }, +// }, +// } +// for i := range containers { +// am.ContainerCallback(containercollection.PubSubEvent{ +// Type: containercollection.EventTypeAddContainer, +// Container: &containers[i], +// }) +// } +// networkEvents := []*tracernetworktype.Event{ +// { +// Port: 6666, +// PktType: "HOST", +// Proto: "tcp", +// PodLabels: map[string]string{"app5": "nginx5"}, +// DstEndpoint: types.L3Endpoint{ +// Namespace: "default", +// Name: "nginx-deployment-cbdccf466-csh9c", +// Kind: "pod", +// PodLabels: map[string]string{"app": "nginx2"}, +// Addr: "19.64.52.5", +// }, +// }, +// // { +// // Port: 8000, +// // PktType: "HOST", +// // Protocol: "tcp", +// // PodLabels: "app=nginx2", +// // Destination: Destination{ +// // Namespace: "default", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindPod, +// // PodLabels: "app=nginx2", +// // IPAddress: "19.64.52.5", +// // }, +// // }, +// // { +// // Port: 80, +// // PktType: "HOST", +// // Protocol: "tcp", +// // PodLabels: "app=nginx", +// // Destination: Destination{ +// // Namespace: "default", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindService, +// // PodLabels: "SERVICE=nginx2", +// // IPAddress: "19.64.52.4", +// // }, +// // }, +// // { +// // Port: 80, +// // PktType: "HOST", +// // Protocol: "tcp", +// // PodLabels: "app=nginx2", +// // Destination: Destination{ +// // Namespace: "default", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindPod, +// // PodLabels: "app=nginx2", +// // IPAddress: "19.64.52.4", +// // }, +// // }, +// // { +// // Port: 3333, +// // PktType: "OUTGOING", +// // Protocol: "tcp", +// // PodLabels: "", +// // Destination: Destination{ +// // Namespace: "", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindRaw, +// // PodLabels: "", +// // IPAddress: "19.64.52.4", +// // }, +// // }, { +// // Port: 4444, +// // PktType: "OUTGOING", +// // Protocol: "tcp", +// // PodLabels: "", +// // Destination: Destination{ +// // Namespace: "", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindRaw, +// // PodLabels: "", +// // IPAddress: "19.64.52.4", +// // }, +// // }, { +// // Port: 4444, +// // PktType: "OUTGOING", +// // Protocol: "tcp", +// // PodLabels: "", +// // Destination: Destination{ +// // Namespace: "", +// // Name: "nginx-deployment-cbdccf466-csh9c", +// // Kind: EndpointKindRaw, +// // PodLabels: "", +// // IPAddress: "19.64.52.5", +// // }, +// // }, +// } +// time.Sleep(10 * time.Second) +// for i := range networkEvents { +// am.SaveNetworkEvent(containers[0].Runtime.ContainerID, containers[0].K8s.PodName, *networkEvents[i]) +// } +// time.Sleep(150 * time.Second) +// } + +func TestGenerateNeighborsIdentifier(t *testing.T) { + tests := []struct { + name string + input v1beta1.NetworkNeighbor + expected string + }{ + { + name: "external", + input: v1beta1.NetworkNeighbor{ + Type: "external", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port1", Protocol: "TCP"}}, + PodSelector: nil, + NamespaceSelector: nil, + IPAddress: "192.168.1.1", + }, + expected: "a13ce4ca8de4083d05986cdc9874c5bc75870f93a89363acc36e12511ceae5d8", + }, + { + name: "external - different IP has different identifier", + input: v1beta1.NetworkNeighbor{ + Type: "external", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port1", Protocol: "TCP"}}, + PodSelector: nil, + NamespaceSelector: nil, + IPAddress: "192.168.1.3", + }, + expected: "5e620390e1aa074ccca30576eb9e09db9254a07b1d6cef9b45d7f98a1f72c863", + }, + { + name: "internal", + input: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port1", Protocol: "TCP"}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + NamespaceSelector: nil, + IPAddress: "192.168.1.1", + }, + expected: "fd41d439d5de80f684d53dc9682ca335f93f6f754031d6e3624a9772b8010680", + }, + { + name: "internal - different ports has same identifier", + input: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port2", Protocol: "udp"}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + NamespaceSelector: nil, + IPAddress: "192.168.1.1", + }, + expected: "fd41d439d5de80f684d53dc9682ca335f93f6f754031d6e3624a9772b8010680", + }, + { + name: "internal - different pod labels has different identifier", + input: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port2", Protocol: "udp"}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app2": "nginx"}}, + NamespaceSelector: nil, + IPAddress: "192.168.1.1", + }, + expected: "0848cb483e73375684bbc7333f64d74dfa13260fc9d9ff178cdead9b1f695944", + }, + { + name: "internal - different namespace labels has different identifier", + input: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "example.com", + Ports: []v1beta1.NetworkPort{{Name: "port2", Protocol: "udp"}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app2": "nginx"}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app2": "nginx"}}, + IPAddress: "192.168.1.1", + }, + expected: "d4e9bce7335a0eee24b725edb9de785fecfebad7bfc4f2ea4a49830925b745da", + }, + { + name: "internal - different dns has different identifier", + input: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "another.co m", + Ports: []v1beta1.NetworkPort{{Name: "port2", Protocol: "udp"}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app2": "nginx"}}, + NamespaceSelector: nil, + IPAddress: "192.168.1.1", + }, + expected: "f3dd4abe5311abc6ab3768182af5a15cb96746dd82573a744e2132d9ac90f52d", + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %+v", tc.input), func(t *testing.T) { + result, err := generateNeighborsIdentifier(tc.input) + if err != nil { + t.Fatalf("Error: %v", err) + } + if result != tc.expected { + t.Errorf("in test: %s, Expected: %s, Got: %s", tc.name, tc.expected, result) + } + }) + } +} + +func TestGeneratePortIdentifierFromEvent(t *testing.T) { + testCases := []struct { + input NetworkEvent + expected string + }{ + { + input: NetworkEvent{ + Port: 80, + PktType: "TCP", + Protocol: "HTTP", + Destination: Destination{ + Namespace: "namespace1", + Name: "name1", + Kind: EndpointKindPod, + PodLabels: "label1=labelValue1,label2=labelValue2", + IPAddress: "192.168.1.1", + }, + }, + expected: "HTTP-80", + }, + { + input: NetworkEvent{ + Port: 333, + PktType: "TCP", + Protocol: "UDP", + Destination: Destination{ + Namespace: "namespace1", + Name: "name1", + Kind: EndpointKindPod, + PodLabels: "label1=labelValue1,label2=labelValue2", + IPAddress: "192.168.1.1", + }, + }, + expected: "UDP-333", + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Input: %+v", tc.input), func(t *testing.T) { + result := generatePortIdentifierFromEvent(tc.input) + if result != tc.expected { + t.Errorf("Expected: %s, Got: %s", tc.expected, result) + } + }) + } +} + +func TestGenerateNetworkNeighborsEntries(t *testing.T) { + tests := []struct { + name string + namespace string + networkEvents []NetworkEvent + expectedSpec v1beta1.NetworkNeighborsSpec + }{ + { + name: "empty", + namespace: "default", + expectedSpec: v1beta1.NetworkNeighborsSpec{}, + }, + { + name: "pod from same namespace ingress - should not have namespace selector", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 80, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination", + IPAddress: "", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Ingress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + NamespaceSelector: nil, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + }, + }, + }, + { + name: "pod from same namespace egress - should not have namespace selector", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 80, + PktType: "OUTGOING", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Egress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + NamespaceSelector: nil, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + }, + }, + }, + { + name: "pod from another namespace - should have namespace selector", + namespace: "default", + networkEvents: []NetworkEvent{ + { + Port: 80, + PktType: "OUTGOING", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,pod-template-hash=test", + IPAddress: "", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Egress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + IPAddress: "", + Identifier: "c86024d63c2bfddde96a258c3005e963e06fb9d8ee941a6de3003d6eae5dd7cc", + }, + }, + }, + }, + { + name: "raw IP", + namespace: "default", + networkEvents: []NetworkEvent{ + { + Port: 80, + PktType: "OUTGOING", + Protocol: "UDP", + Destination: Destination{ + Kind: EndpointKindRaw, + IPAddress: "143.54.53.21", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Egress: []v1beta1.NetworkNeighbor{ + { + Type: "external", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "UDP-80", Protocol: "UDP", Port: ptr.To(int32(80))}}, + IPAddress: "143.54.53.21", + Identifier: "3bbd32606a8516f97e7e3c11b0e914744c56cd6b8a2cadf010dd5fc648285535", + }, + }, + }, + }, + { + name: "raw IP localhost - should be ignored", + namespace: "default", + networkEvents: []NetworkEvent{ + { + Port: 80, + PktType: "OUTGOING", + Protocol: "TCP", + Destination: Destination{ + Kind: EndpointKindRaw, + IPAddress: "127.0.0.1", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{}, + }, + { + name: "multiple events with different ports - ports are merged", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + { + Port: 2, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + { + Port: 3, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + { + Port: 3, + PktType: "OUTGOING", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Ingress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-1", Protocol: "TCP", Port: ptr.To(int32(1))}, {Name: "TCP-2", Protocol: "TCP", Port: ptr.To(int32(2))}, {Name: "TCP-3", Protocol: "TCP", Port: ptr.To(int32(3))}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + NamespaceSelector: nil, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + }, + Egress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-3", Protocol: "TCP", Port: ptr.To(int32(3))}}, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + }, + }, + }, + { + name: "multiple events - different ip labels are saved separately", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Kind: EndpointKindRaw, + IPAddress: "1.2.3.4", + }, + }, + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Kind: EndpointKindRaw, + IPAddress: "4.3.2.1", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Ingress: []v1beta1.NetworkNeighbor{ + { + Type: "external", + DNS: "", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-1", + Protocol: "TCP", + Port: ptr.To(int32(1)), + }, + }, + IPAddress: "1.2.3.4", + Identifier: "24fe17e6c3ee75d94d0b3ab7ff3ffb8d60b8a108df505aae1bab241cc8f8ae91", + }, + { + Type: "external", + DNS: "", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-1", + Protocol: "TCP", + Port: ptr.To(int32(1)), + }, + }, + IPAddress: "4.3.2.1", + Identifier: "b94b02766fdf0694c9d2d03696f41c70e0df0784b4dc9e2ce2c9b1808bc8d273", + }, + }, + }, + }, + { + name: "multiple events - different pod labels are saved separately", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + IPAddress: "", + }, + }, + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Namespace: "kubescape", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: EndpointKindPod, + PodLabels: "app=destination2,controller-revision-hash=hash", + IPAddress: "", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Ingress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-1", + Protocol: "TCP", + Port: ptr.To(int32(1)), + }, + }, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + NamespaceSelector: nil, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + { + Type: "internal", + DNS: "", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-1", + Protocol: "TCP", + Port: ptr.To(int32(1)), + }, + }, + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination2"}}, + NamespaceSelector: nil, + IPAddress: "", + Identifier: "4c4c30e0f156db2ec7212a9ce68f17613a4a755325e647084ef9379f8eb6caaa", + }, + }, + }, + }, + { + name: "multiple events - different name are saved together", + namespace: "kubescape", + networkEvents: []NetworkEvent{ + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + Namespace: "kubescape", + Name: "one", + IPAddress: "1.2.3.4", + }, + }, + { + Port: 1, + PktType: "HOST", + Protocol: "TCP", + PodLabels: "app=nginx", + Destination: Destination{ + Kind: EndpointKindPod, + PodLabels: "app=destination,controller-revision-hash=hash", + Namespace: "kubescape", + Name: "two", + IPAddress: "1.2.3.4", + }, + }, + }, + expectedSpec: v1beta1.NetworkNeighborsSpec{ + Ingress: []v1beta1.NetworkNeighbor{ + { + Type: "internal", + PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "destination"}}, + DNS: "", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-1", + Protocol: "TCP", + Port: ptr.To(int32(1)), + }, + }, + IPAddress: "", + Identifier: "0d13d659ca4ba62f02f78781a15e1bfb4f88b29761d06c1b90cfa8834d9845c7", + }, + }, + }, + }, + } + + am := CreateNetworkManager(context.TODO(), config.Config{}, nil, nil, "") + + for _, tc := range tests { + networkEventsSet := mapset.NewSet[NetworkEvent]() + for _, ne := range tc.networkEvents { + networkEventsSet.Add(ne) + } + t.Run(fmt.Sprintf("Input: %+v", tc.networkEvents), func(t *testing.T) { + result := am.generateNetworkNeighborsEntries(tc.namespace, networkEventsSet) + + assert.Equal(t, len(result.Ingress), len(tc.expectedSpec.Ingress), "Ingress IP address is not equal in test %s", tc.name) + found := 0 + for _, ingress := range result.Ingress { + for _, expectedIngress := range tc.expectedSpec.Ingress { + if ingress.Identifier == expectedIngress.Identifier { + assert.Equal(t, ingress.Type, expectedIngress.Type, "Ingress type is not equal in test %s", tc.name) + assert.Equal(t, ingress.DNS, expectedIngress.DNS, "Ingress DNS is not equal in test %s", tc.name) + assert.Equal(t, ingress.Ports, expectedIngress.Ports, "Ingress ports are not equal in test %s", tc.name) + assert.Equal(t, ingress.PodSelector, expectedIngress.PodSelector, "Ingress pod selector is not equal in test %s", tc.name) + assert.Equal(t, ingress.NamespaceSelector, expectedIngress.NamespaceSelector, "Ingress namespace selector is not equal in test %s", tc.name) + assert.Equal(t, ingress.IPAddress, expectedIngress.IPAddress, "Ingress IP address is not equal in test %s", tc.name) + + assert.Equal(t, len(ingress.Ports), len(expectedIngress.Ports), "Ingress ports are not equal in test %s", tc.name) + + for _, port := range ingress.Ports { + foundPort := false + for _, expectedPort := range expectedIngress.Ports { + if port.Name == expectedPort.Name && *port.Port == *expectedPort.Port && port.Protocol == expectedPort.Protocol { + foundPort = true + break + } + } + assert.True(t, foundPort, "Port %+v not found in ingress %+v", port, ingress) + } + + found++ + } + } + + } + assert.Equal(t, found, len(tc.expectedSpec.Ingress), "Ingress IP address is not equal in test %s", tc.name) + + assert.Equal(t, len(result.Egress), len(tc.expectedSpec.Egress), "Egress IP address is not equal in test %s", tc.name) + found = 0 + for _, egress := range result.Egress { + for _, expectedEgress := range tc.expectedSpec.Egress { + if egress.Identifier == expectedEgress.Identifier { + assert.Equal(t, egress.Type, expectedEgress.Type, "Egress type is not equal in test %s", tc.name) + assert.Equal(t, egress.DNS, expectedEgress.DNS, "Egress DNS is not equal in test %s", tc.name) + assert.Equal(t, egress.Ports, expectedEgress.Ports, "Egress ports are not equal in test %s", tc.name) + assert.Equal(t, egress.PodSelector, expectedEgress.PodSelector, "Egress pod selector is not equal in test %s", tc.name) + assert.Equal(t, egress.NamespaceSelector, expectedEgress.NamespaceSelector, "Egress namespace selector is not equal in test %s", tc.name) + assert.Equal(t, egress.IPAddress, expectedEgress.IPAddress, "Egress IP address is not equal in test %s", tc.name) + + assert.Equal(t, len(egress.Ports), len(expectedEgress.Ports), "Egress ports are not equal in test %s", tc.name) + + for _, port := range egress.Ports { + foundPort := false + for _, expectedPort := range expectedEgress.Ports { + if port.Name == expectedPort.Name && *port.Port == *expectedPort.Port && port.Protocol == expectedPort.Protocol { + foundPort = true + break + } + } + assert.True(t, foundPort, "Port %+v not found in ingress %+v", port, egress) + } + + found++ + } + } + } + assert.Equal(t, found, len(tc.expectedSpec.Egress), "Egress IP address is not equal in test %s", tc.name) + + }) + } +} + +func TestGetNamespaceMatchLabels(t *testing.T) { + tests := []struct { + name string + destinationNamespace string + sourceNamespace string + expected map[string]string + }{ + { + name: "same namespace - should not have namespace selector", + destinationNamespace: "default", + sourceNamespace: "default", + expected: nil, + }, + { + name: "different namespace - should have the destination namespace as selector", + sourceNamespace: "default", + destinationNamespace: "kubescape", + expected: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %s", tc.name), func(t *testing.T) { + result := getNamespaceMatchLabels(tc.destinationNamespace, tc.sourceNamespace) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestIsValidEvent(t *testing.T) { + tests := []struct { + name string + event tracernetworktype.Event + expected bool + }{ + { + name: "invalid pkt type", + event: tracernetworktype.Event{ + PktType: "INVALID", + }, + expected: false, + }, + { + name: "pkt to itself", + event: tracernetworktype.Event{ + PktType: "HOST", + PodHostIP: "1.2.3.4", + DstEndpoint: types.L3Endpoint{ + Addr: "1.2.3.4", + }, + }, + expected: false, + }, + { + name: "host network", + event: tracernetworktype.Event{ + Event: types.Event{ + CommonData: types.CommonData{ + K8s: types.K8sMetadata{ + HostNetwork: true, + }, + }, + }, + }, + expected: false, + }, + { + name: "localhost IP", + event: tracernetworktype.Event{ + DstEndpoint: types.L3Endpoint{ + Addr: "169.254.169.254", + }, + }, + expected: false, + }, + { + name: "valid event", + event: tracernetworktype.Event{ + Port: 80, + PktType: "HOST", + Proto: "tcp", + PodLabels: map[string]string{"app": "nginx"}, + DstEndpoint: types.L3Endpoint{ + Namespace: "default", + Name: "nginx-deployment-cbdccf466-csh9c", + Kind: "pod", + PodLabels: map[string]string{"app": "nginx2"}, + Addr: "19.64.52.5", + }, + }, + expected: true, + }, + } + + am := CreateNetworkManager(context.TODO(), config.Config{}, nil, nil, "test") + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %s", tc.name), func(t *testing.T) { + result := am.isValidEvent(tc.event) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestGeneratePortIdentifier(t *testing.T) { + tests := []struct { + name string + port int32 + protocol string + expected string + }{ + { + name: "http", + port: 80, + protocol: "TCP", + expected: "TCP-80", + }, + { + name: "udp", + port: 333, + protocol: "UDP", + expected: "UDP-333", + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %s", tc.name), func(t *testing.T) { + result := generatePortIdentifier(tc.protocol, tc.port) + assert.Equal(t, tc.expected, result) + }) + } +} + +// saveNeighborEntry(networkEvent NetworkEvent, neighborEntry v1beta1.NetworkNeighbor, egressIdentifiersMap map[string]v1beta1.NetworkNeighbor, ingressIdentifiersMap map[string]v1beta1.NetworkNeighbor) +func TestSaveNeighborEntry(t *testing.T) { + tests := []struct { + name string + networkEvent NetworkEvent + neighborEntry v1beta1.NetworkNeighbor + egressIdentifiersMap map[string]v1beta1.NetworkNeighbor + ingressIdentifiersMap map[string]v1beta1.NetworkNeighbor + expectedEgressMap map[string]v1beta1.NetworkNeighbor + expectedIngressMap map[string]v1beta1.NetworkNeighbor + }{ + { + name: "egress event is saved", + networkEvent: NetworkEvent{ + Port: 80, + PktType: "OUTGOING", + Protocol: "TCP", + }, + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + egressIdentifiersMap: map[string]v1beta1.NetworkNeighbor{}, + expectedEgressMap: map[string]v1beta1.NetworkNeighbor{ + "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9": { + Type: "internal", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9", + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + }, + }, + { + name: "ingress event is saved", + networkEvent: NetworkEvent{ + Port: 80, + PktType: "HOST", + Protocol: "TCP", + }, + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + ingressIdentifiersMap: map[string]v1beta1.NetworkNeighbor{}, + expectedIngressMap: map[string]v1beta1.NetworkNeighbor{ + "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9": { + Type: "internal", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9", + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + }, + }, + { + name: "existing data in map - map is updated", + networkEvent: NetworkEvent{ + Port: 80, + PktType: "HOST", + Protocol: "TCP", + }, + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + ingressIdentifiersMap: map[string]v1beta1.NetworkNeighbor{ + "710652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9": { + Type: "internal", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "710652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9", + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test": "test"}, + }, + }, + }, + expectedIngressMap: map[string]v1beta1.NetworkNeighbor{ + "710652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9": { + Type: "internal", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "710652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9", + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test": "test"}, + }, + }, + "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9": { + Type: "internal", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "9f0652b5eef6b239f6a7c83778e56ab1ac3a2ad700ca7097f1cb59b1502ecee9", + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + }, + }, + { + name: "external event is saved", + networkEvent: NetworkEvent{ + Port: 80, + PktType: "OUTGOING", + Protocol: "TCP", + }, + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "external", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + }, + egressIdentifiersMap: map[string]v1beta1.NetworkNeighbor{}, + expectedEgressMap: map[string]v1beta1.NetworkNeighbor{ + "1db1cf596388ac2f0d5eecbe6bcc9b57199beaa7d87e53a049ae18744dd62045": { + Type: "external", + Ports: []v1beta1.NetworkPort{ + { + Name: "TCP-80", + Protocol: "TCP", + Port: ptr.To(int32(80)), + }, + }, + Identifier: "1db1cf596388ac2f0d5eecbe6bcc9b57199beaa7d87e53a049ae18744dd62045", + }, + }, + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %+v", tc.networkEvent), func(t *testing.T) { + saveNeighborEntry(tc.networkEvent, tc.neighborEntry, tc.egressIdentifiersMap, tc.ingressIdentifiersMap) + assert.Equal(t, tc.expectedEgressMap, tc.egressIdentifiersMap) + assert.Equal(t, tc.expectedIngressMap, tc.ingressIdentifiersMap) + }) + } + +} + +// addToMap(identifiersMap map[string]v1beta1.NetworkNeighbor, identifier string, portIdentifier string, neighborEntry v1beta1.NetworkNeighbor) +func TestAddToMap(t *testing.T) { + tests := []struct { + name string + identifiersMap map[string]v1beta1.NetworkNeighbor + identifier string + portIdentifier string + neighborEntry v1beta1.NetworkNeighbor + expectedMap map[string]v1beta1.NetworkNeighbor + }{ + { + name: "new identifier is added", + identifiersMap: map[string]v1beta1.NetworkNeighbor{}, + identifier: "identifier", + portIdentifier: "portIdentifier", + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + expectedMap: map[string]v1beta1.NetworkNeighbor{ + "identifier": { + Type: "internal", + DNS: "", + Identifier: "identifier", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + }, + { + name: "same identifier with new ports - ports are appended", + identifiersMap: map[string]v1beta1.NetworkNeighbor{ + "identifier": { + Type: "internal", + DNS: "", + Identifier: "identifier", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + identifier: "identifier", + portIdentifier: "TCP-50", + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-50", Protocol: "TCP", Port: ptr.To(int32(50))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + expectedMap: map[string]v1beta1.NetworkNeighbor{ + "identifier": { + Type: "internal", + DNS: "", + Identifier: "identifier", + Ports: []v1beta1.NetworkPort{ + {Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}, + {Name: "TCP-50", Protocol: "TCP", Port: ptr.To(int32(50))}, + }, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + }, + { + name: "different identifier - identifiers are appended", + identifiersMap: map[string]v1beta1.NetworkNeighbor{ + "identifier1": { + Type: "internal", + DNS: "", + Identifier: "identifier1", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + identifier: "identifier2", + portIdentifier: "TCP-80", + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + expectedMap: map[string]v1beta1.NetworkNeighbor{ + "identifier1": { + Type: "internal", + DNS: "", + Identifier: "identifier1", + Ports: []v1beta1.NetworkPort{ + {Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}, + }, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + "identifier2": { + Type: "internal", + DNS: "", + Identifier: "identifier2", + Ports: []v1beta1.NetworkPort{ + {Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}, + }, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + }, + { + name: "same identifier with same ports - nothing happens", + identifiersMap: map[string]v1beta1.NetworkNeighbor{ + "identifier": { + Type: "internal", + DNS: "", + Identifier: "identifier", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + identifier: "identifier", + portIdentifier: "TCP-80", + neighborEntry: v1beta1.NetworkNeighbor{ + Type: "internal", + DNS: "", + Identifier: "", + Ports: []v1beta1.NetworkPort{{Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}, + }, + }, + expectedMap: map[string]v1beta1.NetworkNeighbor{ + "identifier": { + Type: "internal", + DNS: "", + Identifier: "identifier", + Ports: []v1beta1.NetworkPort{ + {Name: "TCP-80", Protocol: "TCP", Port: ptr.To(int32(80))}, + }, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": "kubescape"}}, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("Input: %+v", tc.name), func(t *testing.T) { + addToMap(tc.identifiersMap, tc.identifier, tc.portIdentifier, tc.neighborEntry) + assert.Equal(t, tc.expectedMap, tc.identifiersMap) + }) + } +} diff --git a/pkg/networkmanager/network_neighbors.go b/pkg/networkmanager/network_neighbors.go new file mode 100644 index 00000000..368c972c --- /dev/null +++ b/pkg/networkmanager/network_neighbors.go @@ -0,0 +1,86 @@ +package networkmanager + +import ( + "fmt" + "strings" + + "github.com/armosec/utils-k8s-go/wlid" + "github.com/kubescape/k8s-interface/instanceidhandler/v1" + instanceidhandlerV1 "github.com/kubescape/k8s-interface/instanceidhandler/v1" + "github.com/kubescape/k8s-interface/k8sinterface" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + completeStatus = "complete" + incompleteStatus = "incomplete" +) + +func generateNetworkNeighborsCRD(parentWorkload k8sinterface.IWorkload, parentWorkloadSelector *metav1.LabelSelector) *v1beta1.NetworkNeighbors { + if parentWorkloadSelector.MatchLabels == nil && parentWorkloadSelector.MatchExpressions == nil { + parentWorkloadSelector.MatchLabels = parentWorkload.GetLabels() + } + + return &v1beta1.NetworkNeighbors{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkNeighbors", + APIVersion: "softwarecomposition.kubescape.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + instanceidhandler.StatusMetadataKey: incompleteStatus, + }, + Name: generateNetworkNeighborsNameFromWorkload(parentWorkload), + Namespace: parentWorkload.GetNamespace(), + Labels: generateNetworkNeighborsLabels(parentWorkload), + }, + Spec: v1beta1.NetworkNeighborsSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: parentWorkloadSelector.MatchLabels, + MatchExpressions: parentWorkloadSelector.MatchExpressions, + }, + }, + } +} + +func generateNetworkNeighborsNameFromWorkload(workload k8sinterface.IWorkload) string { + return fmt.Sprintf("%s-%s", strings.ToLower(workload.GetKind()), workload.GetName()) +} + +func generateNetworkNeighborsNameFromWlid(parentWlid string) string { + return fmt.Sprintf("%s-%s", strings.ToLower(wlid.GetKindFromWlid(parentWlid)), wlid.GetNameFromWlid(parentWlid)) +} + +func generateNetworkNeighborsLabels(workload k8sinterface.IWorkload) map[string]string { + var apiVersion, apiGroup string + apiVersionSplitted := strings.Split(workload.GetApiVersion(), "/") + if len(apiVersionSplitted) == 2 { + apiGroup = apiVersionSplitted[0] + apiVersion = apiVersionSplitted[1] + } else if len(apiVersionSplitted) == 1 { + apiVersion = apiVersionSplitted[0] + } + + return map[string]string{ + instanceidhandlerV1.ApiGroupMetadataKey: apiGroup, + instanceidhandlerV1.ApiVersionMetadataKey: apiVersion, + instanceidhandlerV1.NamespaceMetadataKey: workload.GetNamespace(), + instanceidhandlerV1.KindMetadataKey: strings.ToLower(workload.GetKind()), + instanceidhandlerV1.NameMetadataKey: workload.GetName(), + } +} + +// filterLabels filters out labels that are not relevant for the network neighbor +func filterLabels(labels map[string]string) map[string]string { + filteredLabels := make(map[string]string) + + for i := range labels { + if _, ok := defaultLabelsToIgnore[i]; ok { + continue + } + filteredLabels[i] = labels[i] + } + + return filteredLabels +} diff --git a/pkg/networkmanager/network_neighbors_test.go b/pkg/networkmanager/network_neighbors_test.go new file mode 100644 index 00000000..08500fe8 --- /dev/null +++ b/pkg/networkmanager/network_neighbors_test.go @@ -0,0 +1,352 @@ +package networkmanager + +import ( + "testing" + + _ "embed" + + instanceidhandlerV1 "github.com/kubescape/k8s-interface/instanceidhandler/v1" + "github.com/kubescape/k8s-interface/workloadinterface" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +//go:embed testdata/deployment.json +var deploymentJson []byte + +//go:embed testdata/daemonset.json +var daemonsetJson []byte + +//go:embed testdata/pod.json +var podJson []byte + +func TestGenerateNetworkNeighborsCRD(t *testing.T) { + tests := []struct { + name string + parentWorkload []byte + parentWorkloadLabels map[string]string + expectedNetworkNeighbors *v1beta1.NetworkNeighbors + }{ + { + name: "deployment", + parentWorkload: deploymentJson, + parentWorkloadLabels: map[string]string{ + "app": "nginx", + }, + expectedNetworkNeighbors: &v1beta1.NetworkNeighbors{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkNeighbors", + APIVersion: "softwarecomposition.kubescape.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + instanceidhandlerV1.StatusMetadataKey: "incomplete", + }, + Name: "deployment-nginx-deployment", + Namespace: "default", + Labels: map[string]string{ + "kubescape.io/workload-api-group": "apps", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "default", + "kubescape.io/workload-kind": "deployment", + "kubescape.io/workload-name": "nginx-deployment", + }, + }, + Spec: v1beta1.NetworkNeighborsSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + }, + }, + }, + { + name: "daemonset", + parentWorkload: daemonsetJson, + parentWorkloadLabels: map[string]string{ + "match": "fluentd-elasticsearch", + }, + expectedNetworkNeighbors: &v1beta1.NetworkNeighbors{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkNeighbors", + APIVersion: "softwarecomposition.kubescape.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + instanceidhandlerV1.StatusMetadataKey: "incomplete", + }, + Name: "daemonset-fluentd-elasticsearch", + Namespace: "kube-system", + Labels: map[string]string{ + "kubescape.io/workload-api-group": "apps", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "kube-system", + "kubescape.io/workload-kind": "daemonset", + "kubescape.io/workload-name": "fluentd-elasticsearch", + }, + }, + Spec: v1beta1.NetworkNeighborsSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "match": "fluentd-elasticsearch", + }, + }, + }, + }, + }, + { + name: "pod", + parentWorkload: podJson, + parentWorkloadLabels: map[string]string{ + "match": "test", + }, + expectedNetworkNeighbors: &v1beta1.NetworkNeighbors{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkNeighbors", + APIVersion: "softwarecomposition.kubescape.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + instanceidhandlerV1.StatusMetadataKey: "incomplete", + }, + Name: "pod-nginx-deployment-fcc867f7-dgjrg", + Namespace: "default", + Labels: map[string]string{ + "kubescape.io/workload-api-group": "", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "default", + "kubescape.io/workload-kind": "pod", + "kubescape.io/workload-name": "nginx-deployment-fcc867f7-dgjrg", + }, + }, + Spec: v1beta1.NetworkNeighborsSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "bla3": "blu3", + "pod-template-hash": "fcc867f7", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + wl, err := workloadinterface.NewWorkload(test.parentWorkload) + assert.Nil(t, err) + selector, err := wl.GetSelector() + assert.Nil(t, err) + + actualNetworkNeighbors := generateNetworkNeighborsCRD(wl, selector) + assert.Equal(t, test.expectedNetworkNeighbors, actualNetworkNeighbors) + }) + } +} + +func TestGenerateNetworkNeighborsNameFromWorkload(t *testing.T) { + tests := []struct { + name string + workload []byte + expectedName string + }{ + { + name: "deployment", + workload: deploymentJson, + expectedName: "deployment-nginx-deployment", + }, + { + name: "daemonset", + workload: daemonsetJson, + expectedName: "daemonset-fluentd-elasticsearch", + }, + { + name: "pod", + workload: podJson, + expectedName: "pod-nginx-deployment-fcc867f7-dgjrg", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + wl, err := workloadinterface.NewWorkload(test.workload) + assert.Nil(t, err) + + actualName := generateNetworkNeighborsNameFromWorkload(wl) + if actualName != test.expectedName { + t.Errorf("expected name %s, got %s", test.expectedName, actualName) + } + }) + } +} + +func TestGenerateNetworkNeighborsNameFromWlid(t *testing.T) { + tests := []struct { + name string + parentWlid string + expectedName string + }{ + { + name: "deployment", + parentWlid: "wlid://cluster-test-cluster/namespace-default/deployment-nginx-deployment", + expectedName: "deployment-nginx-deployment", + }, + { + name: "pod", + parentWlid: "wlid://cluster-test-cluster/namespace-default/pod-test", + expectedName: "pod-test", + }, + { + name: "daemonset", + parentWlid: "wlid://cluster-test-cluster/namespace-default/daemonset-test", + expectedName: "daemonset-test", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actualName := generateNetworkNeighborsNameFromWlid(test.parentWlid) + if actualName != test.expectedName { + t.Errorf("expected name %s, got %s", test.expectedName, actualName) + } + }) + } +} + +func TestGenerateNetworkNeighborsLabels(t *testing.T) { + tests := []struct { + name string + workload []byte + expectedLabels map[string]string + }{ + { + name: "deployment", + workload: deploymentJson, + expectedLabels: map[string]string{ + "kubescape.io/workload-api-group": "apps", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "default", + "kubescape.io/workload-kind": "deployment", + "kubescape.io/workload-name": "nginx-deployment", + }, + }, + + { + name: "daemonset", + workload: daemonsetJson, + expectedLabels: map[string]string{ + "kubescape.io/workload-api-group": "apps", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "kube-system", + "kubescape.io/workload-kind": "daemonset", + "kubescape.io/workload-name": "fluentd-elasticsearch", + }, + }, + + { + name: "pod", + workload: podJson, + expectedLabels: map[string]string{ + "kubescape.io/workload-api-group": "", + "kubescape.io/workload-api-version": "v1", + "kubescape.io/workload-namespace": "default", + "kubescape.io/workload-kind": "pod", + "kubescape.io/workload-name": "nginx-deployment-fcc867f7-dgjrg", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + wl, err := workloadinterface.NewWorkload(test.workload) + assert.Nil(t, err) + + actualLabels := generateNetworkNeighborsLabels(wl) + if len(actualLabels) != len(test.expectedLabels) { + t.Errorf("expected %d labels, got %d", len(test.expectedLabels), len(actualLabels)) + } + + for key, value := range test.expectedLabels { + if actualLabels[key] != value { + t.Errorf("expected label %s with value %s, got %s", key, value, actualLabels[key]) + } + } + }) + } +} + +func TestFilterLabels(t *testing.T) { + tests := []struct { + name string + labels map[string]string + expectedLabels map[string]string + }{ + { + name: "one label", + labels: map[string]string{ + "test": "1", + }, + expectedLabels: map[string]string{ + "test": "1", + }, + }, + { + name: "multiple labels", + labels: map[string]string{ + "test": "1", + "test2": "2", + "test3": "3", + }, + expectedLabels: map[string]string{ + "test": "1", + "test2": "2", + "test3": "3", + }, + }, + { + name: "multiple labels with one ignore label", + labels: map[string]string{ + "controller-revision-hash": "1", + "test": "1", + "test2": "2", + }, + expectedLabels: map[string]string{ + "test": "1", + "test2": "2", + }, + }, + { + name: "multiple labels with multiple ignore labels", + labels: map[string]string{ + "controller-revision-hash": "1", + "pod-template-generation": "1", + "pod-template-hash": "1", + "test": "1", + "test2": "2", + "test3": "3", + }, + expectedLabels: map[string]string{ + "test": "1", + "test2": "2", + "test3": "3", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actualLabels := filterLabels(test.labels) + if len(actualLabels) != len(test.expectedLabels) { + t.Errorf("expected %d labels, got %d", len(test.expectedLabels), len(actualLabels)) + } + + for key, value := range test.expectedLabels { + if actualLabels[key] != value { + t.Errorf("expected label %s with value %s, got %s", key, value, actualLabels[key]) + } + } + }) + } +} diff --git a/pkg/networkmanager/testdata/daemonset.json b/pkg/networkmanager/testdata/daemonset.json new file mode 100644 index 00000000..38f96c33 --- /dev/null +++ b/pkg/networkmanager/testdata/daemonset.json @@ -0,0 +1,103 @@ +{ + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "1", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"labels\":{\"k8s-app\":\"fluentd-logging\"},\"name\":\"fluentd-elasticsearch\",\"namespace\":\"kube-system\"},\"spec\":{\"selector\":{\"matchLabels\":{\"name\":\"fluentd-elasticsearch\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"fluentd-elasticsearch\"}},\"spec\":{\"containers\":[{\"image\":\"quay.io/fluentd_elasticsearch/fluentd:v2.5.2\",\"name\":\"fluentd-elasticsearch\",\"resources\":{\"limits\":{\"memory\":\"200Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"200Mi\"}},\"volumeMounts\":[{\"mountPath\":\"/var/log\",\"name\":\"varlog\"}]}],\"terminationGracePeriodSeconds\":30,\"tolerations\":[{\"effect\":\"NoSchedule\",\"key\":\"node-role.kubernetes.io/control-plane\",\"operator\":\"Exists\"},{\"effect\":\"NoSchedule\",\"key\":\"node-role.kubernetes.io/master\",\"operator\":\"Exists\"}],\"volumes\":[{\"hostPath\":{\"path\":\"/var/log\"},\"name\":\"varlog\"}]}}}}\n" + }, + "creationTimestamp": "2023-10-19T12:44:15Z", + "generation": 1, + "labels": { + "k8s-app": "fluentd-logging" + }, + "name": "fluentd-elasticsearch", + "namespace": "kube-system", + "resourceVersion": "266406", + "uid": "b04727f3-d60e-4f76-ab03-3df32b7f3c87" + }, + "spec": { + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "match": "fluentd-elasticsearch" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "match": "fluentd-elasticsearch" + } + }, + "spec": { + "containers": [ + { + "image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2", + "imagePullPolicy": "IfNotPresent", + "name": "fluentd-elasticsearch", + "resources": { + "limits": { + "memory": "200Mi" + }, + "requests": { + "cpu": "100m", + "memory": "200Mi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/log", + "name": "varlog" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "node-role.kubernetes.io/control-plane", + "operator": "Exists" + }, + { + "effect": "NoSchedule", + "key": "node-role.kubernetes.io/master", + "operator": "Exists" + } + ], + "volumes": [ + { + "hostPath": { + "path": "/var/log", + "type": "" + }, + "name": "varlog" + } + ] + } + }, + "updateStrategy": { + "rollingUpdate": { + "maxSurge": 0, + "maxUnavailable": 1 + }, + "type": "RollingUpdate" + } + }, + "status": { + "currentNumberScheduled": 1, + "desiredNumberScheduled": 1, + "numberMisscheduled": 0, + "numberReady": 0, + "numberUnavailable": 1, + "observedGeneration": 1, + "updatedNumberScheduled": 1 + } +} diff --git a/pkg/networkmanager/testdata/deployment.json b/pkg/networkmanager/testdata/deployment.json new file mode 100644 index 00000000..5df6885d --- /dev/null +++ b/pkg/networkmanager/testdata/deployment.json @@ -0,0 +1,92 @@ +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "1", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"nginx\"},\"name\":\"nginx-deployment\",\"namespace\":\"default\"},\"spec\":{\"replicas\":3,\"selector\":{\"matchLabels\":{\"bla3\":\"blu3\"}},\"template\":{\"metadata\":{\"labels\":{\"bla3\":\"blu3\"}},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80}]}]}}}}\n" + }, + "creationTimestamp": "2023-10-22T10:56:49Z", + "generation": 1, + "labels": { + "app": "nginx" + }, + "name": "nginx-deployment", + "namespace": "default", + "resourceVersion": "339815", + "uid": "8f8bc18d-9429-4045-8832-f917e469bc21" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 3, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": "25%" + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + } + }, + "status": { + "availableReplicas": 3, + "conditions": [ + { + "lastTransitionTime": "2023-10-22T10:56:52Z", + "lastUpdateTime": "2023-10-22T10:56:52Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2023-10-22T10:56:49Z", + "lastUpdateTime": "2023-10-22T10:56:52Z", + "message": "ReplicaSet \"nginx-deployment-fcc867f7\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 1, + "readyReplicas": 3, + "replicas": 3, + "updatedReplicas": 3 + } +} diff --git a/pkg/networkmanager/testdata/pod.json b/pkg/networkmanager/testdata/pod.json new file mode 100644 index 00000000..47cf90dd --- /dev/null +++ b/pkg/networkmanager/testdata/pod.json @@ -0,0 +1,171 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "creationTimestamp": "2023-10-22T10:56:49Z", + "generateName": "nginx-deployment-fcc867f7-", + "labels": { + "bla3": "blu3", + "pod-template-hash": "fcc867f7" + }, + "name": "nginx-deployment-fcc867f7-dgjrg", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "nginx-deployment-fcc867f7", + "uid": "a5dcc8ae-9c9a-4778-88a1-50a121e17400" + } + ], + "resourceVersion": "339809", + "uid": "3ab622d3-ca1f-433d-8c13-3648f548697b" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-nk9mw", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "kube-api-access-nk9mw", + "projected": { + "defaultMode": 420, + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-root-ca.crt" + } + }, + { + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } + } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2023-10-22T10:56:49Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-10-22T10:56:52Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-10-22T10:56:52Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-10-22T10:56:49Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://bbd1e19c4c974915dedaf3ab4e6855e216f10af5f5a3a37d1977a2634cb471c0", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2023-10-22T10:56:51Z" + } + } + } + ], + "hostIP": "192.168.49.2", + "phase": "Running", + "podIP": "10.244.0.43", + "podIPs": [ + { + "ip": "10.244.0.43" + } + ], + "qosClass": "BestEffort", + "startTime": "2023-10-22T10:56:49Z" + } +} diff --git a/pkg/relevancymanager/v1/relevancy_manager.go b/pkg/relevancymanager/v1/relevancy_manager.go index 893a06b5..00349680 100644 --- a/pkg/relevancymanager/v1/relevancy_manager.go +++ b/pkg/relevancymanager/v1/relevancy_manager.go @@ -150,7 +150,6 @@ func (rm *RelevancyManager) handleRelevancy(ctx context.Context, watchedContaine logger.L().Debug("failed to get file list", helpers.String("container ID", containerID), helpers.String("k8s workload", watchedContainer.K8sContainerID), helpers.Error(err)) return } - logger.L().Debug("fileList generated", helpers.String("container ID", containerID), helpers.String("k8s workload", watchedContainer.K8sContainerID), helpers.String("file list", fmt.Sprintf("%v", fileList))) ctx, span := otel.Tracer("").Start(ctxPostSBOM, "SBOMClient.FilterSBOM") if err = rm.sbomHandler.FilterSBOM(watchedContainer, fileList); err != nil { diff --git a/pkg/storage/storage_interface.go b/pkg/storage/storage_interface.go index c6aceb03..3a472b7a 100644 --- a/pkg/storage/storage_interface.go +++ b/pkg/storage/storage_interface.go @@ -1,6 +1,8 @@ package storage -import "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" +import ( + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" +) type StorageClient interface { CreateApplicationActivity(activity *v1beta1.ApplicationActivity, namespace string) error @@ -13,4 +15,8 @@ type StorageClient interface { PatchFilteredSBOM(name string, SBOM *v1beta1.SBOMSPDXv2p3Filtered) error IncrementImageUse(imageID string) DecrementImageUse(imageID string) + GetNetworkNeighbors(namespace, name string) (*v1beta1.NetworkNeighbors, error) + CreateNetworkNeighbors(networkNeighbors *v1beta1.NetworkNeighbors, namespace string) error + PatchNetworkNeighborsMatchLabels(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error + PatchNetworkNeighborsIngressAndEgress(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error } diff --git a/pkg/storage/storage_mock.go b/pkg/storage/storage_mock.go index 64e9a7ea..2e0c4ecc 100644 --- a/pkg/storage/storage_mock.go +++ b/pkg/storage/storage_mock.go @@ -6,6 +6,7 @@ import ( "os" "path" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -20,6 +21,7 @@ type StorageHttpClientMock struct { ApplicationProfiles []*spdxv1beta1.ApplicationProfile ApplicationProfileSummaries []*spdxv1beta1.ApplicationProfileSummary FilteredSBOMs []*spdxv1beta1.SBOMSPDXv2p3Filtered + NetworkNeighborses []*v1beta1.NetworkNeighbors ImageCounters map[string]int nginxSBOMSpdxBytes *spdxv1beta1.SBOMSPDXv2p3 } @@ -75,6 +77,11 @@ func (sc *StorageHttpClientMock) CreateApplicationProfileSummary(summary *spdxv1 return nil } +func (sc *StorageHttpClientMock) CreateNetworkNeighbors(networkNeighbors *v1beta1.NetworkNeighbors, namespace string) error { + sc.NetworkNeighborses = append(sc.NetworkNeighborses, networkNeighbors) + return nil +} + func (sc *StorageHttpClientMock) CreateFilteredSBOM(SBOM *spdxv1beta1.SBOMSPDXv2p3Filtered) error { sc.FilteredSBOMs = append(sc.FilteredSBOMs, SBOM) return nil @@ -105,3 +112,20 @@ func (sc *StorageHttpClientMock) DecrementImageUse(imageID string) { sc.ImageCounters[imageID]-- } + +func (sc *StorageHttpClientMock) GetNetworkNeighbors(namespace, name string) (*v1beta1.NetworkNeighbors, error) { + for _, nn := range sc.NetworkNeighborses { + if nn.Name == name { + return nn, nil + } + } + return nil, nil +} + +func (sc *StorageHttpClientMock) PatchNetworkNeighborsMatchLabels(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error { + return nil +} + +func (sc *StorageHttpClientMock) PatchNetworkNeighborsIngressAndEgress(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error { + return nil +} diff --git a/pkg/storage/v1/storage_nocache.go b/pkg/storage/v1/storage_nocache.go index 7a4dadbb..fd5fe467 100644 --- a/pkg/storage/v1/storage_nocache.go +++ b/pkg/storage/v1/storage_nocache.go @@ -58,6 +58,38 @@ func CreateFakeStorageNoCache(namespace string) (*StorageNoCache, error) { }, nil } +func (sc StorageNoCache) CreateNetworkNeighbors(networkNeighbors *v1beta1.NetworkNeighbors, namespace string) error { + _, err := sc.StorageClient.NetworkNeighborses(namespace).Create(context.Background(), networkNeighbors, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +func (sc StorageNoCache) GetNetworkNeighbors(namespace, name string) (*v1beta1.NetworkNeighbors, error) { + return sc.StorageClient.NetworkNeighborses(namespace).Get(context.Background(), name, metav1.GetOptions{}) +} + +func (sc StorageNoCache) PatchNetworkNeighborsIngressAndEgress(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error { + bytes, err := json.Marshal(networkNeighbors) + if err != nil { + return err + } + + _, err = sc.StorageClient.NetworkNeighborses(namespace).Patch(context.Background(), name, types.StrategicMergePatchType, bytes, metav1.PatchOptions{}) + if err != nil { + return err + } + + return nil +} + +func (sc StorageNoCache) PatchNetworkNeighborsMatchLabels(name, namespace string, networkNeighbors *v1beta1.NetworkNeighbors) error { + _, err := sc.StorageClient.NetworkNeighborses(namespace).Update(context.Background(), networkNeighbors, metav1.UpdateOptions{}) + + return err +} + func (sc StorageNoCache) CreateApplicationActivity(activity *v1beta1.ApplicationActivity, namespace string) error { _, err := sc.StorageClient.ApplicationActivities(namespace).Create(context.Background(), activity, metav1.CreateOptions{}) if err != nil {