This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new a036f1858 fix(basic-auth): reject an empty consumer password (#13884)
a036f1858 is described below
commit a036f1858b4b84d7f23d408dab33e9618c4c32ec
Author: Mohammad Izzraff Janius
<[email protected]>
AuthorDate: Thu Aug 27 17:19:38 2026 +0900
fix(basic-auth): reject an empty consumer password (#13884)
---
apisix/plugins/basic-auth.lua | 17 ++-
docs/en/latest/plugins/basic-auth.md | 2 +-
docs/zh/latest/plugins/basic-auth.md | 2 +-
t/admin/plugins.t | 4 +-
t/plugin/basic-auth.t | 210 ++++++++++++++++++++++++++++++++++-
5 files changed, 228 insertions(+), 7 deletions(-)
diff --git a/apisix/plugins/basic-auth.lua b/apisix/plugins/basic-auth.lua
index 5d3f46a94..ba22c0ab7 100644
--- a/apisix/plugins/basic-auth.lua
+++ b/apisix/plugins/basic-auth.lua
@@ -43,7 +43,7 @@ local consumer_schema = {
title = "work with consumer object",
properties = {
username = { type = "string" },
- password = { type = "string" },
+ password = { type = "string", minLength = 1 },
},
encrypt_fields = {"password"},
required = {"username", "password"},
@@ -148,7 +148,20 @@ local function find_consumer(ctx)
return nil, nil, "Invalid user authorization"
end
- if cur_consumer.auth_conf.password ~= password then
+ -- the schema rejects an empty password on write and on load, but a secret
+ -- reference ($secret:// or $env://) is resolved after validation and can
+ -- yield "": fail closed so such a consumer never authenticates
+ local expected = cur_consumer.auth_conf.password
+ if expected == "" then
+ err = "empty password configured for consumer: " ..
cur_consumer.consumer_name
+ if auth_utils.is_running_under_multi_auth(ctx) then
+ return nil, nil, err
+ end
+ core.log.warn(err)
+ return nil, nil, "Invalid user authorization"
+ end
+
+ if expected ~= password then
return nil, nil, "Invalid user authorization"
end
diff --git a/docs/en/latest/plugins/basic-auth.md
b/docs/en/latest/plugins/basic-auth.md
index a50e99f20..1e5056f8a 100644
--- a/docs/en/latest/plugins/basic-auth.md
+++ b/docs/en/latest/plugins/basic-auth.md
@@ -48,7 +48,7 @@ For Consumer/Credentials:
| Name | Type | Required | Default | Valid values | Description |
|------|------|----------|---------|--------------|-------------|
| username | string | True | | | Unique basic auth username for a Consumer. |
-| password | string | True | | | Basic auth password for the Consumer. The
password is encrypted with AES before being stored in etcd. You can also store
it in an environment variable and reference it using the `env://` prefix, or in
a secret manager such as HashiCorp Vault's KV secrets engine, and reference it
using the `secret://` prefix. |
+| password | string | True | | non-empty string | Basic auth password for the
Consumer. The password is encrypted with AES before being stored in etcd. You
can also store it in an environment variable and reference it using the
`env://` prefix, or in a secret manager such as HashiCorp Vault's KV secrets
engine, and reference it using the `secret://` prefix. |
For Route:
diff --git a/docs/zh/latest/plugins/basic-auth.md
b/docs/zh/latest/plugins/basic-auth.md
index d554b55e2..cc499d926 100644
--- a/docs/zh/latest/plugins/basic-auth.md
+++ b/docs/zh/latest/plugins/basic-auth.md
@@ -48,7 +48,7 @@ import TabItem from '@theme/TabItem';
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|------|------|--------|--------|--------|------|
| username | string | 是 | | | 消费者的唯一基本认证用户名。 |
-| password | string | 是 | | | 消费者的基本认证密码。密码在存储到 etcd 之前会使用 AES
加密。你也可以将其存储在环境变量中并使用 `env://` 前缀引用,或存储在 HashiCorp Vault 等密钥管理器中并使用 `secret://`
前缀引用。 |
+| password | string | 是 | | 非空字符串 | 消费者的基本认证密码。密码在存储到 etcd 之前会使用 AES
加密。你也可以将其存储在环境变量中并使用 `env://` 前缀引用,或存储在 HashiCorp Vault 等密钥管理器中并使用 `secret://`
前缀引用。 |
Route 端:
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index ef70e46d9..b5d96031e 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -264,7 +264,7 @@ plugins:
ngx.HTTP_GET,
nil,
[[
-{"title":"work with consumer
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
+{"title":"work with consumer
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string","minLength":1}},"type":"object"}
]]
)
@@ -371,7 +371,7 @@
qr/\[\{"name":"multi-auth","priority":2600\},\{"name":"wolf-rbac","priority":255
}
}
--- response_body eval
-qr/\{"encrypt_fields":\["password"\],"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
with consumer object","type":"object"\}/
+qr/\{"encrypt_fields":\["password"\],"properties":\{"password":\{"minLength":1,"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
with consumer object","type":"object"\}/
diff --git a/t/plugin/basic-auth.t b/t/plugin/basic-auth.t
index 2e19a2f6d..8a141859e 100644
--- a/t/plugin/basic-auth.t
+++ b/t/plugin/basic-auth.t
@@ -344,7 +344,7 @@ GET /t
ngx.HTTP_GET,
nil,
[[
-{"title":"work with consumer
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
+{"title":"work with consumer
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string","minLength":1}},"type":"object"}
]]
)
ngx.status = code
@@ -708,3 +708,211 @@ Authorization: bASiC Zm9vOmJhcg==
hello world
--- error_log
find consumer foo
+
+
+
+=== TEST 31: reject an empty password on the consumer
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "foo",
+ "plugins": {
+ "basic-auth": {
+ "username": "foo",
+ "password": ""
+ }
+ }
+ }]]
+ )
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"invalid plugins configuration: failed to check the configuration
of plugin basic-auth err: property \"password\" validation failed: string too
short, expected at least 1, got 0"}
+
+
+
+=== TEST 32: reject an empty password on the credential
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body =
t('/apisix/admin/consumers/foo/credentials/cred_a',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "basic-auth": {
+ "username": "bar",
+ "password": ""
+ }
+ }
+ }]]
+ )
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"invalid plugins configuration: failed to check the configuration
of plugin basic-auth err: property \"password\" validation failed: string too
short, expected at least 1, got 0"}
+
+
+
+=== TEST 33: store an empty secret into vault
+--- exec
+VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put
kv/apisix/empty passwd=
+--- response_body
+Success! Data written to: kv/apisix/empty
+
+
+
+=== TEST 34: set basic-auth conf: password uses a secret ref that resolves to
an empty string
+--- request
+GET /t
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/secrets/vault/test1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "http://127.0.0.1:8200",
+ "prefix" : "kv/apisix",
+ "token" : "root"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+
+ code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "foo",
+ "plugins": {
+ "basic-auth": {
+ "username": "foo",
+ "password": "$secret://vault/test1/empty/passwd"
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+
+ code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "basic-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 35: verify, empty password on the wire (foo:) is rejected
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOg==
+--- error_code: 401
+
+
+
+=== TEST 36: verify, whitespace-only password on the wire (foo: ) is rejected
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOiA=
+--- error_code: 401
+--- response_body
+{"message":"Invalid user authorization"}
+--- error_log
+empty password configured for consumer: foo
+
+
+
+=== TEST 37: set basic-auth conf: password uses an env ref that resolves to an
empty string
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "foo",
+ "plugins": {
+ "basic-auth": {
+ "username": "foo",
+ "password": "$env://BASIC_AUTH_EMPTY_PASSWORD"
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 38: verify, empty password on the wire (foo:) is rejected
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOg==
+--- error_code: 401
+
+
+
+=== TEST 39: verify, whitespace-only password on the wire (foo: ) is rejected
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOiA=
+--- error_code: 401
+--- response_body
+{"message":"Invalid user authorization"}
+--- error_log
+empty password configured for consumer: foo