Skip to content

Commit

Permalink
fix(misconf): escape all special sequences (#7558)
Browse files Browse the repository at this point in the history
Signed-off-by: nikpivkin <[email protected]>
  • Loading branch information
nikpivkin authored Sep 28, 2024
1 parent 9baf658 commit ea0cf03
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 10 deletions.
40 changes: 30 additions & 10 deletions pkg/iac/terraform/resource_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package terraform
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
)
Expand Down Expand Up @@ -106,19 +105,14 @@ func renderPrimitive(val any) string {
func parseStringPrimitive(input string) string {
// we must escape templating
// ref: https://developer.hashicorp.com/terraform/language/expressions/strings#escape-sequences-1
r := regexp.MustCompile(`((\$|\%)\{.+\})`)
ff := r.ReplaceAllStringFunc(input, func(s string) string {
s = strings.Replace(s, "$", "$$", 1)
s = strings.Replace(s, "%", "%%", 1)
return s
})
if strings.Contains(ff, "\n") {
input = escapeSpecialSequences(input)
if strings.Contains(input, "\n") {
return fmt.Sprintf(`<<EOF
%s
EOF
`, ff)
`, input)
}
return fmt.Sprintf("%q", ff)
return fmt.Sprintf("%q", input)
}

func isMapSlice(vars []any) bool {
Expand Down Expand Up @@ -171,3 +165,29 @@ func renderMap(val map[string]any) string {
result = fmt.Sprintf("%s}", result)
return result
}

func escapeSpecialSequences(input string) string {
var sb strings.Builder
sb.Grow(len(input))
for i, r := range input {
if r == '$' || r == '%' {
sb.WriteRune(r)
remain := input[i+1:]

// it's not a special sequence
if remain == "" || remain[0] != '{' {
continue
}

// sequence is already escaped
if i > 0 && rune(input[i-1]) == r {
continue
}

sb.WriteRune(r)
} else {
sb.WriteRune(r)
}
}
return sb.String()
}
62 changes: 62 additions & 0 deletions pkg/iac/terraform/resource_block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package terraform

import (
"testing"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_EscapeSpecialSequences(t *testing.T) {
tests := []struct {
name string
inp string
expected string
}{
{
name: "without special sequences",
inp: `"hello world\\"`,
expected: `"hello world\\"`,
},
{
name: "interpolation",
inp: `"Hello, ${var.name}!"`,
expected: `"Hello, $${var.name}!"`,
},
{
name: "directive",
inp: `"Hello, %{ if true }foo%{ else }bar%{ endif }!"`,
expected: `"Hello, %%{ if true }foo%%{ else }bar%%{ endif }!"`,
},
{
name: "interpolation already escaped",
inp: `"Hello, $${var.name}!"`,
expected: `"Hello, $${var.name}!"`,
},
{
name: "start with special character",
inp: `${var.name}!"`,
expected: `$${var.name}!"`,
},
{
name: "grok pattern",
inp: "# Grok Pattern Template\ngrok_pattern = \"%{TIMESTAMP_ISO8601:time} \\\\[%{NUMBER:pid}\\\\] %{GREEDYDATA:message}\"",
expected: "# Grok Pattern Template\ngrok_pattern = \"%%{TIMESTAMP_ISO8601:time} \\\\[%%{NUMBER:pid}\\\\] %%{GREEDYDATA:message}\"",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := escapeSpecialSequences(tt.inp)
assert.Equal(t, tt.expected, got)

// We make sure that the characters are properly escaped
_, diag := hclsyntax.ParseTemplate([]byte(got), "", hcl.InitialPos)
if diag.HasErrors() {
require.NoError(t, diag)
}
})
}
}

0 comments on commit ea0cf03

Please sign in to comment.