Skip to content

Commit a6ceff7

Browse files
authored
fix(misconf): ensure boolean metadata values are correctly interpreted (#9770)
Signed-off-by: nikpivkin <[email protected]>
1 parent c8d5ab7 commit a6ceff7

File tree

8 files changed

+182
-42
lines changed

8 files changed

+182
-42
lines changed

pkg/iac/adapters/terraform/google/compute/instances.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package compute
22

33
import (
4-
"github.com/zclconf/go-cty/cty"
5-
64
"github.com/aquasecurity/trivy/pkg/iac/providers/google/compute"
75
"github.com/aquasecurity/trivy/pkg/iac/terraform"
86
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
@@ -31,9 +29,6 @@ func adaptInstances(modules terraform.Modules) (instances []compute.Instance) {
3129
OSLoginEnabled: iacTypes.BoolDefault(true, instanceBlock.GetMetadata()),
3230
EnableProjectSSHKeyBlocking: iacTypes.BoolDefault(false, instanceBlock.GetMetadata()),
3331
EnableSerialPort: iacTypes.BoolDefault(false, instanceBlock.GetMetadata()),
34-
NetworkInterfaces: nil,
35-
BootDisks: nil,
36-
AttachedDisks: nil,
3732
}
3833

3934
// network interfaces
@@ -60,16 +55,11 @@ func adaptInstances(modules terraform.Modules) (instances []compute.Instance) {
6055
}
6156

6257
// metadata
63-
if metadataAttr := instanceBlock.GetAttribute("metadata"); metadataAttr.IsNotNil() {
64-
if val := metadataAttr.MapValue("enable-oslogin"); val.Type() == cty.Bool {
65-
instance.OSLoginEnabled = iacTypes.BoolExplicit(val.True(), metadataAttr.GetMetadata())
66-
}
67-
if val := metadataAttr.MapValue("block-project-ssh-keys"); val.Type() == cty.Bool {
68-
instance.EnableProjectSSHKeyBlocking = iacTypes.BoolExplicit(val.True(), metadataAttr.GetMetadata())
69-
}
70-
if val := metadataAttr.MapValue("serial-port-enable"); val.Type() == cty.Bool {
71-
instance.EnableSerialPort = iacTypes.BoolExplicit(val.True(), metadataAttr.GetMetadata())
72-
}
58+
if attr := instanceBlock.GetAttribute("metadata"); attr.IsNotNil() {
59+
flags := parseMetadataFlags(attr)
60+
instance.OSLoginEnabled = flags.EnableOSLogin
61+
instance.EnableProjectSSHKeyBlocking = flags.BlockProjectSSHKeys
62+
instance.EnableSerialPort = flags.EnableSerialPort
7363
}
7464

7565
// disks

pkg/iac/adapters/terraform/google/compute/instances_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,8 @@ func Test_adaptInstances(t *testing.T) {
8181
IsDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()),
8282
},
8383
CanIPForward: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
84-
OSLoginEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()),
8584
EnableProjectSSHKeyBlocking: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
8685
EnableSerialPort: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
87-
8886
BootDisks: []compute.Disk{
8987
{
9088
Metadata: iacTypes.NewTestMetadata(),
@@ -155,6 +153,26 @@ func Test_adaptInstances(t *testing.T) {
155153
},
156154
},
157155
},
156+
{
157+
name: "handles metadata values in various formats",
158+
terraform: `resource "google_compute_instance" "example" {
159+
name = "test"
160+
161+
metadata = {
162+
enable-oslogin = "True"
163+
block-project-ssh-keys = 1
164+
serial-port-enable = "yes"
165+
}
166+
}`,
167+
expected: []compute.Instance{
168+
{
169+
Name: iacTypes.StringTest("test"),
170+
OSLoginEnabled: iacTypes.BoolTest(true),
171+
EnableSerialPort: iacTypes.BoolTest(true),
172+
EnableProjectSSHKeyBlocking: iacTypes.BoolTest(true),
173+
},
174+
},
175+
},
158176
}
159177

