apm-server icon indicating copy to clipboard operation
apm-server copied to clipboard

Special characters in env var used in config causes error "can not convert 'object' into 'string' accessing"

Open carsonip opened this issue 1 year ago • 2 comments

If an env var is used in apm-server.yml, and that the env var contains certain special characters, it may cause an error during parsing, e.g. Error: error unpacking output.elasticsearch for fetching agent config: can not convert 'object' into 'string' accessing 'output.elasticsearch.password' (source:'apm-server.yml')

To reproduce, run test:

func TestPassword(t *testing.T) {
	t.Setenv("FOO", "abc,123")
	initCfgfile(t, `
apm-server:
  host: :8200
output.elasticsearch:
  username: user
  password: "${FOO}"
  `)
	cfg, rawConfig, keystore, err := LoadConfig()
	require.NoError(t, err)
	assert.NotNil(t, cfg)
	assert.NotNil(t, rawConfig)
	assert.NotNil(t, keystore)

	assertConfigEqual(t, map[string]interface{}{
		"apm-server": map[string]interface{}{
			"host": ":8200",
		},
		"path": map[string]interface{}{
			"config": paths.Paths.Config,
			"logs":   paths.Paths.Logs,
			"data":   paths.Paths.Data,
			"home":   paths.Paths.Home,
		},
		"output": map[string]interface{}{
			"elasticsearch": map[string]interface{}{
				"username": "user",
				"password": "abc,123",
			},
		},
	}, rawConfig)

	assertConfigEqual(t, map[string]interface{}{"host": ":8200"}, cfg.APMServer)
	assert.Equal(t, "elasticsearch", cfg.Output.Name())
	assertConfigEqual(t, map[string]interface{}{"username": "user", "password": "abc,123"}, cfg.Output.Config())
}

Test output:

=== RUN   TestPassword
    config_test.go:195: 
        	Error Trace:	/home/carson/projects/apm-server/internal/beatcmd/config_test.go:148
        	            				/home/carson/projects/apm-server/internal/beatcmd/config_test.go:195
        	Error:      	Not equal: 
        	            	expected: map[string]interface {}{"apm-server":map[string]interface {}{"host":":8200"}, "output":map[string]interface {}{"elasticsearch":map[string]interface {}{"password":"abc,123", "username":"user"}}, "path":map[string]interface {}{"config":"/tmp/TestPassword4154751502/001", "data":"/tmp/TestPassword4154751502/001/data", "home":"/tmp/TestPassword4154751502/001", "logs":"/tmp/TestPassword4154751502/001/logs"}}
        	            	actual  : map[string]interface {}{"apm-server":map[string]interface {}{"host":":8200"}, "output":map[string]interface {}{"elasticsearch":map[string]interface {}{"password":[]interface {}{"abc", 0x7b}, "username":"user"}}, "path":map[string]interface {}{"config":"/tmp/TestPassword4154751502/001", "data":"/tmp/TestPassword4154751502/001/data", "home":"/tmp/TestPassword4154751502/001", "logs":"/tmp/TestPassword4154751502/001/logs"}}
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -6,3 +6,6 @@
        	            	   (string) (len=13) "elasticsearch": (map[string]interface {}) (len=2) {
        	            	-   (string) (len=8) "password": (string) (len=7) "abc,123",
        	            	+   (string) (len=8) "password": ([]interface {}) (len=2) {
        	            	+    (string) (len=3) "abc",
        	            	+    (uint64) 123
        	            	+   },
        	            	    (string) (len=8) "username": (string) (len=4) "user"
        	Test:       	TestPassword
    config_test.go:215: 
        	Error Trace:	/home/carson/projects/apm-server/internal/beatcmd/config_test.go:148
        	            				/home/carson/projects/apm-server/internal/beatcmd/config_test.go:215
        	Error:      	Not equal: 
        	            	expected: map[string]interface {}{"password":"abc,123", "username":"user"}
        	            	actual  : map[string]interface {}{"password":[]interface {}{"abc", 0x7b}, "username":"user"}
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1,3 +1,6 @@
        	            	 (map[string]interface {}) (len=2) {
        	            	- (string) (len=8) "password": (string) (len=7) "abc,123",
        	            	+ (string) (len=8) "password": ([]interface {}) (len=2) {
        	            	+  (string) (len=3) "abc",
        	            	+  (uint64) 123
        	            	+ },
        	            	  (string) (len=8) "username": (string) (len=4) "user"
        	Test:       	TestPassword
--- FAIL: TestPassword (0.00s)

This is actually as a feature such that env var could provide lists, see docs, but it is surprising to users who expect this setup to work, until the env var contains certain characters, e.g. a comma in a randomly generated password to set output.elasticsearch.password.

go-ucfg added an IgnoreCommas config to opt-out of this behavior, and Elastic Agent already uses it. However, adopting this by default would be a breaking change for users who rely on this parsing behavior.

Related issue in beats: https://github.com/elastic/beats/issues/22460

carsonip avatar Aug 19 '24 13:08 carsonip

If the IgnoreCommas were supported and enabled, would there still be an option to configure lists or would it be an either or configuration decision?

@carsonip I changed the label from bug to enhancement - while this might not be optimal, it currently works as designed.

simitt avatar Aug 20 '24 06:08 simitt

If the IgnoreCommas were supported and enabled, would there still be an option to configure lists or would it be an either or configuration decision?

I am not aware of other ways to configure a list from env var other than this.

Either FOO="['foo',1]" or FOO="'foo',1" will cause

apm-server:
  host: :8200
output.elasticsearch:
  username: user
  password: "${FOO}"

output.elasticsearch.password to be a list. The argument still stands - this complex object parsing behavior is going to catch unsuspecting users off guard, but setting IgnoreCommas by default would remove the ability to configure lists.

carsonip avatar Aug 20 '24 14:08 carsonip

@axw do you have an opinion on whether this is worth a change in 9.0?

simitt avatar Dec 02 '24 07:12 simitt

I'm inclined to close it as WONTFIX:

  • there's a (now documented) workaround
  • it seems to be a very rarely encountered issue

axw avatar Dec 02 '24 08:12 axw

++ on closing this as I don't think the fix would lead to global improvements.

@carsonip any objections or concerns?

simitt avatar Jan 13 '25 08:01 simitt

++, closing it as WONTFIX.

carsonip avatar Jan 15 '25 13:01 carsonip