Skip to content
Snippets Groups Projects
Unverified Commit 749bbd5d authored by Tuomo Tanskanen's avatar Tuomo Tanskanen Committed by GitHub
Browse files

fix unmarshaling of expanded environment variables with special characters (#3770)


If we expand environment values directly with os.ExpandEnv() over whole
config, we might end up in a situation where the environment variable
has escape characters that break the resulting JSON, and unmarshalling
fails. Instead of expanding the entire config with single call, we
recurse through the config and expand the values in leaves one by one.

Signed-off-by: default avatarTuomo Tanskanen <tuomo.tanskanen@est.tech>
parent e7c0682e
No related branches found
No related tags found
No related merge requests found
...@@ -284,6 +284,27 @@ func getORMBasedSQLStorage(normal, entBased StorageConfig) func() StorageConfig ...@@ -284,6 +284,27 @@ func getORMBasedSQLStorage(normal, entBased StorageConfig) func() StorageConfig
} }
} }
// Recursively expand environment variables in the map to avoid
// issues with JSON special characters and escapes
func expandEnvInMap(m map[string]interface{}) {
for k, v := range m {
switch vt := v.(type) {
case string:
m[k] = os.ExpandEnv(vt)
case map[string]interface{}:
expandEnvInMap(vt)
case []interface{}:
for i, item := range vt {
if itemMap, ok := item.(map[string]interface{}); ok {
expandEnvInMap(itemMap)
} else if itemString, ok := item.(string); ok {
vt[i] = os.ExpandEnv(itemString)
}
}
}
}
}
var storages = map[string]func() StorageConfig{ var storages = map[string]func() StorageConfig{
"etcd": func() StorageConfig { return new(etcd.Etcd) }, "etcd": func() StorageConfig { return new(etcd.Etcd) },
"kubernetes": func() StorageConfig { return new(kubernetes.Config) }, "kubernetes": func() StorageConfig { return new(kubernetes.Config) },
...@@ -312,9 +333,24 @@ func (s *Storage) UnmarshalJSON(b []byte) error { ...@@ -312,9 +333,24 @@ func (s *Storage) UnmarshalJSON(b []byte) error {
if len(store.Config) != 0 { if len(store.Config) != 0 {
data := []byte(store.Config) data := []byte(store.Config)
if featureflags.ExpandEnv.Enabled() { if featureflags.ExpandEnv.Enabled() {
// Caution, we're expanding in the raw JSON/YAML source. This may not be what the admin expects. var rawMap map[string]interface{}
data = []byte(os.ExpandEnv(string(store.Config))) if err := json.Unmarshal(store.Config, &rawMap); err != nil {
return fmt.Errorf("unmarshal config for env expansion: %v", err)
}
// Recursively expand environment variables in the map to avoid
// issues with JSON special characters and escapes
expandEnvInMap(rawMap)
// Marshal the expanded map back to JSON
expandedData, err := json.Marshal(rawMap)
if err != nil {
return fmt.Errorf("marshal expanded config: %v", err)
}
data = expandedData
} }
if err := json.Unmarshal(data, storageConfig); err != nil { if err := json.Unmarshal(data, storageConfig); err != nil {
return fmt.Errorf("parse storage config: %v", err) return fmt.Errorf("parse storage config: %v", err)
} }
...@@ -358,13 +394,29 @@ func (c *Connector) UnmarshalJSON(b []byte) error { ...@@ -358,13 +394,29 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
if len(conn.Config) != 0 { if len(conn.Config) != 0 {
data := []byte(conn.Config) data := []byte(conn.Config)
if featureflags.ExpandEnv.Enabled() { if featureflags.ExpandEnv.Enabled() {
// Caution, we're expanding in the raw JSON/YAML source. This may not be what the admin expects. var rawMap map[string]interface{}
data = []byte(os.ExpandEnv(string(conn.Config))) if err := json.Unmarshal(conn.Config, &rawMap); err != nil {
return fmt.Errorf("unmarshal config for env expansion: %v", err)
}
// Recursively expand environment variables in the map to avoid
// issues with JSON special characters and escapes
expandEnvInMap(rawMap)
// Marshal the expanded map back to JSON
expandedData, err := json.Marshal(rawMap)
if err != nil {
return fmt.Errorf("marshal expanded config: %v", err)
}
data = expandedData
} }
if err := json.Unmarshal(data, connConfig); err != nil { if err := json.Unmarshal(data, connConfig); err != nil {
return fmt.Errorf("parse connector config: %v", err) return fmt.Errorf("parse connector config: %v", err)
} }
} }
*c = Connector{ *c = Connector{
Type: conn.Type, Type: conn.Type,
Name: conn.Name, Name: conn.Name,
......
...@@ -273,7 +273,8 @@ func checkUnmarshalConfigWithEnv(t *testing.T, dexExpandEnv string, wantExpandEn ...@@ -273,7 +273,8 @@ func checkUnmarshalConfigWithEnv(t *testing.T, dexExpandEnv string, wantExpandEn
os.Setenv("DEX_FOO_USER_PASSWORD", "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy") os.Setenv("DEX_FOO_USER_PASSWORD", "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy")
// For os.ExpandEnv ($VAR -> value_of_VAR): // For os.ExpandEnv ($VAR -> value_of_VAR):
os.Setenv("DEX_FOO_POSTGRES_HOST", "10.0.0.1") os.Setenv("DEX_FOO_POSTGRES_HOST", "10.0.0.1")
os.Setenv("DEX_FOO_OIDC_CLIENT_SECRET", "bar") os.Setenv("DEX_FOO_POSTGRES_PASSWORD", `psql"test\pass`)
os.Setenv("DEX_FOO_OIDC_CLIENT_SECRET", `abc"def\ghi`)
if dexExpandEnv != "UNSET" { if dexExpandEnv != "UNSET" {
os.Setenv("DEX_EXPAND_ENV", dexExpandEnv) os.Setenv("DEX_EXPAND_ENV", dexExpandEnv)
} else { } else {
...@@ -288,6 +289,7 @@ storage: ...@@ -288,6 +289,7 @@ storage:
# Env variables are expanded in raw YAML source. # Env variables are expanded in raw YAML source.
# Single quotes work fine, as long as the env variable doesn't contain any. # Single quotes work fine, as long as the env variable doesn't contain any.
host: '$DEX_FOO_POSTGRES_HOST' host: '$DEX_FOO_POSTGRES_HOST'
password: '$DEX_FOO_POSTGRES_PASSWORD'
port: 65432 port: 65432
maxOpenConns: 5 maxOpenConns: 5
maxIdleConns: 3 maxIdleConns: 3
...@@ -350,10 +352,12 @@ logger: ...@@ -350,10 +352,12 @@ logger:
// This is not a valid hostname. It's only used to check whether os.ExpandEnv was applied or not. // This is not a valid hostname. It's only used to check whether os.ExpandEnv was applied or not.
wantPostgresHost := "$DEX_FOO_POSTGRES_HOST" wantPostgresHost := "$DEX_FOO_POSTGRES_HOST"
wantPostgresPassword := "$DEX_FOO_POSTGRES_PASSWORD"
wantOidcClientSecret := "$DEX_FOO_OIDC_CLIENT_SECRET" wantOidcClientSecret := "$DEX_FOO_OIDC_CLIENT_SECRET"
if wantExpandEnv { if wantExpandEnv {
wantPostgresHost = "10.0.0.1" wantPostgresHost = "10.0.0.1"
wantOidcClientSecret = "bar" wantPostgresPassword = `psql"test\pass`
wantOidcClientSecret = `abc"def\ghi`
} }
want := Config{ want := Config{
...@@ -363,6 +367,7 @@ logger: ...@@ -363,6 +367,7 @@ logger:
Config: &sql.Postgres{ Config: &sql.Postgres{
NetworkDB: sql.NetworkDB{ NetworkDB: sql.NetworkDB{
Host: wantPostgresHost, Host: wantPostgresHost,
Password: wantPostgresPassword,
Port: 65432, Port: 65432,
MaxOpenConns: 5, MaxOpenConns: 5,
MaxIdleConns: 3, MaxIdleConns: 3,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment