allow use of literal $ in config.yaml (#2012)
This commit is contained in:
parent
ce60c7b056
commit
47cc60bda9
4 changed files with 193 additions and 1 deletions
|
@ -9,6 +9,7 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csstring"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
|
||||
)
|
||||
|
@ -53,7 +54,7 @@ func NewConfig(configFile string, disableAgent bool, disableAPI bool, quiet bool
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configData := os.ExpandEnv(string(fcontent))
|
||||
configData := csstring.StrictExpand(string(fcontent), os.LookupEnv)
|
||||
cfg := Config{
|
||||
FilePath: &configFile,
|
||||
DisableAgent: disableAgent,
|
||||
|
|
75
pkg/csstring/expand.go
Normal file
75
pkg/csstring/expand.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package csstring
|
||||
|
||||
func seekClosingBracket(s string, i int) int {
|
||||
for ; i < len(s); i++ {
|
||||
if s[i] == '}' {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func seekEndVarname(s string, i int) int {
|
||||
// envvar names are more strict but this is good enough
|
||||
for ; i < len(s); i++ {
|
||||
if (s[i] < 'a' || s[i] > 'z') && (s[i] < 'A' || s[i] > 'Z') && (s[i] < '0' || s[i] > '9') && s[i] != '_' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func replaceVarBracket(s string, i int, mapping func(string) (string, bool)) string {
|
||||
j := seekClosingBracket(s, i+2)
|
||||
if j < 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
if j < len(s) {
|
||||
varName := s[i+2 : j]
|
||||
if val, ok := mapping(varName); ok {
|
||||
s = s[:i] + val + s[j+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func replaceVar(s string, i int, mapping func(string) (string, bool)) string {
|
||||
if s[i+1] == '{' {
|
||||
return replaceVarBracket(s, i, mapping)
|
||||
}
|
||||
|
||||
j := seekEndVarname(s, i+1)
|
||||
if j < 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
if j > i+1 {
|
||||
varName := s[i+1 : j]
|
||||
if val, ok := mapping(varName); ok {
|
||||
s = s[:i] + val + s[j:]
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// StrictExpand replaces ${var} or $var in the string according to the mapping
|
||||
// function, like os.Expand. The difference is that the mapping function
|
||||
// returns a boolean indicating whether the variable was found.
|
||||
// If the variable was not found, the string is not modified.
|
||||
//
|
||||
// Whereas os.ExpandEnv uses os.Getenv, here we can use os.LookupEnv
|
||||
// to distinguish between an empty variable and an undefined one.
|
||||
func StrictExpand(s string, mapping func(string) (string, bool)) string {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '$' {
|
||||
s = replaceVar(s, i, mapping)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
98
pkg/csstring/expand_test.go
Normal file
98
pkg/csstring/expand_test.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package csstring_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csstring"
|
||||
)
|
||||
|
||||
func TestStrictExpand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testenv := func(key string) (string, bool) {
|
||||
switch key {
|
||||
case "USER":
|
||||
return "testuser", true
|
||||
case "HOME":
|
||||
return "/home/testuser", true
|
||||
case "empty":
|
||||
return "", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
home, _ := testenv("HOME")
|
||||
user, _ := testenv("USER")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "$HOME",
|
||||
expected: home,
|
||||
},
|
||||
{
|
||||
input: "${USER}",
|
||||
expected: user,
|
||||
},
|
||||
{
|
||||
input: "Hello, $USER!",
|
||||
expected: fmt.Sprintf("Hello, %s!", user),
|
||||
},
|
||||
{
|
||||
input: "My home directory is ${HOME}",
|
||||
expected: fmt.Sprintf("My home directory is %s", home),
|
||||
},
|
||||
{
|
||||
input: "This is a $SINGLE_VAR string with ${HOME}",
|
||||
expected: fmt.Sprintf("This is a $SINGLE_VAR string with %s", home),
|
||||
},
|
||||
{
|
||||
input: "This is a $SINGLE_VAR string with $HOME",
|
||||
expected: fmt.Sprintf("This is a $SINGLE_VAR string with %s", home),
|
||||
},
|
||||
{
|
||||
input: "This variable does not exist: $NON_EXISTENT_VAR",
|
||||
expected: "This variable does not exist: $NON_EXISTENT_VAR",
|
||||
},
|
||||
{
|
||||
input: "This is a $MULTI_VAR string with ${HOME} and ${USER}",
|
||||
expected: fmt.Sprintf("This is a $MULTI_VAR string with %s and %s", home, user),
|
||||
},
|
||||
{
|
||||
input: "This is a ${MULTI_VAR} string with $HOME and $USER",
|
||||
expected: fmt.Sprintf("This is a ${MULTI_VAR} string with %s and %s", home, user),
|
||||
},
|
||||
{
|
||||
input: "This is a plain string with no variables",
|
||||
expected: "This is a plain string with no variables",
|
||||
},
|
||||
{
|
||||
input: "$empty",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: "$USER:$empty:$HOME",
|
||||
expected: fmt.Sprintf("%s::%s", user, home),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := csstring.StrictExpand(tc.input, testenv)
|
||||
assert.Equal(t, tc.expected, output)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -279,3 +279,21 @@ declare stderr
|
|||
rune -0 cscli explain --log "Sep 19 18:33:22 scw-d95986 sshd[24347]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=1.2.3.4" --type syslog --crowdsec "$CROWDSEC"
|
||||
assert_output - <"$BATS_TEST_DIRNAME"/testdata/explain/explain-log.txt
|
||||
}
|
||||
|
||||
@test "Allow variable expansion and literal \$ characters in passwords' {
|
||||
export DB_PASSWORD='P@ssw0rd'
|
||||
# shellcheck disable=SC2016
|
||||
config_set '.db_config.password="$DB_PASSWORD"'
|
||||
rune -0 cscli config show --key Config.DbConfig.Password
|
||||
assert_output 'P@ssw0rd'
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
config_set '.db_config.password="$3cureP@ssw0rd"'
|
||||
rune -0 cscli config show --key Config.DbConfig.Password
|
||||
# shellcheck disable=SC2016
|
||||
assert_output '$3cureP@ssw0rd'
|
||||
|
||||
config_set '.db_config.password="P@ssw0rd$"'
|
||||
rune -0 cscli config show --key Config.DbConfig.Password
|
||||
assert_output 'P@ssw0rd$'
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue