Files
coder/provisioner/terraform/tfparse/funcs.go
Cian Johnston 4ba0b39f03 feat(provisioner/terraform/tfparse): add support for built-in Terraform functions (#16183)
Relates to https://github.com/coder/coder/issues/15977

Adds support for some functions in `tfparse` (only functions that do not
reference local files).
NOTE: for now, I'm importing trivy-iac. If we prefer to avoid a little
dependency, I can do a little copying instead.
2025-01-20 11:03:57 +00:00

163 lines
7.4 KiB
Go

package tfparse
import (
"github.com/aquasecurity/trivy-iac/pkg/scanners/terraform/parser/funcs"
"github.com/hashicorp/hcl/v2/ext/tryfunc"
ctyyaml "github.com/zclconf/go-cty-yaml"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
"golang.org/x/xerrors"
)
// Functions returns a set of functions that are safe to use in the context of
// evaluating Terraform expressions without any ability to reference local files.
// Functions that refer to file operations are replaced with stubs that return a
// descriptive error to the user.
func Functions() map[string]function.Function {
return allFunctions
}
var (
// Adapted from github.com/aquasecurity/trivy-iac@v0.8.0/pkg/scanners/terraform/parser/functions.go
// We cannot support all available functions here, as the result of reading a file will be different
// depending on the execution environment.
safeFunctions = map[string]function.Function{
"abs": stdlib.AbsoluteFunc,
"basename": funcs.BasenameFunc,
"base64decode": funcs.Base64DecodeFunc,
"base64encode": funcs.Base64EncodeFunc,
"base64gzip": funcs.Base64GzipFunc,
"base64sha256": funcs.Base64Sha256Func,
"base64sha512": funcs.Base64Sha512Func,
"bcrypt": funcs.BcryptFunc,
"can": tryfunc.CanFunc,
"ceil": stdlib.CeilFunc,
"chomp": stdlib.ChompFunc,
"cidrhost": funcs.CidrHostFunc,
"cidrnetmask": funcs.CidrNetmaskFunc,
"cidrsubnet": funcs.CidrSubnetFunc,
"cidrsubnets": funcs.CidrSubnetsFunc,
"coalesce": funcs.CoalesceFunc,
"coalescelist": stdlib.CoalesceListFunc,
"compact": stdlib.CompactFunc,
"concat": stdlib.ConcatFunc,
"contains": stdlib.ContainsFunc,
"csvdecode": stdlib.CSVDecodeFunc,
"dirname": funcs.DirnameFunc,
"distinct": stdlib.DistinctFunc,
"element": stdlib.ElementFunc,
"chunklist": stdlib.ChunklistFunc,
"flatten": stdlib.FlattenFunc,
"floor": stdlib.FloorFunc,
"format": stdlib.FormatFunc,
"formatdate": stdlib.FormatDateFunc,
"formatlist": stdlib.FormatListFunc,
"indent": stdlib.IndentFunc,
"index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible
"join": stdlib.JoinFunc,
"jsondecode": stdlib.JSONDecodeFunc,
"jsonencode": stdlib.JSONEncodeFunc,
"keys": stdlib.KeysFunc,
"length": funcs.LengthFunc,
"list": funcs.ListFunc,
"log": stdlib.LogFunc,
"lookup": funcs.LookupFunc,
"lower": stdlib.LowerFunc,
"map": funcs.MapFunc,
"matchkeys": funcs.MatchkeysFunc,
"max": stdlib.MaxFunc,
"md5": funcs.Md5Func,
"merge": stdlib.MergeFunc,
"min": stdlib.MinFunc,
"parseint": stdlib.ParseIntFunc,
"pow": stdlib.PowFunc,
"range": stdlib.RangeFunc,
"regex": stdlib.RegexFunc,
"regexall": stdlib.RegexAllFunc,
"replace": funcs.ReplaceFunc,
"reverse": stdlib.ReverseListFunc,
"rsadecrypt": funcs.RsaDecryptFunc,
"setintersection": stdlib.SetIntersectionFunc,
"setproduct": stdlib.SetProductFunc,
"setsubtract": stdlib.SetSubtractFunc,
"setunion": stdlib.SetUnionFunc,
"sha1": funcs.Sha1Func,
"sha256": funcs.Sha256Func,
"sha512": funcs.Sha512Func,
"signum": stdlib.SignumFunc,
"slice": stdlib.SliceFunc,
"sort": stdlib.SortFunc,
"split": stdlib.SplitFunc,
"strrev": stdlib.ReverseFunc,
"substr": stdlib.SubstrFunc,
"timestamp": funcs.TimestampFunc,
"timeadd": stdlib.TimeAddFunc,
"title": stdlib.TitleFunc,
"tostring": funcs.MakeToFunc(cty.String),
"tonumber": funcs.MakeToFunc(cty.Number),
"tobool": funcs.MakeToFunc(cty.Bool),
"toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)),
"tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)),
"tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
"transpose": funcs.TransposeFunc,
"trim": stdlib.TrimFunc,
"trimprefix": stdlib.TrimPrefixFunc,
"trimspace": stdlib.TrimSpaceFunc,
"trimsuffix": stdlib.TrimSuffixFunc,
"try": tryfunc.TryFunc,
"upper": stdlib.UpperFunc,
"urlencode": funcs.URLEncodeFunc,
"uuid": funcs.UUIDFunc,
"uuidv5": funcs.UUIDV5Func,
"values": stdlib.ValuesFunc,
"yamldecode": ctyyaml.YAMLDecodeFunc,
"yamlencode": ctyyaml.YAMLEncodeFunc,
"zipmap": stdlib.ZipmapFunc,
}
// the below functions are not safe for usage in the context of tfparse, as their return
// values may change depending on the underlying filesystem.
stubFileFunctions = map[string]function.Function{
"abspath": makeStubFunction("abspath", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"file": makeStubFunction("file", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"fileexists": makeStubFunction("fileexists", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"fileset": makeStubFunction("fileset", cty.String, function.Parameter{Name: "path", Type: cty.String}, function.Parameter{Name: "pattern", Type: cty.String}),
"filebase64": makeStubFunction("filebase64", cty.String, function.Parameter{Name: "path", Type: cty.String}, function.Parameter{Name: "pattern", Type: cty.String}),
"filebase64sha256": makeStubFunction("filebase64sha256", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"filebase64sha512": makeStubFunction("filebase64sha512", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"filemd5": makeStubFunction("filemd5", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"filesha1": makeStubFunction("filesha1", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"filesha256": makeStubFunction("filesha256", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"filesha512": makeStubFunction("filesha512", cty.String, function.Parameter{Name: "path", Type: cty.String}),
"pathexpand": makeStubFunction("pathexpand", cty.String, function.Parameter{Name: "path", Type: cty.String}),
}
allFunctions = mergeMaps(safeFunctions, stubFileFunctions)
)
// mergeMaps returns a new map which is the result of merging each key and value
// of all maps in ms, in order. Successive maps may override values of previous
// maps.
func mergeMaps[K, V comparable](ms ...map[K]V) map[K]V {
merged := make(map[K]V)
for _, m := range ms {
for k, v := range m {
merged[k] = v
}
}
return merged
}
// makeStubFunction returns a function.Function with the required return type and parameters
// that will always return an unknown type and an error.
func makeStubFunction(name string, returnType cty.Type, params ...function.Parameter) function.Function {
var spec function.Spec
spec.Params = params
spec.Type = function.StaticReturnType(returnType)
spec.Impl = func(_ []cty.Value, _ cty.Type) (cty.Value, error) {
return cty.UnknownVal(returnType), xerrors.Errorf("function %q may not be used here", name)
}
return function.New(&spec)
}