160178
for _, test := range tests {
Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,49 @@
11
package compute
22

33
import (
4-
"github.com/zclconf/go-cty/cty"
5-
64
"github.com/aquasecurity/trivy/pkg/iac/providers/google/compute"
75
"github.com/aquasecurity/trivy/pkg/iac/terraform"
86
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
97
)
108

9+
// TODO: add support for google_compute_project_metadata_item
10+
// https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_project_metadata_item
1111
func adaptProjectMetadata(modules terraform.Modules) compute.ProjectMetadata {
1212
metadata := compute.ProjectMetadata{
13-
Metadata: iacTypes.NewUnmanagedMetadata(),
14-
EnableOSLogin: iacTypes.BoolUnresolvable(
15-
iacTypes.NewUnmanagedMetadata(),
16-
),
13+
Metadata: iacTypes.NewUnmanagedMetadata(),
14+
EnableOSLogin: iacTypes.BoolUnresolvable(iacTypes.NewUnmanagedMetadata()),
1715
}
16+
1817
for _, metadataBlock := range modules.GetResourcesByType("google_compute_project_metadata") {
1918
metadata.Metadata = metadataBlock.GetMetadata()
20-
if metadataAttr := metadataBlock.GetAttribute("metadata"); metadataAttr.IsNotNil() {
21-
if val := metadataAttr.MapValue("enable-oslogin"); val.Type() == cty.Bool {
22-
metadata.EnableOSLogin = iacTypes.BoolExplicit(val.True(), metadataAttr.GetMetadata())
23-
}
19+
if attr := metadataBlock.GetAttribute("metadata"); attr.IsNotNil() {
20+
flags := parseMetadataFlags(attr)
21+
metadata.EnableOSLogin = flags.EnableOSLogin
2422
}
2523
}
2624
return metadata
2725
}
26+
27+
func parseMetadataFlags(attr *terraform.Attribute) compute.MetadataFlags {
28+
flags := compute.MetadataFlags{
29+
EnableOSLogin: iacTypes.BoolDefault(false, attr.GetMetadata()),
30+
BlockProjectSSHKeys: iacTypes.BoolDefault(false, attr.GetMetadata()),
31+
EnableSerialPort: iacTypes.BoolDefault(false, attr.GetMetadata()),
32+
}
33+
34+
if attr.IsNil() {
35+
return flags
36+
}
37+
38+
meta := attr.GetMetadata()
39+
if val, ok := iacTypes.BoolFromCtyValue(attr.MapValue("enable-oslogin"), meta); ok {
40+
flags.EnableOSLogin = val
41+
}
42+
if val, ok := iacTypes.BoolFromCtyValue(attr.MapValue("block-project-ssh-keys"), meta); ok {
43+
flags.BlockProjectSSHKeys = val
44+
}
45+
if val, ok := iacTypes.BoolFromCtyValue(attr.MapValue("serial-port-enable"), meta); ok {
46+
flags.EnableSerialPort = val
47+
}
48+
return flags
49+
}

pkg/iac/adapters/terraform/google/compute/metadata_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ func Test_adaptProjectMetadata(t *testing.T) {
2525
}
2626
`,
2727
expected: compute.ProjectMetadata{
28-
Metadata: iacTypes.NewTestMetadata(),
29-
EnableOSLogin: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
28+
EnableOSLogin: iacTypes.BoolTest(true),
3029
},
3130
},
3231
{
@@ -38,8 +37,21 @@ func Test_adaptProjectMetadata(t *testing.T) {
3837
}
3938
`,
4039
expected: compute.ProjectMetadata{
41-
Metadata: iacTypes.NewTestMetadata(),
42-
EnableOSLogin: iacTypes.Bool(false, iacTypes.NewTestMetadata()),
40+
EnableOSLogin: iacTypes.BoolTest(false),
41+
},
42+
},
43+
{
44+
name: "handles metadata values in various formats",
45+
terraform: `resource "google_compute_project_metadata" "example" {
46+
metadata = {
47+
enable-oslogin = "TRUE"
48+
block-project-ssh-keys = 1
49+
serial-port-enable = "yes"
50+
}
51+
}
52+
`,
53+
expected: compute.ProjectMetadata{
54+
EnableOSLogin: iacTypes.BoolTest(true),
4355
},
4456
},
4557
}

pkg/iac/adapters/terraform/google/gke/adapt.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package gke
22

