lang: Redact sensitive values from function errors

Some function errors include values derived from arguments. This commit
is the result of a manual audit of these errors, which resulted in:

- Adding a helper function to redact sensitive values;
- Applying that helper function where errors include values derived from
  possibly-sensitive arguments;
- Cleaning up other errors which need not include those values, or were
  otherwise incorrect.
This commit is contained in:
Alisdair McDiarmid 2021-12-01 13:10:54 -05:00
parent ba6a64eb35
commit 5d7cb81c0c
11 changed files with 394 additions and 123 deletions

View File

@ -311,8 +311,8 @@ var LookupFunc = function.New(&function.Spec{
return defaultVal.WithMarks(markses...), nil
}
return cty.UnknownVal(cty.DynamicPseudoType).WithMarks(markses...), fmt.Errorf(
"lookup failed to find '%s'", lookupKey)
return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
"lookup failed to find key %s", redactIfSensitive(lookupKey, keyMarks))
},
})

View File

@ -5,6 +5,7 @@ import (
"math"
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
@ -899,6 +900,46 @@ func TestLookup(t *testing.T) {
}
}
func TestLookup_error(t *testing.T) {
simpleMap := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
})
tests := map[string]struct {
Values []cty.Value
WantErr string
}{
"failed to find non-sensitive key": {
[]cty.Value{
simpleMap,
cty.StringVal("boop"),
},
`lookup failed to find key "boop"`,
},
"failed to find sensitive key": {
[]cty.Value{
simpleMap,
cty.StringVal("boop").Mark(marks.Sensitive),
},
"lookup failed to find key (sensitive value)",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
_, err := Lookup(test.Values...)
if err == nil {
t.Fatal("succeeded; want error")
}
if err.Error() != test.WantErr {
t.Errorf("wrong error\ngot: %#v\nwant: %#v", err, test.WantErr)
}
})
}
}
func TestMatchkeys(t *testing.T) {
tests := []struct {
Keys cty.Value

View File

@ -20,20 +20,22 @@ var Base64DecodeFunc = function.New(&function.Spec{
{
Name: "str",
Type: cty.String,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
s := args[0].AsString()
str, strMarks := args[0].Unmark()
s := str.AsString()
sDec, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data '%s'", s)
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data %s", redactIfSensitive(s, strMarks))
}
if !utf8.Valid([]byte(sDec)) {
log.Printf("[DEBUG] the result of decoding the provided string is not valid UTF-8: %s", sDec)
log.Printf("[DEBUG] the result of decoding the provided string is not valid UTF-8: %s", redactIfSensitive(sDec, strMarks))
return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the provided string is not valid UTF-8")
}
return cty.StringVal(string(sDec)), nil
return cty.StringVal(string(sDec)).WithMarks(strMarks), nil
},
})
@ -125,7 +127,7 @@ var TextDecodeBase64Func = function.New(&function.Spec{
case base64.CorruptInputError:
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given value is has an invalid base64 symbol at offset %d", int(err))
default:
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid source string: %T", err)
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid source string: %w", err)
}
}
@ -156,13 +158,13 @@ var Base64GzipFunc = function.New(&function.Spec{
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write([]byte(s)); err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: '%s'", s)
return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: %w", err)
}
if err := gz.Flush(); err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: '%s'", s)
return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: %w", err)
}
if err := gz.Close(); err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: '%s'", s)
return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: %w", err)
}
return cty.StringVal(base64.StdEncoding.EncodeToString(b.Bytes())), nil
},

View File

