Skip to content

Commit ea0cf03

Browse files
authored
fix(misconf): escape all special sequences (#7558)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
1 parent 9baf658 commit ea0cf03

File tree

2 files changed

+92
-10
lines changed

2 files changed

+92
-10
lines changed

pkg/iac/terraform/resource_block.go

+30-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package terraform
33
import (
44
"bytes"
55
"fmt"
6-
"regexp"
76
"strings"
87
"text/template"
98
)
@@ -106,19 +105,14 @@ func renderPrimitive(val any) string {
106105
func parseStringPrimitive(input string) string {
107106
// we must escape templating
108107
// ref: https://developer.hashicorp.com/terraform/language/expressions/strings#escape-sequences-1
109-
r := regexp.MustCompile(`((\$|\%)\{.+\})`)
110-
ff := r.ReplaceAllStringFunc(input, func(s string) string {
111-
s = strings.Replace(s, "$", "$$", 1)
112-
s = strings.Replace(s, "%", "%%", 1)
113-
return s
114-
})
115-
if strings.Contains(ff, "\n") {
108+
input = escapeSpecialSequences(input)
109+
if strings.Contains(input, "\n") {
116110
return fmt.Sprintf(`<<EOF
117111
%s
118112
EOF
119-
`, ff)
113+
`, input)
120114
}
121-
return fmt.Sprintf("%q", ff)
115+
return fmt.Sprintf("%q", input)
122116
}
123117

124118
func isMapSlice(vars []any) bool {
@@ -171,3 +165,29 @@ func renderMap(val map[string]any) string {
171165
result = fmt.Sprintf("%s}", result)
172166
return result
173167
}
168+
169+
func escapeSpecialSequences(input string) string {
170+
var sb strings.Builder
171+
sb.Grow(len(input))
172+
for i, r := range input {
173+
if r == '$' || r == '%' {
174+
sb.WriteRune(r)
175+
remain := input[i+1:]
176+
177+
// it's not a special sequence
178+
if remain == "" || remain[0] != '{' {
179+
continue
180+
}
181+
182+
// sequence is already escaped
183+
if i > 0 && rune(input[i-1]) == r {
184+
continue
185+
}
186+
187+
sb.WriteRune(r)
188+
} else {
189+
sb.WriteRune(r)
190+
}
191+
}
192+
return sb.String()
193+
}
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package terraform
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/hashicorp/hcl/v2/hclsyntax"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func Test_EscapeSpecialSequences(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
inp string
16+
expected string
17+
}{
18+
{
19+
name: "without special sequences",
20+
inp: `"hello world\\"`,
21+
expected: `"hello world\\"`,
22+
},
23+
{
24+
name: "interpolation",
25+
inp: `"Hello, ${var.name}!"`,
26+
expected: `"Hello, $${var.name}!"`,
27+
},
28+
{
29+
name: "directive",
30+
inp: `"Hello, %{ if true }foo%{ else }bar%{ endif }!"`,
31+
expected: `"Hello, %%{ if true }foo%%{ else }bar%%{ endif }!"`,
32+
},
33+
{
34+
name: "interpolation already escaped",
35+
inp: `"Hello, $${var.name}!"`,
36+
expected: `"Hello, $${var.name}!"`,
37+
},
38+
{
39+
name: "start with special character",
40+
inp: `${var.name}!"`,
41+
expected: `$${var.name}!"`,
42+
},
43+
{
44+
name: "grok pattern",
45+
inp: "# Grok Pattern Template\ngrok_pattern = \"%{TIMESTAMP_ISO8601:time} \\\\[%{NUMBER:pid}\\\\] %{GREEDYDATA:message}\"",
46+
expected: "# Grok Pattern Template\ngrok_pattern = \"%%{TIMESTAMP_ISO8601:time} \\\\[%%{NUMBER:pid}\\\\] %%{GREEDYDATA:message}\"",
47+
},
48+
}
49+
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
got := escapeSpecialSequences(tt.inp)
53+
assert.Equal(t, tt.expected, got)
54+
55+
// We make sure that the characters are properly escaped
56+
_, diag := hclsyntax.ParseTemplate([]byte(got), "", hcl.InitialPos)
57+
if diag.HasErrors() {
58+
require.NoError(t, diag)
59+
}
60+
})
61+
}
62+
}

0 commit comments

Comments
 (0)