add wrappers to verify file function results

Create wrappers to inject function results verification into all the
filesystem function implementations.
pull/37001/head
James Bardin 12 months ago
parent a6ec17cb77
commit f78adc889e

@ -80,8 +80,8 @@ var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.Enc
// MakeFileBase64Sha256Func constructs a function that is like Base64Sha256Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileBase64Sha256Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString)
func MakeFileBase64Sha256Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString, wrap)
}
// Base64Sha512Func constructs a function that computes the SHA256 hash of a given string
@ -90,8 +90,8 @@ var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.Enc
// MakeFileBase64Sha512Func constructs a function that is like Base64Sha512Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileBase64Sha512Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString)
func MakeFileBase64Sha512Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString, wrap)
}
// BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher.
@ -138,8 +138,8 @@ var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString)
// MakeFileMd5Func constructs a function that is like Md5Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileMd5Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString)
func MakeFileMd5Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString, wrap)
}
// RsaDecryptFunc constructs a function that decrypts an RSA-encrypted ciphertext.
@ -198,8 +198,8 @@ var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString)
// MakeFileSha1Func constructs a function that is like Sha1Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileSha1Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString)
func MakeFileSha1Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString, wrap)
}
// Sha256Func contructs a function that computes the SHA256 hash of a given string
@ -208,8 +208,8 @@ var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString)
// MakeFileSha256Func constructs a function that is like Sha256Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileSha256Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString)
func MakeFileSha256Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString, wrap)
}
// Sha512Func contructs a function that computes the SHA512 hash of a given string
@ -218,8 +218,8 @@ var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString)
// MakeFileSha512Func constructs a function that is like Sha512Func but reads the
// contents of a file rather than hashing a given literal string.
func MakeFileSha512Func(baseDir string) function.Function {
return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString)
func MakeFileSha512Func(baseDir string, wrap ImplWrapper) function.Function {
return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString, wrap)
}
func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function {
@ -242,7 +242,7 @@ func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) functi
})
}
func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function {
func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string, wrap ImplWrapper) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
@ -252,7 +252,7 @@ func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte)
},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
Impl: wrap(func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
path := args[0].AsString()
f, err := openFile(baseDir, path)
if err != nil {
@ -267,7 +267,7 @@ func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte)
}
rv := enc(h.Sum(nil))
return cty.StringVal(rv), nil
},
}),
})
}