33
import (
44
"github.com/google/uuid"
5-
"github.com/zclconf/go-cty/cty"
65

76
"github.com/aquasecurity/trivy/pkg/iac/providers/google/gke"
87
"github.com/aquasecurity/trivy/pkg/iac/terraform"
@@ -285,16 +284,8 @@ func adaptNodeConfig(resource *terraform.Block) gke.NodeConfig {
285284

286285
if metadata := resource.GetAttribute("metadata"); metadata.IsNotNil() {
287286
disableLegacy := metadata.MapValue("disable-legacy-endpoints")
288-
if disableLegacy.IsKnown() {
289-
var enableLegacyEndpoints bool
290-
switch disableLegacy.Type() {
291-
case cty.Bool:
292-
enableLegacyEndpoints = disableLegacy.False()
293-
case cty.String:
294-
enableLegacyEndpoints = disableLegacy.AsString() == "false"
295-
}
296-
297-
config.EnableLegacyEndpoints = iacTypes.Bool(enableLegacyEndpoints, metadata.GetMetadata())
287+
if val, ok := iacTypes.BoolFromCtyValue(disableLegacy, metadata.GetMetadata()); ok {
288+
config.EnableLegacyEndpoints = val.Invert()
298289
}
299290
}
300291

pkg/iac/providers/google/compute/metadata.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import (
44
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
55
)
66

7+
type MetadataFlags struct {
8+
EnableOSLogin iacTypes.BoolValue
9+
BlockProjectSSHKeys iacTypes.BoolValue
10+
EnableSerialPort iacTypes.BoolValue
11+
}
12+
713
type ProjectMetadata struct {
814
Metadata iacTypes.Metadata
915
EnableOSLogin iacTypes.BoolValue

pkg/iac/types/bool.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package types
22

33
import (
44
"encoding/json"
5+
"strings"
6+
7+
"github.com/zclconf/go-cty/cty"
58
)
69

710
type BoolValue struct {
@@ -89,8 +92,55 @@ func (b BoolValue) IsFalse() bool {
8992
return !b.Value()
9093
}
9194

95+
func (b BoolValue) Invert() BoolValue {
96+
return BoolValue{
97+
BaseAttribute: b.BaseAttribute,
98+
value: !b.value,
99+
}
100+
}
101+
92102
func (b BoolValue) ToRego() any {
93103
m := b.metadata.ToRego().(map[string]any)
94104
m["value"] = b.Value()
95105
return m
96106
}
107+
108+
// BoolFromCtyValue converts a cty.Value to iacTypes.BoolValue.
109+
// Returns the BoolValue and true if conversion to bool succeeded.
110+
func BoolFromCtyValue(val cty.Value, metadata Metadata) (BoolValue, bool) {
111+
if val.IsNull() || !val.IsKnown() {
112+
return BoolUnresolvable(metadata), false
113+
}
114+
115+
unmarked, _ := val.Unmark()
116+
v, ok := ctyToBool(unmarked)
117+
if !ok {
118+
return BoolUnresolvable(metadata), false
119+
}
120+
121+
return BoolExplicit(v, metadata), true
122+
}
123+
124+
func ctyToBool(val cty.Value) (bool, bool) {
125+
switch val.Type() {
126+
case cty.Bool:
127+
return val.True(), true
128+
case cty.String:
129+
switch strings.ToLower(val.AsString()) {
130+
case "true", "yes", "y", "1":
131+
return true, true
132+
case "false", "no", "n", "0":
133+
return false, true
134+
}
135+
case cty.Number:
136+
v, _ := val.AsBigFloat().Int64()
137+
switch v {
138+
case 1:
139+
return true, true
140+
case 0:
141+
return false, true
142+
}
143+
}
144+
145+
return false, false
146+
}

pkg/iac/types/bool_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
9+
"github.com/zclconf/go-cty/cty"
910
)
1011

1112
var fakeMetadata = NewMetadata(NewRange("main.tf", 123, 123, "", nil), "")
@@ -43,3 +44,53 @@ func Test_BoolJSON(t *testing.T) {
4344

4445
assert.Equal(t, val, restored)
4546
}
47+
48+
func TestGetBoolFromValue(t *testing.T) {
49+
metadata := NewTestMetadata()
50+
51+
tests := []struct {
52+
name string
53+
ctyVal cty.Value
54+
expected bool
55+
ok bool
56+
}{
57+
// Bool
58+
{"bool true", cty.BoolVal(true), true, true},
59+
{"bool false", cty.BoolVal(false), false, true},
60+
61+
// Strings (true)
62+
{"string 'true'", cty.StringVal("true"), true, true},
63+
{"string 'TRUE'", cty.StringVal("TRUE"), true, true},
64+
{"string 'yes'", cty.StringVal("yes"), true, true},
65+
{"string '1'", cty.StringVal("1"), true, true},
66+
67+
// Strings (false)
68+
{"string 'false'", cty.StringVal("false"), false, true},
69+
{"string 'NO'", cty.StringVal("NO"), false, true},
70+
{"string '0'", cty.StringVal("0"), false, true},
71+
72+
// Numbers
73+
{"number 1", cty.NumberIntVal(1), true, true},
74+
{"number 0", cty.NumberIntVal(0), false, true},
75+
{"number 42 (invalid)", cty.NumberIntVal(42), false, false},
76+
77+
// Null / Unknown
78+
{"null", cty.NullVal(cty.Bool), false, false},
79+
{"unknown", cty.UnknownVal(cty.Bool), false, false},
80+
81+
// Invalid string
82+
{"string 'maybe'", cty.StringVal("maybe"), false, false},
83+
}
84+
85+
for _, tt := range tests {
86+
t.Run(tt.name, func(t *testing.T) {
87+
got, ok := BoolFromCtyValue(tt.ctyVal, metadata)
88+
assert.Equal(t, tt.ok, ok)
89+
if ok {
90+
assert.Equal(t, tt.expected, got.Value())
91+
} else {
92+
assert.False(t, got.Value())
93+
}
94+
})
95+
}
96+
}

0 commit comments

Comments
 (0)