Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2eedbfc

Browse files
committed
feat(tfparse): add support for built-in Terraform functions
1 parent 4f438e7 commit 2eedbfc

File tree

5 files changed

+220
-3
lines changed

5 files changed

+220
-3
lines changed

go.mod

+7
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,11 @@ require (
437437
sigs.k8s.io/yaml v1.4.0 // indirect
438438
)
439439

440+
require (
441+
github.com/aquasecurity/trivy-iac v0.8.0
442+
github.com/zclconf/go-cty-yaml v1.0.3
443+
)
444+
440445
require (
441446
github.com/DataDog/datadog-agent/pkg/proto v0.58.0 // indirect
442447
github.com/DataDog/datadog-agent/pkg/trace v0.58.0 // indirect
@@ -445,6 +450,8 @@ require (
445450
github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 // indirect
446451
github.com/DataDog/go-sqllexer v0.0.14 // indirect
447452
github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect
453+
github.com/apparentlymart/go-cidr v1.1.0 // indirect
454+
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
448455
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
449456
github.com/json-iterator/go v1.1.12 // indirect
450457
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,13 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
8888
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
8989
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
9090
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
91+
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
92+
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
9193
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
9294
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
9395
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
96+
github.com/aquasecurity/trivy-iac v0.8.0 h1:NKFhk/BTwQ0jIh4t74V8+6UIGUvPlaxO9HPlSMQi3fo=
97+
github.com/aquasecurity/trivy-iac v0.8.0/go.mod h1:ARiMeNqcaVWOXJmp8hmtMnNm/Jd836IOmDBUW5r4KEk=
9498
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
9599
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
96100
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
@@ -167,6 +171,8 @@ github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
167171
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
168172
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
169173
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
174+
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
175+
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
170176
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
171177
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
172178
github.com/bramvdbogaerde/go-scp v1.5.0 h1:a9BinAjTfQh273eh7vd3qUgmBC+bx+3TRDtkZWmIpzM=
@@ -965,6 +971,8 @@ github.com/zclconf/go-cty v1.16.0 h1:xPKEhst+BW5D0wxebMZkxgapvOE/dw7bFTlgSc9nD6w
965971
github.com/zclconf/go-cty v1.16.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
966972
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
967973
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
974+
github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc=
975+
github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
968976
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
969977
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
970978
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package tfparse
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/aquasecurity/trivy-iac/pkg/scanners/terraform/parser/funcs"
7+
"github.com/hashicorp/hcl/v2/ext/tryfunc"
8+
ctyyaml "github.com/zclconf/go-cty-yaml"
9+
"github.com/zclconf/go-cty/cty"
10+
"github.com/zclconf/go-cty/cty/function"
11+
"github.com/zclconf/go-cty/cty/function/stdlib"
12+
)
13+
14+
// Functions returns a set of functions that are safe to use in the context of
15+
// evaluating Terraform expressions without any ability to reference local files.
16+
// Functions that refer to file operations are replaced with stubs that return a
17+
// descriptive error to the user.
18+
func Functions() map[string]function.Function {
19+
return allFunctions
20+
}
21+
22+
var (
23+
// Adapted from github.com/aquasecurity/[email protected]/pkg/scanners/terraform/parser/functions.go
24+
// We cannot support all available functions here, as the result of reading a file will be different
25+
// depending on the execution environment.
26+
safeFunctions = map[string]function.Function{
27+
"abs": stdlib.AbsoluteFunc,
28+
"basename": funcs.BasenameFunc,
29+
"base64decode": funcs.Base64DecodeFunc,
30+
"base64encode": funcs.Base64EncodeFunc,
31+
"base64gzip": funcs.Base64GzipFunc,
32+
"base64sha256": funcs.Base64Sha256Func,
33+
"base64sha512": funcs.Base64Sha512Func,
34+
"bcrypt": funcs.BcryptFunc,
35+
"can": tryfunc.CanFunc,
36+
"ceil": stdlib.CeilFunc,
37+
"chomp": stdlib.ChompFunc,
38+
"cidrhost": funcs.CidrHostFunc,
39+
"cidrnetmask": funcs.CidrNetmaskFunc,
40+
"cidrsubnet": funcs.CidrSubnetFunc,
41+
"cidrsubnets": funcs.CidrSubnetsFunc,
42+
"coalesce": funcs.CoalesceFunc,
43+
"coalescelist": stdlib.CoalesceListFunc,
44+
"compact": stdlib.CompactFunc,
45+
"concat": stdlib.ConcatFunc,
46+
"contains": stdlib.ContainsFunc,
47+
"csvdecode": stdlib.CSVDecodeFunc,
48+
"dirname": funcs.DirnameFunc,
49+
"distinct": stdlib.DistinctFunc,
50+
"element": stdlib.ElementFunc,
51+
"chunklist": stdlib.ChunklistFunc,
52+
"flatten": stdlib.FlattenFunc,
53+
"floor": stdlib.FloorFunc,
54+
"format": stdlib.FormatFunc,
55+
"formatdate": stdlib.FormatDateFunc,
56+
"formatlist": stdlib.FormatListFunc,
57+
"indent": stdlib.IndentFunc,
58+
"index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible
59+
"join": stdlib.JoinFunc,
60+
"jsondecode": stdlib.JSONDecodeFunc,
61+
"jsonencode": stdlib.JSONEncodeFunc,
62+
"keys": stdlib.KeysFunc,
63+
"length": funcs.LengthFunc,
64+
"list": funcs.ListFunc,
65+
"log": stdlib.LogFunc,
66+
"lookup": funcs.LookupFunc,
67+
"lower": stdlib.LowerFunc,
68+
"map": funcs.MapFunc,
69+
"matchkeys": funcs.MatchkeysFunc,
70+
"max": stdlib.MaxFunc,
71+
"md5": funcs.Md5Func,
72+
"merge": stdlib.MergeFunc,
73+
"min": stdlib.MinFunc,
74+
"parseint": stdlib.ParseIntFunc,
75+
"pow": stdlib.PowFunc,
76+
"range": stdlib.RangeFunc,
77+
"regex": stdlib.RegexFunc,
78+
"regexall": stdlib.RegexAllFunc,
79+
"replace": funcs.ReplaceFunc,
80+
"reverse": stdlib.ReverseListFunc,
81+
"rsadecrypt": funcs.RsaDecryptFunc,
82+
"setintersection": stdlib.SetIntersectionFunc,
83+
"setproduct": stdlib.SetProductFunc,
84+
"setsubtract": stdlib.SetSubtractFunc,
85+
"setunion": stdlib.SetUnionFunc,
86+
"sha1": funcs.Sha1Func,
87+
"sha256": funcs.Sha256Func,
88+
"sha512": funcs.Sha512Func,
89+
"signum": stdlib.SignumFunc,
90+
"slice": stdlib.SliceFunc,
91+
"sort": stdlib.SortFunc,
92+
"split": stdlib.SplitFunc,
93+
"strrev": stdlib.ReverseFunc,
94+
"substr": stdlib.SubstrFunc,
95+
"timestamp": funcs.TimestampFunc,
96+
"timeadd": stdlib.TimeAddFunc,
97+
"title": stdlib.TitleFunc,
98+
"tostring": funcs.MakeToFunc(cty.String),
99+
"tonumber": funcs.MakeToFunc(cty.Number),
100+
"tobool": funcs.MakeToFunc(cty.Bool),
101+
"toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)),
102+
"tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)),
103+
"tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
104+
"transpose": funcs.TransposeFunc,
105+
"trim": stdlib.TrimFunc,
106+
"trimprefix": stdlib.TrimPrefixFunc,
107+
"trimspace": stdlib.TrimSpaceFunc,
108+
"trimsuffix": stdlib.TrimSuffixFunc,
109+
"try": tryfunc.TryFunc,
110+
"upper": stdlib.UpperFunc,
111+
"urlencode": funcs.URLEncodeFunc,
112+
"uuid": funcs.UUIDFunc,
113+
"uuidv5": funcs.UUIDV5Func,
114+
"values": stdlib.ValuesFunc,
115+
"yamldecode": ctyyaml.YAMLDecodeFunc,
116+
"yamlencode": ctyyaml.YAMLEncodeFunc,
117+
"zipmap": stdlib.ZipmapFunc,
118+
}
119+
120+
// the below functions are not safe for usage in the context of tfparse, as their return
121+
// values may change depending on the underlying filesystem.
122+
unsafeFileFunctions = map[string]function.Function{
123+
"abspath": makeStubFunction("abspath", cty.String, function.Parameter{Name: "path", Type: cty.String}),
124+
"file": makeStubFunction("file", cty.String, function.Parameter{Name: "path", Type: cty.String}),
125+
"fileexists": makeStubFunction("fileexists", cty.String, function.Parameter{Name: "path", Type: cty.String}),
126+
"fileset": makeStubFunction("fileset", cty.String, function.Parameter{Name: "path", Type: cty.String}, function.Parameter{Name: "pattern", Type: cty.String}),
127+
"filebase64": makeStubFunction("filebase64", cty.String, function.Parameter{Name: "path", Type: cty.String}, function.Parameter{Name: "pattern", Type: cty.String}),
128+
"filebase64sha256": makeStubFunction("filebase64sha256", cty.String, function.Parameter{Name: "path", Type: cty.String}),
129+
"filebase64sha512": makeStubFunction("filebase64sha512", cty.String, function.Parameter{Name: "path", Type: cty.String}),
130+
"filemd5": makeStubFunction("filemd5", cty.String, function.Parameter{Name: "path", Type: cty.String}),
131+
"filesha1": makeStubFunction("filesha1", cty.String, function.Parameter{Name: "path", Type: cty.String}),
132+
"filesha256": makeStubFunction("filesha256", cty.String, function.Parameter{Name: "path", Type: cty.String}),
133+
"filesha512": makeStubFunction("filesha512", cty.String, function.Parameter{Name: "path", Type: cty.String}),
134+
"pathexpand": makeStubFunction("pathexpand", cty.String, function.Parameter{Name: "path", Type: cty.String})}
135+
136+
allFunctions = mergeMaps(safeFunctions, unsafeFileFunctions)
137+
)
138+
139+
// mergeMaps returns a new map which is the result of merging each key and value
140+
// of all maps in ms, in order. Successive maps may override values of previous
141+
// maps.
142+
func mergeMaps[K, V comparable](ms ...map[K]V) map[K]V {
143+
merged := make(map[K]V)
144+
for _, m := range ms {
145+
for k, v := range m {
146+
merged[k] = v
147+
}
148+
}
149+
return merged
150+
}
151+
152+
// makeStubFunction returns a function.Function with the required return type and parameters
153+
// that will always return an unknown type and an error.
154+
func makeStubFunction(name string, returnType cty.Type, params ...function.Parameter) function.Function {
155+
var spec function.Spec
156+
spec.Params = params
157+
spec.Type = function.StaticReturnType(returnType)
158+
spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) {
159+
return cty.UnknownVal(returnType), fmt.Errorf("function %q may not be used here", name)
160+
}
161+
return function.New(&spec)
162+
}

