From 9c11a6e58ebd9b0cf3353793b0171a897c42555d Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Mon, 20 Jul 2026 13:08:16 +0800 Subject: [PATCH 1/2] fix(cli): quote nginx_config.envs entries so values with spaces work The `env` directive parameters were rendered verbatim, so an entry like `TEST=a b` produced `env TEST=a b;` which nginx tokenizes into two arguments and rejects with `invalid number of arguments in "env" directive`. Render each entry as a double-quoted parameter and escape embedded `\` and `"` where all envs sources converge. Reject control characters at the CLI schema level. Fixes #11467 --- apisix/cli/ngx_tpl.lua | 2 +- apisix/cli/ops.lua | 9 +++++ apisix/cli/schema.lua | 4 +++ t/cli/test_kubernetes.sh | 18 +++++----- t/cli/test_main.sh | 76 ++++++++++++++++++++++++++++++++++------ t/cli/test_standalone.sh | 2 +- 6 files changed, 89 insertions(+), 22 deletions(-) diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua index ce4cf8020e24..be78bfd70025 100644 --- a/apisix/cli/ngx_tpl.lua +++ b/apisix/cli/ngx_tpl.lua @@ -57,7 +57,7 @@ env GCP_SERVICE_ACCOUNT; {% if envs then %} {% for _, name in ipairs(envs) do %} -env {*name*}; +env "{*name*}"; {% end %} {% end %} diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index d514de4a8c45..9c8802a82487 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -911,6 +911,15 @@ Please modify "admin_key" in conf/config.yaml . sys_conf["discovery_shared_dicts"]["consul"] = consul_conf.shared_size or "64m" end + -- env entries are rendered as double-quoted nginx directive parameters + -- (`env "{*name*}";` in ngx_tpl.lua), so embedded `\` and `"` must be + -- escaped; nginx unescapes them when reading the quoted token + if sys_conf["envs"] then + for i, cfg_env in ipairs(sys_conf["envs"]) do + sys_conf["envs"][i] = cfg_env:gsub('[\\"]', '\\%0') + end + end + -- fix up lua path sys_conf["extra_lua_path"] = get_lua_path(yaml_conf.apisix.extra_lua_path) sys_conf["extra_lua_cpath"] = get_lua_path(yaml_conf.apisix.extra_lua_cpath) diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua index b57b30fed3ce..3e3e855106c8 100644 --- a/apisix/cli/schema.lua +++ b/apisix/cli/schema.lua @@ -291,6 +291,10 @@ local config_schema = { minItems = 1, items = { type = "string", + -- NUL can never be carried through the C environ and + -- other control chars (newline etc.) have no sane + -- config source; reject early with a clear error + pattern = [[\A[^\x00-\x1f]*\z]], } } }, diff --git a/t/cli/test_kubernetes.sh b/t/cli/test_kubernetes.sh index c74718409a9a..5734bcc47e2b 100755 --- a/t/cli/test_kubernetes.sh +++ b/t/cli/test_kubernetes.sh @@ -30,17 +30,17 @@ discovery: make init -if ! grep "env HOST_ENV" conf/nginx.conf; then +if ! grep 'env "HOST_ENV"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env KUBERNETES_SERVICE_PORT" conf/nginx.conf; then +if ! grep 'env "KUBERNETES_SERVICE_PORT"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env TOKEN_ENV" conf/nginx.conf; then +if ! grep 'env "TOKEN_ENV"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi @@ -70,32 +70,32 @@ discovery: make init -if ! grep "env DEV_HOST" conf/nginx.conf; then +if ! grep 'env "DEV_HOST"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env DEV_PORT" conf/nginx.conf; then +if ! grep 'env "DEV_PORT"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env DEV_TOKEN" conf/nginx.conf; then +if ! grep 'env "DEV_TOKEN"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env PRO_HOST" conf/nginx.conf; then +if ! grep 'env "PRO_HOST"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env PRO_PORT" conf/nginx.conf; then +if ! grep 'env "PRO_PORT"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi -if ! grep "env PRO_TOKEN" conf/nginx.conf; then +if ! grep 'env "PRO_TOKEN"' conf/nginx.conf; then echo "kubernetes discovery env inject failed" exit 1 fi diff --git a/t/cli/test_main.sh b/t/cli/test_main.sh index 4dbd96ae5cfc..0a9dbf1cc1cc 100755 --- a/t/cli/test_main.sh +++ b/t/cli/test_main.sh @@ -226,7 +226,7 @@ nginx_config: make init -grep "env TEST;" conf/nginx.conf > /dev/null +grep 'env "TEST";' conf/nginx.conf > /dev/null if [ ! $? -eq 0 ]; then echo "failed: failed to update env" exit 1 @@ -234,6 +234,60 @@ fi echo "passed: change default env" +# env value with spaces must be rendered as a quoted directive (#11467) +echo ' +nginx_config: + envs: + - TEST=a b +' > conf/config.yaml + +make init + +if ! grep 'env "TEST=a b";' conf/nginx.conf > /dev/null; then + echo "failed: env value with spaces should be quoted" + exit 1 +fi + +mkdir -p logs +if ! openresty -p "$PWD" -c "$PWD/conf/nginx.conf" -t; then + echo "failed: nginx rejects generated conf with quoted env" + exit 1 +fi + +# embedded quote / backslash are escaped +cat > conf/config.yaml <<'EOF' +nginx_config: + envs: + - "TEST=a\"b\\c" +EOF + +make init + +if ! grep -F 'env "TEST=a\"b\\c";' conf/nginx.conf > /dev/null; then + echo "failed: quote/backslash in env value should be escaped" + exit 1 +fi + +if ! openresty -p "$PWD" -c "$PWD/conf/nginx.conf" -t; then + echo "failed: nginx rejects generated conf with escaped env" + exit 1 +fi + +# control characters are rejected at schema level +cat > conf/config.yaml <<'EOF' +nginx_config: + envs: + - "TEST=a\nb" +EOF + +out=$(make init 2>&1 || true) +if ! echo "$out" | grep "failed to validate config"; then + echo "failed: env value with control chars should be rejected" + exit 1 +fi + +echo "passed: env value quoting (#11467)" + # support environment variables echo ' nginx_config: @@ -243,7 +297,7 @@ nginx_config: var_test=TEST FOO=bar make init -if ! grep "env TEST_bar;" conf/nginx.conf > /dev/null; then +if ! grep 'env "TEST_bar";' conf/nginx.conf > /dev/null; then echo "failed: failed to resolve variables" exit 1 fi @@ -328,7 +382,7 @@ nginx_config: var_test=TEST FOO=bar make init -if ! grep "env TEST_bar;" conf/nginx.conf > /dev/null; then +if ! grep 'env "TEST_bar";' conf/nginx.conf > /dev/null; then echo "failed: failed to resolve variables wrapped with whitespace" exit 1 fi @@ -348,7 +402,7 @@ deployment: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if ! grep "env ETCD_HOST;" conf/nginx.conf > /dev/null; then +if ! grep 'env "ETCD_HOST";' conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -369,12 +423,12 @@ nginx_config: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if grep "env ETCD_HOST=.*;" conf/nginx.conf > /dev/null; then +if grep 'env "ETCD_HOST=.*";' conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi -if ! grep "env ETCD_HOST;" conf/nginx.conf > /dev/null; then +if ! grep 'env "ETCD_HOST";' conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -394,12 +448,12 @@ nginx_config: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if grep "env ETCD_HOST;" conf/nginx.conf > /dev/null; then +if grep 'env "ETCD_HOST";' conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi -if ! grep "env ETCD_HOST=1.1.1.1;" conf/nginx.conf > /dev/null; then +if ! grep 'env "ETCD_HOST=1.1.1.1";' conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -414,7 +468,7 @@ tests: make init -if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then +if ! grep 'env "TEST_ENV";' conf/nginx.conf > /dev/null; then echo "failed: should use default value when environment not set" exit 1 fi @@ -426,7 +480,7 @@ tests: make init -if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then +if ! grep 'env "TEST_ENV";' conf/nginx.conf > /dev/null; then echo "failed: should use default value when environment not set" exit 1 fi @@ -438,7 +492,7 @@ tests: TEST_ENV=127.0.0.1 make init -if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then +if ! grep 'env "TEST_ENV";' conf/nginx.conf > /dev/null; then echo "failed: should use environment variable when environment is set" exit 1 fi diff --git a/t/cli/test_standalone.sh b/t/cli/test_standalone.sh index c1e728d7ace9..e366182b1fc8 100755 --- a/t/cli/test_standalone.sh +++ b/t/cli/test_standalone.sh @@ -54,7 +54,7 @@ routes: # check for resolve variables var_test_path=/test make init -if ! grep "env var_test_path;" conf/nginx.conf > /dev/null; then +if ! grep 'env "var_test_path";' conf/nginx.conf > /dev/null; then echo "failed: failed to resolve variables" exit 1 fi From 7e6de78351774f4772f76ec9c077456027a053db Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Mon, 20 Jul 2026 14:08:33 +0800 Subject: [PATCH 2/2] fix(cli): reject control characters in synthesized env entries The config schema validates nginx_config.envs, but kubernetes discovery appends entries afterwards by copying whatever sits between ` ${` and `}` in service.host/port and client.token/token_file. Those never see the schema pattern, and the token_file pattern in particular accepts control characters, so a newline could reach nginx.conf and produce an error pointing at a line the user never wrote. Check the final list instead of trusting the schema alone. --- apisix/cli/ops.lua | 8 ++++++++ t/cli/test_main.sh | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index 9c8802a82487..0f6a4fae6a00 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -914,8 +914,16 @@ Please modify "admin_key" in conf/config.yaml . -- env entries are rendered as double-quoted nginx directive parameters -- (`env "{*name*}";` in ngx_tpl.lua), so embedded `\` and `"` must be -- escaped; nginx unescapes them when reading the quoted token + -- entries synthesized after schema validation (kubernetes discovery copies + -- whatever sits between `${` and `}`) never went through the schema + -- pattern, so re-check them here rather than emitting a conf that nginx + -- rejects with an error pointing at a line the user never wrote if sys_conf["envs"] then for i, cfg_env in ipairs(sys_conf["envs"]) do + if cfg_env:find("%c") then + util.die("invalid environment variable entry: ", + "control characters are not allowed\n") + end sys_conf["envs"][i] = cfg_env:gsub('[\\"]', '\\%0') end end diff --git a/t/cli/test_main.sh b/t/cli/test_main.sh index 0a9dbf1cc1cc..c0e7d90c094c 100755 --- a/t/cli/test_main.sh +++ b/t/cli/test_main.sh @@ -286,6 +286,21 @@ if ! echo "$out" | grep "failed to validate config"; then exit 1 fi +# entries synthesized by kubernetes discovery bypass the config schema, so the +# control-character check must also run on the final list +cat > conf/config.yaml <<'EOF' +discovery: + kubernetes: + client: + token_file: "${A\nB}" +EOF + +out=$(make init 2>&1 || true) +if ! echo "$out" | grep "control characters are not allowed"; then + echo "failed: synthesized env entry with control chars should be rejected" + exit 1 +fi + echo "passed: env value quoting (#11467)" # support environment variables