@ -147,7 +147,7 @@ func TestFileBase64Sha256(t *testing.T) {
},
}
fileSHA256 := MakeFileBase64Sha256Func(".")
fileSHA256 := MakeFileBase64Sha256Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filebase64sha256(%#v)", test.Path), func(t *testing.T) {
@ -228,7 +228,7 @@ func TestFileBase64Sha512(t *testing.T) {
},
}
fileSHA512 := MakeFileBase64Sha512Func(".")
fileSHA512 := MakeFileBase64Sha512Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filebase64sha512(%#v)", test.Path), func(t *testing.T) {
@ -346,7 +346,7 @@ func TestFileMD5(t *testing.T) {
},
}
fileMD5 := MakeFileMd5Func(".")
fileMD5 := MakeFileMd5Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filemd5(%#v)", test.Path), func(t *testing.T) {
@ -503,7 +503,7 @@ func TestFileSHA1(t *testing.T) {
},
}
fileSHA1 := MakeFileSha1Func(".")
fileSHA1 := MakeFileSha1Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filesha1(%#v)", test.Path), func(t *testing.T) {
@ -581,7 +581,7 @@ func TestFileSHA256(t *testing.T) {
},
}
fileSHA256 := MakeFileSha256Func(".")
fileSHA256 := MakeFileSha256Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filesha256(%#v)", test.Path), func(t *testing.T) {
@ -659,7 +659,7 @@ func TestFileSHA512(t *testing.T) {
},
}
fileSHA512 := MakeFileSha512Func(".")
fileSHA512 := MakeFileSha512Func(".", noopWrapper)
for _, test := range tests {
t.Run(fmt.Sprintf("filesha512(%#v)", test.Path), func(t *testing.T) {

@ -24,7 +24,7 @@ import (
// MakeFileFunc constructs a function that takes a file path and returns the
// contents of that file, either directly as a string (where valid UTF-8 is
// required) or as a string containing base64 bytes.
func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
func MakeFileFunc(baseDir string, encBase64 bool, wrap ImplWrapper) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
@ -36,7 +36,7 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
Impl: wrap(func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
if !pathArg.IsKnown() {
@ -60,7 +60,7 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
}
return cty.StringVal(string(src)).WithMarks(pathMarks), nil
}
},
}),
})
}
@ -77,7 +77,7 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
// As a special exception, a referenced template file may not recursively call
// the templatefile function, since that would risk the same file being
// included into itself indefinitely.
func MakeTemplateFileFunc(baseDir string, funcsCb func() (funcs map[string]function.Function, fsFuncs collections.Set[string], templateFuncs collections.Set[string])) function.Function {
func MakeTemplateFileFunc(baseDir string, funcsCb func() (funcs map[string]function.Function, fsFuncs collections.Set[string], templateFuncs collections.Set[string]), wrap ImplWrapper) function.Function {
loadTmpl := func(fn string, marks cty.ValueMarks) (hcl.Expression, cty.ValueMarks, error) {
// We re-use File here to ensure the same filename interpretation
// as it does, along with its other safety checks.
@ -133,7 +133,7 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() (funcs map[string]funct
val, err := renderTmpl(expr, vars)
return val.Type(), err
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
Impl: wrap(func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
vars, varsMarks := args[1].UnmarkDeep()
@ -148,14 +148,13 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() (funcs map[string]funct
}
result, err := renderTmpl(expr, vars)
return result.WithMarks(tmplMarks, varsMarks), err
},
}),
})
}
// MakeFileExistsFunc constructs a function that takes a path
// and determines whether a file exists at that path
func MakeFileExistsFunc(baseDir string) function.Function {
func MakeFileExistsFunc(baseDir string, wrap ImplWrapper) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
@ -167,7 +166,7 @@ func MakeFileExistsFunc(baseDir string) function.Function {
},
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
Impl: wrap(func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
if !pathArg.IsKnown() {
@ -223,13 +222,13 @@ func MakeFileExistsFunc(baseDir string) function.Function {
}
return cty.False, err
},
}),
})
}
// MakeFileSetFunc constructs a function that takes a glob pattern
// and enumerates a file set from that pattern
func MakeFileSetFunc(baseDir string) function.Function {
func MakeFileSetFunc(baseDir string, wrap ImplWrapper) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
@ -247,7 +246,7 @@ func MakeFileSetFunc(baseDir string) function.Function {
},
Type: function.StaticReturnType(cty.Set(cty.String)),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
Impl: wrap(func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
patternArg, patternMarks := args[1].Unmark()
@ -304,7 +303,7 @@ func MakeFileSetFunc(baseDir string) function.Function {
}
return cty.SetVal(matchVals).WithMarks(marks...), nil
},
}),
})
}
@ -416,7 +415,7 @@ func readFileBytes(baseDir, path string, marks cty.ValueMarks) ([]byte, error) {
// directory, so this wrapper takes a base directory string and uses it to
// construct the underlying function before calling it.
func File(baseDir string, path cty.Value) (cty.Value, error) {
fn := MakeFileFunc(baseDir, false)
fn := MakeFileFunc(baseDir, false, noopWrapper)
return fn.Call([]cty.Value{path})
}
@ -426,7 +425,7 @@ func File(baseDir string, path cty.Value) (cty.Value, error) {
// directory, so this wrapper takes a base directory string and uses it to
// construct the underlying function before calling it.
func FileExists(baseDir string, path cty.Value) (cty.Value, error) {
fn := MakeFileExistsFunc(baseDir)
fn := MakeFileExistsFunc(baseDir, noopWrapper)
return fn.Call([]cty.Value{path})
}
@ -436,7 +435,7 @@ func FileExists(baseDir string, path cty.Value) (cty.Value, error) {
// directory, so this wrapper takes a base directory string and uses it to
// construct the underlying function before calling it.
func FileSet(baseDir string, path, pattern cty.Value) (cty.Value, error) {
fn := MakeFileSetFunc(baseDir)
fn := MakeFileSetFunc(baseDir, noopWrapper)
return fn.Call([]cty.Value{path, pattern})
}
@ -448,7 +447,7 @@ func FileSet(baseDir string, path, pattern cty.Value) (cty.Value, error) {
// directory, so this wrapper takes a base directory string and uses it to
// construct the underlying function before calling it.
func FileBase64(baseDir string, path cty.Value) (cty.Value, error) {
fn := MakeFileFunc(baseDir, true)
fn := MakeFileFunc(baseDir, true, noopWrapper)
return fn.Call([]cty.Value{path})
}
@ -482,3 +481,12 @@ func Dirname(path cty.Value) (cty.Value, error) {
func Pathexpand(path cty.Value) (cty.Value, error) {
return PathExpandFunc.Call([]cty.Value{path})
}
// ImplWrapper allows us to pass in a wrapper function to inject behavior into
// function implementations, because we don't have access to the function.Spec
// from the returned function.Function
type ImplWrapper func(function.ImplFunc) function.ImplFunc
func noopWrapper(fn function.ImplFunc) function.ImplFunc {
return fn
}

@ -251,7 +251,7 @@ func TestTemplateFile(t *testing.T) {
funcsFunc := func() (funcTable map[string]function.Function, fsFuncs collections.Set[string], templateFuncs collections.Set[string]) {
return funcs, collections.NewSetCmp[string](), collections.NewSetCmp[string]("templatefile")
}
templateFileFn := MakeTemplateFileFunc(".", funcsFunc)
templateFileFn := MakeTemplateFileFunc(".", funcsFunc, noopWrapper)
funcs["templatefile"] = templateFileFn
funcs["core::templatefile"] = templateFileFn

@ -91,16 +91,16 @@ func (s *Scope) Functions() map[string]function.Function {
"endswith": funcs.EndsWithFunc,
"ephemeralasnull": funcs.EphemeralAsNullFunc,
"chunklist": stdlib.ChunklistFunc,
"file": funcs.MakeFileFunc(s.BaseDir, false),
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
"fileset": funcs.MakeFileSetFunc(s.BaseDir),
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir),
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir),
"filemd5": funcs.MakeFileMd5Func(s.BaseDir),
"filesha1": funcs.MakeFileSha1Func(s.BaseDir),
"filesha256": funcs.MakeFileSha256Func(s.BaseDir),
"filesha512": funcs.MakeFileSha512Func(s.BaseDir),
"file": funcs.MakeFileFunc(s.BaseDir, false, immutableResults("file", s.FunctionResults)),
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir, immutableResults("fileexists", s.FunctionResults)),
"fileset": funcs.MakeFileSetFunc(s.BaseDir, immutableResults("fileset", s.FunctionResults)),
"filebase64": funcs.MakeFileFunc(s.BaseDir, true, immutableResults("filebase64", s.FunctionResults)),
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir, immutableResults("filebase64sha256", s.FunctionResults)),
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir, immutableResults("filebase64sha512", s.FunctionResults)),
"filemd5": funcs.MakeFileMd5Func(s.BaseDir, immutableResults("filemd5", s.FunctionResults)),
"filesha1": funcs.MakeFileSha1Func(s.BaseDir, immutableResults("filesha1", s.FunctionResults)),
"filesha256": funcs.MakeFileSha256Func(s.BaseDir, immutableResults("filesha256", s.FunctionResults)),
"filesha512": funcs.MakeFileSha512Func(s.BaseDir, immutableResults("filesha512", s.FunctionResults)),
"flatten": stdlib.FlattenFunc,
"floor": stdlib.FloorFunc,
"format": stdlib.FormatFunc,
@ -190,7 +190,7 @@ func (s *Scope) Functions() map[string]function.Function {
// overwriting the relevant entries.
return s.funcs, filesystemFunctions, templateFunctions
}
coreFuncs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, funcsFunc)
coreFuncs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, funcsFunc, immutableResults("templatefile", s.FunctionResults))
coreFuncs["templatestring"] = funcs.MakeTemplateStringFunc(funcsFunc)
if s.ConsoleMode {
@ -301,16 +301,16 @@ func baseFunctions(baseDir string) map[string]function.Function {
"element": stdlib.ElementFunc,
"endswith": funcs.EndsWithFunc,
"chunklist": stdlib.ChunklistFunc,
"file": funcs.MakeFileFunc(baseDir, false),
"fileexists": funcs.MakeFileExistsFunc(baseDir),
"fileset": funcs.MakeFileSetFunc(baseDir),
"filebase64": funcs.MakeFileFunc(baseDir, true),
"filebase64sha256": funcs.MakeFileBase64Sha256Func(baseDir),
"filebase64sha512": funcs.MakeFileBase64Sha512Func(baseDir),
"filemd5": funcs.MakeFileMd5Func(baseDir),
"filesha1": funcs.MakeFileSha1Func(baseDir),
"filesha256": funcs.MakeFileSha256Func(baseDir),
"filesha512": funcs.MakeFileSha512Func(baseDir),
"file": funcs.MakeFileFunc(baseDir, false, noopWrapper),
"fileexists": funcs.MakeFileExistsFunc(baseDir, noopWrapper),
"fileset": funcs.MakeFileSetFunc(baseDir, noopWrapper),
"filebase64": funcs.MakeFileFunc(baseDir, true, noopWrapper),
"filebase64sha256": funcs.MakeFileBase64Sha256Func(baseDir, noopWrapper),
"filebase64sha512": funcs.MakeFileBase64Sha512Func(baseDir, noopWrapper),
"filemd5": funcs.MakeFileMd5Func(baseDir, noopWrapper),
"filesha1": funcs.MakeFileSha1Func(baseDir, noopWrapper),
"filesha256": funcs.MakeFileSha256Func(baseDir, noopWrapper),
"filesha512": funcs.MakeFileSha512Func(baseDir, noopWrapper),
"flatten": stdlib.FlattenFunc,
"floor": stdlib.FloorFunc,
"format": stdlib.FormatFunc,
@ -394,7 +394,7 @@ func baseFunctions(baseDir string) map[string]function.Function {
// The templatefile function prevents recursive calls to itself
// by copying this map and overwriting the "templatefile" entry.
return fs, filesystemFunctions, templateFunctions
})
}, noopWrapper)
return fs
}
@ -441,3 +441,33 @@ func (s *Scope) experimentalFunction(experiment experiments.Experiment, fn funct
type ExternalFuncs struct {
Provider map[string]map[string]function.Function
}
// immutableResults is a wrapper for cty function implementations which may
// otherwise not return consistent results because they depends on data outside
// of Terraform. Due to the fact that the cty functions are a concrete type, and
// the implementation is hidden within a private struct field, we need to pass
// along these closures to get the data to the actual call site.
func immutableResults(name string, priorResults *FunctionResults) func(fn function.ImplFunc) function.ImplFunc {
if priorResults == nil {
return func(fn function.ImplFunc) function.ImplFunc {
return fn
}
}
return func(fn function.ImplFunc) function.ImplFunc {
return func(args []cty.Value, retType cty.Type) (cty.Value, error) {
res, err := fn(args, retType)
if err != nil {
return res, err
}
err = priorResults.CheckPrior(name, args, res)
if err != nil {
return cty.UnknownVal(retType), err
}
return res, err
}
}
}
func noopWrapper(fn function.ImplFunc) function.ImplFunc {
return fn
}

Loading…
Cancel
Save