Skip to content

Commit c8dddd7

Browse files
authored
Fix secret value logged when type checking fails (#3159)
In the typechecker we have to unwrap the secret value to do the nested type checking, but if it fails we log the value for the user. While it is useful to see the actual data to know what shape the data is in, we have to prevent secrets from being logged. They will still see the `type`, they just won't be able to see the actual value. fixes #3158
1 parent 1f64e24 commit c8dddd7

File tree

2 files changed

+55
-13
lines changed

2 files changed

+55
-13
lines changed

pkg/tfbridge/typechecker/typechecker.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func (v *TypeChecker) validatePropertyMap(
5656
propertyMap resource.PropertyMap,
5757
propertyTypes map[string]pschema.PropertySpec,
5858
propertyPath resource.PropertyPath,
59+
isSecret bool,
5960
) []Failure {
6061
stableKeys := propertyMap.StableKeys()
6162
failures := []Failure{}
@@ -77,7 +78,7 @@ func (v *TypeChecker) validatePropertyMap(
7778
continue
7879
}
7980
subPropertyPath := append(propertyPath, string(objectKey))
80-
failure := v.validatePropertyValue(objectValue, objType.TypeSpec, subPropertyPath)
81+
failure := v.validatePropertyValue(objectValue, objType.TypeSpec, subPropertyPath, isSecret)
8182
if failure != nil {
8283
failures = append(failures, failure...)
8384
}
@@ -91,6 +92,7 @@ func (v *TypeChecker) validatePropertyValue(
9192
propertyValue resource.PropertyValue,
9293
typeSpec pschema.TypeSpec,
9394
propertyPath resource.PropertyPath,
95+
isSecret bool,
9496
) []Failure {
9597
// don't type check
9698
// - resource references (not yet)
@@ -111,13 +113,13 @@ func (v *TypeChecker) validatePropertyValue(
111113
// for known first-class outputs simply validate their known value
112114
if propertyValue.IsOutput() && propertyValue.OutputValue().Known {
113115
elementValue := propertyValue.OutputValue().Element
114-
return v.validatePropertyValue(elementValue, typeSpec, propertyPath)
116+
return v.validatePropertyValue(elementValue, typeSpec, propertyPath, isSecret)
115117
}
116118

117119
// for secrets validate their inner value
118120
if propertyValue.IsSecret() {
119121
elementValue := propertyValue.SecretValue().Element
120-
return v.validatePropertyValue(elementValue, typeSpec, propertyPath)
122+
return v.validatePropertyValue(elementValue, typeSpec, propertyPath, true)
121123
}
122124

123125
// now we are going to switch on the desired type
@@ -141,13 +143,14 @@ func (v *TypeChecker) validatePropertyValue(
141143
}
142144

143145
if !propertyValue.IsObject() {
144-
return []Failure{newTypeFailure(propertyPath, "object", propertyValue)}
146+
return []Failure{newTypeFailure(propertyPath, "object", propertyValue, isSecret)}
145147
}
146148

147149
return v.validatePropertyMap(
148150
propertyValue.ObjectValue(),
149151
objType.Properties,
150152
propertyPath,
153+
isSecret,
151154
)
152155
}
153156

@@ -173,19 +176,19 @@ func (v *TypeChecker) validatePropertyValue(
173176
}
174177
}
175178
if !propertyValue.IsBool() && !boolString {
176-
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue)}
179+
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue, isSecret)}
177180
}
178181
return nil
179182
case "integer", "number":
180183
// The bridge permits coalescing strings to numbers, hence skip strings.
181184
if !propertyValue.IsNumber() && !propertyValue.IsString() {
182-
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue)}
185+
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue, isSecret)}
183186
}
184187
return nil
185188
case "string":
186189
// The bridge permits coalescing numbers and booleans to strings, hence skip these.
187190
if !propertyValue.IsString() && !propertyValue.IsNumber() && !propertyValue.IsBool() {
188-
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue)}
191+
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue, isSecret)}
189192
}
190193
return nil
191194
case "array":
@@ -198,14 +201,14 @@ func (v *TypeChecker) validatePropertyValue(
198201
failures := []Failure{}
199202
for idx, arrayValue := range propertyValue.ArrayValue() {
200203
pb := append(propertyPath, idx)
201-
failure := v.validatePropertyValue(arrayValue, *typeSpec.Items, pb)
204+
failure := v.validatePropertyValue(arrayValue, *typeSpec.Items, pb, isSecret)
202205
if failure != nil {
203206
failures = append(failures, failure...)
204207
}
205208
}
206209
return failures
207210
}
208-
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue)}
211+
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue, isSecret)}
209212
case "object":
210213
// This is not really an object but a map type with some element type, which is assumed to be string if
211214
// unspecified. Check accordingly. This should be very similar to the "array" case.
@@ -223,14 +226,14 @@ func (v *TypeChecker) validatePropertyValue(
223226
continue
224227
}
225228
pb := append(propertyPath, string(propertyKey))
226-
failure := v.validatePropertyValue(objectValue[propertyKey], *typeSpec.AdditionalProperties, pb)
229+
failure := v.validatePropertyValue(objectValue[propertyKey], *typeSpec.AdditionalProperties, pb, isSecret)
227230
if failure != nil {
228231
failures = append(failures, failure...)
229232
}
230233
}
231234
return failures
232235
}
233-
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue)}
236+
return []Failure{newTypeFailure(propertyPath, typeSpec.Type, propertyValue, isSecret)}
234237
default:
235238
// Unrecognized type, assume no errors.
236239
return nil
@@ -240,11 +243,17 @@ func (v *TypeChecker) validatePropertyValue(
240243
func newTypeFailure(
241244
path resource.PropertyPath,
242245
expectedType string, actualValue resource.PropertyValue,
246+
isSecret bool,
243247
) Failure {
248+
actualValueType := actualValue.TypeString()
249+
if isSecret {
250+
actualValue = resource.MakeSecret(actualValue)
251+
}
252+
actualPreview := previewPropertyValue(actualValue)
244253
return Failure{
245254
ResourcePath: path.String(),
246255
Reason: fmt.Sprintf("expected %s type, got %s of type %s",
247-
expectedType, previewPropertyValue(actualValue), actualValue.TypeString(),
256+
expectedType, actualPreview, actualValueType,
248257
),
249258
}
250259
}
@@ -256,7 +265,7 @@ func previewPropertyValue(v resource.PropertyValue) string {
256265
case v.IsComputed():
257266
return "(unknown value)"
258267
case v.IsSecret():
259-
return "secret(" + previewPropertyValue(v.SecretValue().Element) + ")"
268+
return "secret(<redacted>)"
260269
case v.IsString():
261270
preview = fmt.Sprintf("%q", v.StringValue())
262271
default:
@@ -296,6 +305,7 @@ func (v *TypeChecker) ValidateInputs(resourceToken tokens.Type, inputs resource.
296305
inputs,
297306
resourceSpec.InputProperties,
298307
resource.PropertyPath{},
308+
false,
299309
)
300310
}
301311

@@ -314,5 +324,6 @@ func (v *TypeChecker) ValidateConfig(inputs resource.PropertyMap) []Failure {
314324
inputsToValidate,
315325
v.schema.Config.Variables,
316326
resource.PropertyPath{},
327+
false,
317328
)
318329
}

