diff --git a/go.mod b/go.mod index e796f6ff..8c0d2cc4 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,12 @@ module github.com/jetstack/preflight go 1.22.0 require ( - github.com/Jeffail/gabs/v2 v2.7.0 github.com/cenkalti/backoff v2.2.1+incompatible github.com/d4l3k/messagediff v1.2.1 github.com/fatih/color v1.17.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/jetstack/venafi-connection-lib v0.1.1-0.20240909145535-cd2301fd4e7c - github.com/json-iterator/go v1.1.12 github.com/maxatome/go-testdeep v1.14.0 github.com/microcosm-cc/bluemonday v1.0.27 github.com/pkg/errors v0.9.1 @@ -44,6 +42,7 @@ require ( github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sosodev/duration v1.2.0 // indirect diff --git a/go.sum b/go.sum index ced946d7..7361b47d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg= -github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw= github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w= github.com/Khan/genqlient v0.7.0/go.mod h1:HNyy3wZvuYwmW3Y7mkoQLZsa/R5n5yIRajS1kPBvSFM= github.com/Venafi/vcert/v5 v5.7.1 h1:gUDbSuP6NE4yAslWp+D+ZoJlYOSRWhQora48oExuEN4= diff --git a/pkg/datagatherer/k8s/fieldfilter.go b/pkg/datagatherer/k8s/fieldfilter.go index 02fef251..f3f34c89 100644 --- a/pkg/datagatherer/k8s/fieldfilter.go +++ b/pkg/datagatherer/k8s/fieldfilter.go @@ -1,143 +1,88 @@ package k8s import ( - "fmt" - "strings" - - "github.com/Jeffail/gabs/v2" - json "github.com/json-iterator/go" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // SecretSelectedFields is the list of fields sent from Secret objects to the // backend -var SecretSelectedFields = []string{ - "kind", - "apiVersion", - "metadata.annotations", - "metadata.labels", - "metadata.name", - "metadata.namespace", - "metadata.ownerReferences", - "metadata.selfLink", - "metadata.uid", - "type", - "/data/tls.crt", - "/data/ca.crt", +var SecretSelectedFields = []FieldPath{ + {"kind"}, + {"apiVersion"}, + {"metadata", "annotations"}, + {"metadata", "labels"}, + {"metadata", "name"}, + {"metadata", "namespace"}, + {"metadata", "ownerReferences"}, + {"metadata", "selfLink"}, + {"metadata", "uid"}, + + {"type"}, + {"data", "tls.crt"}, + {"data", "ca.crt"}, } // RouteSelectedFields is the list of fields sent from OpenShift Route objects to the // backend -var RouteSelectedFields = []string{ - "kind", - "apiVersion", - "metadata.annotations", - "metadata.name", - "metadata.namespace", - "metadata.ownerReferences", - "metadata.selfLink", - "metadata.uid", - "spec.host", - "spec.to.kind", - "spec.to.port", - "spec.to.name", - "spec.to.weight", - "spec.tls.termination", - "spec.tls.certificate", - "spec.tls.caCertificate", - "spec.tls.destinationCACertificate", - "spec.tls.insecureEdgeTerminationPolicy", - "spec.wildcardPolicy", - "status", +var RouteSelectedFields = []FieldPath{ + {"kind"}, + {"apiVersion"}, + {"metadata", "annotations"}, + {"metadata", "name"}, + {"metadata", "namespace"}, + {"metadata", "ownerReferences"}, + {"metadata", "selfLink"}, + {"metadata", "uid"}, + + {"spec", "host"}, + {"spec", "to", "kind"}, + {"spec", "to", "name"}, + {"spec", "to", "weight"}, + {"spec", "tls", "termination"}, + {"spec", "tls", "certificate"}, + {"spec", "tls", "caCertificate"}, + {"spec", "tls", "destinationCACertificate"}, + {"spec", "tls", "insecureEdgeTerminationPolicy"}, + {"spec", "wildcardPolicy"}, + {"status"}, } // RedactFields are removed from all objects -var RedactFields = []string{ - "metadata.managedFields", - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration", +var RedactFields = []FieldPath{ + {"metadata", "managedFields"}, + {"metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"}, } -// Select removes all but the supplied fields from the resource -func Select(fields []string, resource *unstructured.Unstructured) error { - // convert the object to JSON for field filtering - asJSON, err := json.Marshal(resource) - if err != nil { - return fmt.Errorf("failed to marshal json for resource: %s", err) - } +type FieldPath []string - // parse the JSON for processing in gabs - jsonParsed, err := gabs.ParseJSON(asJSON) - if err != nil { - return fmt.Errorf("failed to parse generated json for resource: %s", err) +// Select removes all but the supplied fields from the resource +func Select(fields []FieldPath, resource *unstructured.Unstructured) error { + newResource := unstructured.Unstructured{ + Object: map[string]interface{}{}, } - // craft a new object containing only selected fields - filteredObject := gabs.New() - for _, v := range fields { - // also support JSONPointers for keys containing '.' chars - if strings.HasPrefix(v, "/") { - gObject, err := jsonParsed.JSONPointer(v) - if err != nil { - // fail to select field if missing, just continue - continue - } - pathComponents, err := gabs.JSONPointerToSlice(v) - if err != nil { - return fmt.Errorf("invalid JSONPointer: %s", v) - } - filteredObject.Set(gObject.Data(), pathComponents...) - } else { - if jsonParsed.ExistsP(v) { - filteredObject.SetP(jsonParsed.Path(v).Data(), v) - } + for _, field := range fields { + value, found, err := unstructured.NestedFieldNoCopy(resource.Object, field...) + if err != nil { + return err + } + if !found { + continue + } + if err := unstructured.SetNestedField(newResource.Object, value, field...); err != nil { + return err } } - // load the filtered JSON back into the resource - err = json.Unmarshal(filteredObject.Bytes(), resource) - if err != nil { - return fmt.Errorf("failed to update resource: %s", err) - } + resource.Object = newResource.Object return nil } // Redact removes the supplied fields from the resource -func Redact(fields []string, resource *unstructured.Unstructured) error { - // convert the object to JSON for field filtering - asJSON, err := json.Marshal(resource) - if err != nil { - return fmt.Errorf("failed to marshal json for resource: %s", err) - } - - // parse the JSON for processing in gabs - jsonParsed, err := gabs.ParseJSON(asJSON) - if err != nil { - return fmt.Errorf("failed to parse generated json for resource: %s", err) - } - - // craft a new object excluding redacted fields - for _, v := range fields { - // also support JSONPointers for keys containing '.' chars - if strings.HasPrefix(v, "/") { - pathComponents, err := gabs.JSONPointerToSlice(v) - if err != nil { - return fmt.Errorf("invalid JSONPointer: %s", v) - } - if jsonParsed.Exists(pathComponents...) { - jsonParsed.Delete(pathComponents...) - } - } else { - if jsonParsed.ExistsP(v) { - jsonParsed.DeleteP(v) - } - } - } - - // load the filtered JSON back into the resource - err = json.Unmarshal(jsonParsed.Bytes(), resource) - if err != nil { - return fmt.Errorf("failed to update resource: %s", err) +func Redact(fields []FieldPath, resource *unstructured.Unstructured) error { + for _, field := range fields { + unstructured.RemoveNestedField(resource.Object, field...) } return nil diff --git a/pkg/datagatherer/k8s/fieldfilter_test.go b/pkg/datagatherer/k8s/fieldfilter_test.go index 52c691f9..c94a63cd 100644 --- a/pkg/datagatherer/k8s/fieldfilter_test.go +++ b/pkg/datagatherer/k8s/fieldfilter_test.go @@ -18,10 +18,10 @@ func TestSelect(t *testing.T) { "metadata": map[string]interface{}{ "name": "example", "namespace": "example", - "annotations": map[string]string{ + "annotations": map[string]interface{}{ "kubectl.kubernetes.io/last-applied-configuration": "secret", }, - "labels": map[string]string{ + "labels": map[string]interface{}{ "foo": "bar", }, }, @@ -61,16 +61,16 @@ func TestSelect(t *testing.T) { "kind": "Route", "metadata": map[string]interface{}{ "name": "example", - "annotations": map[string]string{ + "annotations": map[string]interface{}{ "kubectl.kubernetes.io/last-applied-configuration": "secret", }, - "labels": map[string]string{ + "labels": map[string]interface{}{ "foo": "bar", }, }, "spec": map[string]interface{}{ "host": "www.example.com", - "to": map[string]string{ + "to": map[string]interface{}{ "kind": "Service", "name": "frontend", }, @@ -112,7 +112,7 @@ func TestSelect(t *testing.T) { )) } -func run_TestSelect(given map[string]interface{}, givenSelect []string, expect map[string]interface{}) func(*testing.T) { +func run_TestSelect(given map[string]interface{}, givenSelect []FieldPath, expect map[string]interface{}) func(*testing.T) { return func(t *testing.T) { t.Helper() givenPtr := unstructured.Unstructured{Object: given} @@ -130,10 +130,9 @@ func TestSelectMissingSelectedField(t *testing.T) { }, } - fieldsToSelect := []string{ - "kind", // required for unstructured unmarshal - "missing", - "/missing", + fieldsToSelect := []FieldPath{ + {"kind"}, // required for unstructured unmarshal + {"missing"}, } err := Select(fieldsToSelect, resource) @@ -156,7 +155,7 @@ func TestRedactSecret(t *testing.T) { "metadata": map[string]interface{}{ "name": "example", "namespace": "example", - "annotations": map[string]string{ + "annotations": map[string]interface{}{ "kubectl.kubernetes.io/last-applied-configuration": "secret", }, "managedFields": nil, @@ -169,10 +168,10 @@ func TestRedactSecret(t *testing.T) { }, } - fieldsToRedact := []string{ - "metadata.managedFields", - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration", - "/data/tls.key", + fieldsToRedact := []FieldPath{ + {"metadata", "managedFields"}, + {"metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"}, + {"data", "tls.key"}, } err := Redact(fieldsToRedact, resource) @@ -213,8 +212,8 @@ func TestRedactPod(t *testing.T) { }, } - fieldsToRedact := []string{ - "metadata.managedFields", + fieldsToRedact := []FieldPath{ + {"metadata", "managedFields"}, } err := Redact(fieldsToRedact, resource) @@ -244,9 +243,8 @@ func TestRedactMissingField(t *testing.T) { }, } - fieldsToRedact := []string{ - "missing", - "/missing", + fieldsToRedact := []FieldPath{ + {"missing"}, } err := Redact(fieldsToRedact, resource)