Skip to content

Commit a994453

Browse files
authored
fix(misconf): fix incorrect k8s locations due to JSON to YAML conversion (#8073)
Signed-off-by: nikpivkin <[email protected]>
1 parent 4820eb7 commit a994453

File tree

6 files changed

+156
-15
lines changed

6 files changed

+156
-15
lines changed

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ require (
4848
github.com/docker/go-units v0.5.0
4949
github.com/fatih/color v1.18.0
5050
github.com/go-git/go-git/v5 v5.13.2
51+
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // Replace with encoding/json/v2 when proposal is accepted. Track https://github.com/golang/go/issues/71497
5152
github.com/go-openapi/runtime v0.28.0 // indirect
5253
github.com/go-openapi/strfmt v0.23.0 // indirect
5354
github.com/go-redis/redis/v8 v8.11.5
@@ -133,7 +134,7 @@ require (
133134
k8s.io/api v0.32.2
134135
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
135136
modernc.org/sqlite v1.35.0
136-
sigs.k8s.io/yaml v1.4.0
137+
sigs.k8s.io/yaml v1.4.0 // indirect
137138
)
138139

139140
require (

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,8 @@ github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7
11261126
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
11271127
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
11281128
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
1129+
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
1130+
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
11291131
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
11301132
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
11311133
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=

pkg/iac/scanners/kubernetes/parser/manifest.go

+71
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package parser
22

33
import (
4+
"bytes"
5+
"errors"
46
"fmt"
7+
"io"
8+
"reflect"
59

10+
"github.com/go-json-experiment/json"
11+
"github.com/go-json-experiment/json/jsontext"
612
"gopkg.in/yaml.v3"
713
)
814

@@ -31,3 +37,68 @@ func (m *Manifest) UnmarshalYAML(value *yaml.Node) error {
3137
func (m *Manifest) ToRego() any {
3238
return m.Content.ToRego()
3339
}
40+
41+
func ManifestFromJSON(path string, data []byte) (*Manifest, error) {
42+
root := &ManifestNode{
43+
Path: path,
44+
}
45+
46+
if err := json.Unmarshal(data, root, json.WithUnmarshalers(
47+
json.UnmarshalFromFunc(func(dec *jsontext.Decoder, node *ManifestNode, opts json.Options) error {
48+
startOffset := dec.InputOffset()
49+
if err := unmarshalManifestNode(dec, node); err != nil {
50+
return err
51+
}
52+
endOffset := dec.InputOffset()
53+
node.StartLine = 1 + countLines(data, int(startOffset))
54+
node.EndLine = 1 + countLines(data, int(endOffset))
55+
node.Path = path
56+
return nil
57+
})),
58+
); err != nil && !errors.Is(err, io.EOF) {
59+
return nil, err
60+
}
61+
62+
return &Manifest{
63+
Path: path,
64+
Content: root,
65+
}, nil
66+
}
67+
68+
func unmarshalManifestNode(dec *jsontext.Decoder, node *ManifestNode) error {
69+
var valPtr any
70+
var nodeType TagType
71+
switch k := dec.PeekKind(); k {
72+
case 't', 'f':
73+
valPtr = new(bool)
74+
nodeType = TagBool
75+
case '"':
76+
nodeType = TagStr
77+
valPtr = new(string)
78+
case '0':
79+
nodeType = TagInt
80+
valPtr = new(uint64)
81+
case '[', 'n':
82+
valPtr = new([]*ManifestNode)
83+
nodeType = TagSlice
84+
case '{':
85+
valPtr = new(map[string]*ManifestNode)
86+
nodeType = TagMap
87+
case 0:
88+
return dec.SkipValue()
89+
default:
90+
return fmt.Errorf("unexpected token kind %q at %d", k.String(), dec.InputOffset())
91+
}
92+
93+
if err := json.UnmarshalDecode(dec, valPtr); err != nil {
94+
return err
95+
}
96+
97+
node.Value = reflect.ValueOf(valPtr).Elem().Interface()
98+
node.Type = nodeType
99+
return nil
100+
}
101+
102+
func countLines(data []byte, offset int) int {
103+
return bytes.Count(data[:offset], []byte("\n"))
104+
}

pkg/iac/scanners/kubernetes/parser/manifest_node.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (r *ManifestNode) ToRego() any {
4949
return t.Format(time.RFC3339)
5050
case TagSlice:
5151
var output []any
52-
for _, node := range r.Value.([]ManifestNode) {
52+
for _, node := range r.Value.([]*ManifestNode) {
5353
output = append(output, node.ToRego())
5454
}
5555
return output
@@ -61,7 +61,7 @@ func (r *ManifestNode) ToRego() any {
6161
"filepath": r.Path,
6262
"offset": r.Offset,
6363
}
64-
for key, node := range r.Value.(map[string]ManifestNode) {
64+
for key, node := range r.Value.(map[string]*ManifestNode) {
6565
output[key] = node.ToRego()
6666
}
6767
return output
@@ -122,7 +122,7 @@ func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error {
122122
}
123123

124124
func (r *ManifestNode) handleSliceTag(node *yaml.Node) error {
125-
var nodes []ManifestNode
125+
var nodes []*ManifestNode
126126
maxLine := node.Line
127127
for _, contentNode := range node.Content {
128128
newNode := new(ManifestNode)
@@ -133,15 +133,15 @@ func (r *ManifestNode) handleSliceTag(node *yaml.Node) error {
133133
if newNode.EndLine > maxLine {
134134
maxLine = newNode.EndLine
135135
}
136-
nodes = append(nodes, *newNode)
136+
nodes = append(nodes, newNode)
137137
}
138138
r.EndLine = maxLine
139139
r.Value = nodes
140140
return nil
141141
}
142142

143143
func (r *ManifestNode) handleMapTag(node *yaml.Node) error {
144-
output := make(map[string]ManifestNode)
144+
output := make(map[string]*ManifestNode)
145145
var key string
146146
maxLine := node.Line
147147
for i, contentNode := range node.Content {
@@ -153,7 +153,7 @@ func (r *ManifestNode) handleMapTag(node *yaml.Node) error {
153153
if err := contentNode.Decode(newNode); err != nil {
154154
return err
155155
}
156-
output[key] = *newNode
156+
output[key] = newNode
157157
if newNode.EndLine > maxLine {
158158
maxLine = newNode.EndLine
159159
}

pkg/iac/scanners/kubernetes/parser/manifest_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,79 @@ import (
1010
"github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser"
1111
)
1212

13+
func TestJsonManifestToRego(t *testing.T) {
14+
content := `{
15+
"apiVersion": "v1",
16+
"kind": "Pod",
17+
"metadata": {
18+
"name": "hello-cpu-limit"
19+
},
20+
"spec": {
21+
"containers": [
22+
{
23+
"command": [
24+
"sh",
25+
"-c",
26+
"echo 'Hello' && sleep 1h"
27+
],
28+
"image": "busybox",
29+
"name": "hello"
30+
}
31+
]
32+
}
33+
}`
34+
35+
const filePath = "pod.json"
36+
manifest, err := parser.ManifestFromJSON(filePath, []byte(content))
37+
require.NoError(t, err)
38+
39+
expected := map[string]any{
40+
"__defsec_metadata": map[string]any{
41+
"filepath": filePath,
42+
"offset": 0,
43+
"startline": 1,
44+
"endline": 20,
45+
},
46+
"apiVersion": "v1",
47+
"kind": "Pod",
48+
"metadata": map[string]any{
49+
"__defsec_metadata": map[string]any{
50+
"filepath": filePath,
51+
"offset": 0,
52+
"startline": 4,
53+
"endline": 6,
54+
},
55+
"name": "hello-cpu-limit",
56+
},
57+
"spec": map[string]any{
58+
"__defsec_metadata": map[string]any{
59+
"filepath": filePath,
60+
"offset": 0,
61+
"startline": 7,
62+
"endline": 19,
63+
},
64+
"containers": []any{
65+
map[string]any{
66+
"__defsec_metadata": map[string]any{
67+
"filepath": filePath,
68+
"offset": 0,
69+
"startline": 8,
70+
"endline": 17,
71+
},
72+
"command": []any{
73+
"sh",
74+
"-c",
75+
"echo 'Hello' && sleep 1h",
76+
},
77+
"image": "busybox",
78+
"name": "hello",
79+
},
80+
},
81+
},
82+
}
83+
assert.Equal(t, expected, manifest.ToRego())
84+
}
85+
1386
func TestManifestToRego(t *testing.T) {
1487
tests := []struct {
1588
name string

pkg/iac/scanners/kubernetes/parser/parser.go

+2-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ package parser
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"io"
87
"regexp"
98
"strings"
109

1110
"gopkg.in/yaml.v3"
12-
kyaml "sigs.k8s.io/yaml"
1311
)
1412

1513
func Parse(_ context.Context, r io.Reader, path string) ([]any, error) {
@@ -23,15 +21,11 @@ func Parse(_ context.Context, r io.Reader, path string) ([]any, error) {
2321
}
2422

2523
if strings.TrimSpace(string(contents))[0] == '{' {
26-
var target any
27-
if err := json.Unmarshal(contents, &target); err != nil {
28-
return nil, err
29-
}
30-
31-
contents, err = kyaml.JSONToYAML(contents) // convert into yaml to reuse file parsing logic
24+
manifest, err := ManifestFromJSON(path, contents)
3225
if err != nil {
3326
return nil, err
3427
}
28+
return []any{manifest.ToRego()}, nil
3529
}
3630

3731
var results []any

0 commit comments

Comments
 (0)