provisioner/terraform/tfparse/tfparse.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ func BuildEvalContext(vars map[string]string, params map[string]string) *hcl.Eva
477477
// The default function map for Terraform is not exposed, so we would essentially
478478
// have to re-implement or copy the entire map or a subset thereof.
479479
// ref: https://github.com/hashicorp/terraform/blob/e044e569c5bc81f82e9a4d7891f37c6fbb0a8a10/internal/lang/functions.go#L54
480-
Functions: nil,
480+
Functions: Functions(),
481481
}
482482
if len(varDefaultsM) != 0 {
483483
evalCtx.Variables["var"] = cty.MapVal(varDefaultsM)

provisioner/terraform/tfparse/tfparse_test.go

+42-2
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,52 @@ func Test_WorkspaceTagDefaultsFromFile(t *testing.T) {
416416
expectError: `There is no variable named "foo_bar"`,
417417
},
418418
{
419-
name: "main.tf with functions in workspace tags",
419+
name: "main.tf with allowed functions in workspace tags",
420420
files: map[string]string{
421421
"main.tf": `
422422
provider "foo" {}
423423
resource "foo_bar" "baz" {
424424
name = "foobar"
425425
}
426+
locals {
427+
some_path = pathexpand("file.txt")
428+
}
429+
variable "region" {
430+
type = string
431+
default = "us"
432+
}
433+
data "coder_parameter" "unrelated" {
434+
name = "unrelated"
435+
type = "list(string)"
436+
default = jsonencode(["a", "b"])
437+
}
438+
data "coder_parameter" "az" {
439+
name = "az"
440+
type = "string"
441+
default = "a"
442+
}
443+
data "coder_workspace_tags" "tags" {
444+
tags = {
445+
"platform" = "kubernetes",
446+
"cluster" = "${"devel"}${"opers"}"
447+
"region" = try(split(".", var.region)[1], "placeholder")
448+
"az" = try(split(".", data.coder_parameter.az.value)[1], "placeholder")
449+
}
450+
}`,
451+
},
452+
expectTags: map[string]string{"platform": "kubernetes", "cluster": "developers", "region": "placeholder", "az": "placeholder"},
453+
},
454+
{
455+
name: "main.tf with disallowed functions in workspace tags",
456+
files: map[string]string{
457+
"main.tf": `
458+
provider "foo" {}
459+
resource "foo_bar" "baz" {
460+
name = "foobar"
461+
}
462+
locals {
463+
some_path = pathexpand("file.txt")
464+
}
426465
variable "region" {
427466
type = string
428467
default = "region.us"
@@ -443,11 +482,12 @@ func Test_WorkspaceTagDefaultsFromFile(t *testing.T) {
443482
"cluster" = "${"devel"}${"opers"}"
444483
"region" = try(split(".", var.region)[1], "placeholder")
445484
"az" = try(split(".", data.coder_parameter.az.value)[1], "placeholder")
485+
"some_path" = pathexpand("file.txt")
446486
}
447487
}`,
448488
},
449489
expectTags: nil,
450-
expectError: `Function calls not allowed; Functions may not be called here.`,
490+
expectError: `function "pathexpand" may not be used here`,
451491
},
452492
{
453493
name: "supported types",

0 commit comments

Comments
 (0)