Skip to content

Commit a937e5f

Browse files
authored
feat(config): Support environment variable replacement syntax. (#343)
Replace placeholder in configuration of the form "${MY_ENV_VAR}" with the actual environment variable. Closes #332 Signed-off-by: Lennard Eijsackers <[email protected]>
1 parent e9d3d53 commit a937e5f

File tree

4 files changed

+75
-0
lines changed

4 files changed

+75
-0
lines changed

config/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package config
22

33
import (
4+
"bytes"
45
"crypto/tls"
56
"fmt"
67
"os"
8+
"regexp"
79
"strings"
810
"time"
911

@@ -1131,6 +1133,9 @@ func LoadFile(filename string) (*Config, error) {
11311133
if err != nil {
11321134
return nil, err
11331135
}
1136+
1137+
content = findAndReplacePlaceholders(content)
1138+
11341139
cfg := &Config{}
11351140
if err := yaml.Unmarshal(content, cfg); err != nil {
11361141
return nil, err
@@ -1162,6 +1167,22 @@ func LoadFile(filename string) (*Config, error) {
11621167
return cfg, nil
11631168
}
11641169

1170+
var envVarRegex = regexp.MustCompile(`\${([a-zA-Z_][a-zA-Z0-9_]*)}`)
1171+
1172+
// findAndReplacePlaceholders finds all environment variables placeholders in the config.
1173+
// Each placeholder is a string like ${VAR_NAME}. They will be replaced with the value of the
1174+
// corresponding environment variable. It returns the new content with replaced placeholders.
1175+
func findAndReplacePlaceholders(content []byte) []byte {
1176+
for _, match := range envVarRegex.FindAllSubmatch(content, -1) {
1177+
envVar := os.Getenv(string(match[1]))
1178+
if envVar != "" {
1179+
content = bytes.ReplaceAll(content, match[0], []byte(envVar))
1180+
}
1181+
}
1182+
1183+
return content
1184+
}
1185+
11651186
func (c Config) checkVulnerabilities() error {
11661187
if c.HackMePlease {
11671188
return nil

config/config_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66
"net"
7+
"os"
78
"testing"
89
"time"
910

@@ -932,3 +933,32 @@ connection_pool:
932933

933934
}
934935
}
936+
937+
func TestConfigReplaceEnvVars(t *testing.T) {
938+
var testCases = []struct {
939+
name string
940+
file string
941+
expectedPassword string
942+
}{
943+
{
944+
"replace env vars with the style of ${}",
945+
"testdata/envvars.simple.yml",
946+
"MyPassword",
947+
},
948+
}
949+
950+
for _, tc := range testCases {
951+
t.Run(tc.name, func(t *testing.T) {
952+
os.Setenv("CHPROXY_PASSWORD", tc.expectedPassword)
953+
954+
cfg, err := LoadFile(tc.file)
955+
if err != nil {
956+
t.Fatalf("unexpected error: %s", err)
957+
}
958+
got := cfg.Users[0].Password
959+
if got != tc.expectedPassword {
960+
t.Fatalf("got password %v; expected to have: %v", got, tc.expectedPassword)
961+
}
962+
})
963+
}
964+
}

config/testdata/envvars.simple.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
server:
2+
http:
3+
listen_addr: ":8080"
4+
allowed_networks: ["127.0.0.1"]
5+
6+
users:
7+
- name: "default"
8+
password: ${CHPROXY_PASSWORD}
9+
to_cluster: "cluster"
10+
to_user: "default"
11+
12+
clusters:
13+
- name: "cluster"
14+
nodes: ["127.0.0.1:8123"]

docs/content/en/configuration/security.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,13 @@ By default `chproxy` tries detecting the most obvious configuration errors such
1313

1414
Special option `hack_me_please: true` may be used for disabling all the security-related checks during config validation (if you are feeling lucky :) ).
1515

16+
For sensitive configuration options, such as user passwords, chproxy supports loading configuration from environment variables. In order to load a configuration variable from a environment variable, a placeholder needs to be put in it's place
17+
in the configuration file. Placeholder are of the form `${ENV_VAR_NAME}`. As an example, to load a user password from the environment variable `MY_PASSWORD` you can use a placeholder as in the following snippet:
18+
19+
```yaml
20+
users:
21+
- name: "default"
22+
password: ${MY_PASSWORD}
23+
```
24+
25+
This will be replaced by the actual environment variable once the configuration is (re)loaded from disk. If the environment variable isn't found the placeholder will remain and won't be replaced.

0 commit comments

Comments
 (0)