@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
@ -18,6 +19,11 @@ func TestBase64Decode(t *testing.T) {
cty.StringVal("abc123!?$*&()'-=@~"),
false,
},
{
cty.StringVal("YWJjMTIzIT8kKiYoKSctPUB+").Mark(marks.Sensitive),
cty.StringVal("abc123!?$*&()'-=@~").Mark(marks.Sensitive),
false,
},
{ // Invalid base64 data decoding
cty.StringVal("this-is-an-invalid-base64-data"),
cty.UnknownVal(cty.String),
@ -50,6 +56,40 @@ func TestBase64Decode(t *testing.T) {
}
}
func TestBase64Decode_error(t *testing.T) {
tests := map[string]struct {
String cty.Value
WantErr string
}{
"invalid base64": {
cty.StringVal("dfg"),
`failed to decode base64 data "dfg"`,
},
"sensitive invalid base64": {
cty.StringVal("dfg").Mark(marks.Sensitive),
`failed to decode base64 data (sensitive value)`,
},
"invalid utf-8": {
cty.StringVal("whee"),
"the result of decoding the provided string is not valid UTF-8",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
_, err := Base64Decode(test.String)
if err == nil {
t.Fatal("succeeded; want error")
}
if err.Error() != test.WantErr {
t.Errorf("wrong error result\ngot: %#v\nwant: %#v", err.Error(), test.WantErr)
}
})
}
}
func TestBase64Encode(t *testing.T) {
tests := []struct {
String cty.Value

View File

@ -25,12 +25,14 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
{
Name: "path",
Type: cty.String,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
src, err := readFileBytes(baseDir, path)
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
src, err := readFileBytes(baseDir, path, pathMarks)
if err != nil {
err = function.NewArgError(0, err)
return cty.UnknownVal(cty.String), err
@ -39,12 +41,12 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
switch {
case encBase64:
enc := base64.StdEncoding.EncodeToString(src)
return cty.StringVal(enc), nil
return cty.StringVal(enc).WithMarks(pathMarks), nil
default:
if !utf8.Valid(src) {
return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", path)
return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", redactIfSensitive(path, pathMarks))
}
return cty.StringVal(string(src)), nil
return cty.StringVal(string(src)).WithMarks(pathMarks), nil
}
},
})
@ -69,6 +71,7 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Fun
{
Name: "path",
Type: cty.String,
AllowMarked: true,
},
{
Name: "vars",
@ -76,10 +79,10 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Fun
},
}
loadTmpl := func(fn string) (hcl.Expression, error) {
loadTmpl := func(fn string, marks cty.ValueMarks) (hcl.Expression, error) {
// We re-use File here to ensure the same filename interpretation
// as it does, along with its other safety checks.
tmplVal, err := File(baseDir, cty.StringVal(fn))
tmplVal, err := File(baseDir, cty.StringVal(fn).WithMarks(marks))
if err != nil {
return nil, err
}
@ -159,7 +162,9 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Fun
// We'll render our template now to see what result type it produces.
// A template consisting only of a single interpolation an potentially
// return any type.
expr, err := loadTmpl(args[0].AsString())
pathArg, pathMarks := args[0].Unmark()
expr, err := loadTmpl(pathArg.AsString(), pathMarks)
if err != nil {
return cty.DynamicPseudoType, err
}
@ -170,11 +175,13 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Fun
return val.Type(), err
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
expr, err := loadTmpl(args[0].AsString())
pathArg, pathMarks := args[0].Unmark()
expr, err := loadTmpl(pathArg.AsString(), pathMarks)
if err != nil {
return cty.DynamicVal, err
}
return renderTmpl(expr, args[1])
result, err := renderTmpl(expr, args[1])
return result.WithMarks(pathMarks), err
},
})
@ -188,14 +195,16 @@ func MakeFileExistsFunc(baseDir string) function.Function {
{
Name: "path",
Type: cty.String,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
path, err := homedir.Expand(path)
if err != nil {
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %s", err)
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %w", err)
}
if !filepath.IsAbs(path) {
@ -208,17 +217,17 @@ func MakeFileExistsFunc(baseDir string) function.Function {
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return cty.False, nil
return cty.False.WithMarks(pathMarks), nil
}
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path)
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", redactIfSensitive(path, pathMarks))
}
if fi.Mode().IsRegular() {
return cty.True, nil
return cty.True.WithMarks(pathMarks), nil
}
return cty.False, fmt.Errorf("%s is not a regular file, but %q",
path, fi.Mode().String())
redactIfSensitive(path, pathMarks), fi.Mode().String())
},
})
}
@ -231,16 +240,22 @@ func MakeFileSetFunc(baseDir string) function.Function {
{
Name: "path",
Type: cty.String,
AllowMarked: true,
},
{
Name: "pattern",
Type: cty.String,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Set(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
pattern := args[1].AsString()
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
patternArg, patternMarks := args[1].Unmark()
pattern := patternArg.AsString()
marks := []cty.ValueMarks{pathMarks, patternMarks}
if !filepath.IsAbs(path) {
path = filepath.Join(baseDir, path)
@ -253,7 +268,7 @@ func MakeFileSetFunc(baseDir string) function.Function {
matches, err := doublestar.Glob(pattern)
if err != nil {
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern (%s): %s", pattern, err)
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern %s: %w", redactIfSensitive(pattern, marks...), err)
}
var matchVals []cty.Value
@ -261,7 +276,7 @@ func MakeFileSetFunc(baseDir string) function.Function {
fi, err := os.Stat(match)
if err != nil {
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat (%s): %s", match, err)
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat %s: %w", redactIfSensitive(match, marks...), err)
}
if !fi.Mode().IsRegular() {
@ -272,7 +287,7 @@ func MakeFileSetFunc(baseDir string) function.Function {
match, err = filepath.Rel(path, match)
if err != nil {
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to trim path of match (%s): %s", match, err)
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to trim path of match %s: %w", redactIfSensitive(match, marks...), err)
}
// Replace any remaining file separators with forward slash (/)
@ -283,10 +298,10 @@ func MakeFileSetFunc(baseDir string) function.Function {
}
if len(matchVals) == 0 {
return cty.SetValEmpty(cty.String), nil
return cty.SetValEmpty(cty.String).WithMarks(marks...), nil
}
return cty.SetVal(matchVals), nil
return cty.SetVal(matchVals).WithMarks(marks...), nil
},
})
}
@ -355,7 +370,7 @@ var PathExpandFunc = function.New(&function.Spec{
func openFile(baseDir, path string) (*os.File, error) {
path, err := homedir.Expand(path)
if err != nil {
return nil, fmt.Errorf("failed to expand ~: %s", err)
return nil, fmt.Errorf("failed to expand ~: %w", err)
}
if !filepath.IsAbs(path) {
@ -368,12 +383,12 @@ func openFile(baseDir, path string) (*os.File, error) {
return os.Open(path)
}
func readFileBytes(baseDir, path string) ([]byte, error) {
func readFileBytes(baseDir, path string, marks cty.ValueMarks) ([]byte, error) {
f, err := openFile(baseDir, path)
if err != nil {
if os.IsNotExist(err) {
// An extra Terraform-specific hint for this situation
return nil, fmt.Errorf("no file exists at %s; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource", path)
return nil, fmt.Errorf("no file exists at %s; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource", redactIfSensitive(path, marks))
}
return nil, err
}
@ -381,7 +396,7 @@ func readFileBytes(baseDir, path string) ([]byte, error) {
src, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read %s", path)
return nil, fmt.Errorf("failed to read file: %w", err)
}
return src, nil

View File

@ -2,9 +2,11 @@ package funcs
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
homedir "github.com/mitchellh/go-homedir"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
@ -15,22 +17,32 @@ func TestFile(t *testing.T) {
tests := []struct {
Path cty.Value
Want cty.Value
Err bool
Err string
}{
{
cty.StringVal("testdata/hello.txt"),
cty.StringVal("Hello World"),
false,
``,
},
{
cty.StringVal("testdata/icon.png"),
cty.NilVal,
true, // Not valid UTF-8
`contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
},
{
cty.StringVal("testdata/icon.png").Mark(marks.Sensitive),
cty.NilVal,
`contents of (sensitive value) are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
},
{
cty.StringVal("testdata/missing"),
cty.NilVal,
true, // no file exists
`no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
},
{
cty.StringVal("testdata/missing").Mark(marks.Sensitive),
cty.NilVal,
`no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
},
}
@ -38,10 +50,13 @@ func TestFile(t *testing.T) {
t.Run(fmt.Sprintf("File(\".\", %#v)", test.Path), func(t *testing.T) {
got, err := File(".", test.Path)
if test.Err {
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
@ -71,13 +86,19 @@ func TestTemplateFile(t *testing.T) {
cty.StringVal("testdata/icon.png"),
cty.EmptyObjectVal,
cty.NilVal,
`contents of testdata/icon.png are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
`contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
},
{
cty.StringVal("testdata/missing"),
cty.EmptyObjectVal,
cty.NilVal,
`no file exists at testdata/missing; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
`no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
},
{
cty.StringVal("testdata/secrets.txt").Mark(marks.Sensitive),
cty.EmptyObjectVal,
cty.NilVal,
`no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
},
{
cty.StringVal("testdata/hello.tmpl"),
@ -197,33 +218,61 @@ func TestFileExists(t *testing.T) {
tests := []struct {
Path cty.Value
Want cty.Value
Err bool
Err string
}{
{
cty.StringVal("testdata/hello.txt"),
cty.BoolVal(true),
false,
``,
},
{
cty.StringVal(""), // empty path
cty.StringVal(""),
cty.BoolVal(false),
true,
`"." is not a regular file, but "drwxr-xr-x"`,
},
{
cty.StringVal("testdata").Mark(marks.Sensitive),
cty.BoolVal(false),
`(sensitive value) is not a regular file, but "drwxr-xr-x"`,
},
{
cty.StringVal("testdata/missing"),
cty.BoolVal(false),
false, // no file exists
``,
},
{
cty.StringVal("testdata/unreadable/foobar"),
cty.BoolVal(false),
`failed to stat "testdata/unreadable/foobar"`,
},
{
cty.StringVal("testdata/unreadable/foobar").Mark(marks.Sensitive),
cty.BoolVal(false),
`failed to stat (sensitive value)`,
},
}
// Ensure "unreadable" directory cannot be listed during the test run
fi, err := os.Lstat("testdata/unreadable")
if err != nil {
t.Fatal(err)
}
os.Chmod("testdata/unreadable", 0000)
defer func(mode os.FileMode) {
os.Chmod("testdata/unreadable", mode)
}(fi.Mode())
for _, test := range tests {
t.Run(fmt.Sprintf("FileExists(\".\", %#v)", test.Path), func(t *testing.T) {
got, err := FileExists(".", test.Path)
if test.Err {
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
@ -241,49 +290,49 @@ func TestFileSet(t *testing.T) {
Path cty.Value
Pattern cty.Value
Want cty.Value
Err bool
Err string
}{
{
cty.StringVal("."),
cty.StringVal("testdata*"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("testdata"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("{testdata,missing}"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("testdata/missing"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("testdata/missing*"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("*/missing"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("**/missing"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("."),
@ -291,7 +340,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -299,7 +348,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -307,7 +356,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -316,7 +365,7 @@ func TestFileSet(t *testing.T) {
cty.StringVal("testdata/hello.tmpl"),
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -325,7 +374,7 @@ func TestFileSet(t *testing.T) {
cty.StringVal("testdata/hello.tmpl"),
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -333,7 +382,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -341,7 +390,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -350,7 +399,7 @@ func TestFileSet(t *testing.T) {
cty.StringVal("testdata/hello.tmpl"),
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -359,7 +408,7 @@ func TestFileSet(t *testing.T) {
cty.StringVal("testdata/hello.tmpl"),
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
@ -368,31 +417,37 @@ func TestFileSet(t *testing.T) {
cty.StringVal("testdata/hello.tmpl"),
cty.StringVal("testdata/hello.txt"),
}),
false,
``,
},
{
cty.StringVal("."),
cty.StringVal("["),
cty.SetValEmpty(cty.String),
true,
`failed to glob pattern "[": syntax error in pattern`,
},
{
cty.StringVal("."),
cty.StringVal("[").Mark(marks.Sensitive),
cty.SetValEmpty(cty.String),
`failed to glob pattern (sensitive value): syntax error in pattern`,
},
{
cty.StringVal("."),
cty.StringVal("\\"),
cty.SetValEmpty(cty.String),
true,
`failed to glob pattern "\\": syntax error in pattern`,
},
{
cty.StringVal("testdata"),
cty.StringVal("missing"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("testdata"),
cty.StringVal("missing*"),
cty.SetValEmpty(cty.String),
false,
``,
},
{
cty.StringVal("testdata"),
@ -400,7 +455,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("hello.txt"),
}),
false,
``,
},
{
cty.StringVal("testdata"),
@ -408,7 +463,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("hello.txt"),
}),
false,
``,
},
{
cty.StringVal("testdata"),
@ -416,7 +471,7 @@ func TestFileSet(t *testing.T) {
cty.SetVal([]cty.Value{
cty.StringVal("hello.txt"),
}),
false,
``,
},
{
cty.StringVal("testdata"),
@ -425,7 +480,7 @@ func TestFileSet(t *testing.T) {
cty.StringVal("hello.tmpl"),
cty.StringVal("hello.txt"),
}),
false,
``,
},
}
@ -433,10 +488,13 @@ func TestFileSet(t *testing.T) {
t.Run(fmt.Sprintf("FileSet(\".\", %#v, %#v)", test.Path, test.Pattern), func(t *testing.T) {
got, err := FileSet(".", test.Path, test.Pattern)
if test.Err {
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)

View File

@ -97,10 +97,12 @@ var ParseIntFunc = function.New(&function.Spec{
{
Name: "number",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
{
Name: "base",
Type: cty.Number,
AllowMarked: true,
},
},
@ -116,11 +118,13 @@ var ParseIntFunc = function.New(&function.Spec{
var base int
var err error
if err = gocty.FromCtyValue(args[0], &numstr); err != nil {
numArg, numMarks := args[0].Unmark()
if err = gocty.FromCtyValue(numArg, &numstr); err != nil {
return cty.UnknownVal(cty.String), function.NewArgError(0, err)
}
if err = gocty.FromCtyValue(args[1], &base); err != nil {
baseArg, baseMarks := args[1].Unmark()
if err = gocty.FromCtyValue(baseArg, &base); err != nil {
return cty.UnknownVal(cty.Number), function.NewArgError(1, err)
}
@ -135,13 +139,13 @@ var ParseIntFunc = function.New(&function.Spec{
if !ok {
return cty.UnknownVal(cty.Number), function.NewArgErrorf(
0,
"cannot parse %q as a base %d integer",
numstr,
base,
"cannot parse %s as a base %s integer",
redactIfSensitive(numstr, numMarks),
redactIfSensitive(base, baseMarks),
)
}
parsedNum := cty.NumberVal((&big.Float{}).SetInt(num))
parsedNum := cty.NumberVal((&big.Float{}).SetInt(num)).WithMarks(numMarks, baseMarks)
return parsedNum, nil
},

View File

@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
@ -187,139 +188,175 @@ func TestParseInt(t *testing.T) {
Num cty.Value
Base cty.Value
Want cty.Value
Err bool
Err string
}{
{
cty.StringVal("128"),
cty.NumberIntVal(10),
cty.NumberIntVal(128),
false,
``,
},
{
cty.StringVal("128").Mark(marks.Sensitive),
cty.NumberIntVal(10),
cty.NumberIntVal(128).Mark(marks.Sensitive),
``,
},
{
cty.StringVal("128"),
cty.NumberIntVal(10).Mark(marks.Sensitive),
cty.NumberIntVal(128).Mark(marks.Sensitive),
``,
},
{
cty.StringVal("128").Mark(marks.Sensitive),
cty.NumberIntVal(10).Mark(marks.Sensitive),
cty.NumberIntVal(128).Mark(marks.Sensitive),
``,
},
{
cty.StringVal("128").Mark(marks.Raw),
cty.NumberIntVal(10).Mark(marks.Sensitive),
cty.NumberIntVal(128).WithMarks(cty.NewValueMarks(marks.Raw, marks.Sensitive)),
``,
},
{
cty.StringVal("-128"),
cty.NumberIntVal(10),
cty.NumberIntVal(-128),
false,
``,
},
{
cty.StringVal("00128"),
cty.NumberIntVal(10),
cty.NumberIntVal(128),
false,
``,
},
{
cty.StringVal("-00128"),
cty.NumberIntVal(10),
cty.NumberIntVal(-128),
false,
``,
},
{
cty.StringVal("FF00"),
cty.NumberIntVal(16),
cty.NumberIntVal(65280),
false,
``,
},
{
cty.StringVal("ff00"),
cty.NumberIntVal(16),
cty.NumberIntVal(65280),
false,
``,
},
{
cty.StringVal("-FF00"),
cty.NumberIntVal(16),
cty.NumberIntVal(-65280),
false,
``,
},
{
cty.StringVal("00FF00"),
cty.NumberIntVal(16),
cty.NumberIntVal(65280),
false,
``,
},
{
cty.StringVal("-00FF00"),
cty.NumberIntVal(16),
cty.NumberIntVal(-65280),
false,
``,
},
{
cty.StringVal("1011111011101111"),
cty.NumberIntVal(2),
cty.NumberIntVal(48879),
false,
``,
},
{
cty.StringVal("aA"),
cty.NumberIntVal(62),
cty.NumberIntVal(656),
false,
``,
},
{
cty.StringVal("Aa"),
cty.NumberIntVal(62),
cty.NumberIntVal(2242),
false,
``,
},
{
cty.StringVal("999999999999999999999999999999999999999999999999999999999999"),
cty.NumberIntVal(10),
cty.MustParseNumberVal("999999999999999999999999999999999999999999999999999999999999"),
false,
``,
},
{
cty.StringVal("FF"),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
true,
`cannot parse "FF" as a base 10 integer`,
},
{
cty.StringVal("FF").Mark(marks.Sensitive),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
`cannot parse (sensitive value) as a base 10 integer`,
},
{
cty.StringVal("FF").Mark(marks.Sensitive),
cty.NumberIntVal(10).Mark(marks.Sensitive),
cty.UnknownVal(cty.Number),
`cannot parse (sensitive value) as a base (sensitive value) integer`,
},
{
cty.StringVal("00FF"),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
true,
`cannot parse "00FF" as a base 10 integer`,
},
{
cty.StringVal("-00FF"),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
true,
`cannot parse "-00FF" as a base 10 integer`,
},
{
cty.NumberIntVal(2),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
true,
`first argument must be a string, not number`,
},
{
cty.StringVal("1"),
cty.NumberIntVal(63),
cty.UnknownVal(cty.Number),
true,
`base must be a whole number between 2 and 62 inclusive`,
},
{
cty.StringVal("1"),
cty.NumberIntVal(-1),
cty.UnknownVal(cty.Number),
true,
`base must be a whole number between 2 and 62 inclusive`,
},
{
cty.StringVal("1"),
cty.NumberIntVal(1),
cty.UnknownVal(cty.Number),
true,
`base must be a whole number between 2 and 62 inclusive`,
},
{
cty.StringVal("1"),
cty.NumberIntVal(0),
cty.UnknownVal(cty.Number),
true,
`base must be a whole number between 2 and 62 inclusive`,
},
{
cty.StringVal("1.2"),
cty.NumberIntVal(10),
cty.UnknownVal(cty.Number),
true,
`cannot parse "1.2" as a base 10 integer`,
},
}
@ -327,10 +364,13 @@ func TestParseInt(t *testing.T) {
t.Run(fmt.Sprintf("parseint(%#v, %#v)", test.Num, test.Base), func(t *testing.T) {
got, err := ParseInt(test.Num, test.Base)
if test.Err {
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)

View File

@ -0,0 +1,20 @@
package funcs
import (
"fmt"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
func redactIfSensitive(value interface{}, markses ...cty.ValueMarks) string {
if marks.Has(cty.DynamicVal.WithMarks(markses...), marks.Sensitive) {
return "(sensitive value)"
}
switch v := value.(type) {
case string:
return fmt.Sprintf("%q", v)
default:
return fmt.Sprintf("%v", v)
}
}

View File

@ -0,0 +1,51 @@
package funcs
import (
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
func TestRedactIfSensitive(t *testing.T) {
testCases := map[string]struct {
value interface{}
marks []cty.ValueMarks
want string
}{
"sensitive string": {
value: "foo",
marks: []cty.ValueMarks{cty.NewValueMarks(marks.Sensitive)},
want: "(sensitive value)",
},
"raw non-sensitive string": {
value: "foo",
marks: []cty.ValueMarks{cty.NewValueMarks(marks.Raw)},
want: `"foo"`,
},
"raw sensitive string": {
value: "foo",
marks: []cty.ValueMarks{cty.NewValueMarks(marks.Raw), cty.NewValueMarks(marks.Sensitive)},
want: "(sensitive value)",
},
"sensitive number": {
value: 12345,
marks: []cty.ValueMarks{cty.NewValueMarks(marks.Sensitive)},
want: "(sensitive value)",
},
"non-sensitive number": {
value: 12345,
marks: []cty.ValueMarks{},
want: "12345",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got := redactIfSensitive(tc.value, tc.marks...)
if got != tc.want {
t.Errorf("wrong result, got %v, want %v", got, tc.want)
}
})
}
}

View File