diff --git a/apisix/consumer.lua b/apisix/consumer.lua index 0eb55b5e0994..ff00406caef0 100644 --- a/apisix/consumer.lua +++ b/apisix/consumer.lua @@ -163,6 +163,10 @@ function _M.plugin(plugin_name) return plugin_conf[plugin_name] end +function _M.consumers_conf(plugin_name) + return _M.plugin(plugin_name) +end + -- attach chosen consumer to the ctx, used in auth plugin function _M.attach_consumer(ctx, consumer, conf) @@ -208,6 +212,20 @@ function _M.consumers_kv(plugin_name, consumer_conf, key_attr) return consumers end + +function _M.find_consumer(plugin_name, key, key_value) + local consumer + local consumer_conf + consumer_conf = _M.plugin(plugin_name) + if not consumer_conf then + return nil, nil, "Missing related consumer" + end + local consumers = _M.consumers_kv(plugin_name, consumer_conf, key) + consumer = consumers[key_value] + return consumer, consumer_conf +end + + local function check_consumer(consumer, key) local data_valid local err @@ -251,5 +269,33 @@ function _M.init_worker() end end +local function get_anonymous_consumer_from_local_cache(name) + local anon_consumer_raw = consumers:get(name) + + if not anon_consumer_raw or not anon_consumer_raw.value or + not anon_consumer_raw.value.id or not anon_consumer_raw.modifiedIndex then + return nil, nil, "failed to get anonymous consumer " .. name + end + + -- make structure of anon_consumer similar to that of consumer_mod.consumers_kv's response + local anon_consumer = anon_consumer_raw.value + anon_consumer.consumer_name = anon_consumer_raw.value.id + anon_consumer.modifiedIndex = anon_consumer_raw.modifiedIndex + + local anon_consumer_conf = { + conf_version = anon_consumer_raw.modifiedIndex + } + + return anon_consumer, anon_consumer_conf +end + + +function _M.get_anonymous_consumer(name) + local anon_consumer, anon_consumer_conf, err + anon_consumer, anon_consumer_conf, err = get_anonymous_consumer_from_local_cache(name) + + return anon_consumer, anon_consumer_conf, err +end + return _M diff --git a/apisix/plugins/basic-auth.lua b/apisix/plugins/basic-auth.lua index ec10c648a54f..aac1ef9abb7c 100644 --- a/apisix/plugins/basic-auth.lua +++ b/apisix/plugins/basic-auth.lua @@ -18,6 +18,7 @@ local core = require("apisix.core") local ngx = ngx local ngx_re = require("ngx.re") local consumer = require("apisix.consumer") +local schema_def = require("apisix.schema_def") local auth_utils = require("apisix.utils.auth") local lrucache = core.lrucache.new({ @@ -33,6 +34,7 @@ local schema = { default = false, } }, + anonymous_consumer = schema_def.anonymous_consumer_schema, } local consumer_schema = { @@ -122,47 +124,59 @@ local function extract_auth_header(authorization) end -function _M.rewrite(conf, ctx) - core.log.info("plugin access phase, conf: ", core.json.delay_encode(conf)) - - -- 1. extract authorization from header +local function find_consumer(ctx) local auth_header = core.request.header(ctx, "Authorization") if not auth_header then core.response.set_header("WWW-Authenticate", "Basic realm='.'") - return 401, { message = "Missing authorization in request" } + return nil, nil, "Missing authorization in request" end local username, password, err = extract_auth_header(auth_header) if err then if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + return nil, nil, err end core.log.warn(err) - return 401, { message = "Invalid authorization in request" } + return nil, nil, "Invalid authorization in request" end - -- 2. get user info from consumer plugin - local consumer_conf = consumer.plugin(plugin_name) - if not consumer_conf then - return 401, { message = "Missing related consumer" } + local cur_consumer, consumer_conf, err = consumer.find_consumer(plugin_name, + "username", username) + if not cur_consumer then + err = "failed to find user: " .. (err or "invalid user") + 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 - local consumers = consumer.consumers_kv(plugin_name, consumer_conf, "username") - - -- 3. check user exists - local cur_consumer = consumers[username] - if not cur_consumer then - return 401, { message = "Invalid user authorization" } + if cur_consumer.auth_conf.password ~= password then + return nil, nil, "Invalid user authorization" end - core.log.info("consumer: ", core.json.delay_encode(cur_consumer)) + return cur_consumer, consumer_conf, err +end - -- 4. check the password is correct - if cur_consumer.auth_conf.password ~= password then - return 401, { message = "Invalid user authorization" } + +function _M.rewrite(conf, ctx) + core.log.info("plugin access phase, conf: ", core.json.delay_encode(conf)) + + local cur_consumer, consumer_conf, err = find_consumer(ctx) + if not cur_consumer then + if not conf.anonymous_consumer then + return 401, { message = err } + end + cur_consumer, consumer_conf, err = consumer.get_anonymous_consumer(conf.anonymous_consumer) + if not cur_consumer then + err = "basic-auth failed to authenticate the request, code: 401. error: " .. err + core.log.error(err) + return 401, { message = "Invalid user authorization" } + end end - -- 5. hide `Authorization` request header if `hide_credentials` is `true` + core.log.info("consumer: ", core.json.delay_encode(cur_consumer)) + if conf.hide_credentials then core.request.set_header(ctx, "Authorization", nil) end diff --git a/apisix/plugins/hmac-auth.lua b/apisix/plugins/hmac-auth.lua index e216d06ccfdb..30e8db0712b2 100644 --- a/apisix/plugins/hmac-auth.lua +++ b/apisix/plugins/hmac-auth.lua @@ -28,6 +28,7 @@ local ngx_encode_base64 = ngx.encode_base64 local plugin_name = "hmac-auth" local ALLOWED_ALGORITHMS = {"hmac-sha1", "hmac-sha256", "hmac-sha512"} local resty_sha256 = require("resty.sha256") +local schema_def = require("apisix.schema_def") local auth_utils = require("apisix.utils.auth") local schema = { @@ -62,6 +63,7 @@ local schema = { default = false, }, hide_credentials = {type = "boolean", default = false}, + anonymous_consumer = schema_def.anonymous_consumer_schema, }, } @@ -124,19 +126,13 @@ local function get_consumer(key_id) return nil, "missing key_id" end - local consumer_conf = consumer.plugin(plugin_name) - if not consumer_conf then - return nil, "Missing related consumer" + local cur_consumer, _, err = consumer.find_consumer(plugin_name, "key_id", key_id) + if not cur_consumer then + return nil, err or "Invalid key_id" end + core.log.info("consumer: ", core.json.delay_encode(consumer, true)) - local consumers = consumer.consumers_kv(plugin_name, consumer_conf, "key_id") - local consumer = consumers[key_id] - if not consumer then - return nil, "Invalid key_id" - end - core.log.info("consumer: ", core.json.delay_encode(consumer)) - - return consumer + return cur_consumer end @@ -187,6 +183,10 @@ end local function validate(ctx, conf, params) + if not params then + return nil + end + if not params.keyId or not params.signature then return nil, "keyId or signature missing" end @@ -321,34 +321,51 @@ local function retrieve_hmac_fields(ctx) return hmac_params end - -function _M.rewrite(conf, ctx) +local function find_consumer(conf, ctx) local params,err = retrieve_hmac_fields(ctx) if err then - err = "client request can't be validated: " .. err - if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + if not auth_utils.is_running_under_multi_auth(ctx) then + core.log.warn("client request can't be validated: ", err) end - core.log.warn(err) - return 401, {message = err} + return nil, nil, "client request can't be validated: " .. err end - if conf.hide_credentials then - core.request.set_header("Authorization", nil) - end local validated_consumer, err = validate(ctx, conf, params) if not validated_consumer then err = "client request can't be validated: " .. (err or "Invalid signature") if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + return nil, nil, err end core.log.warn(err) - return 401, {message = "client request can't be validated"} + return nil, nil, "client request can't be validated" + end + + local consumers_conf = consumer.consumers_conf(plugin_name) + return validated_consumer, consumers_conf, err +end + + +function _M.rewrite(conf, ctx) + local cur_consumer, consumers_conf, err = find_consumer(conf, ctx) + if not cur_consumer then + if not conf.anonymous_consumer then + return 401, { message = err } + end + cur_consumer, consumers_conf, err = consumer.get_anonymous_consumer(conf.anonymous_consumer) + if not cur_consumer then + if auth_utils.is_running_under_multi_auth(ctx) then + return 401, err + end + core.log.error(err) + return 401, { message = "Invalid user authorization" } + end + end + + if conf.hide_credentials then + core.request.set_header("Authorization", nil) end - local consumer_conf = consumer.plugin(plugin_name) - consumer.attach_consumer(ctx, validated_consumer, consumer_conf) - core.log.info("hit hmac-auth rewrite") + consumer.attach_consumer(ctx, cur_consumer, consumers_conf) end diff --git a/apisix/plugins/jwt-auth.lua b/apisix/plugins/jwt-auth.lua index db8e49c8a0e5..2c00b6bf7f7c 100644 --- a/apisix/plugins/jwt-auth.lua +++ b/apisix/plugins/jwt-auth.lua @@ -29,6 +29,7 @@ local table_insert = table.insert local table_concat = table.concat local ngx_re_gmatch = ngx.re.gmatch local plugin_name = "jwt-auth" +local schema_def = require("apisix.schema_def") local schema = { @@ -55,6 +56,7 @@ local schema = { default = "key", minLength = 1, }, + anonymous_consumer = schema_def.anonymous_consumer_schema, }, } @@ -237,12 +239,12 @@ local function get_auth_secret(auth_conf) end end -function _M.rewrite(conf, ctx) +local function find_consumer(conf, ctx) -- fetch token and hide credentials if necessary local jwt_token, err = fetch_jwt_token(conf, ctx) if not jwt_token then core.log.info("failed to fetch JWT token: ", err) - return 401, {message = "Missing JWT token in request"} + return nil, nil, "Missing JWT token in request" end local jwt_obj = jwt:load_jwt(jwt_token) @@ -250,28 +252,22 @@ function _M.rewrite(conf, ctx) if not jwt_obj.valid then err = "JWT token invalid: " .. jwt_obj.reason if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + return nil, nil, err end core.log.warn(err) - return 401, {message = "JWT token invalid"} + return nil, nil, "JWT token invalid" end local key_claim_name = conf.key_claim_name local user_key = jwt_obj.payload and jwt_obj.payload[key_claim_name] if not user_key then - return 401, {message = "missing user key in JWT token"} - end - - local consumer_conf = consumer_mod.plugin(plugin_name) - if not consumer_conf then - return 401, {message = "Missing related consumer"} + return nil, nil, "missing user key in JWT token" end - local consumers = consumer_mod.consumers_kv(plugin_name, consumer_conf, "key") - - local consumer = consumers[user_key] + local consumer, consumer_conf, err = consumer_mod.find_consumer(plugin_name, "key", user_key) if not consumer then - return 401, {message = "Invalid user key in JWT token"} + core.log.warn("failed to find consumer: ", err or "invalid user key") + return nil, nil, "Invalid user key in JWT token" end core.log.info("consumer: ", core.json.delay_encode(consumer)) @@ -279,10 +275,10 @@ function _M.rewrite(conf, ctx) if not auth_secret then err = "failed to retrieve secrets, err: " .. err if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + return nil, nil, err end core.log.error(err) - return 503, {message = "failed to verify jwt"} + return nil, nil, "failed to verify jwt" end local claim_specs = jwt:get_default_validation_options(jwt_obj) claim_specs.lifetime_grace_period = consumer.auth_conf.lifetime_grace_period @@ -293,12 +289,32 @@ function _M.rewrite(conf, ctx) if not jwt_obj.verified then err = "failed to verify jwt: " .. jwt_obj.reason if auth_utils.is_running_under_multi_auth(ctx) then - return 401, err + return nil, nil, err end core.log.warn(err) - return 401, {message = "failed to verify jwt"} + return nil, nil, "failed to verify jwt" end + return consumer, consumer_conf +end + + +function _M.rewrite(conf, ctx) + local consumer, consumer_conf, err = find_consumer(conf, ctx) + if not consumer then + if not conf.anonymous_consumer then + return 401, { message = err } + end + consumer, consumer_conf, err = consumer_mod.get_anonymous_consumer(conf.anonymous_consumer) + if not consumer then + err = "jwt-auth failed to authenticate the request, code: 401. error: " .. err + core.log.error(err) + return 401, { message = "Invalid user authorization"} + end + end + + core.log.info("consumer: ", core.json.delay_encode(consumer)) + consumer_mod.attach_consumer(ctx, consumer, consumer_conf) core.log.info("hit jwt-auth rewrite") end diff --git a/apisix/plugins/key-auth.lua b/apisix/plugins/key-auth.lua index 7196a13e6da0..539a489990d3 100644 --- a/apisix/plugins/key-auth.lua +++ b/apisix/plugins/key-auth.lua @@ -17,7 +17,7 @@ local core = require("apisix.core") local consumer_mod = require("apisix.consumer") local plugin_name = "key-auth" - +local schema_def = require("apisix.schema_def") local schema = { type = "object", @@ -33,7 +33,8 @@ local schema = { hide_credentials = { type = "boolean", default = false, - } + }, + anonymous_consumer = schema_def.anonymous_consumer_schema, }, } @@ -65,8 +66,7 @@ function _M.check_schema(conf, schema_type) end end - -function _M.rewrite(conf, ctx) +local function find_consumer(ctx, conf) local from_header = true local key = core.request.header(ctx, conf.header) @@ -77,18 +77,13 @@ function _M.rewrite(conf, ctx) end if not key then - return 401, {message = "Missing API key in request"} + return nil, nil, "Missing API key in request" end - local consumer_conf = consumer_mod.plugin(plugin_name) - if not consumer_conf then - return 401, {message = "Missing related consumer"} - end - - local consumers = consumer_mod.consumers_kv(plugin_name, consumer_conf, "key") - local consumer = consumers[key] + local consumer, consumer_conf, err = consumer_mod.find_consumer(plugin_name, "key", key) if not consumer then - return 401, {message = "Invalid API key in request"} + core.log.warn("failed to find consumer: ", err or "invalid api key") + return nil, nil, "Invalid API key in request" end core.log.info("consumer: ", core.json.delay_encode(consumer)) @@ -102,6 +97,25 @@ function _M.rewrite(conf, ctx) end end + return consumer, consumer_conf +end + + +function _M.rewrite(conf, ctx) + local consumer, consumer_conf, err = find_consumer(ctx, conf) + if not consumer then + if not conf.anonymous_consumer then + return 401, { message = err} + end + consumer, consumer_conf, err = consumer_mod.get_anonymous_consumer(conf.anonymous_consumer) + if not consumer then + err = "key-auth failed to authenticate the request, code: 401. error: " .. err + core.log.error(err) + return 401, { message = "Invalid user authorization"} + end + end + + core.log.info("consumer: ", core.json.delay_encode(consumer)) consumer_mod.attach_consumer(ctx, consumer, consumer_conf) core.log.info("hit key-auth rewrite") end diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index b4241ff2d9f5..4e4514678b06 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -27,6 +27,11 @@ local plugins_schema = { type = "object" } +_M.anonymous_consumer_schema = { + type = "string", + minLength = "1" +} + local id_schema = { anyOf = { { diff --git a/t/plugin/basic-auth-anonymous-consumer.t b/t/plugin/basic-auth-anonymous-consumer.t new file mode 100644 index 000000000000..64a84f05fcbc --- /dev/null +++ b/t/plugin/basic-auth-anonymous-consumer.t @@ -0,0 +1,224 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use t::APISIX 'no_plan'; + + +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $user_yaml_config = <<_EOC_; +apisix: + data_encryption: + enable_encrypt_fields: false +_EOC_ + $block->set_value("yaml_config", $user_yaml_config); +}); + + +run_tests; + +__DATA__ + +=== TEST 1: add consumer jack and anonymous +--- 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": "jack", + "plugins": { + "basic-auth": { + "username": "foo", + "password": "bar" + }, + "limit-count": { + "count": 4, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "anonymous", + "plugins": { + "limit-count": { + "count": 1, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +passed + + + +=== TEST 2: add basic auth plugin using admin api +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "basic-auth": { + "anonymous_consumer": "anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 3: normal consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + + for i = 1, 5, 1 do + local code, body = t('/hello', + ngx.HTTP_GET, + nil, + nil, + { + Authorization = "Basic Zm9vOmJhcg==" + } + ) + + if code >= 300 then + ngx.say("failed" .. code) + return + end + ngx.say(body .. i) + end + } + } +--- request +GET /t +--- response_body +passed1 +passed2 +passed3 +passed4 +failed503 + + + +=== TEST 4: request without basic-auth header will be from anonymous consumer and it will pass +--- request +GET /hello +--- response_body +hello world + + + +=== TEST 5: request without basic-auth header will be from anonymous consumer and different rate limit will apply +--- pipelined_requests eval +["GET /hello", "GET /hello", "GET /hello", "GET /hello"] +--- error_code eval +[200, 503, 503, 503] + + + +=== TEST 6: add basic auth plugin with non-existent anonymous_consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "basic-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 7: anonymous-consumer configured in the route should not be found +--- request +GET /hello +--- error_code: 401 +--- error_log +failed to get anonymous consumer not-found-anonymous +--- response_body +{"message":"Invalid user authorization"} diff --git a/t/plugin/hmac-auth-anonymous-consumer.t b/t/plugin/hmac-auth-anonymous-consumer.t new file mode 100644 index 000000000000..ea80ea2322fe --- /dev/null +++ b/t/plugin/hmac-auth-anonymous-consumer.t @@ -0,0 +1,189 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use t::APISIX 'no_plan'; + + +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $user_yaml_config = <<_EOC_; +apisix: + data_encryption: + enable_encrypt_fields: false +_EOC_ + $block->set_value("yaml_config", $user_yaml_config); +}); + + +run_tests; + +__DATA__ + +=== TEST 1: add consumer jack and anonymous +--- 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": "jack", + "plugins": { + "hmac-auth": { + "key_id": "user-key", + "secret_key": "my-secret-key" + }, + "limit-count": { + "count": 4, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "anonymous", + "plugins": { + "limit-count": { + "count": 1, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +passed + + + +=== TEST 2: add hmac auth plugin using admin api +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "hmac-auth": { + "anonymous_consumer": "anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 3: request without hmac-auth header will be from anonymous consumer and it will pass +--- request +GET /hello +--- response_body +hello world + + + +=== TEST 4: request without hmac-auth header will be from anonymous consumer and different rate limit will apply +--- pipelined_requests eval +["GET /hello", "GET /hello", "GET /hello", "GET /hello"] +--- error_code eval +[200, 503, 503, 503] + + + +=== TEST 5: add hmac auth plugin with non-existent anonymous_consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "hmac-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 6: anonymous-consumer configured in the route should not be found +--- request +GET /hello +--- error_code: 401 +--- error_log +failed to get anonymous consumer not-found-anonymous +--- response_body +{"message":"Invalid user authorization"} diff --git a/t/plugin/jwt-auth-anonymous-consumer.t b/t/plugin/jwt-auth-anonymous-consumer.t new file mode 100644 index 000000000000..17cda616b41e --- /dev/null +++ b/t/plugin/jwt-auth-anonymous-consumer.t @@ -0,0 +1,224 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use t::APISIX 'no_plan'; + + +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $user_yaml_config = <<_EOC_; +apisix: + data_encryption: + enable_encrypt_fields: false +_EOC_ + $block->set_value("yaml_config", $user_yaml_config); +}); + + +run_tests; + +__DATA__ + +=== TEST 1: add consumer jack and anonymous +--- 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": "jack", + "plugins": { + "jwt-auth": { + "key": "user-key", + "secret": "my-secret-key" + }, + "limit-count": { + "count": 4, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "anonymous", + "plugins": { + "limit-count": { + "count": 1, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +passed + + + +=== TEST 2: add jwt auth plugin using admin api +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "jwt-auth": { + "anonymous_consumer": "anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 3: normal consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + + for i = 1, 5, 1 do + local code, body = t('/hello', + ngx.HTTP_GET, + nil, + nil, + { + Authorization = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTg3OTMxODU0MX0.fNtFJnNmJgzbiYmGB0Yjvm-l6A6M4jRV1l4mnVFSYjs" + } + ) + + if code >= 300 then + ngx.say("failed" .. code) + return + end + ngx.say(body .. i) + end + } + } +--- request +GET /t +--- response_body +passed1 +passed2 +passed3 +passed4 +failed503 + + + +=== TEST 4: request without jwt-auth header will be from anonymous consumer and it will pass +--- request +GET /hello +--- response_body +hello world + + + +=== TEST 5: request without jwt-auth header will be from anonymous consumer and different rate limit will apply +--- pipelined_requests eval +["GET /hello", "GET /hello", "GET /hello", "GET /hello"] +--- error_code eval +[200, 503, 503, 503] + + + +=== TEST 6: add jwt auth plugin with non-existent anonymous_consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "jwt-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 7: anonymous-consumer configured in the route should not be found +--- request +GET /hello +--- error_code: 401 +--- error_log +failed to get anonymous consumer not-found-anonymous +--- response_body +{"message":"Invalid user authorization"} diff --git a/t/plugin/key-auth-anonymous-consumer.t b/t/plugin/key-auth-anonymous-consumer.t new file mode 100644 index 000000000000..20448a0a8d77 --- /dev/null +++ b/t/plugin/key-auth-anonymous-consumer.t @@ -0,0 +1,223 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use t::APISIX 'no_plan'; + + +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $user_yaml_config = <<_EOC_; +apisix: + data_encryption: + enable_encrypt_fields: false +_EOC_ + $block->set_value("yaml_config", $user_yaml_config); +}); + + +run_tests; + +__DATA__ + +=== TEST 1: add consumer jack and anonymous +--- 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": "jack", + "plugins": { + "key-auth": { + "key": "auth-one" + }, + "limit-count": { + "count": 4, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "anonymous", + "plugins": { + "limit-count": { + "count": 1, + "time_window": 60 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +passed + + + +=== TEST 2: add key auth plugin using admin api +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "key-auth": { + "anonymous_consumer": "anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 3: normal consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + + for i = 1, 5, 1 do + local code, body = t('/hello', + ngx.HTTP_GET, + nil, + nil, + { + apikey = "auth-one" + } + ) + + if code >= 300 then + ngx.say("failed" .. code) + return + end + ngx.say(body .. i) + end + } + } +--- request +GET /t +--- response_body +passed1 +passed2 +passed3 +passed4 +failed503 + + + +=== TEST 4: request without key-auth header will be from anonymous consumer and it will pass +--- request +GET /hello +--- response_body +hello world + + + +=== TEST 5: request without key-auth header will be from anonymous consumer and different rate limit will apply +--- pipelined_requests eval +["GET /hello", "GET /hello", "GET /hello", "GET /hello"] +--- error_code eval +[200, 503, 503, 503] + + + +=== TEST 6: add key auth plugin with non-existent anonymous_consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "key-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 7: anonymous-consumer configured in the route should not be found +--- request +GET /hello +--- error_code: 401 +--- error_log +failed to get anonymous consumer not-found-anonymous +--- response_body +{"message":"Invalid user authorization"} diff --git a/t/plugin/multi-auth2.t b/t/plugin/multi-auth2.t index d8af3cf6885b..bc15a369d24e 100644 --- a/t/plugin/multi-auth2.t +++ b/t/plugin/multi-auth2.t @@ -16,6 +16,7 @@ # use t::APISIX 'no_plan'; +repeat_each(2); no_long_string(); no_root_location(); no_shuffle(); @@ -37,9 +38,8 @@ __DATA__ "username": "foo", "password": "bar" }, - "jwt-auth": { - "key": "user-key", - "secret": "my-secret-key" + "key-auth": { + "key": "auth-one" } } }]] @@ -71,6 +71,9 @@ passed { "basic-auth": {} }, + { + "key-auth": {} + }, { "jwt-auth": {} }, @@ -103,7 +106,36 @@ passed -=== TEST 3: invalid basic-auth credentials +=== TEST 3: invalid key-auth apikey +--- request +GET /hello +--- more_headers +apikey: 123 +--- error_code: 401 +--- response_body +{"message":"Authorization Failed"} +--- error_log +basic-auth failed to authenticate the request, code: 401. error: Missing authorization in request +key-auth failed to authenticate the request, code: 401. error: Invalid API key in request +jwt-auth failed to authenticate the request, code: 401. error: Missing JWT token in request +hmac-auth failed to authenticate the request, code: 401. error: client request can't be validated: missing Authorization header + + + +=== TEST 4: valid key-auth apikey +--- request +GET /hello +--- more_headers +apikey: auth-one +--- error_code: 200 +--- response_body +hello world +--- no_error_log +failed to authenticate the request + + + +=== TEST 5: invalid basic-auth credentials --- request GET /hello --- more_headers @@ -112,13 +144,14 @@ Authorization: Basic YmFyOmJhcgo= --- response_body {"message":"Authorization Failed"} --- error_log -basic-auth failed to authenticate the request, code: 401. error: Invalid user authorization +basic-auth failed to authenticate the request, code: 401. error: failed to find user: invalid user +key-auth failed to authenticate the request, code: 401. error: Missing API key in request jwt-auth failed to authenticate the request, code: 401. error: JWT token invalid: invalid jwt string hmac-auth failed to authenticate the request, code: 401. error: client request can't be validated: Authorization header does not start with 'Signature' -=== TEST 4: valid basic-auth creds +=== TEST 6: valid basic-auth creds --- request GET /hello --- more_headers @@ -130,7 +163,7 @@ failed to authenticate the request -=== TEST 5: missing hmac auth authorization header +=== TEST 7: missing hmac auth authorization header --- request GET /hello --- error_code: 401 @@ -141,7 +174,7 @@ hmac-auth failed to authenticate the request, code: 401. error: client request c -=== TEST 6: hmac auth missing algorithm +=== TEST 8: hmac auth missing algorithm --- request GET /hello --- more_headers @@ -155,7 +188,48 @@ hmac-auth failed to authenticate the request, code: 401. error: client request c -=== TEST 7: add consumer with username and jwt-auth plugins +=== TEST 9: test with invalid jwt-auth token +--- request +GET /hello +--- more_headers +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTU2Mzg3MDUwMX0.pPNVvh-TQsdDzorRwa-uuiLYiEBODscp9wv0cwD6c68 +--- error_code: 401 +--- response_body +{"message":"Authorization Failed"} +--- error_log +jwt-auth failed to authenticate the request, code: 401. error: Invalid user key in JWT token + + + +=== TEST 10: create public API route (jwt-auth sign) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/2', + ngx.HTTP_PUT, + [[{ + "plugins": { + "public-api": {} + }, + "uri": "/apisix/plugin/jwt/sign" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 11: add consumer with username and jwt-auth plugins --- config location /t { content_by_lua_block { @@ -186,7 +260,7 @@ passed -=== TEST 8: test with expired jwt token +=== TEST 12: test with expired jwt token --- request GET /hello --- error_code: 401 @@ -199,7 +273,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtle -=== TEST 9: test with jwt token containing wrong signature +=== TEST 13: test with jwt token containing wrong signature --- request GET /hello --- error_code: 401 @@ -212,7 +286,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtle -=== TEST 10: verify jwt-auth +=== TEST 14: verify jwt-auth --- request GET /hello --- more_headers @@ -221,3 +295,74 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtle hello world --- no_error_log failed to authenticate the request + + + +=== TEST 15: enable multi auth plugin with non-existent anonymous consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "multi-auth": { + "auth_plugins": [ + { + "basic-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + { + "key-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + { + "jwt-auth": { + "anonymous_consumer": "not-found-anonymous" + } + }, + { + "hmac-auth": { + "anonymous_consumer": "anonymous" + } + } + ] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 16: invalid basic-auth credentials +--- request +GET /hello +--- error_code: 401 +--- response_body +{"message":"Authorization Failed"} +--- error_log +basic-auth failed to authenticate the request, code: 401. error: failed to get anonymous consumer not-found-anonymous +key-auth failed to authenticate the request, code: 401. error: failed to get anonymous consumer not-found-anonymous +jwt-auth failed to authenticate the request, code: 401. error: failed to get anonymous consumer not-found-anonymous +hmac-auth failed to authenticate the request, code: 401. error: failed to get anonymous consumer anonymous