Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions apisix/plugins/basic-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/en/latest/plugins/basic-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/latest/plugins/basic-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 端:

Expand Down
4 changes: 2 additions & 2 deletions t/admin/plugins.t
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
]]
)

Expand Down Expand Up @@ -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"\}/



Expand Down
210 changes: 209 additions & 1 deletion t/plugin/basic-auth.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading