diff --git a/.github/workflows/pr-created.yaml b/.github/workflows/pr-created.yaml index 0d39dcd..5a54e9d 100644 --- a/.github/workflows/pr-created.yaml +++ b/.github/workflows/pr-created.yaml @@ -20,7 +20,7 @@ jobs: pr-created: uses: kubescape/workflows/.github/workflows/incluster-comp-pr-created.yaml@main with: - GO_VERSION: "1.21" + GO_VERSION: "1.22" CGO_ENABLED: 0 secrets: inherit @@ -55,7 +55,7 @@ jobs: CGO_ENABLED: 0 uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version: "1.22" - name: Run test run: | diff --git a/.github/workflows/pr-merged.yaml b/.github/workflows/pr-merged.yaml index 2d4e952..a828445 100644 --- a/.github/workflows/pr-merged.yaml +++ b/.github/workflows/pr-merged.yaml @@ -2,15 +2,15 @@ name: build on: pull_request_target: types: [closed] - branches: + branches: - 'main' paths-ignore: - '**.md' ### Ignore running when README.MD changed. - '.github/workflows/*' ### Ignore running when files under path: .github/workflows/* changed. - + jobs: pr-merged: - if: ${{ github.event.pull_request.merged == true }} ## Skip if not merged + if: ${{ github.event.pull_request.merged == true }} ## Skip if not merged uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main with: IMAGE_NAME: quay.io/${{ github.repository_owner }}/synchronizer @@ -19,7 +19,7 @@ jobs: CGO_ENABLED: 0 GO111MODULE: "on" BUILD_PLATFORM: linux/amd64,linux/arm64 - GO_VERSION: "1.21" + GO_VERSION: "1.22" REQUIRED_TESTS: '[]' COSIGN: true HELM_E2E_TEST: false # TODO: Enable this when helm e2e test is ready diff --git a/adapters/incluster/v1/client.go b/adapters/incluster/v1/client.go index f6d540a..0dae1bf 100644 --- a/adapters/incluster/v1/client.go +++ b/adapters/incluster/v1/client.go @@ -13,6 +13,8 @@ import ( "github.com/cenkalti/backoff/v4" "go.uber.org/multierr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/pager" mapset "github.com/deckarep/golang-set/v2" jsonpatch "github.com/evanphx/json-patch" @@ -55,6 +57,8 @@ type Client struct { client dynamic.Interface account string cluster string + excludeNamespaces []string + includeNamespaces []string operatorNamespace string // the namespace where the kubescape operator is running kind *domain.Kind multiplier int @@ -74,6 +78,8 @@ func NewClient(client dynamic.Interface, cfg config.InCluster, r config.Resource return &Client{ account: cfg.Account, client: client, + excludeNamespaces: cfg.ExcludeNamespaces, + includeNamespaces: cfg.IncludeNamespaces, operatorNamespace: cfg.Namespace, cluster: cfg.ClusterName, kind: &domain.Kind{ @@ -268,9 +274,15 @@ func (c *Client) isFiltered(workload *unstructured.Unstructured) bool { if workload == nil { return false } - // workload is not filtered if it is in the kubescape-operator namespace - if c.operatorNamespace != "" && workload.GetNamespace() == c.operatorNamespace { - return false + if namespace := workload.GetNamespace(); namespace != "" { + // workload is not filtered if it is in the kubescape-operator namespace + if namespace == c.operatorNamespace { + return false + } + // workload is filtered if it is in a skipped namespace + if c.skipNamespace(namespace) { + return true + } } // for all other workloads, we filter out those that have a parent return hasParent(workload) @@ -478,6 +490,8 @@ func (c *Client) verifyObject(id domain.KindName, newChecksum string) ([]byte, e func (c *Client) getExistingStorageObjects(ctx context.Context) (string, error) { logger.L().Debug("getting existing objects from storage", helpers.String("resource", c.res.Resource)) + // no need for pager since list returns only metadatas + // no need for skip ns since these are our CRDs list, err := c.client.Resource(c.res).Namespace("").List(context.Background(), metav1.ListOptions{}) if err != nil { return "", fmt.Errorf("list resources: %w", err) @@ -570,16 +584,18 @@ func reconcileBatchProcessingFunc(ctx context.Context, c *Client, items domain.B } // create a map of resources from the client - list, err := c.client.Resource(c.res).Namespace("").List(context.Background(), metav1.ListOptions{}) - if err != nil { - return fmt.Errorf("reconciliation: list resources: %w", err) - } clientItems := map[string]unstructured.Unstructured{} clientItemsSet := mapset.NewSet[string]() - for _, item := range list.Items { + if err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + return c.client.Resource(c.res).Namespace("").List(ctx, opts) + }).EachListItem(context.Background(), metav1.ListOptions{}, func(obj runtime.Object) error { + item := obj.(*unstructured.Unstructured) k := fmt.Sprintf("%s/%s", item.GetNamespace(), item.GetName()) - clientItems[k] = item + clientItems[k] = *item clientItemsSet.Add(k) + return nil + }); err != nil { + return fmt.Errorf("reconciliation: list resources: %w", err) } // create a map of resources from the server @@ -591,6 +607,8 @@ func reconcileBatchProcessingFunc(ctx context.Context, c *Client, items domain.B serverItemsSet.Add(k) } + var err error + // resources that should not be in server, send delete for _, k := range serverItemsSet.Difference(clientItemsSet).ToSlice() { item := serverItems[k] @@ -745,6 +763,21 @@ func (c *Client) getResource(namespace string, name string) (*unstructured.Unstr return c.client.Resource(c.res).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) } +func (c *Client) skipNamespace(ns string) bool { + if includeNamespaces := c.includeNamespaces; len(includeNamespaces) > 0 { + if !slices.Contains(includeNamespaces, ns) { + // skip ns not in IncludeNamespaces + return true + } + } else if excludeNamespaces := c.excludeNamespaces; len(excludeNamespaces) > 0 { + if slices.Contains(excludeNamespaces, ns) { + // skip ns in ExcludeNamespaces + return true + } + } + return false +} + func stripSuffix(name string) string { lastHyphen := strings.LastIndex(name, "-") if lastHyphen != -1 && strings.HasPrefix(name[lastHyphen:], "-") { diff --git a/adapters/incluster/v1/client_test.go b/adapters/incluster/v1/client_test.go index 664ff3c..077a00a 100644 --- a/adapters/incluster/v1/client_test.go +++ b/adapters/incluster/v1/client_test.go @@ -195,3 +195,65 @@ func TestClient_filterAndMarshal(t *testing.T) { }) } } + +func TestClient_isFiltered(t *testing.T) { + tests := []struct { + name string + workload *unstructured.Unstructured + filtered bool + }{ + { + name: "nil workload", + filtered: false, + }, + { + name: "pod", + workload: &unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{"namespace": "default"}}}, + filtered: false, + }, + { + name: "pod with ownerReferences", + workload: &unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{map[string]interface{}{ + "apiVersion": "batch/v1"}}}}}, + filtered: true, + }, + { + name: "pod with pod-template-hash", + workload: &unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "pod-template-hash": "12345"}}}}, + filtered: true, + }, + { + name: "pod from kubescape namespace", // special case, never filter out + workload: &unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{"namespace": "default"}}}, + filtered: false, + }, + { + name: "pod from filtered namespace", + workload: &unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{"namespace": "kube-system"}}}, + filtered: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + excludeNamespaces: []string{"kube-system", "kubescape"}, + includeNamespaces: []string{}, + operatorNamespace: "kubescape", + } + assert.Equalf(t, tt.filtered, c.isFiltered(tt.workload), "isFiltered(%v)", tt.workload) + }) + } +} diff --git a/build/Dockerfile b/build/Dockerfile index 332d1d3..cc2d3c4 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.21-bullseye as builder +FROM --platform=$BUILDPLATFORM golang:1.22-bullseye AS builder ENV GO111MODULE=on CGO_ENABLED=0 WORKDIR /work diff --git a/config/config.go b/config/config.go index 14cf7e4..355fa6c 100644 --- a/config/config.go +++ b/config/config.go @@ -35,12 +35,14 @@ type Backend struct { } type InCluster struct { - ServerUrl string `mapstructure:"serverUrl"` - Namespace string `mapstructure:"namespace"` - ClusterName string `mapstructure:"clusterName"` - Account string `mapstructure:"account"` - AccessKey string `mapstructure:"accessKey"` - Resources []Resource `mapstructure:"resources"` + ServerUrl string `mapstructure:"serverUrl"` + Namespace string `mapstructure:"namespace"` + ClusterName string `mapstructure:"clusterName"` + ExcludeNamespaces []string `mapstructure:"excludeNamespaces"` + IncludeNamespaces []string `mapstructure:"includeNamespaces"` + Account string `mapstructure:"account"` + AccessKey string `mapstructure:"accessKey"` + Resources []Resource `mapstructure:"resources"` } type HTTPEndpoint struct { diff --git a/config/config_test.go b/config/config_test.go index 139acb7..2d47a6c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -65,10 +65,13 @@ func TestLoadConfig(t *testing.T) { path: "../configuration/client", want: Config{ InCluster: InCluster{ - ServerUrl: "ws://127.0.0.1:8080/", - ClusterName: "cluster-1", - Account: "11111111-2222-3333-4444-11111111", - AccessKey: "xxxxxxxx-1111-1111-1111-xxxxxxxx", + ServerUrl: "ws://127.0.0.1:8080/", + Namespace: "kubescape", + ClusterName: "cluster-1", + ExcludeNamespaces: []string{"kube-system", "kubescape"}, + IncludeNamespaces: []string{}, + Account: "11111111-2222-3333-4444-11111111", + AccessKey: "xxxxxxxx-1111-1111-1111-xxxxxxxx", Resources: []Resource{ {Group: "", Version: "v1", Resource: "pods", Strategy: "patch"}, {Group: "", Version: "v1", Resource: "nodes", Strategy: "patch"}, diff --git a/configuration/client/config.json b/configuration/client/config.json index 7843984..e13a090 100644 --- a/configuration/client/config.json +++ b/configuration/client/config.json @@ -1,7 +1,10 @@ { "inCluster": { "serverUrl": "ws://127.0.0.1:8080/", + "namespace": "kubescape", "clusterName": "cluster-1", + "excludeNamespaces": "kube-system,kubescape", + "includeNamespaces": "", "account": "11111111-2222-3333-4444-11111111", "accessKey": "xxxxxxxx-1111-1111-1111-xxxxxxxx", "resources": [ @@ -48,4 +51,4 @@ } ] } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index d8401d8..ecd9b5f 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/kubescape/synchronizer -go 1.21.3 - -toolchain go1.21.5 +go 1.22.5 require ( github.com/SergJa/jsonhash v0.0.0-20210531165746-fc45f346aa74 diff --git a/tests/go.mod b/tests/go.mod index f97e108..287e28b 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,8 +1,6 @@ module github.com/kubescape/synchronizer/tests -go 1.21.3 - -toolchain go1.21.5 +go 1.22.5 require ( github.com/apache/pulsar-client-go v0.12.1 diff --git a/tests/synchronizer_integration_test.go b/tests/synchronizer_integration_test.go index 0859549..ee2a984 100644 --- a/tests/synchronizer_integration_test.go +++ b/tests/synchronizer_integration_test.go @@ -506,6 +506,8 @@ func createAndStartSynchronizerClient(t *testing.T, cluster *TestKubernetesClust // set cluster config clientCfg.InCluster.Namespace = kubescapeNamespace clientCfg.InCluster.ClusterName = cluster.cluster + clientCfg.InCluster.ExcludeNamespaces = []string{"kube-system", "kubescape"} + clientCfg.InCluster.IncludeNamespaces = []string{} clientCfg.InCluster.Account = cluster.account clientCfg.InCluster.ServerUrl = syncServer.serverUrl if watchDefaults {