pkg/tfbridge/typechecker/typechecker_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,37 @@ func TestValidateInputType_toplevel(t *testing.T) {
14951495
},
14961496
},
14971497
},
1498+
{
1499+
name: "secret_type_mismatch_redacted",
1500+
input: resource.NewSecretProperty(&resource.Secret{
1501+
Element: resource.NewObjectProperty(
1502+
resource.NewPropertyMapFromMap(map[string]interface{}{"password": "SuperSecretValue123"}),
1503+
),
1504+
}),
1505+
failures: autogold.Expect([]Failure{{
1506+
Reason: "expected string type, got secret(<redacted>) of type object",
1507+
ResourcePath: "secret_type_mismatch_redacted",
1508+
}}),
1509+
inputProperties: map[string]pschema.PropertySpec{
1510+
"secret_type_mismatch_redacted": {TypeSpec: pschema.TypeSpec{Type: "string"}},
1511+
},
1512+
},
1513+
{
1514+
name: "secret_nested_mismatch_redacted",
1515+
input: resource.NewSecretProperty(&resource.Secret{
1516+
Element: resource.NewObjectProperty(resource.NewPropertyMapFromMap(map[string]interface{}{"inner": []int{42}})),
1517+
}),
1518+
failures: autogold.Expect([]Failure{{
1519+
Reason: "expected string type, got secret(<redacted>) of type []",
1520+
ResourcePath: "secret_nested_mismatch_redacted.inner",
1521+
}}),
1522+
inputProperties: map[string]pschema.PropertySpec{
1523+
"secret_nested_mismatch_redacted": {TypeSpec: pschema.TypeSpec{
1524+
Type: "object",
1525+
AdditionalProperties: &pschema.TypeSpec{Type: "string"},
1526+
}},
1527+
},
1528+
},
14981529
{
14991530
name: "output_type_success",
15001531
input: resource.NewOutputProperty(resource.Output{Element: resource.NewStringProperty("foo")}),

0 commit comments

Comments
 (0)