From f12054e669f9df93c6322ba2755036dbccacaa83 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 23 Jan 2025 10:20:02 +0600 Subject: [PATCH] fix(misconf): correctly handle all YAML tags in K8S templates (#8259) Signed-off-by: nikpivkin --- .../kubernetes/parser/manifest_node.go | 55 +++++++++++---- .../kubernetes/parser/manifest_test.go | 69 +++++++++++++++++++ 2 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 pkg/iac/scanners/kubernetes/parser/manifest_test.go diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go index 4110c9035646..0bc3c483ad7b 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_node.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -1,22 +1,28 @@ package parser import ( + "encoding/base64" "fmt" "strconv" + "time" "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/log" ) type TagType string const ( - TagBool TagType = "!!bool" - TagInt TagType = "!!int" - TagFloat TagType = "!!float" - TagStr TagType = "!!str" - TagString TagType = "!!string" - TagSlice TagType = "!!seq" - TagMap TagType = "!!map" + TagBool TagType = "!!bool" + TagInt TagType = "!!int" + TagFloat TagType = "!!float" + TagStr TagType = "!!str" + TagString TagType = "!!string" + TagSlice TagType = "!!seq" + TagMap TagType = "!!map" + TagTimestamp TagType = "!!timestamp" + TagBinary TagType = "!!binary" ) type ManifestNode struct { @@ -33,8 +39,14 @@ func (r *ManifestNode) ToRego() any { return nil } switch r.Type { - case TagBool, TagInt, TagString, TagStr: + case TagBool, TagInt, TagFloat, TagString, TagStr, TagBinary: return r.Value + case TagTimestamp: + t, ok := r.Value.(time.Time) + if !ok { + return nil + } + return t.Format(time.RFC3339) case TagSlice: var output []any for _, node := range r.Value.([]ManifestNode) { @@ -58,40 +70,53 @@ func (r *ManifestNode) ToRego() any { } func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error { - r.StartLine = node.Line r.EndLine = node.Line r.Type = TagType(node.Tag) switch TagType(node.Tag) { case TagString, TagStr: - r.Value = node.Value case TagInt: val, err := strconv.Atoi(node.Value) if err != nil { - return err + return fmt.Errorf("failed to parse int: %w", err) } r.Value = val case TagFloat: val, err := strconv.ParseFloat(node.Value, 64) if err != nil { - return err + return fmt.Errorf("failed to parse float: %w", err) } r.Value = val case TagBool: val, err := strconv.ParseBool(node.Value) if err != nil { - return err + return fmt.Errorf("failed to parse bool: %w", err) + } + r.Value = val + case TagTimestamp: + var val time.Time + if err := node.Decode(&val); err != nil { + return fmt.Errorf("failed to decode timestamp: %w", err) + } + r.Value = val + case TagBinary: + val, err := base64.StdEncoding.DecodeString(node.Value) + if err != nil { + return fmt.Errorf("failed to decode binary data: %w", err) } r.Value = val case TagMap: return r.handleMapTag(node) case TagSlice: return r.handleSliceTag(node) - default: - return fmt.Errorf("node tag is not supported %s", node.Tag) + log.WithPrefix("k8s").Debug("Skipping unsupported node tag", + log.String("tag", node.Tag), + log.FilePath(r.Path), + log.Int("line", node.Line), + ) } return nil } diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_test.go b/pkg/iac/scanners/kubernetes/parser/manifest_test.go new file mode 100644 index 000000000000..eda343de1a0e --- /dev/null +++ b/pkg/iac/scanners/kubernetes/parser/manifest_test.go @@ -0,0 +1,69 @@ +package parser_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" +) + +func TestManifestToRego(t *testing.T) { + tests := []struct { + name string + src string + expected any + }{ + { + name: "timestamp tag", + src: `field: !!timestamp 2024-04-01`, + expected: map[string]any{ + "__defsec_metadata": map[string]any{ + "filepath": "", + "offset": 0, + "startline": 1, + "endline": 1, + }, + "field": "2024-04-01T00:00:00Z", + }, + }, + { + name: "binary tag", + src: `field: !!binary dGVzdA==`, + expected: map[string]any{ + "__defsec_metadata": map[string]any{ + "filepath": "", + "offset": 0, + "startline": 1, + "endline": 1, + }, + "field": []uint8{0x74, 0x65, 0x73, 0x74}, + }, + }, + { + name: "float tag", + src: `field: 1.1`, + expected: map[string]any{ + "__defsec_metadata": map[string]any{ + "filepath": "", + "offset": 0, + "startline": 1, + "endline": 1, + }, + "field": 1.1, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var manifest parser.Manifest + err := yaml.Unmarshal([]byte(tt.src), &manifest) + require.NoError(t, err) + data := manifest.ToRego() + assert.Equal(t, tt.expected, data) + }) + } +}